• c++学习笔记2_继承与多态


    熟悉C++的继承与多态 并完成自己定义的购物车

    一、基类和派生类

    1.1 继承

    C++提供了比修改代码更好的方法来扩展和修改类。这种方法叫作类继承,它能够从已有的类派生出新的类,而派生类继承了原有类(称为基类)的特征,包括方法
    正如继承一笔财产要比自己白手起家容易一样,通过继承派生出的类通常比设计新类要容易得多。下面是可以通过继承完成的一些工作。
    在这里插入图片描述
    当一个类派生出另一个类,原始类称为基类,继承类成为派生类

    1.2 派生类

    派生类对象将具有以下特征:

    • 派生类对象存储了基类的数据成员(派生类继承了基类的实现)
    • 派生类对象可以使用基类的方法(派生类继承了基类的接口)

    需要在继承属性中添加:

    • 派生类需要自己的构造函数
    • 派生类可以根据需要添加额外的数据成员和成员函数
      在这里插入图片描述

    1.2.1 派生类成员的访问属性

    1、公用继承(public inheritance)
    基类的公有成员和受保护成员在派生类中保持原有的访问属性,其私有成员仍为基类私有(基类的封装性)。
    公有继承是一种is a关系,即派生类对象也是一个基类对象,可以对基类对象执行任何操作,也可以对派生类对象执行。

    2、 私有继承(private inheritance)
    基类的公有成员和受保护成员在派生类中成为了私有成员。其私有成员仍为基类私有。

    3、受保护的继承(protected inheritance)
    基类的公有成员和受保护成员在派生类中成为了受保护成员。其私有成员仍为基类私有。
    受保护成员:不能被外界引用,但可以被派生类的成员引用。
    在这里插入图片描述
    在这里插入图片描述

    1.2.2 构造函数

    派生类不能直接访问基类的私有成员,必须通过基类的方法进行访问。故派生类的构造函数必须使用基类的狗仔函数。
    在创建派生类对象之前,程序首先先创造基类对象。
    在这里插入图片描述
    如果不显式的调用基类的构造函数(使用初始化器列表来指名要使用的基类构造函数),则默认使用基类的构造函数:BandAcount()

    在这里插入图片描述

    //写法一(写一种就好)
    	Bulk_item(const string &strName,double dPrice,int minNum,double dDis):Item_Base(strName,dPrice),m_nMinNum(minNum),m_dDis(dDis){}
    	//使用成员初始化列表 只有参数名而不包含参数类型=>这些参数是实参而不是形参
    	
    //写法二(写一种就好)
    	Bulk_item(const string &strName,double dPrice,int minNum,double dDis):Item_Base(strName,dPrice){
    		//函数体内部只对派生类新增的数据成员初始化
    		m_nMinNum=minNum;
    		m_dDis=dDis;
    	}
    	
    //写法一(写一种就好)
    	Bulk_item(const Bulk_item & in){
    		*this=in; 
    	} 
    	
    //写法二(写一种就好)
    	Bulk_item(const Bulk_item & in){
    		m_nMinNum=in.m_nMinNum;
    		m_dDis=in.m_dDis;
    	} 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    一个类不仅可以派生出一个新的类,派生类还可以继续派生,形成派生的层次结构。
    在这里插入图片描述

    1.2.3 析构函数

    释放对象的顺序与创建对象的顺序相反
    对象过期时,程序将首先使用派生类析构函数,然后再(自动的)调用基类析构函数。

    在派生时,派生类不能继承基类的析构函数,需要通过自己的析构函数去调用基类的析构函数。

    1.3 多重继承

    1.3.1 声明多重继承

    在这里插入图片描述

    1.3.2多重继承的构造函数

    在这里插入图片描述

    1.3.3多重继承的二义性

    (1)两个基类有同名成员
    用基类限定名来限定
    使用作用域解析运算符(::)来标识函数所属的类

    (2)两个基类和派生类三者都有同名函数
    基类的同名成员在派生类中被屏蔽(不可见),即派生类新增加的同名函数覆盖了基类中的同名成员。相当于重载
    注意:函数名与特征标相同时才为函数重载
    特征标——函数的参数列表(特征标相同==参数数目和类型相同+参数的排列顺序相同(与变量名无关))

    (3)如果类A和类B是从同一个基类派生的
    在这里插入图片描述
    在这里插入图片描述
    通过类N的直接派生类名来指出要访问的时类N的哪一个派生类的基类成员。
    在这里插入图片描述

    1.4 派生类和基类之间的相互转换

    (1)赋值
    赋值兼容:不同类型数据之间的自动转换和赋值。
    只能用派生类对象对基类对象赋值 ,不能用基类对象对派生类对象赋值 ,因为基类对象不包含派生类成员。同理,同一基类的不同派生类对象之间也不能赋值。
    (2)派生类对象可以代替基类对象对基类的应用进行赋值和初始化
    派生类对象可以代替基类对象对基类的应用进行赋值和初始化
    如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象
    (2)指针
    指向基类对象的指针,只能访问派生类中的基类成员,而不能访问派生类增加的成员。
    可以使用static_cast强制编译器进行转换。或者,可以用dynamic_cast申请在运行时进行检查。
    在这里插入图片描述

    1.5 组合(composition)

    类的组合:在一个类中以另一个类的对象作为数据成员。
    继承是”“的关系,是纵向的,派生类是基类。
    组合是”“的关系,是横向的。

    1.6 虚基类(virtual base class)

    C++提供虚基类方法,在继承间接共同基类时只保留一份成员。
    在这里插入图片描述
    虚基类不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。
    当派生类通过多条派生路径继承虚基类时,基类成员只保留一次(虚基类在派生类中只被继承一次)
    注意:应该在该基类的所有派生类中直接声明为虚基类,否则仍会出现多次继承。

    二、多态性与虚函数

    多态性(polymorphism):
    向不同的对象发送同一条信息(调用函数),不同的对象在接收时回产生不同的行为、方法(执行不同的函数)
    例如函数的重载、运算符重载都是多态。
    静态多态性(编译时的多态):在程序编译时就知道调用函数的全部信息,系统就能决定要调用哪个函数。通过函数重载实现。
    动态多态性(运行时的多态):在程序运行过程中动态地确定操作所针对的对象,通过虚函数(virtual function)实现。

    2.1虚函数

    C++编译器 对非虚方法使用静态联编(static binding) 对虚方法使用动态联编(dynamic binding)

    虚函数:在基类声明函数时虚拟的,并不是实际存在的函数,然后再派生类中才正式定义此函数。
    作用:允许派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问派生类中的同名函数。
    若需要在派生类中重新定义基类的方法,通常应该将基类方法声明为虚的

    虚函数的使用方法是:
    (1)在基类中用virtual声明成员函数为虚函数。在类外定义虚函数时,不必再加virtual。
    (2)在派生类中重新定义此函数,函数名、函数类型、函数参数个数和类型必须与基类的虚函数相同,根据派生类的需要重新定义函数体。
    当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加 virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。
    如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。
    (3)定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
    (4)通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。

    通过虚函数与指向基类对象的指针变量的配合使用,就能实现动态的多态性
    如果想调用同一类族中不同类的同名函数,只要先使用基类指针指向该类对象即可。

    若在基类中定义的非虚函数再派生类中被重新定义。
    基类指针 =>对象中基类部分的成员函数
    派生类指针 =>派生类对象中的成员函数
    这并不是多态性行为(使用的是不同类型的指针),没有用到虚函数的功能。

    2.1.1 虚函数虚析构函数

    我们希望:通过基类的指针删除派生类对象时,正确的析构函数被调用即程序将首先使用派生类析构函数,然后再(自动的)调用基类析构函数
    若基类的析构函数不为虚析构函数时,用基类指针指向派生类对象的时候,只会调用基类析构函数。

    当基类的析构函数为虚函数时,无论指针指的是同一类族中的哪一个类对象,系统都会采用动态关联,调用相应类的析构函数,对该对象进行清理工作。
    在这里插入图片描述
    通常,给基类提供一个虚析构函数(即使它不需要虚构函数)

    2.1.2 虚函数工作原理

    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

    2.1.3 注意

    (1)构造函数
    构造函数不能是虚函数(因为在执行构造函数时类对象还未完成建立过程)
    (2)析构函数
    析构函数应该是虚函数,除非类不用做基类。
    (3)友元函数
    友元不能是虚函数,因为友元不是类成员,只有成员才能是虚函数。
    (可以让友元函数是使用虚成员函数)
    (4)没有重新定义
    如果派生类没有重新定义函数,则使用该函数的基类版本。
    (5)重新定义
    第一,如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针(这种例外是新出现的)。这种特性被称为返回类型协变(covariance of return type),因为允许返回类型随类类型的变化而变化:这种例外只适用于返回值,而不适用于参数。
    第二,如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。

    2.2 纯虚函数与抽象类

    纯虚函数(pure virtual function)
    在这里插入图片描述

    抽象基类(abstract base class,ABC)
    目的:用它作为基类去建立派生类。
    包含纯虚函数的类都是抽象类(因为纯虚函数是不能被调用的,包含虚函数的类是无法建立对象的)

    实验部分(熟悉C++的继承与多态智能指针 并完成自己定义的购物车)

    sy2.h

    #ifndef SY
    #define SY
    #include 
    using namespace std;
    class Bulk_item;
    
    
    //不使用折扣策略的基类  Item_Base
    class Item_Base{
    	private:
    		
    	protected:
    		string m_strName;		//商品名  
    		double m_dPrice;		//价格 
    	public:
    		Item_Base(const string &strName="",double dPrice=10.0):m_strName(strName),m_dPrice(dPrice){}
    		Item_Base(const Item_Base & ib){
    			m_strName=ib.m_strName;
    			m_dPrice=ib.m_dPrice;
    		}
    		
    		//返回商品名 
    		string book() const{
    			return m_strName;	
    		}
    		//基类不须要折扣策略
    		virtual double net_price(int n) const{
    			return n*m_dPrice;
    		}
    		virtual Item_Base *Clone() const{
    			return new Item_Base(*this);
    		} 
    		//析构函数
    		virtual	~Item_Base(){}
    		
    		//赋值操作符声明
    		Item_Base & operator=(const Item_Base &ib){
    			m_strName=ib.m_strName;
    			m_dPrice=ib.m_dPrice;
    		}
    		friend ostream & operator<<(ostream &out,const Item_Base &s){
    			out<<s.m_strName<<"\t"<<s.m_dPrice;
    			return out;
    		}
    };
    
    //批量购买折扣策略:大于一定的数量才有折扣
    //派生类  Bulk_item (公有继承) 
    class Bulk_item:public Item_Base{
    	protected:
    		int m_nMinNum;	//实现折扣策略的购买量
    		double m_dDis;	//折扣率
    	public:
    		//构造函数
    		Bulk_item(const string &strName,double dPrice,int minNum,double dDis):Item_Base(strName,dPrice),m_nMinNum(minNum),m_dDis(dDis){}
    		Bulk_item(const Bulk_item & in){
    			m_nMinNum=in.m_nMinNum;
    			m_dDis=in.m_dDis;
    			*this=in; 
    		} 
    		
    		virtual double net_price(int n) const{
    			return n>=m_nMinNum?n*m_dPrice*m_dDis:n*m_dPrice;
    		}
    		virtual Bulk_item *Clone() const{ 
    			return new Bulk_item(*this);
    		} 
    		//析构函数 
    		~Bulk_item(){
    			delete []this;
    			m_nMinNum=0;
    			m_dDis=0;
    		}
    		friend ostream & operator<<(ostream &out,const Bulk_item &s){
    			out<<s.m_strName<<"\t"<<s.m_dPrice<<"\t"<<s.m_nMinNum<<"\t"<<s.m_dDis;
    			return out;
    		}		
    };
    #endif //SY2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    main.cpp

    #include 
    #include 
    #include 
    #include "sy2.h"
    using namespace std;
    
    /* run this program using the console pauser or add your own getch, system("pause") or input loop */
    double total(Item_Base &ib){
    	cout<<"total="<<ib. net_price(10)<<endl;
    }
    
    void printT(Item_Base &bi){				// 形参是 Item_Base类对象的引用
    	//由于派生类对象与基类对象赋值兼容。派生类对象可以自动转换类型 
    	cout<<"printT "<<bi.net_price(10)<<endl;
    }
    
    int main() {
    	cout<<"--------------------------------------------------"<<endl;
    	Item_Base base("《C++》",10);			//定义 Item_Base类对象 base 
    	Item_Base base1("《C++》",10);			//定义 Item_Base类对象 base1 
    	Bulk_item b1("《C++》",10,10,0.8);		//定义 Bulk_item类对象 b1
    	cout<<base<<endl;
    	cout<<b1<<endl;
    	Item_Base base2(base);					//定义 Item_Base类对象 base2
    	Bulk_item b2(b1);						//定义 Bulk_item类对象 b2 
    	cout<<base2<<endl;
    	cout<<b2<<endl;
    	cout<<base.book()<<"base.net_price(10):"<<base.net_price(10)<<endl;
    	cout<<b1.book()<<"bi.net_price(10):"<<b1.net_price(10)<<endl;
    	
    	cout<<"--------------------------------------------------"<<endl;
    	Item_Base *pBase1=&base;				//定义指向 Item_Base类对象的指针 pBase1,指向base
    	cout<<"pBase1->net_price(10):"<<pBase1->net_price(10)<<endl;
    	
    	pBase1=&b1;								//指针 pBase1,指向 Bulk_item类对象 b1
    	cout<<"函数重载 (不是多态) :"<<endl;
    	cout<<"*pBase1:"<<(*pBase1)<<endl;		//运算符<<重载(不是多态 
    	cout<<"b1:"<<b1<<endl;
    	cout<<"虚函数(多态): "<<endl;
    	cout<<"pBase1->net_price(10):"<<pBase1->net_price(10)<<endl;//net_price() 多态 
    	cout<<"b1.net_price(10):"<<b1.net_price(10)<<endl;
    	
    	Item_Base *pBase2=pBase1->Clone();		//定义指向 Item_Base类对象的指针 pBase2,指向 pBase1->Clone()
    	cout<<"*pBase2:"<<(*pBase2)<<endl;
    	Item_Base *pBase3=pBase1->Clone();		//定义指向 Item_Base类对象的指针 pBase3,指向 pBase1->Clone() 多态 
    	cout<<"*pBase3:"<<pBase3->net_price(10)<<endl;		
    	Item_Base *pBase4=b1.Clone();			//定义指向 Item_Base类对象的指针 pBase3,指向 b1.Clone()
    	cout<<"*pBase4:"<<pBase4->net_price(10)<<endl;
    	
    	cout<<"--------------------------------------------------"<<endl;
    	//赋值转换 
    	//b2=base2;		//errorn 不能用基类对象对派生类对象赋值 (应为基类中不包含 
    	base2=b2;		//只能用派生类对象对基类对象赋值 
    	cout<< base2.net_price(10)<<endl;
    	cout<< b2.net_price(10)<<endl;
    	//指针转换 
    	Item_Base *pBase5=&base;
    	Bulk_item *pA=static_cast<Bulk_item*>(pBase5); //base
    	printT(*pA);
    	
    	Bulk_item *pB=dynamic_cast<Bulk_item*>(pBase1);//b1
    	printT(*pB);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    运行结果:

    在这里插入图片描述

  • 相关阅读:
    基于测地距离场的三维人脸参数化方法
    一键安装|卸载 mysql 8.2.0 shell脚本
    【C++】string 之 assign、at、append函数的学习
    状态机的技术选型看这篇就够了,最后一个直叫好!!!
    交叉编译tcpdump
    目标检测YOLO系列算法的进化史
    【树莓派触摸屏等学习笔记】
    《吐血整理》高级系列教程-吃透Fiddler抓包教程(24)-Fiddler如何优雅地在正式和测试环境之间来回切换-中篇
    PositiveSSL的泛域名SSL证书
    Express项目
  • 原文地址:https://blog.csdn.net/zxm_jimin/article/details/125792782