继承的本质和原理
继承的本质:a.代码的复用 b.在基类中给所有派生类提供统一的虚函数接口,让派生类进行重写,然后就可以使用多态了
类和类之间的关系
组合: a part of 一部分的关系
继承:a kind of 一种的关系
继承方式 基类的访问限定 派生类的访问限定 (main)外部的访问限定
public public public yes
protected protected no
private 不可见 no
protected public protected no
protected protected no
private 不可见 no
private public private no
protected private no
private 不可见 no
外部只能访问对象public的成员,protected和private的成员无法直接访问
在继承结构中,派生类可以从基类继承过来private的成员,但派生类却无法直接访问
在基类中定义的成员,想被派生类访问,但是不想被外部直接访问,那么在基类中,把相关成员定义成protected保护的,如果派生类和外部都不打算访问,那么在基类中,就把相关成员定义成private私有的
class定义派生类,默认继承方式是private私有的
struct定义派生类,默认继承方式是public私有的
派生类的构造过程
1.派生类从继承可以继承来所以的成员(变量和方法),除了构造函数和析构函数
派生类怎么初始化从基类继承来的成员变量呢
解答:通过调用基类相应的构造函数来初始化
派生类的构造函数和析构函数,负责初始化和清理派生类部分
派生类从基类继承的成员,的初始化和清理是由基类的构造和析构函数来负责的
#include <iostream>
#include <vector>
using namespace std;
class Base
{
public:
Base(int data)
:ma(data)
{
cout << "Base " << endl;
}
~Base()
{
cout << "~Base" << endl;
}
protected:
int ma;
};
class Derive :public Base
{
public:
Derive(int data)
:Base(data),mb(data)
{
cout << "Derive " << endl;
}
~Derive()
{
cout << "~Derive" << endl;
}
private:
int mb;
};
int main()
{
Derive d1(20);
cout << endl;
return 0;
}
派生类对象构造和析构的过程是:
1.派生类调用基类的构造函数,初始化从基类继承来的成员
2.调用派生类自己的构造函数,初始化派生类自己特有的成员
派生类对象的作用域到期了
3.调用派生类的析构函数,释放派生类成员可能占用的外部资源
4.调用基类的析构函数,释放派生类内存中从基类继承来的成员可能占用的外部资源
重载、隐藏、覆盖
1.重载关系
一组函数要重载,必须处在同一作用域中,而且函数名字相同,参数列表不同
2.隐藏(作用域的隐藏)关系
在继承结构当中,派生类的同名成员把基类的同名成员给隐藏调了
3.覆盖
基类和派生类的方法,返回值,函数名以及参数列表都相同,而且基类的方法是虚函数,
那么派生类的方法就自动处理成虚函数,它们之间成为覆盖关系
========================
1.把继承结构,也说成从上(基类)到下(派生类)的结构
2.
基类对象 <-派生类对象 类型从上到下的转换 可以
派生类对象<-基类对象 类型从下到上的转换 不可以
基类指针(引用)指向 派生类对象 类型从下到上的转换 可以
只能访问派生类从继承来的基类成员
派生类指针(引用)指向 基类对象 类型从上到下的转换 不可以
在继承结构中进行上下的类型转换,默认只支持从下到上的类型转换
虚函数,静态绑定和静态绑定
静态(编译时期)的绑定(函数的调用)
动态(运行时期)的绑定(函数的调用)
#include <iostream>
#include <vector>
using namespace std;
class Base
{
public:
Base(int data = 10)
:ma(data)
{}
~Base()
{}
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)
{}
~Derive()
{}
void show()
{
cout << "Derive show()" << endl;
}
private:
int mb;
};
int main()
{
Derive d;
Base* p = &d;
p->show();
p->show(10);
return 0;
}
加virtual 变成虚函数,一个类添加了虚函数,对这个类有什么影响:
总结一:
一个类里面定义了虚函数,那么编译阶段,编译器需要给这个类类型产生应该唯一的vftable虚函数表,虚函数表中主要存储的内容就是RTTI指针和虚函数的地址。当程序运行时,每一张虚函数表都会加载到内存的.rodata区,只能读不能改,
总结二:
一个类里面定义了虚函数,那么这个类定义的对象,其运行时,内存中开始部分,多存储一个vfptr虚函数指针,指向相应的虚函数表vftable,一个类型定义的n个对象,它们的虚函数指针vfptr指向同一张虚函数表
总结三:
一个类里面虚函数的个数,不影响对象内存大小(vfptr),影响的是虚函数表的大小
总结四:
如果派生类中的方法和基类继承来的某个方法,返回值,函数名,参数列表相同,而且基类的方法是virtual虚函数,那么派生类的这个方法,自动处理成虚函数
覆盖:虚函数表中虚函数地址的覆盖
问题一:哪些函数不能实现成虚函数?
虚函数依赖:
1.虚函数能产生地址,存储在vftable当中
2.对象必须存在(vfptr->vftable->虚函数地址)
构造函数
1.virtual+构造函数 NO!
2.构造函数中(调用的任何函数,都是静态绑定的)调用虚函数,也不会发生静态绑定
派生类对象构造过程 先调用的是基类的构造函数才调用派生类的构造函数
static静态成员方法 NO! 不能virtual + static
问题二:虚析构函数 析构函数调用的时候,对象是存在的!
#include <iostream>
#include <vector>
using namespace std;
class Base
{
public:
Base(int data = 10)
:ma(data)
{
cout << "Base" << endl;
}
~Base()
{
cout << "~Base" << endl;
}
void show() { cout << "Base::show() "<<endl; }
protected:
int ma;
};
class Derive :public Base
{
public:
Derive(int data = 20)
:Base(data),mb(data)
{
cout << "Derive " << endl;
}
~Derive()
{
cout << "~Derive " << endl;
}
void show()
{
cout << "Derive show()" << endl;
}
private:
int mb;
};
int main()
{
Base* p = new Derive(10);
delete p;
return 0;
}
这样程序结束时,只会析构基类对象,而不会调用派生类的析构函数,会造成内存泄漏
所以,要把基类的析构函数写成virtual虚函数,派生类的析构函数自动成为虚函数
#include <iostream>
#include <vector>
using namespace std;
class Base
{
public:
Base(int data = 10)
:ma(data)
{
cout << "Base" << endl;
}
virtual ~Base()
{
cout << "~Base" << endl;
}
void show() { cout << "Base::show() "<<endl; }
protected:
int ma;
};
class Derive :public Base
{
public:
Derive(int data = 20)
:Base(data),mb(data)
{
cout << "Derive " << endl;
}
~Derive()
{
cout << "~Derive " << endl;
}
void show()
{
cout << "Derive show()" << endl;
}
private:
int mb;
};
int main()
{
Base* p = new Derive(10);
delete p;
return 0;
}
当基类的指针(引用)指向堆上new处理的派生类对象的时候,delete 基类指针,它调用析构函数时,必须发生动态绑定,基类的析构函数必须实现成虚函数,否则会导致派生类的析构函数无法调用
再谈虚函数和动态绑定
是不是虚函数的调用一定是动态绑定?肯定不是
在类的构造函数当中,调用虚函数,也是静态绑定(构造函数中调用其他函数(虚函数),不会发生动态绑定)
用对象本身调用虚函数,是静态绑定
必须由指针(引用)调用虚函数才会发生动态绑定
如果不是通过指针或者引用变量来调用虚函数,那就是静态绑定
如何解释多态
静态(编译时期)多态:函数重载、模板(函数模板和类模板)
动态(运行十七)多态:
在继承结构中,基类指针(引用)指向派生类对象,通过该指针(引用)调用同名覆盖方法(虚函数),基类指针指向哪个派生类对象,就会调用哪个派生类对象的同名覆盖方法,称为多态
多态底层是通过动态绑定来实现的,访问谁的vfptr继续访问谁的vftable,调用对应的派生类对象的方法
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class animal
{
public:
animal(string name)
:_name(name)
{
}
virtual void bark()
{
}
protected:
string _name;
};
class dog : public animal
{
public:
dog(string name)
:animal(name)
{
}
void bark()
{
cout << _name << ";" << "汪汪" << endl;
}
};
class cat : public animal
{
public:
cat(string name)
:animal(name)
{
}
void bark()
{
cout << _name << ";" << "咪咪" << endl;
}
};
void bark(animal &p)
{
p.bark();
}
int main()
{
dog d("二哈");
cat c("哈吉咪");
bark(d);
bark(c);
return 0;
}
继承的好处是什么?
1.可以做代码的复用
2.在基类中给所有派生类提供统一的虚函数接口,让派生类进行重写,然后就可以使用多态了
抽象类和普通类有什么区别
一般把上面类设计成抽象类
在上述动物代码中,定义animal的初衷,并不是让animal抽象某个实体对象
1.string _name让所有的动物实体类通过继承animal直接复用该属性
2.给所有的派生类保留统一的覆盖/重写接口
class animal
{
public:
animal(string name)
:_name(name)
{
}
virtual void bark() = 0;
protected:
string _name;
};
拥有纯虚函数的类,叫做抽象类!(anmial)
抽象类不能再实例化对象了,但是可以定义指针和引用变量
多重继承
好处:代码的复用,一个派生类有多个基类
坏处:派生类有多份间接基类的数据,设计问题,采用虚基类
抽象类:有纯虚函数的类
虚基类:virtual可以修饰成员方法是虚函数,也可以修饰继承方法是虚继承,被继承的类称作虚基类
基类指针指向派生类对象,永远指向的是派生类基类部分数据的起始地址
菱形继承
虚继承就可以解决菱形继承的多份变量问题
C++语言级别提供的四种类型转换方式
const_cast:去掉常量属性的一个类型转换
static_cast:提供编译器认为安全的类型转换
reinterpret_cast:类似于C风格的强制类型转换
dynamin_cast:主要用在继承结构中,可以支持RTTI类型识别的上下转化
const int a = 10;
int * p = const_cast<int *>(&a);
//const_cast<这里面必须是指针或者引用类型>
int a = 0;
char b = static_cast<int>(a);
int *p = nullptr;
double * b = reinterpret_cast<double*> p;
//不安全,强制类型转换
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class Base
{
public:
virtual void func() = 0;
};
class Derive1 : public Base
{
public:
void func()
{
cout << "Derive1 func" << endl;
}
};
class Derive2 : public Base
{
public:
void func()
{
cout << "Derive2 func" << endl;
}
void func2()
{
cout << "Derive2 func2" << endl;
}
};
void test(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->func2();
}
else
{
p->func();
}
}
int main()
{
Derive1 d1;
Derive2 d2;
test(&d1);
test(&d2);
return 0;
}
本文章总结自施磊老师的视频。