01 继承的本质和原理
1.继承的本质:
a.代码的复用
b.类和类之间的关系:
组合:a part of… …一部分的关系
继承:a kind of… …一种的关系
继承方式 | 基类的访问限定 | 派生类的访问限定 | (mian)外部的访问限定 |
---|---|---|---|
public继承 | |||
public | public | 可访问 | |
protected | protected | 不可访问 | |
privated | 不可见的 | 不可访问(自己或者友元能访问私有的成员) | |
protected继承(基类的成员的访问限定,在派生类里面是不可能超过继承方式的) | |||
public | protected | 不可访问 | |
protected | protected | 不可访问 | |
private | 不可见的 | 不可访问 | |
private继承 | |||
public | private | 不可访问 | |
protected | private | 不可访问 | |
private | 不可见的 | 不可访问 |
总结:
- 外部只能访问对象的public成员,protected和private的成员无法直接访问
- 在继承结构中,派生类从基类可以继承过来private的成员,但是派生类却无法直接访问
- protected和private的区别?
在基类中定义的成员,想被派生类访问,但是不想被外部访问,那么在基类中,把相关成员定义成protected保护的;
如果派生类和外部都不打算访问,那么在基类中,就把相关成员定义成private私有的。
默认的继承方式是什么?
- 要看派生类是class定义的,还是struct定义的?
class定义派生类,默认继承方式就是private私有的
struct定义派生类,默认继承方式就是public公有的
2.派生类的构造过程
派生类可以从基类继承来所有的成员(变量和方法),除了构造函数和析构函数
派生类怎么初始化从基类继承来的成员变量?
通过调用基类相应的构造函数来初始化
派生类的构造函数和析构函数,负责初始化和清理派生类部分
派生类从基类继承来的成员的初始化和清理由谁负责呢?
是由基类的构造函数和析构函数来负责
派生类对象构造和析构的过程是:
- 派生类调用基类的构造函数,初始化从基类继承来的成员
- 调用派生类自己的构造函数,初始化派生类自己特有的成员
…派生类对象的作用域到期了 - 调用派生类的析构函数,释放派生类成员可能占用的外部资源(堆内存,文件)
- 调用基类的析构函数,释放派生类内存中,从基类继承来的成员可能占用的外部资源(堆内存,文件)
02 重载、隐藏、覆盖
-
重载关系
一组函数要重载,必须处在同一作用域当中;而且函数名字相同,参数列表不同 -
隐藏(作用域的隐藏)的关系
在继承结构中,派生类的同名成员,把基类的同名成员给隐藏掉了
———————————————————————
- 把继承结构,也说成从上(基类)到下(派生类)的结构
基类对象 -> 派生类对象 | 类型从上到下的类型 | N |
派生类对象 -> 基类对象 | 类型从下到上的类型 | Y |
基类指针(引用) -> 派生类对象 | 类型从上到下的类型 | N |
派生类指针(引用)-> 基类对象 | 类型从下到上的类型 | Y |
总结:在继承结构中进行上下的类型转换,默认只支持从上到下的类型转换
03 虚函数,动态绑定和静态绑定
总结一
一个类里面定义了虚函数,那么编译阶段,编译器给这个类类型产生一个唯一的vftable虚函数表,虚函数表中主要存储的内容就是RTTI指针和虚函数的地址。当程序运行时,每一张虚函数表都会加载到内存的.rodata区。
总结二
一个类里面定义了虚函数,那么这个类定义的对象,其运行时,内存中开始部分,多存储一个vfptr虚函数指针,指向相应类型的虚函数表vftable。一个类型定义的n个对象,指向的都是同一张虚函数表。
总结三
一个类里面虚函数的个数,不影响对象内存大小(vfptr),影响的是虚函数表的大小。
#include <iostream>
#include <typeinfo>
using namespace std;
class Base {
public:
Base(int data = 10) :ma(data) {}
// 虚函数
virtual void show() { cout << "Base::show()" << endl; }
// 虚函数
virtual void show(int) { cout << "Base::show(int)" << endl; }
protected:
int ma;
};
class Derive :public Base {
public:
Derive(int data=20):Base(data),mb(data){}
/*
总结四
如果派生类中的方法,和基类继承来的某个方法,返回值、函数名、参数列表都相同、
而且基类的方法是virtual虚函数,那么派生类的这个方法,自动处理成虚函数
重写《 = 》覆盖
*/
void show() { cout << "Derive::show()" << endl; }
private:
int mb;
};
int main() {
Derive d(50);
Base* pb = &d;
/*
pb->Base Base::show() 如果发现show是普通函数,就进行静态绑定
call Base::show
pb->Base Base::show() 如果发现show是虚函数,就进行动态绑定了
mov eax, dword ptr[pb]
mov ecx, dword ptr[eax]
call ecx (虚函数的地址) 动态(运行时期)的绑定(函数的调用)
*/
pb->show(); // 静态(编译时期)的绑定(函数的调用)
/*
Base::show(int) 是一个虚函数,此时就得动态绑定了
mov eax, dword ptr[pb]
mov ecx, dword ptr[eax]
call ecx (虚函数的地址) 动态(运行时期)的绑定(函数的调用)
*/
pb->show(10); //
cout << sizeof(Base) << endl;
cout << sizeof(Derive) << endl;
cout << typeid(pb).name() << endl;
/*
pb的类型:Base -> 有没有虚函数
如果Base没有虚函数,*pb识别的就是编译时期的类型 *pb <=> Base类型
如果Base有虚函数,*pb识别的就是运行时期的类型 RTTI类型
pb->d(vfptr)->Derive vftable class Derive
*/
cout << typeid(*pb).name() << endl;
/*
Base::show()
Base::show(int)
4
8
class Base *
class Base
*/
}
04 虚析构函数
问题一:哪些函数不能实现成虚函数
虚函数依赖:
- 虚函数能产生地址,存储在vftable当中
- 对象必须存在(vfptr -> vftable -> 虚函数地址)
构造函数
- virtual + 构造函数 NO!
- 构造函数中(调用的任何函数,都是静态绑定的),调用虚函数也不会发生静态绑定
派生类对象构造过程
- 先调用的是基类的构造函数
- 才调用派生类的构造函数
- static静态成员方法 NO!virtual+static 其不依赖对象
问题二:虚析构函数
析构函数调用的时候,对象是存在的!
什么时候把基类的析构函数必须实现成虚函数?
- 基类的指针(引用)指向堆上new出来的派生类对象的时候,delete pb(基类的指针),它调用析构函数的时候,必须发生动态绑定,否则会导致派生类的析构函数无法调用。
#include <iostream>
using namespace std;
class Base {
public:
Base(int data) :ma(data) { cout << "Base()" << endl; }
//虚析构函数
virtual ~Base() { cout << "~Base"<<endl; }
virtual void show() { cout << "call Base::show()" << endl; }
protected:
int ma;
}; // &Base;;~Base &Base;;show
class Derive :public Base { // &Derive;;~Derive &Base;;show
public:
Derive(int data) :Base(data),mb(data) { cout << "Derive()" << endl; }
// 基类的析构函数是virtual虚函数,那么派生类的析构函数自动成为虚函数
~Derive() { cout << "~Derive()" << endl; }
private:
int mb;
};
int main() {
Base* pb = new Derive(10);
pb->show(); // 动态绑定 pb Base* *pb Derive
delete pb; // 派生类的析构函数未调用(未加virtual前)
/*
先去pb->Base找(普通函数) Base::~Base() 对于析构函数的调用,就是静态绑定
call Base::~Base
pb->Base Base;;~Base()(虚函数) 对于析构函数的调用,就是动态绑定
pb->Derive Derive vftable &Derive;;~Derive
*/
/*Derive d(10);
Base* pb = &d;
pb->show();
*/
return 0;
}
05 动态绑定
虚函数和动态绑定
问题:是不是虚函数的调用一定是动态绑定? 肯定不是
在类的构造函数当中,调用虚函数,也是静态绑定(构造函数中调用其它函数(虚),不会发生动态绑定)
#include <iostream>
using namespace std;
class Base {
public:
Base(int data = 0) :mb(data) { cout << "Base()" << endl; }
~Base() { cout << "~Base()" << endl; }
virtual void show() { cout << "Base::show()" << endl; }
protected:
int mb;
};
class Derive:public Base {
public:
Derive(int data = 0) :Base(data), md(data) { cout << "Derive()" << endl; }
~Derive() { cout << "~Derive()" << endl; }
void show() { cout << "Derive::show()" << endl; }
private:
int md;
};
int main() {
Base b;
Derive d;
// 静态绑定,用对象本身调用虚函数,是静态绑定
b.show(); // 虚函数 call Base::show()
d.show(); // 虚函数 call Base::show()
// 动态绑定(必须由指针调用虚函数)
Base* pb1 = &b;
pb1->show();
Base* pb2 = &d;
pb2->show();
// 动态绑定(必须由引用变量调用虚函数)
Base& rb1 = b;
rb1.show();
Base& rb2 = d;
rb2.show();
// 动态绑定(虚函数通过指针或者引用变量调用,才发生动态绑定)
Derive* pb1 = &d;
pb1->show();
Derive& rb1 = d;
rb1.show();
Derive* pb2 = (Derive*)&b;
pb2->show(); // 动态绑定 pb2 -> b vfptr -> Base vftable Base::show()
return 0;
}
06 多态
如何理解多态
静态(编译时期)的多态:函数重载、模板(函数模板和类模板)
bool compare(int,int){}
bool compare(double,double){}
compare(10,20); call compare_int_int 在编译阶段就确定好调用函数的版本
compare(10.5,20.5); call compare_double_double 在编译阶段就确定好调用函数的版本
template<typename T>
bool compare(T a,T b){}
compare(10,20); => int 实例化一个 compare<int>
compare(10.5,20.5); => double 实例化一个 compare<double>
动态(运行时期)的多态: Base Derive
在继承结构中,基类指针(引用)指向派生类对象,通过该指针(引用)调用同名覆盖方法(虚函数),基类指针指向哪个派生类对象,就会调用哪个派生类对象的同名覆盖方法,称为多态。
pbase->bark();
多态底层是通过动态绑定来实现的
pbase-> 访问谁的
vfptr-> 继续访问谁的
vftable ->调用的就是对应的派生类对象的方法
继承的好处是什么?
- 可以做代码的复用
- 在基类中提供统一的虚函数接口,让派生类重写,然后就可以使用多态了
#include <iostream>
#include <string>
using namespace std;
class Animal {
public:
Animal(string name):_name(name){}
virtual void bark(){}
protected:
string _name;
};
class Cat :public Animal {
public:
Cat(string name):Animal(name){}
void bark() { cout <<_name << "bark:miao miao!" << endl; }
};
class Dog :public Animal {
public:
Dog(string name):Animal(name){}
void bark() { cout <<_name << "bark:wang wang!" << endl; }
};
class Pig :public Animal {
public:
Pig(string name) :Animal(name) {}
void bark() { cout << _name << "bark:heng heng!" << endl; }
};
/*
下面的一组bark API接口无法做到软件涉及的"开-闭"原则
软件设计有六大原则 “开-闭”原则 对修改关闭,对扩展开放
void bark(Cat& cat) {
cat.bark();
}
void bark(Dog& dog) {
dog.bark();
}
void bark(Pig& pig) {
pig.bark();
}
*/
void bark(Animal* p) {
p->bark(); // Animal::bark虚函数,动态绑定了
/*
p->cat Cat vftable &Cat::bark()
p->dog Dog vftable &Dog::bark()
p->pig Pig vftable &Pig::bark()
*/
}
int main() {
Cat cat("猫咪");
Dog dog("二哈");
Pig pig("佩奇");
bark(&cat);
bark(&dog);
bark(&pig);
return 0;
}
07 抽象类
抽象类和普通类有什么区别?
一般把什么类设计成抽象类? 基类
动物基类 泛指 类->抽象一个实体的类型
定义Animal的初衷,并不是让Animal抽象某个实体的类型
- string _name; 让所有的动物实体类通过继承Animal直接复用该属性
- 给所有的派生类保留统一的覆盖/重写接口
拥有纯虚函数的类,叫做抽象类!(Animal)
Animal A; NO!
抽象类不能再实例化对象了,但是可以定义指针和引用变量
class Animal {
public:
Animal(string name) :_name(name) {}
// 纯虚函数
virtual void bark() = 0;
protected:
string _name;
};
// 动物实体类
class Cat :public Animal {
public:
Cat(string name) :Animal(name) {}
void bark() { cout << _name << "bark:miao miao!" << endl; }
};
class Dog :public Animal {
public:
Dog(string name) :Animal(name) {}
void bark() { cout << _name << "bark:wang wang!" << endl; }
};
class Pig :public Animal {
public:
Pig(string name) :Animal(name) {}
void bark() { cout << _name << "bark:heng heng!" << endl; }
};
void bark(Animal* p) {
p->bark(); // Animal::bark虚函数,动态绑定了
/*
p->cat Cat vftable &Cat::bark()
p->dog Dog vftable &Dog::bark()
p->pig Pig vftable &Pig::bark()
*/
}
//汽车的基类
class Car {
};
int main() {
Cat cat("猫咪");
Dog dog("二哈");
Pig pig("佩奇");
bark(&cat);
bark(&dog);
bark(&pig);
return 0;
}
08 虚基类和虚继承
多重继承:代码的复用 一个派生类有多个基类
class C:public A,public B
{
};
抽象类:(有纯虚函数的类)
虚基类 : 被虚继承的类称为虚基类 vbptr,vbtable,virtual:
- 修饰成员方法是虚函数
- 可以修饰继承方式,是虚继承。被虚继承的类,称为虚基类
class A {
public:
virtual void func() { std::cout << "call A::func()" << endl; }
void operator delete(void* ptr) {
cout << "operator delete p:" << ptr << endl;
free(ptr);
}
private:
int ma;
};
class B :virtual public A {
public:
void func() { std::cout << "call B::func()" << endl; }
void* operator new(size_t size) {
void* p = malloc(size);
cout << "operator new p:" << p << endl;
return p;
}
private:
int mb;
};
/*
A a:4个字节
B b;ma,mb 8个字节 + 4(vbptr) = 12 vfptr/vftable vbptr/vbtable
*/
int main() {
// 基类指针指向派生类对象,永远指向的是派生类基类部分数据的起始地址
A* p = new B(); // B::vftable
cout << "main p:" << p << endl;
p->func();
delete p;
return 0;
}
09 菱形继承
C++多重继承——菱形继承问题 派生类有多份间接基类的数据 设计的问题
好处 可以做更多代码的复用
class A {
public:
A(int data) :ma(data) { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
protected:
int ma;
};
//=====================================================
class B:virtual public A {
public:
B(int data) :A(data),mb(data) { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
protected:
int mb;
};
class C :virtual public A {
public:
C(int data) :A(data), mc(data) { cout << "C()" << endl; }
~C() { cout << "~C()" << endl; }
protected:
int mc;
};
//=======================================================
class D :public B,public C {
public:
D(int data) :A(data),B(data),C(data), md(data) { cout << "D()" << endl; }
~D() { cout << "~D()" << endl; }
protected:
int md;
};
int main() {
D d(10);
return 0;
}
10 四种类型转换
C++提供的四种类型转换方式
int a =(double) b;
**const_cast:**去掉(指针或者引用)常量属性的一个类型转换
**staic_cast:**提供编译器认为安全的类型转换(没有任何联系的类型之间的转换就被否定)
**reinterpret_cast:**类似C风格的强制类型转换
**dynamic_cast:**主要用在继承结构中,可以支持RTTI类型识别上下转换
class Base {
public:
virtual void func() = 0;
};
class Derive1 :public Base {
public:
void func() { cout << "call Derive1::func()" << endl; }
};
class Derive2 :public Base {
public:
void func() { cout << "call Derive2::func()" << endl; }
// Derive2实现新功能的API接口函数
void derive02func() {
cout << "call Derive2::derive02func()" << endl;
}
};
/*
typeid(*p).name() == "Derive"
*/
void showfunc(Base* p) {
/*
dynamic_cast会检查p指针是否指向一个Derive2类型的对象?
p->vfptr->vftable RTTI信息,如果是,dynamic_cast转换类型成功,
返回Derive2对象的地址,给pd2;否则返回nullptr。
*/
Derive2* pd2 = dynamic_cast<Derive2*>(p);
if (pd2 != nullptr) {
pd2->derive02func();
}
else {
p->func(); // 动态绑定 *p的类型 Derive2 derive02func
}
}
int main() {
Derive1 d1;
Derive2 d2;
showfunc(&d1);
showfunc(&d2);
//const int a = 10;
//double* p1 = (double*)&a;
//int* p2 = const_cast<int*>(&a); C++转换要与a变量类型一致
//const_cast<这里面必须是指针、引用类型或者指向对象类型成员的指针 int* int&>
//int b = const_cast<int(错)>(a);
//static_cast 基类 《=》 派生类 转换可以用static_cast
int a = 10;
char b = static_cast<int>(a);
/*
不可转换
int* p = nullptr;
short* p = static_cast<short*>(p);
*/
return 0;
}