继承:单继承、派生类成员的访问属性、多继承、菱形继承、虚继承,菱形虚拟继承
2017-04-08 18:36
489 查看
1,派生类的声明方式
2,派生类成员的访问属性
(1)public(公用继承)
下面用一张表来说明公用基类的成员在派生类中的访问属性
(2)private(私有继承)
将上面的程序改为私有继承
结论:(1)不能通过派生类对象引用从私有基类继承过来的任何成员
(2)派生类的成员函数不能访问私有基类的私有成员,但可以访问私有类的公用成员函数
(3)protected(保护继承)
保护基类的公有成员和保护成员在派生类中都成了保护成员,其私有成员仍为基类私有,也就是把基类原有的公有成员也保护起来,不让类外任意访问。
3,多级派生时的访问属性
各成员在不同类中的访问属性
无论哪一种继承方式,在派生类中是不能访问基类的私有成员的,私有成员只能被本类的成员函数访问。
4,派生类的构造函数和析构函数
先定义一个简单的派生类的构造函数
分别在两个构造函数和两个析构函数处大断点,对程序进行调试,打印的结果如图
从结果上看,程序是先调用了基类的构造函数,再调用派生类的构造函数,程序结束后,先析构派生类,再析构基类。
事实上,从程序的调试过程可以看出,程序是先调用派生类的构造函数,此时,执行调用基类构造函数完成对基类的初始化,然后再继续执行派生类构造函数的函数体。此处可上机实践进行验证。
若派生类中有子对象,则在派生类构造函数中对子对象进行初始化。因此,执行派生类构造函数的次序是
(1)调用基类构造函数,对基类数据成员初始化
(2)调用子对象构造函数,对子对象数据成员初始化
(3)再执行派生类构造函数本身,对派生类数据成员初始化
派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。
5,多重继承引起的二义性问题及同名覆盖
编译系统无法判别要访问的是哪个基类的成员,编译出错,这就是多重继承存在的二义性问题。
如果将C类改为如下形式
此时编译通过,那么程序执行时访问的到底是哪一个类中的成员?
此时,访问的是C类的成员,规则:基类的同名成员在派生类中被屏蔽,成为“不可见”的,或者说,派生类新增加的同名成员覆盖了基类中的同名成员。
注意:不同的成员函数,只有在函数名和参数个数相同,类型匹配的情况下才发生同名覆盖
6,菱形继承及虚基类
菱形继承时多重继承中的一种继承方式,也存在二义性问题
这样的继承方式成为菱形继承
从上图看出,不同类中相同的成员在D中产生了多份拷贝,不仅占用内存,而且对访问这些成员增加困难。
下面我们介绍虚基类
虚基类可使得在继承间接共同基类时只保留一份成员
将上面的代码改为
之前学习继承多态的时候学的是似懂非懂的,所以这篇博客内容也只是一些概念性的东西,并未深入探索,再回过头学习时,以前模棱两可的东西突然就明白了一些,所以决定将这篇博客更新。
菱形继承中含有虚函数:
我们通过内存窗口来查看D的虚表:
可以证明,多继承时,子进程中新增的虚函数是写入了第一个继承的父类的虚表中。
菱形虚拟继承中含有虚函数:
通过上图可以看出,菱形虚拟继承中,为了避免二义性,公共父类A有单独的虚表,且图中有两张表,一个是虚基表,里面保存的是虚继承中子类的偏移量,而虚表中保存的是虚函数。
公共数据a在D对象的最底层,且B类和C类中均包含A类,所以的它们的大小为虚表指针+虚基表指针+各自数据大小+A类中公共数据大小。
class Student { public: void display() { cout << "num:" << num << endl; cout << "name:" << name << endl; cout << "sex:" << sex << endl; } private: int num; char name[10]; char sex; }; class Student1 : public Student//声明派生类 { public: void display_1() { cout << "age:" << endl; cout << "address:" << addr << endl; } private: int age; char addr[10]; };
2,派生类成员的访问属性
(1)public(公用继承)
class student { public: void getvalue() { cin >> num >> name >> sex; } void display() { cout << "num:" << num << endl; cout << "name::" << name << endl; cout << "sex:" << sex << endl; } private: int num; char name[10]; char sex[5]; }; class student1:public student { public: void getvalue_1() { getvalue(); cin >> age>>addr; } void display_1() { display();//调用基类公有成员函数来访问基类私有成员变量 cout << "num:" << num << endl;//报错,基类私有成员不可访问 cout << "age:" << age << endl; cout << "addr:" << addr << endl; } int age; char addr[10]; }; int main() { Student1 stud; stud.getvalue_1(); stud.display_1(); system("pause\n"); return 0; }
下面用一张表来说明公用基类的成员在派生类中的访问属性
(2)private(私有继承)
将上面的程序改为私有继承
class student { public: void getvalue() { cin >> num >> name >> sex; } void display() { cout << "num:" << num << endl; cout << "name::" << name << endl; cout << "sex:" << sex << endl; } private: int num; char name[10]; char sex[5]; }; class Student1 :private student { public: void getvalue_1() { getvalue(); cin >> age >> addr; } void display_1() { display(); //cout << "num:" << num << endl; cout << "age:" << age << endl; cout << "addr:" << addr << endl; } private: int age; char addr[10]; }; int main() { Student1 stud; //stud.getvalue();//报错,私有基类的公用成员函数在外界不可访问 stud.display_1(); //stud.age = 18;//错误,外界无法访问派生类的私有成员 system("pause\n"); return 0; }
结论:(1)不能通过派生类对象引用从私有基类继承过来的任何成员
(2)派生类的成员函数不能访问私有基类的私有成员,但可以访问私有类的公用成员函数
(3)protected(保护继承)
class student { private: void display(); protected: int num; char name[10]; char sex[5]; }; class Student1 :protected student { public: void getvalue_1() { cin >> num >> name >> sex; cin >> age >> addr; } void display_1() { cout << "num:" << num << endl; cout << "name:" << num << endl; cout << "sex:" << num << endl; cout << "age:" << age << endl; cout << "addr:" << addr << endl; } private: int age; char addr[10]; }; int main() { Student1 stud; stud.display_1(); //stud.display();//报错,保护基类的公用成员在外界不可访问 stud.display_1(); //stud.age = 18;//报错,基类的保护成员对派生类的外界来说是不可访问的 system("pause\n"); return 0; }
保护基类的公有成员和保护成员在派生类中都成了保护成员,其私有成员仍为基类私有,也就是把基类原有的公有成员也保护起来,不让类外任意访问。
3,多级派生时的访问属性
class A { public: int i; protected: void f1(){} int j; private: int k; }; class B :public A { public: void f2(){} protected: void f3(){} private: int m; }; class C :protected B { public: void f4(); private: int n; }; int main() { C c; B b; b.i = 10;//可访问 //c.f2();//不可访问 }
各成员在不同类中的访问属性
无论哪一种继承方式,在派生类中是不能访问基类的私有成员的,私有成员只能被本类的成员函数访问。
4,派生类的构造函数和析构函数
先定义一个简单的派生类的构造函数
class Student { public: Student(int n, string nam, char s)//定义基类构造函数 :num(n), name(nam), sex(s) { cout << "Student()" << endl; } ~Student() { cout << "~Student()" << endl; } protected: int num; string name; char sex; }; class Student1 :public Student { public: Student1(int n, string nam, char s, int a, string add)//定义派生类构造函数 :Student(n, nam, s), age(a), addr(add)//在初始化列表初始化 { cout << "Student1()"<<endl; } void show() { cout <& 1027b lt; "num:" << num << endl; cout << "name:" << name << endl; cout << "sex:" << sex << endl; cout << "age:" << age << endl; cout << "add:" << addr << endl; } ~Student1() { cout << "~Student1()" << endl; } private: int age; string addr; }; int main() { Student1 stud1(1000, "wangli", 'n', 19, "shanxikejidaxue"); Student1 stud2(1001, "liuyulin", 'n', 18, "xibeihzengfadaxue"); stud1.show(); cout << endl; stud2.show(); //system("pause\n"); return 0; }
分别在两个构造函数和两个析构函数处大断点,对程序进行调试,打印的结果如图
从结果上看,程序是先调用了基类的构造函数,再调用派生类的构造函数,程序结束后,先析构派生类,再析构基类。
事实上,从程序的调试过程可以看出,程序是先调用派生类的构造函数,此时,执行调用基类构造函数完成对基类的初始化,然后再继续执行派生类构造函数的函数体。此处可上机实践进行验证。
若派生类中有子对象,则在派生类构造函数中对子对象进行初始化。因此,执行派生类构造函数的次序是
(1)调用基类构造函数,对基类数据成员初始化
(2)调用子对象构造函数,对子对象数据成员初始化
(3)再执行派生类构造函数本身,对派生类数据成员初始化
派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。
5,多重继承引起的二义性问题及同名覆盖
class A { public: int a; void display(); }; class B { public: int a; void display(); }; class C :public A, public B { public: int b; //void show(); }; int main() { C c; c.a = 10;//报错 c.display();//报错 }
编译系统无法判别要访问的是哪个基类的成员,编译出错,这就是多重继承存在的二义性问题。
如果将C类改为如下形式
class C :public A, public B { public: int a; void display(); }; int main() { C c; c.a = 10;//编译通过 c.display();//编译通过 }
此时编译通过,那么程序执行时访问的到底是哪一个类中的成员?
此时,访问的是C类的成员,规则:基类的同名成员在派生类中被屏蔽,成为“不可见”的,或者说,派生类新增加的同名成员覆盖了基类中的同名成员。
注意:不同的成员函数,只有在函数名和参数个数相同,类型匹配的情况下才发生同名覆盖
6,菱形继承及虚基类
菱形继承时多重继承中的一种继承方式,也存在二义性问题
这样的继承方式成为菱形继承
class A { public: int data; void fun(){} }; class B :public A { public: int data; void fun(){} int _b; }; class C :public A { public: int data; void fun(){} int _c; }; class D :public B, public C { public: int _d; void fun_d(){} };
从上图看出,不同类中相同的成员在D中产生了多份拷贝,不仅占用内存,而且对访问这些成员增加困难。
下面我们介绍虚基类
虚基类可使得在继承间接共同基类时只保留一份成员
将上面的代码改为
class A {}; class B:virtual public A {}; class C:virtual public A {};
之前学习继承多态的时候学的是似懂非懂的,所以这篇博客内容也只是一些概念性的东西,并未深入探索,再回过头学习时,以前模棱两可的东西突然就明白了一些,所以决定将这篇博客更新。
菱形继承中含有虚函数:
class A { public: virtual void Fun1() { cout << "A::Fun1" << endl; } virtual void Fun2() { cout << "A::Fun2" << endl; } int _a; }; class B :public A { public: virtual void Fun1() { cout << "B::Fun1" << endl; } virtual void Fun3() { cout << "B::Fun3" << endl; } int _b; }; class C :public A { public: virtual void Fun1() { cout << "C::Fun1()" << endl; } virtual void Fun4() { cout << "C::Fun4" << endl; } int _c; }; class D :public B, public C { public: virtual void Fun1() { cout << "D::Fun1" << endl; } virtual void Fun5() { cout << "D::Fun5" << endl; } int _d; }; //打印虚表 typedef void(*V_FUNC) (); void PrintVTable(int* vtable) { printf("vtable 0x%p\n", vtable); int** ppVtable = (int**)vtable; for (size_t i = 0; ppVtable[i] != 0; ++i) { printf("vatable[%u]:0x%p->", i, ppVtable[i]); V_FUNC f = (V_FUNC)ppVtable[i]; f(); } cout << "——————————————————————————————" << endl; } void Test1() { D d; d.B::_a = 1; d._b = 2; d.C::_a = 3; d._c = 4; d._d = 5; PrintVTable(*((int**)&d));//第一个虚表B的 PrintVTable(*((int**)((char*)&d + sizeof(B))));//C的虚表 } int main() { Test1(); system("pause"); return 0; }
我们通过内存窗口来查看D的虚表:
可以证明,多继承时,子进程中新增的虚函数是写入了第一个继承的父类的虚表中。
菱形虚拟继承中含有虚函数:
class A { public: virtual void Fun1()//去掉Func1崩溃,因为BC是同级的。虚继承不明确 { cout << "A::Fun1" << endl; } virtual void Fun2() { cout << "A::Fun2" << endl; } int _a; }; class B :virtual public A { public: virtual void Fun1() { cout << "B::Fun1" << endl; } virtual void Fun3() { cout << "B::Fun3" << endl; } int _b; }; class C :virtual public A { public: virtual void Fun1() { cout << "C::Fun1()" << endl; } virtual void Fun4() { cout << "C::Fun4" << endl; } int _c; }; class D :public B, public C { public: virtual void Fun1() { cout << "D::Fun1" << endl; } virtual void Fun5() { cout << "D::Fun5" << endl; } int _d; }; typedef void(*V_FUNC) (); void PrintVTable(int* vtable) { printf("vtable 0x%p\n", vtable); int** ppVtable = (int**)vtable; for (size_t i = 0; ppVtable[i] != 0; ++i) { printf("vatable[%u]:0x%p->", i, ppVtable[i]); V_FUNC f = (V_FUNC)ppVtable[i]; f(); } cout << "——————————————————————————————" << endl; } void Test1() { D d; d.B::_a = 1; d._b = 2; d.C::_a = 3; d._c = 4; d._d = 5; //(*(int**)&d)解引用二级指针,在32位和64位下均能正确使用 PrintVTable(*((int**)&d));//第一个虚表B的 PrintVTable(*((int**)((char*)&d + sizeof(B)-sizeof(A))));//C的虚表 PrintVTable(*((int**)((char*)&d + sizeof(D)-sizeof(A))));//A的虚表 //B的虚表里其实是含有A的 cout << sizeof(A) << endl; cout << sizeof(B) << endl; cout << sizeof(C) << endl; cout << sizeof(D) << endl; B b; b._a = 10; b._b = 11; } int main() { Test1(); system("pause"); return 0; }
通过上图可以看出,菱形虚拟继承中,为了避免二义性,公共父类A有单独的虚表,且图中有两张表,一个是虚基表,里面保存的是虚继承中子类的偏移量,而虚表中保存的是虚函数。
公共数据a在D对象的最底层,且B类和C类中均包含A类,所以的它们的大小为虚表指针+虚基表指针+各自数据大小+A类中公共数据大小。
相关文章推荐
- C++的继承操作---基类指针访问派生类问题---基类成员恢复访问属性问题
- C++基础(八)继承与派生——派生类成员的访问属性
- [转]c++类继承中的using声明,派生类中用using声明改变基类成员的访问权限
- 理解基类中成员的访问限定符和派生类的继承方式
- 第十二周C++【任务1】理解基类中成员的访问限定符和派生类的继承方式
- 第十三周项目一—理解基类中成员的访问限定符和派生类的继承方式
- 在继承中派生类成员的访问权限测试
- 鸡啄米:C++编程入门系列之三十七(继承与派生:派生类对基类成员的访问控制之公有继承)
- 第十二周实验报告(任务一)【派生类成员的访问属性的原则】
- c++类继承中的using声明,派生类中用using声明改变基类成员的访问权限[zz]
- 5月8日实验报告(一)理解基类成员的访问限定符和派生类的继承方式
- 第十三周 项目一:理解基类中成员的访问限定符和派生类的继承方式
- 第十二周任务一理解基类中成员的访问限定符和派生类的继承方式
- 在继承中派生类成员函数的访问权限测试
- 派生类可以恢复继承成员的访问级别, 但不能使访问级别比基类中原来指定的更严格或更宽松 [C++Primer Page484 : 2去除个别成员]
- 第十二周实验指导--任务1--理解基类中成员的访问限定符和派生类的继承方式
- 《C++第十二周实验报告1-1》----理解基类中成员的访问限定符和派生类的继承方式
- 理解基类中成员的访问限定符和派生类的继承方式
- 第十三周上机任务项目1-理解基类中成员的访问限定符和派生类的继承方式
- 在继承中派生类成员函数的访问权限测试