c++:继承相关的要点热点,以及菱形继承的底层实现
来源:互联网 发布:泸州古蔺网络问政平台 编辑:程序博客网 时间:2024/06/06 08:02
一.3种继承关系下基类成员在派生类的访问关系变化:
这个关系表,我相信在大学里学过c++的朋友们来说,一定不陌生,期末考试前,老师会说:”啊~,这是重点啊,给我好好背。”
但是这个需要背吗?我认为根本不需要,这就是访问关系的变化,可以理解为访问权限的缩小,总的来说:
1.基类的私有成员在派生类不可访问,如果基类成员不想在类外被访问但要在派生类中访问,就定义为保护成员。(所以保护成员限定符是因为继承而出现)
2.不论什么继承方式,在派生类都可以访问基类的公有成员和保护成员,基类的私有成员存在但不可见。
3.公有继承是is-a原则,一个派生类就是一个基类。
私有保护继承是has-a原则,基类的部分成员并未完全成为了子类接口的一部分。
二:赋值兼容规则:前提是公有继承 is-a
class Person{public: int _idcard;//身份证};class Student : public Person{public: int _stuid;//学号};class Teacher : public Student{public: int _teaid;//工号};
简单来说,Student继承了Person,Teacher继承了Student(某位研究生,既是学生,也是一些本科生的老师)
1.子类给父类
void test1(){ Teacher t; t._idcard = 111; t._stuid = 222; t._teaid = 333; Student s; s = t;//把子类赋值给父类,可以,切片 Student* p = &t;//把子类的地址传给父类的指针,可以,切片 Student& r = t;//把子类传给父类的引用,可以,切片}
如图:
2.父类给子类:
t = s;
Teacher* p1 = (Teacher*)&s;
Teacher& r1 = (Teacher&)s;
总结来说:
1.子类对象可以赋值给父类对象,父类的指针/引用可以指向子类对象。
2.父类对象不能赋值给子类对象,子类的指针/引用不能指向父类对象(强转可以,但注意不要越界)。
三;隐藏:父类和子类可以定义同名成员,子类成员屏蔽了父类对成员的直接访问
class A{public: int _x;};class B : public A{public: int _x;//该成员与父类的成员名字一样};
这时候就构成了隐藏:
void test1(){ B b; b._x = 10;//子类成员屏蔽了父类对成员的直接访问,所以修改的是子类的_x。}
若想改变父类的_a,需要:
b.A::_x = 20;//这样就对父类的_a进行了访问
下面来看一道据说%90的人都会做错的题目:
class A{public: void f() { cout << "A" << endl; }public: int _x;};class B : public A{public: void f(int a) { cout << "B" << endl; }public: int _x;};void test1(){ B b; b.f();}
改程序输出什么?
首先如果子类的成员函数如果不传参,那么好办,一定输出”B”,现在要传参,既然test里没有传,那就调用父类的喽,那就大错特错了。
答案是: error C2660: “B::f”: 函数不接受 0 个参数.
我要做出解释:
首先,隐藏对于成员函数,不看参数只管函数名!,所以它调用的还是子类的成员函数,那就会出错。
要想调用父类的成员函数,还要不出错,需要:
b.f(10); b.A::f();
所以一定要牢记概念。
四:派生类默认成员函数
class Person{public: Person(const char* name = "") :_name(name) { cout << "父类构造函数"<<endl; } Person(const Person& p) :_name(p._name) { cout << "父类拷贝构造函数" << endl; } Person& operator=(const Person& p) { if (this != &p) { this->_name = p._name; cout << "父类operator="<<endl; } return *this; } ~Person() { cout << "父类析构函数" << endl; }protected: string _name;};
首先,这是一个简单父类,写出了构造,析构,拷贝构造,operator=。
一个子类继承了它:
class Student : public Person;
要实现子类的默认成员函数,要知道,实现子类之前,要先将父类的实现:
构造:
Student(const char* name = "",int id = 1) :Person(name)//先实现父类的构造 , _id(id)//再初始化子类的 { cout << "子类构造函数" << endl; }
拷贝构造:
//s2(s1) Student(const Student& s) :Person(s)//先拷贝父类的 ,_id(s._id) { cout << "子类拷贝构造函数" << endl; }
operator=:
// s1 = s3; Student& operator=(Student& s) { if (this != &s) { Person::operator=(s);//先赋值父类的,传this和s this->_id = s._id;//再赋值子类的 cout << "子类operator=" << endl; } return *this; }
析构函数:次函数较为特殊,虽然~Person()与~Student函数名不同,但是析构函数是特殊的函数,编译器编译时会都变为destucter,所以构成隐藏!并且析构函数满足后进先出,先析构子类,后析构父类。
程序验证:
//子类析构 …… ~Student() { cout << "子类析构函数" << endl; }};void test2(){ Student s;}
所以说,子类析构函数,不要调用父类的析构,若显示调,反而出错甚至崩溃。
五:写一个不能被继承的类。
拿到这个问题,想了一下很简单,直接把这个类的构造函数写成私有的,那么在其他类的无法实例出对象,就不能被继承。
可是这是一个杀敌一万自损八千的办法,因为这个类就不能实例出对象,那不是个花瓶吗?
class A{private: A(int a = 10) :_a(a) {}private: int _a;};class B : public A{public: B(int b = 10) :A(b) , _b(b) { }private: int _b;};//错误是:error C2248: “A::A”: 无法访问 private 成员(在“A”类中声明)
所以现在的问题是,如何既能不被继承,又能实例化出对象。
我有两个方法:
1.类里写一个静态函数(无this),传int型数据,返回A(a),返回一个对象,然后新建一个对象用返回值初始化,等于是调用了拷贝构造函数。
class A{public: static A GetObj(int a) { return A(a); }private: A(int a = 10) :_a(a) {}private: int _a;};void test(){ A a(A::GetObj(10));//拷贝构造}
2.在类里写一个静态函数(无this指针),参数是int型,返回类型是A*,返回一个new A(a),然后用一个A*类型的指针指向返回的地址。
…… static A* GetObj(int a) { return new A(a); }………… A* ptr = A::GetObj(10);
六:菱形继承及底层实现
首先明确一个概念,单继承和多继承:
所谓菱形继承,就是;
B继承了A,C继承了A,D继承了B和C,构成了一个像菱形模型。
代码实现;
class A{public: int _a;};class B : public A{public: int _b;};class C : public A{public: int _c;};class D : public B, public C{public: int _d;};
这样会出问题,什么问题呢,简单来说,就是二义性和数据冗余。
D d;
那么对象d里既有父类B的_a,又有父类C的_a,造成了数据冗余和二义性。
void test1(){ D d; d._a = 1}
就会出错:error C2385: 对“_a”的访问不明确
想要访问明确的_a,还需:
d.B::_a = 1; d.C::_a = 2;
但有时候这种情况是不现实的,比如如果_a是身份证号,一个人就不可能有两个身份证号码。
为了解决二义性和数据冗余,采用了虚继承——virtual。
class B : virtual public A;
class C : virtual public A;
也就是将代码稍作修改:
class A{public: int _a;};class B : virtual public A{public: int _b;};class C : virtual public A{public: int _c;};class D : public B, public C{public: int _d;};
这样改变一个另一个也会变:
需要注意的是,virtual加的位置。
没有virtual时,sizoef(d) = 20;这很好理解,他一共有5个成员(_a,_b,_a,_c,_d),按照逻辑,加上virtual,那么sizeof(d)y应该是16,可以确是24,这是怎么回事呢?
我们应该从内存的角度看,因为监视有时候是化妆的,但内存是素颜。
首先,做一下赋值:
D d; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; d._d = 5;
取d的地址,得到;
那么我可以猜想,最上面两个是B,中间两个是C,最后是A,也就是
那么菱形虚继承的模型就是:
那么为什么是这样,B里的00 d7 df 80和C里的00 d7 d9 88又是什么?
再看内存2:
第一个是00 d7 df 80,第二个是00 d7 d9 88。里面有两个地址,其实是这样的:
经过计算,存的20,12就是偏移量,距离_a的距离。
分别是+20字节到A,+12字节到A。
虽然此例中字节数比不加virtual时更大了,但是如果sizeof(A)特别大时,则会节省空间,节省的字节数为:
sizeof(A)-8。
现在这些问题我相信你已经有了答案:
1.什么是菱形继承
2.菱形继承有什么问题
3.怎么解决这个问题
4.编译器是如何解决的
- c++:继承相关的要点热点,以及菱形继承的底层实现
- 菱形继承引发的问题和解决方案,以及底层实现的原理.
- 菱形继承与菱形虚拟继承的相关问题
- 【C++】菱形继承与虚拟菱形继承的对比分析
- (C++)继承、菱形继承和虚继承的那些事儿
- 菱形的虚拟继承
- 菱形继承的识别
- 菱形继承的内部实现方式
- c++-关于菱形继承&虚继承的问题总结
- 虚继承初始化的底层实现
- 【C++】继承(菱形继承)
- <c++>继承及菱形继承
- 菱形结构的多重继承
- 菱形虚拟继承和菱形继承的对比
- 菱形继承和菱形虚继承的对象模型
- c++ --------- 多态下的菱形继承,菱形虚拟继承
- 【C++】菱形继承
- 【c++】菱形继承
- 连续总结第二十四天
- Jmock 原理简单说明
- 【Unity Shader入门精要】— 中级篇
- 小厚的三角形(SDUT 3559)
- 深入解析Linux 常用命令--ls
- c++:继承相关的要点热点,以及菱形继承的底层实现
- Python 序列之列表的独有的基本操作
- layer.msg弹出会关闭layer.open弹出的窗口问题
- maven学习笔记1
- HTML学习总结
- Python笔记4:控制流
- linux一些稍微高级的命令
- C++宏总结
- hadoop Shell命令详解