C++ class 基础学习分享总结(2) -- 类继承

本文详细阐述了C++中的类继承、构造函数的使用与调用规则、函数联编(静态和动态)、虚函数、多态继承、纯虚函数与抽象基类的概念,以及类指针和继承与动态内存分配、友元函数和多重继承中的常见问题及解决方案。
摘要由CSDN通过智能技术生成

类继承

基类 
    父类 被别人继承的类
派生类 
    子类 继承别人的类
    需要自己的构造函数 
    可以添加额外的属性和方法
继承 
    class Cson : public C1 {}  
    class Cson : private C1 {} 
    class Cson : protected C1 {}  
    class Cson : C1 {}  // 默认 private
    详细解释见下面
函数联编 
   静态联编 
       编译过程中确定使用那个函数代码块的过程 编译时分配内存 
       如函数重载,普通函数调用 效率高 只能编译时确定
   动态联编
       运行过程中确定使用那个函数代码块的过程 运行时分配内存
       如虚函数 效率低 但是可以运行时决定某些事
   以上两种联编模式是为了考虑到效率和概念模型 
       效率      静态 > 动态 
       概念模型   在设计类时,能静态的就静态,部分动态效率会更高
构造函数 
   C1中的构造 和子类中的构造一起使用才是初始化完成
       Cson::Cson(int a,int b):C1(a),numb(b){
       }
   如果没有调用基类的构造函数,则等同于调用了基类的默认构造函数 
       Cson::Cson(int a,int b) : numb(b){
       }
   所以除非是使用默认构造函数 否则都应该显示调用基类构造函数 
构造和析构执行顺序
   创建时 先调用基类的构造函数 再调用派生类的构造函数 
   释放时 先调用派生类的析构函数 再调用基类的析构函数

继承关系

is-a 继承 
	派生类是一个基类对象 例如 香蕉是水果(午饭有香蕉,香蕉不是午饭) 
	需要重写父类某些方法 
	公有继承 public 
	    class Cson : public C1 {}  
	    基类的公有成员是派生类的公有 私有是私有 保护是保护 
	    
	    应用场景 
	        实例化对象 可以直接用基类公有成员
	
	私有继承 
	    class Cson : private C1 {} 
	    class Cson : C1 {}  // 默认 private
	
	    基类的公有成员和保护成员将成为派生类的私有成员。
	    所以基类的公有方法将不再是派生类的公有方法。 只能在派生类的成员方法中调用 
	
	    应用场景 
	        实例化对象 不能直接用基类的任何成员
	
	保护继承 
	    class Cson : protected C1 {} 
	
	    保护继承 是私有继承的变体  
	    使用保护继承时 基类的公有成员和保护成员将成为派生类的保护成员 其他同私有继承
	    与私有继承的区别在于第三代, 私有继承第三代则不能再使用,保护继承可以使用
	继承 使用using 定义访问权限 
	    public:
	        using std::valarray<int>::min;
	
	    这样的写法 使得该类可以使用min方法 
	        c1.min() 等同于 std::valarray<int>::min();
typedef std::vector<int> vlist;
// 类定义 
class C1 : private std::string,private vlist {}   
// 构造函数 实现        
C1(char* str,int n) : std::string(str),vlist(n) {};  
// 成员方法中使用
std::string getStr() const { return (const std::string&)*this; }  
// 成员方法中使用   
int C3::fn1() const {  return vlist::size();}    
	// 使用的是类名+:: 而不是变量名
has-a 组合 复合
	一个新的类包含另一个类
	组合
	    class C1 {
	        public :
	            std::string name; // C1类中 包含std::string 类 
	    }
	
	复合
	    class C1 {
	        public :
	            std::string* name; // C1类中 包含std::string 类的指针 
	    }
typedef std::vector<int> vlist; 
// 类定义
class C1 {
	private:  
		std::string name;
		vlist l1;   
	public:
		std::string getStr() const { return  name; }
		int fn1(){ return l1.size(); }		
}

多态继承

同一个方法在基类和派生类中行为不同,具有多种形态,有两种实现方法 
	派生类中重写基类方法 override 
	虚函数 virtual  主要

virtual 虚函数

虚函数的作用是 指出该方法在基类和派生类行为不同
通过指针或引用调用虚函数将根据是具体哪个类的对象 用哪个类的方法 
函数形参为基类指针或引用 传入实参后 调用virtual方法会根据具体类而定
// 示例
C1 c1;
C1son c2;
C1 & q_c1 = c1;
C1 & q_c2 = c2;
q_c1.show();
q_c2.show();  //show 方法是否有virtual关键字 行为不一样 
/*
如果show 没有 virtual 关键字 
    会根据引用和地址的类型确定 
    c1 c2 都调C1类的show方法
如果show 有 virtual关键字 
    会根据实际指向的对象的类型 确定方法 
    c1 调C1 c2 调C2
*/
要求和注意点 
	派生类会重新定义的方法需要加virtual 变成虚函数 否则设置为非虚方法
	在基类中声明为虚函数,在派生类中也默认,但派生类中显示说明更好 
	通常为基类的析构函数声明成一个虚函数,也是一个好做法 
	只用在原型中, 定义中不用加virtual关键字 
	析构函数应尽可能是虚函数,除非不发生继承关系(没有错误只是效率问题)
	    也叫虚析构 
	    有一个都是父类指针的数组,可能指向父类,也可能指向子类
	    在释放时  
	        如果是普通析构函数,则可能会发生先调用父析构再调用子析构的错误
	        如果是虚析构函数, 则会根据指向对象的类型确定释放顺序
	构造函数,友元函数不能是虚函数 
	如果派生类没有重新实现虚方法,则将调用上一级最先碰到实现的虚函数
	子类新定义一个 不同参数 同名函数的虚函数,可能会引起各种问题 
	    1 基类虚函数被隐藏
	    2 子类并没有虚函数重载  
	    3 返回值 基类返回基类 子类返回子类是可以的 
	基类实现了虚重载 子类需要将全部版本都实现一次,否则会隐藏没实现部分 
	    例如
	    基类实现了 
	        virtual f1(int) 
	        virtual f1(char) 
	        virtual f1(short)
	    派生类则应该全部实现一次,只实现一个 则另外两个会被隐藏 
派生类调用基类方法 
C1Son::show(){
    C1::show(); 如果需要调用基类的方法,需要加上::运算符
    ...
}
C1::Son::getName(){
    getAge(); // 在派生类中没有重新定义基类方法 则可以直接调用基类方法(此时其实是继承过来了)
} 
纯虚函数
   当类中有 纯虚函数时 则该类将不能被实现,只能通过继承实现子类,此种叫抽象基类
   virtual int fn1() const = 0
       在定义结尾处 添加 =0
   提供未实现的函数,需要在子类中实现 
抽象基类
	A类和B类有很多共同点,则抽象出一个公共基类C 再分别派生出A,B;
	C类中包含
	    1 共同的属性 
	    2 共同的方法 
	    3 同样的方法,不同的实现 --- 纯虚函数
	需要至少包含一个纯虚函数,当类中包含了一个纯虚函数时,则不能创建该类的对象
	    纯虚函数的类只用做基类 
	将任意方法定义为抽象的 则该类都是抽象类 C++中  在头文件中 定义成一个抽象的 在定义中进行实现也是可以的
	    void fn(int a) = 0;
	    void C1::fn1(int a){num = a};
	    C1 类为抽象类 虽然没有纯虚函数 
	头文件 virtual int fn1() = 0;
	源文件 int C1::fn1(){}
	这样做 可以通过一个基类 同时管理 多个子类 ,同时确保了子类至少包含基类的指定功能。
	经典示例 
	    普通用户和会员 
类指针和引用 
	基类指针 可以在不进行显式类型转换的情况下指向派生类对象;
	基类引用 可以在不进行显式类型转换的情况下引用派生类对象;
	    Cson cs1 = Cson(1)
	    C1 & q_c1 = cs1;   // 基类引用可以直接指向派生类
	    C1 * p_c1 = *cs1;  // 基类指针可以直接指向派生类
	    但是基类的指针和引用 指向派生类时 只能使用基类的方法,不能使用派生类新创建的方法 
	所以函数形参为基类引用或指针时 可以传递基类或派生类 
继承与动态内存分配 
	情况1 基类使用了new 派生类不使用new  
	    基类使用new 需要处理好 构造,复制,赋值,析构构造函数
	    派生类不使用new 则无需再处理以上4个构造函数 
	情况2 基类使用new 派生类使用new 
	    此时则必须为派生类显示定义 析构,复制,赋值,函数 
	    析构 对自身新创建的new进行delete 
	    复制 必须调用基类的复制构造函数,来处理基类数据 
	        sonDMA::sonDMA(const sonDMA & s1):DMA(s1){
	
	        }
	    赋值  
	        sonDMA& sonDMA::operator=(const sonDMA & s1){
	            if(this == &s1)return *this; // 1 = 1 直接返回1
	            DMA::operator=(s1); // 这句话必须显示定义 为调用基类赋值运算符 
	            ...
	        }
继承与友元函数 
   派生类如何访问基类的友元
       答 使用基类的友元 
   因为友元函数不是成员函数吗,所以不能用作用域解析符
       答 使用强制类型转换 
       os << (baseDMA &) hs;  

多重继承

多重继承 需要谨慎使用 
问题1  从两个不同的基类继承同名方法
    重写一个方法,指定使用哪个类的方法 
    void fn(){
        C1::fn();
    }
问题2 从两个或更多相关基类那里继承同 一个类的多个实例
    一个示例 
        以下继承关系
        class c1 : public cf;
        class c2 : public cf;
        class cPlus : public c1,public c2;

    此时 cPlus 将有两个cf类 会有重复错误 
        cf* p = &cPlush; // 指针不知道指向哪里 (c1的cf 还是c2的cf)
    解决办法 
        cf* p = (c1*) &cPlush;  强制转换指定 
    解决办法2  虚基类 virtual 
        从多个类派生出的对象 只继承一个基类对象
        // 顺序没有关系 多个类 需要有 virtual 关键字 
        class c1 : virtual public cf {}
        class c2 : public virtual cf {}
        class cPlus : public c1,public c2;
            此时不会发生多个基类的问题 
问题3  构造函数问题 
    基于问题2 产生问题3 
    cPlus(1,2,3):cf(2),c1(2,3),c2(2,3){
        // 此时需要显示使用虚基类构造函数 在初始化成员列表中
        // 这是规定 原因也是c1 和c2 调用父构造函数重复问题 
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hamburgerV

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值