综述
1.继承的本质和原理
2.派生类的构造过程
3.重载,覆盖,隐藏
4.静态绑定和动态绑定
5.多态vfptr和vftable
6.抽象类的设计原理
7.多重继承及问题
8.虚基类vbptr和vbtable
9.RTTI
10.C++四种类型强转
继承的本质和原理
#include<iostream>
using namespace std;
/*
继承的本质和原理
本质:代码的复用,
类和类之间的关系,组合和继承
组合:a part of 一部分的关系
继承:a kind of 一种的关系 学生是人的一种
*/
/*
继承方式 基类的访问限定 派生类的访问限定 main外部的访问限定
public public public 可
protected protected 否
private 不可见 否
protected 基类的成员的访问限定,在派生类里面是不可能超过继承方式的
public protected 否
protected protected 否
private 不可见 否
private public private 否
protect private 否
private 不可见 否
1.外部只能访问对象public成员,protected和private的成员无法直接访问
2.在继承结构中,派生类从基类可以继承过来private的成员,但是派生类却无法直接访问
3.protected/private?
protect。 在基类中定义大的成员,想被派生类访问,但是不想被外部访问。
private。 如果派生类和外部都不打算访问
默认访问方式
class定义派生类 默认继承方式就是private私有的
struct定义派生类 默认继承方式就是public公有的
*/
class CA
{
public:
CA(int a, int b, int c)
:m_ID(a)
,m_Name(b)
,m_High(c)
{
}
int m_ID;
private:
//只有自己和友元可以访问私有的成员
int m_Name;
protected:
int m_High;
};
//A 基类/父类 B派生类/子类
//A派生了B B从A继承而来
class CB: private CA
{
public:
void show()
{
cout << m_High << endl;
}
int m_CardID;
private:
int m_Class;
protected:
int m_Room;
};
class CC :public CB
{
//在CC中m_ID的访问限定符是不可见的,但是能继承来。
};
/*
类A是人口类,类B是学生类,学生类拥有自己的信息,但是人口类拥有的信息他也需要。
处理方法~继承或聚合,聚合的关系是has a的关系,继承的关系是增加自己新能力的关系
*/
int main()
{
return 0;
}
派生类的构造过程
#include<iostream>
using namespace std;
/*
派生类的构造过程
*/
class Base
{
public:
Base(int data = 10)
:m_ma(data)
{
cout << "Base()" << endl;
}
protected:
int m_ma;
};
/*
派生类怎么初始化从基类继承来的成员变量
1.派生类可以从基类继承来所有的成员,变量和方法,除了构造函数和析构函数
2.通过调用基类相应的构造函数来初始化
3.派生类的构造和析构函数,负责初始化和清理派生类部分
4.先构造的后析构,后构造的先析构,出对象自动析构
*/
/*
派生类对象构造和析构的过程是:
1.派生类调用基类的构造函数,初始化从基类继承来的成员
2.调用派生类自己的构造函数,初始化派生类自己特有的成员
。。。。。。派生类对象的作用域到期了。。。。。。
3.调用派生类的析构函数,释放派生类成员可能占用的外部资源(堆内存,文件)
4.自动调用基类的析构函数,释放派生类内存中,从积累继承来的成员可能占用的外部资源(堆内存,文件)
*/
class Derive :public Base
{
public:
//"Base":没有合适的构造函数
Derive(int data)
//在派生类函数的初始化列表中不允许对基类继承来的成员进行直接的初始化
:Base(data)
, mb(data)
{
}
private:
int mb;
};
int main()
{
return 0;
}
重载,覆盖,隐藏
#include<iostream>
using namespace std;
/*
重载,隐藏,覆盖
基类和派生类的关系
基类和派生类的互相引用指向问题
1.重载关系
一组函数要重载,必须处在同一个作用域当中,函数名字相同,参数列表不同
注: 基类和派生类同名函数的关系,不在同一个作用域不能构成重载
只能在基类或派生类,谈重载,不能在两者共同当中谈重载,
2.隐藏的关系(作用域)
在继承结构当中,派生类的同名成员把基类的同名成员隐藏掉了
*/
class Base
{
public:
Base(int data = 10)
:ma(data)
{
}
void show()
{
cout << "Base::show()" << endl;
}
void show(int)
{
cout << "Base::show(int)" << endl;
}
protected:
int ma;
};
class Derive :public Base
{
public:
Derive(int data = 20)
:Base(data)
, mb(data)
{
}
void show()
{
cout << "Derive::show()" << endl;
}
void show(int)
{
cout << "Derive::show(int)" << endl;
}
private:
int mb;
};
int main()
{
Derive d;
//派生类没有这个成员名字,才从基类里面去找
d.show();
//如果有,构成隐藏关系
//d.show(10);
//加上作用域就可以调用了,调用了继承的基类的函数
d.Base::show();
//除非函数名字不相同,否则不能直接从派生类调用基类继承来的函数
//优先找的是是派生类自己作用域的show名字,没有的话才去基类里面找
d.show(20);
/*
1.继承结构,也称作,从上(基类方向)到下(派生类方向)结构
2.
基类对象 转化为 派生类对象 不可以,基类对象不能赋值给派生类对象
派生类对象 转化为 基类对象 可以,只将派生类中的基类部分赋值给基类对象(类型从下到上转换)
基类指针(引用) 指向 派生类对象 可以 Base *pb = &d; 只能访问派生类里面的基类成员,指针的类型限制了指针解引用的能力,只能访问基类 (类型从下到上)
派生类指针(引用)指向 基类对象 不可以,非法内存访问 Base
*/
// 在继承结构中进行上下文的类型转换,默认只支持从下到上的类型的转换
return 0;
}
虚函数 静态绑定 动态绑定
#include<iostream>
#include<typeinfo>
using namespace std;
/*
虚函数 静态绑定(编译时期的绑定(普通函数的调用)) 动态绑定(多态的基础)(虚函数的调用)(运行时期)
隐藏:派生类的同名成员隐藏基类作用域
覆盖:派生类和基类虚函数相同的函数,默认为是虚函数,覆盖了在虚函数表中的位置
一个类添加了虚函数 对这个类有什么影响
总结一:一个类如果类里面定义了虚函数,那么编译阶段,编译器给这个类类型产生一个唯一的vftable虚函数表
虚函数表中主要存储得内容就是RTTI指针和虚函数得地址,给一个类型产生了唯一一张虚函数表
当程序运行时,每一张虚函数表都会加载到内存得.rodata区,只能读不能改
总结二:一个类里面定义了虚函数,那么这个类定义的对象,其运行时,内存中开始部分,多存储一个vfptr虚函数指针,指向相应类型的虚函数表vftable
,一个类型定义的N个对象,他们的vfptr指向的是同一张虚函数表,
总结三:一个类里面,虚函数的个数,不影响对象内存的大小,对象只存储一个指向虚函数表的指针,影响的是,虚函数表的大小
总结四:如果派生类中的方法,和基类继承来的某个方法,返回值,函数名,参数列表都相同,而且基类的方法是virtual虚函数,那么这个派生类的方法自动处理成虚函数
*/
class Base
{
public:
Base(int data = 10)
:ma(data)
{
}
//虚函数 *2
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)
{
}
//派生类把同名覆盖方法重写了,参数名,返回值,参数列表都相同
void show() { cout << "Derive::show()" << endl; }
private:
int mb;
};
int main()
{
Derive d(50);
Base c(100);
//指针指向的都是基类从派生类继承而来的
Base* pb = &d;
//Derive* pbb = &(Derive)c;
//都调用基类方法 编译时期就制定了调用基类下的Show方法
//静态(编译时期)的绑定(函数的调用)编译时期的
//call Base::show(01612DAh)
//如果发现show是普通函数,就进行静态绑定
//如果发现show是虚函数,就进行动态绑定
/*
mov eax,dword ptr[pb] 将虚函数表地址赋值给eax寄存器
mov ecx,dword ptr[eax]
call ecx
在编译的时候不知道最后会调用到哪个函数,所以我们将他叫做动态(运行时期)绑定(函数的调用)
*/
pb->show();
pb->show(10);
/*
pb的类型:
1.如果Base没有虚函数,*pb识别的就是编译时期的类型 *pb=Base
2.如果Base有虚函数,*pb识别的就是运行时期的类型,RTTI类型
pb实际上是指向派生类成员d(vfptr)(Derive),指向了派生类对象的虚函数表 所以其中的RTTI是Dervie
*/
d.show();
//d.show(10);
cout << sizeof(Base) << endl;
cout << sizeof(Derive) << endl;
cout << typeid(pb).name() << endl;
cout << typeid(*pb).name() << endl;
}
虚析构函数
#include<iostream>
#include<ios>
using namespace std;
/*
问题一:那些函数不能实现成虚函数
问题二:虚析构函数
问题一:那些函数不能实现成虚函数
1.要成为虚函数,函数地址就要存放在虚函数表中(虚函数能产生函数地址)
2.依赖对象: 虚函数要能产生地址,存放在vftable当中
对象存在,vfptr->vftable->虚函数地址
构造函数不能成为虚函数,因为构造函数构造完成后对象才产生
1.不能 virtual+构造函数
2.构造函数中调用虚函数,也不会发生动态绑定(调用任何函数,都是静态绑定的)
派生类的构造过程, 1.先调用的基类的构造函数
2.派生类还没有初始化,
静态对象不依赖对象,虚函数依赖,对象虚函数表指针
问题二:虚析构函数
析构函数调用的时候,对象是存在的
什么时候把基类的析构函数必须实现成虚函数:
基类的指针,引用指向了堆上new出来的派生类对象,delete pb基类的指针
他调用析构函数的时候,必须发生动态绑定,否则会导致派生类的析构函数无法调用
*/
class Base
{
public:
Base(int a = 10)
:ma(a)
{
cout << "Base()" << endl;
}
virtual ~Base()
{
cout << "~Base()" << endl;
}
virtual void show()
{
cout << "Base::show" << endl;
}
protected:
int ma;
};
class Derive :public Base
{
public:
Derive(int b = 10)
:Base(10)
,mb(b)
{
cout << "Derive()" << endl;
}
~Derive()
{
cout << "~Derive()" << endl;
}
//基类的析构函数是virtual是虚函数,那么派生类的析构函数自动成为虚函数
private:
virtual void show()
{
cout << "Derive :: show()" << endl;
}
int mb;
};
int main()
{
Base* pb = new Derive(10);
//静态绑定识别编译时类型,动态绑定识别RTTI对象
pb->show();
//派生类的析构函数没有被调用到!!!
/*
看pb的类型是Base类型,那就去Base类型里面找析构函数
对于析构函数的调用,就是静态绑定
虚析构函数就动态绑定,析构不重名也覆盖
派生类析构,自动调用基类析构函数
*/
delete pb;
return 0;
}
动态绑定(不是虚函数调用一定发生动态绑定)
#include<iostream>
#include<ios>
using namespace std;
/*
虚函数和动态绑定
是不是虚函数的调用,一定就是动态绑定? 不是
在类的构造函数中调用虚函数也是静态绑定(构造其他函数,不会发生动态绑定)
虚函数通过指针引用调用才发生动态绑定
*/
class Base
{
public:
Base(int data = 0)
:ma(data)
{
}
virtual void show()
{
cout << "Base::show()" << endl;
}
protected:
int ma;
};
class Derive :public Base
{
public:
Derive(int data = 0)
:Base(data)
,mb(data)
{
}
void show()
{
cout << "Derive::show()" << endl;
}
private:
int mb;
};
int main()
{
Base b;
Derive d;
//用对象本身调用虚函数,是静态绑定
b.show();
d.show();
//对象头取虚函数指针,虚函数表地址 在虚函数表中取虚函数地址 call寄存器
//典型的动态绑定
//动态绑定(必须由指针调用虚函数)
Base* pb1 = &b;
pb1->show();
Base* pb2 = &d;
pb2->show();
//指针和引用的本质在底层是一样的
Base& rb1 = b;
rb1.show();
Base& rb2 = d;
rb2.show();
return 0;
}
理解多态到底是啥
#include<iostream>
#include<string>
using namespace std;
/*
如何解释多态?多种多样的形态
静态的多态(编译时期):
函数重载(函数名相同,展现出不同的形态),最终调用哪个函数,是在编译时期确定好调用的函数版本
模板(函数模板,类模板)
动态的多态(运行时期):
在继承结构中,基类指针(引用)指向派生类对象,通过改指针调用同名覆盖方法,基类指针指向那个派生类对象,就会指向那个派生类的覆盖方法,成为多态
多态底层是通过动态绑定实现的 pbase->访问谁的bfptr->访问谁的vftable->对应派生类对应的方法
*/
/*
继承的好处:
1.可以做代码的复用
2.在基类中提供统一的虚函数接口,然后让派生类进行重写,然后就可以使用多态了,基类指针指向不同的派生类对象
*/
//动物基类
class Animal
{
public:
Animal(string name) :m_name(name) {}
virtual void bark() {}
protected:
string m_name;
};
//动物实体类
class Cat :public Animal
{
public:
Cat(string name) :Animal(name) {}
void bark() { cout << m_name <<"bark miao miao"<< endl; }
protected:
};
class Dog :public Animal
{
public:
Dog(string name) :Animal(name) {}
void bark() { cout << m_name <<"bark wang wang"<< endl; }
protected:
};
class Pig :public Animal
{
public:
Pig(string name) :Animal(name) {}
void bark() { cout << m_name << "bark heng heng" << endl; }
protected:
};
//静态的多态 除了构造函数外,剩下的函数调用虚函数都是动态绑定的(对象本身调用也不发生动态绑定)
//下面一组bark API接口无法做到我们软件涉及的开闭原则
/*q
void bark(Cat& cat)
{
cat.bark();
}
void bark(Pig& pig)
{
pig.bark();
}
void bark(Dog& dog)
{
dog.bark();
}
*/
//可以用统一的基类型接受派生类对象,基类指针可以指向派生类对象
void bark(Animal& animal)
{
animal.bark();
}
void bark(Animal* animal)
{
animal->bark();
}
int main()
{
Cat cat("咪咪");
Dog dog("二哈");
Pig pig("佩奇");
bark(cat);
bark(dog);
bark(pig);
return 0;
}
理解抽象类
#include<iostream>
#include<string>
using namespace std;
/*
抽象类和普通类有什么区别
一般把什么类设计为抽象类? 基类
拥有纯虚函数的类,叫做抽象类(将基类里面的方法,给派生类保留的纯虚函数接口,实现成纯虚函数接口)
抽象类不能再实例化对象了,但是可以定义指针或者引用变量。(基类指针指向派生类对象 多态)
*/
//动物的基类 泛指 类-》抽象一个实体的类型
/*
定义Animal的初衷,并不是让Animal抽象某个实体的类型,
1.让所有的动物实体类通过继承直接复用该属性
2.给送有的派生类保留统一的覆盖/重写接口,
*/
class Animal
{
public:
Animal(string name) :m_name(name) {}
virtual void bark() = 0;
protected:
string m_name;
};
class Cat :public Animal
{
public:
Cat(string name) :Animal(name) {}
void bark() { cout << m_name << "bark miao miao" << endl; }
protected:
};
class Dog :public Animal
{
public:
Dog(string name) :Animal(name) {}
void bark() { cout << m_name << "bark wang wang" << endl; }
protected:
};
class Pig :public Animal
{
public:
Pig(string name) :Animal(name) {}
void bark() { cout << m_name << "bark heng heng" << endl; }
protected:
};
问题
Animal* p1 = new Cat("加菲猫");
Animal* p2 = new Dog("二哈");
int* p11 = (int*)p1;
int* p22 = (int*)p2;
int temp = p11[0];
p11[0] = p22[0];
p22[0] = p11[0];
p1->bark();
p2->bark();
虚继承
#include<iostream>
#include<string>
using namespace std;
/*
多重继承:继承的本质就是代码的复用 C 有两个基类
class C: public A,public B
{
}
抽象类(有纯虚函数的类)/虚基类
virtual
1.修饰成员方法是虚函数
2.修饰继承方式是虚继承,被虚继承的类叫做虚基类
虚基类的内存要放到继承的最后面,开头加一个vbptr 指针
vbptr B::vbtable 虚基类表 偏移量
mb 距离虚基类数据的偏移量
-------
A::ma
*/
class A
{
public:
private:
int ma;
};
class B :virtual public A
{
public:
private:
int mb;
};
/*
类A 有成员ma 占四个字节
类B 有成员mb 还有从A继承来的成员ma vbptr 12个字节
vfptr/vbptr
*/
int main()
{
//基类指针指向派生类对象,永远指向的是派生类基类部分数据起始地址
A* p = new B();
//只会收了基类之下部分,派生类没回收
//不涉及堆内存,不出错
return 0;
}
多重继承
四种类型转换
#include<iostream>
#include<string>
using namespace std;
/*
C++四种类型转换方式
int a = (int)b
const_cast :去掉常量属性的类型转换
static_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;
}
void func22()
{
cout << "call Derive2:func22" << endl;
}
};
//全局的接口
void showFunc(Base* p)
{
//用指针调用对象成员,肯定有了动态绑定
//要求 只要指向Derival2就调用func22
p->func();
//typeid(*p).name() == "Derive2"
//dynamic_cast会检查p指针是否指向的是一个Derive2类型的对象
//p->vfptr->vftable RTTI信息如果是,dynamic_cast类型转换成功
//返回Derive2对象的地址,否则返回nullptr
Derive2* pd2 = dynamic_cast<Derive2*>(p);
if (pd2 != nullptr)
{
pd2->func22();
}
else
{
p->func();
}
}
int main()
{
const int a = 10;
int* p1 = (int*)&a;
//const_cast<必须是指针或者引用类型>
int* p2 = const_cast<int*>(&a);
int b = 20;
//没有任何联系的类型转换将被否定
//可以转换基类和派生类之间的关系
char c = static_cast<int>(a);
/*
int* p = nullptr;
short* b = static_cast<short*>(p);
*/
//和C一样,啥都直接强转
int* p = nullptr;
double* e = reinterpret_cast<double*>(p);
//支持RTTI类型的强置转换
Derive1 d1;
Derive2 d2;
showFunc(&d1);
showFunc(&d2);
return 0;
}