浅谈C++继承

来源:互联网 发布:云南管家婆软件总代理 编辑:程序博客网 时间:2024/05/29 14:32

关于C++有三大特性:封装、继承和多态

关于继承我们可以实现代码复用减少工作量,在C++里面所谓继承就是在已经存在的类的基础上建立一个新的类。原有的类我们称为基类(父类),新类我们称为派生类(子类),这个新类从已有的类里获取已有类的特性,这种现象叫做继承

派生类的声明为:class +派生类类名+ :+[继承方式]+基类名

例1、公有继承的方式

#include<iostream>#include<stdlib.h>class Base{public:int _pub;protected:int _pro;private:int _pri;};class Derived:public Base//class +派生类类名+ :+[继承方式]+基类名{private:int _d;};int main(){Derived d;d._pub = 0;system("pause");return 0;}
Derived 这个类继承了基类里面所有的成员,不论是私有成员还是公有成员还是保护的。

这个类是以公有的方式继承的因此基类的公有成员和保护成员在派生类中保持原有的访问属性,而私有成员仍然是基类私有的。

如果我们在类外通过派生类的对象去访问基类里面的保护成员是访问不了的

这也就是在继承的时候保护成员和私有成员的区别。而我们在这个派生类类里面是可以访问基类的保护成员的。

class Derived:public Base    //class +派生类类名+ :+[继承方式]+基类名{public:void Display(){cout << _pro << endl;}private:int _d;};
我们可以看到在在派生类里面是可以访问的,但如果在类外去访问protected成员就相当于访问的是基类的私有成员是非法的。

例2、保护继承的方式:

将例1的继承方式修改成为保护的,我们可以看到:


则在类外也无法通过派生类的对象去访问基类的公有公有成员变量,这是因为以保护的方式继承会将基类的公有成员的访问属性也修改成保护的,这样在类外当然无法去访问。当然在派生类里面依旧无法访问基类的私有成员变量。

例3、私有继承的方式

以私有的继承权限,那么会将基类的保护成员和公有成员的访问属性也修改成私有的,同上述的保护继承的验证方法相似。

因此通过三种继承方式可以总结出:

1、无论是以哪种方式继承,基类私有的还是私有的在派生类里面无权访问

2、保护继承和私有继承都会将基类里面的非私有成员的访问属性修改成与继承方式相同的属性。

在继承还存在同名隐藏,如果我们在基类public里定义了一个成员,在派生类里面也定义了同名的成员,那么在类外创建一个派生类的对象时通过对象去访问这个成员时,它一定是访问的派生类的这个成员而并非基类的成员,它将你基类这个同名的成员隐藏了,你是无法直接去访问的除非加上作用域,函数也是如此,并且同名隐藏和函数的参数返回值均无关,只和名字是否相同有关。

关于继承还需要注意:友元函数不是类的成员函数,因此它不能被继承下来,而静态成员变量或者静态成员函数属于类的它是可以被继承下来的,只是静态的成员变量和静态的成员函数是属于类的成员不属于某个对象的成员因此可以直接通过类名去访问,而不需要用对象去激活它。

以公有的方式继承

如果以公有的方式继承那么基类的对象也就是派生类的对象,因为基类的成员也都是父类的成员

因此可以直接用派生类的对象去给基类的对象赋值

#include<iostream>#include<stdlib.h>using namespace std;class Base{public:int _pub;protected: int _pro;private:int _pri;};class Derived:public  Base    //class +派生类类名+ :+[继承方式]+基类名{public:void Display(){cout << _pro << endl;}Derived(){cout << "Derived" << endl;}private:int _d;};int main(){Derived d;Base b;b = d;system("pause");return 0;}

派生类的对象去给基类的对象赋值编译可以通过而用基类对象给派生类的对象赋值,编译时则会报错。

原因如下图:


如果使用引用的方式或者以指针的方式也是同理

Derived d;Base *b = &d;
Derived d;Base &b = d;

基类和派生类的构造函数和析构函数

构造函数:

在创建一个派生类的对象时通过下图可以看出先调用的是派生类里面的构造函数,因为在派生类的构造函数的初始化列表要对基类的成员初始化因此才去调用基类里面的构造函数,最后又返回到派生类的构造函数,因此它的调用顺序是先调用派生类的构造函数再调用基类的构造函数然后又从基类的构造函数里面返回到派生类的构造函数里面


关于系统为基类和派生类合成的默认的构造函数的规则:

如果在基类里面没有定义构造函数,在派生类里面也没有定义构造函数

如以下代码:

#include<iostream>#include<stdlib.h>using namespace std;class Base{public:int _pub;  /* Base()   {   cout << "Base()" << endl;   }   ~Base()   {   cout << "~Base()" << endl;   }*/protected: int _pro;private:int _pri;};class Derived:public  Base    //class +派生类类名+ :+[继承方式]+基类名{public:void Display(){cout << _pro << endl;}/*Derived(){cout << "Derived()" << endl;}~Derived(){cout << "~Derived()" << endl;}*/private:int _d;};int main(){Base b;Derived d;system("pause");return 0;}
再转到反汇编时发现
Base b;Derived d;
并没有对应的汇编代码说明系统并没有为基类和派生类合成默认的构造函数,因为一般系统是在有需求的时候才会合成默认的构造函数。


如果在基类里面显式的定义了缺省的构造函数,那么在它的派生类里面系统才会合成成默认的构造函数。

如果在基类显式的定义了不是缺省的构造函数,那么在它的派生类里面系统不会合成默认的构造函数。

如果在基类显式的定义了不是缺省的构造函数,在派生类里面定义了缺省的构造函数,那么必须在派生类的构造函数的初始化列表里去显式的调用带参数的基类的构造函数,否则定义基类对象的 类 "Base" 不存在默认构造函数

析构函数:

在析构掉这个派生类的对象的时候也是先调用派生类里面的析构函数,再调用基类里面的构造函数。


析构的原则就是先创建的后析构,后创建的先析构

关于多继承:

#include<iostream>#include<stdlib.h>using namespace std;class Base1{public:int _pub1;   Base1()   {   cout << "Base()" << endl;   }   ~Base1()   {   cout << "~Base()" << endl;   }protected: int _pro1;private:int _pri1;};class Base2{public:int _pub2;   Base2()   {   cout << "Base()" << endl;   }   ~Base2()   {   cout << "~Base()" << endl;   }protected: int _pro2;private:int _pri2;};class Derived:public  Base1,public Base2    //class +派生类类名+ :+[继承方式]+基类名{public:Derived(){cout << "Derived()" << endl;}~Derived(){cout << "~Derived()" << endl;}public:int _d;};int main(){Derived d;int i = sizeof(d);d._pub1 = 1;d._pub2 = 2;d._d = 3;cout << i << endl;system("pause");return 0;};
在多继承的时候
class Derived:public  Base1,public Base2 
Base 2前面一定要加上public否则会认为Base 2以私有的方式继承

关于多继承的模型布局:

多继承的时候

Base1 &_pub = d;Base2 &_pub = d;
看似这两段代码形式一样但根据上图的模型布局可以知晓下面这个Base 2的引用是加了偏移量的。

关于菱形继承:

菱形继承的图示:


关于菱形继承产生的问题:

#include<iostream>#include<stdlib.h>using namespace std;class A{public:A(){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}public:int _a;};class B1:public A{public:B1(){cout << "B1()" << endl;}~B1(){cout << "~B1()" << endl;}   public:int _b1;};class B2 :public A{public:B2(){cout << "B2()" << endl;}~B2(){cout << "~B2()" << endl;}   public:int _b2;};class C :public B1, public B2{public:C(){cout << "C()" << endl;}~C(){cout << "~C()" << endl;}    public:int _c;};int main(){C object;object._a;system("pause");return 0;}
但是在访问A类的成员会出现问题,这是因为B1和B2都继承了A类的成员,然后C同时继承了B1和B2,于是通过C去访问A类中的_a成员时会不明确,不明白是访问的从B1里面继承的_a,还是从B2类里面继承的_a.这就是关于菱形继承产生二义性的问题。

为了解决菱形继承产生的二义性的问题引入了虚拟继承。

如果将派生类B1和派生类B2继承基类A时加上virtual关键字即class B2 :virtual public A,class B1 :virtual public A

这样就可以解决菱形二义性的问题。

但此时求这个object对象的字节大小会发现它的字节大小变成了24


而之前没有加virtual这个关键字的时候object这个对象字节的大小是20.

关于多出来的4个字节其实是虚指针的大小。


可以看到B1类的虚指针偏移20个字节刚好是基类A里的成员_a;

可以看到B2类的虚指针偏移12个字节刚好是基类A里的成员_a;

有关C类的构造函数和普通构造函数的区别:


多了一句push 1,而没有虚拟继承的构造函数是没有push 1的这句反汇编的,



如果是以下代码:

#include<iostream>#include<stdlib.h>using namespace std;class A{public:A(){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}public:int _a;};class B1: public A{public:B1(){cout << "B1()" << endl;}~B1(){cout << "~B1()" << endl;}   public:int _b1;};class B2 : public A{public:B2(){cout << "B2()" << endl;}~B2(){cout << "~B2()" << endl;}   public:int _b2;};class C :virtual public B1, virtual public B2{public:C(){cout << "C()" << endl;}~C(){cout << "~C()" << endl;}    public:int _c;};int main(){C object;object._a=1;object._b1 = 2;object._b2 = 3;object._c = 4;cout << sizeof(object) << endl;system("pause");return 0;}

class C :virtual public B1, virtual public B2
这样依然无法解决菱形继承的二义性它的模型布局:



2 0
原创粉丝点击