C++继承总结
来源:互联网 发布:淘宝链接前加了s.click 编辑:程序博客网 时间:2024/05/21 12:42
继承:继承是面向对象复用的重要手段,通过继承定义一个类,继承是关系类型之间的建模,
共享共有的东西,实现各自本质不同的东西。
在继承关系当中,派生类继承父类的成员由此达到复用手段。
public继承是一个接口继承,保持is-a原则,每个父类可用的成员,对于子类也都可用。
因为每个子类对象也都是一个父类成员
protected/private继承是一个实现继承,是has-a的原则,基类的部分成员函数并未
完全的成为子类接口的一部分。
通俗的来讲:is-a表示了一种是的关系;例如 白马是马,香蕉是水果的这种关系。
has-a 表示了有的关系,例如:午餐有香蕉,但是香蕉不是午餐,而是午餐的一种
继承与转换–赋值兼容规则–public继承
- 子类对象可以赋值给父类对象(切割/切片)
- 父类对象不可以赋值给子类对象
- 父类的指针和引用可以指向子类对象
- 子类的指针和引用不可以指向父类对象(可以通过强制类型转换)
继承体系中的作用域
- 在继承体系当中基类和派生类都有自己独立的作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问,
(在子类当中,可以使用基类::基类成员 访问),隐藏重定义。 - 在实际中在继承体系当中最好不要定义同名成员。
例如:
#pragma onceclass Person{public: Person(const char* name = "", int num = 0) :_name(name) , _num(num) {}protected: string _name; int _num; //身份证号};class Student :public Person{public: Student(const char* name, int id, int sum) :Person(name, id) , _num(sum) {} void Print() { cout << "身份证号:" << Person::_num << endl; cout << "学号:" << _num << endl; }protected: int _num;};void Testper(){ Student s("zahngsan", 120, 11); s.Print();}
单继承&多继承
1. 单继承–一个子类只有一个直接父类时称这个继承关系为单继承
2. 多继承– 一个子类有两个或以上直接父类时称这个继承关系为多重继承
菱形继承是典型的多继承
菱形继承:两个子类同时继承一个父类,而又有子类同时继承这两个子类
菱形继承在内存中的布局:
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>using namespace std;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;};int main(){ D dd; dd.B::_a = 1; dd.B::_b = 2; dd.C::_a = 3; dd.C::_c = 4; dd._d = 5; cout << sizeof(dd) << endl; system("pause"); return 0;}
思考这里的sizeof(dd)是多少?
它在内存中的布局:
D的对象中有两份A成员
菱形继承存在二义性和数据冗余的问题
虚继承可以解决———菱形继承存在二义性和数据冗余的问题
#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>using namespace std;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;};int main(){ D dd; dd.B::_a = 1; dd.B::_b = 2; dd.C::_a = 3; dd.C::_c = 4; dd._d = 5; cout << sizeof(dd) << endl; system("pause"); return 0;}
思考这里的sizeof(dd)是多少?
相信很多人都认为这里的值为16,大家可能都会想虚继承后B中的A和C中的A会指向同一块空间
运行后的结果如图所示:
很多人可能都会有疑问sizeof的值不仅不变小怎么还增大了
请看它在内存中的布局
将间接存放A的偏移量的地址输入可以看到偏移量:
虚继承体系看起来好复杂,在实际应用中我们通常不会定义如此复杂的继承体系,⼀般不到万不得已都不要
定义菱形结构的虚继承体系结构,因为虚继承体系解决数据冗余问题也带来了性能上的损耗。
问题:上边的虚基表里为什么不直接存储偏移量,而在偏移量上边留了四个字节的位置?
验证:设置一个菱形继承,且为虚继承,每个子类既重写了父类的虚函数,还拥有自己的虚函数。
#pragma onceclass A{public: virtual void Fun1() { 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;}; void TestABCD(){ D d; d.B::_a = 1; d.B::_b = 2; d.C::_a = 3; d.C::_c = 4; d._d = 5; cout << sizeof(d) << endl;}
所以那里预留出的四个字节是给虚函数表留的。
虚函数&多态
虚函数–类的成员函数前⾯加virtual关键字,则这个成员函数称为虚函数。
虚函数重写–当在⼦类的定义了⼀个与⽗类完全相同的虚函数时,则称⼦类的这个
函数重写(也称覆盖)了⽗类的这个虚函数。
例子:
#pragma onceclass Person{public: virtual void BuyTickets() { cout << "买票-全价" << endl; }protected: string _name;};class Student :public Person{public: virtual void BuyTickets() { cout << "买票--半价" << endl; }protected: int _num;};void Fun(Person& p){ p.BuyTickets();}void Test(){ Person p; Student s; Fun(p); Fun(s);}
构成多态的两个条件:
1. 虚函数的重写
2. 父类的指针或者引用
编译阶段 –检查是否构成多态
不构成–静态连编 按对象类型去调用
构成– 动态连编 跟对象的类型无关 跟指向对象有关 –在汇编的时候找到虚表
总结:
1. 派生类重写基类的虚函数实现多态,要求函数名,参数列表、返回值完全相同。(协变除外)
2. 基类定义了虚函数,在派生类中该函数始终保持虚函数的特性
3. 只有类的成员函数才能成为虚函数
4. 静态成员函数不能定义为虚函数
5. 如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数不能加virtual
6. 构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但是最好不要这样定义容易引起混淆。
7. 不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,
可能会发生未定义行为。
8. 最好把基类的析构函数声明为虚函数
#pragma onceclass AA{public: ~AA() { cout << "~AA()" << endl; }};class BB :public AA{public: ~BB() { cout << "~BB()" << endl; }};void test(){ AA *p = new BB; delete p;}
这里析构函数只会调用AA的显然是不对的。
解决方法:将父类的析构函数定义为虚函数就可以了。
纯虚函数:
在成员函数的形参后面写上=0,则成员函数称为虚函数,包含纯虚函数的类叫做抽象类(也叫接口类),
抽象类不能实例出对象,纯虚函数在派生类重新定义以后,派生类才能实例化出对象。
友元与继承:
友元关系不能被继承,也就是说基类友元函数不能访问子类私有和保护成员。
继承与静态成员:
基类定义了static成员,则整个继承体系只有这样一个成员,无论派生出多少个子类,
都只有一个static成员。
- 【C++】继承总结
- C++——【继承】总结
- c++-关于菱形继承&虚继承的问题总结
- 继承总结
- 继承总结
- 继承总结
- 继承总结
- 继承总结
- 继承总结
- 继承总结
- 黑马程序员_ Objective C 封装,继承,多态总结
- C++___的虚拟继承的一些总结
- 【C++】第13章 类继承 知识点总结
- c继承
- C++----------------继承
- 【c#】继承
- C++:继承
- C++::继承
- 嵌入式 Linux进程间通信(五)——进程间通信简介
- 嵌入式 Linux进程间通信(六)——管道
- 嵌入式 Linux进程间通信(七)——消息队列
- 嵌入式 Linux进程间通信(八)——共享内存
- 嵌入式 Linux网络编程(一)——Socket网络编程基础
- C++继承总结
- 嵌入式 Linux网络编程(二)——TCP编程模型
- 嵌入式 Linux网络编程(三)——UDP编程模型
- 嵌入式 Linux网络编程(四)——Select机制
- 寒假作业
- 嵌入式 Linux网络编程(五)——epoll机制
- 电脑设置双屏显示
- 嵌入式 Linux进程间通信(十一)——多线程简介
- 嵌入式 Linux进程间通信(十二)——多线程同步