06 继承与多态

01 继承的本质和原理

1.继承的本质:

a.代码的复用
b.类和类之间的关系:
组合:a part of… …一部分的关系
继承:a kind of… …一种的关系

继承方式基类的访问限定派生类的访问限定(mian)外部的访问限定
public继承
publicpublic可访问
protectedprotected不可访问
privated不可见的不可访问(自己或者友元能访问私有的成员)
protected继承(基类的成员的访问限定,在派生类里面是不可能超过继承方式的)
publicprotected不可访问
protectedprotected不可访问
private不可见的不可访问
private继承
publicprivate不可访问
protectedprivate不可访问
private不可见的不可访问

总结:

  1. 外部只能访问对象的public成员,protected和private的成员无法直接访问
  2. 在继承结构中,派生类从基类可以继承过来private的成员,但是派生类却无法直接访问
  3. protected和private的区别?
    在基类中定义的成员,想被派生类访问,但是不想被外部访问,那么在基类中,把相关成员定义成protected保护的;

如果派生类和外部都不打算访问,那么在基类中,就把相关成员定义成private私有的。

默认的继承方式是什么?

  • 要看派生类是class定义的,还是struct定义的?
    class定义派生类,默认继承方式就是private私有的
    struct定义派生类,默认继承方式就是public公有的

2.派生类的构造过程

派生类可以从基类继承来所有的成员(变量和方法),除了构造函数和析构函数

派生类怎么初始化从基类继承来的成员变量?
通过调用基类相应的构造函数来初始化

派生类的构造函数和析构函数,负责初始化和清理派生类部分
派生类从基类继承来的成员的初始化和清理由谁负责呢?
是由基类的构造函数和析构函数来负责

派生类对象构造和析构的过程是:

  1. 派生类调用基类的构造函数,初始化从基类继承来的成员
  2. 调用派生类自己的构造函数,初始化派生类自己特有的成员
    …派生类对象的作用域到期了
  3. 调用派生类的析构函数,释放派生类成员可能占用的外部资源(堆内存,文件)
  4. 调用基类的析构函数,释放派生类内存中,从基类继承来的成员可能占用的外部资源(堆内存,文件)

02 重载、隐藏、覆盖

  1. 重载关系
    一组函数要重载,必须处在同一作用域当中;而且函数名字相同,参数列表不同

  2. 隐藏(作用域的隐藏)的关系
    在继承结构中,派生类的同名成员,把基类的同名成员给隐藏掉了

———————————————————————

  1. 把继承结构,也说成从上(基类)到下(派生类)的结构
基类对象 -> 派生类对象类型从上到下的类型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 虚析构函数

问题一:哪些函数不能实现成虚函数
虚函数依赖:

  1. 虚函数能产生地址,存储在vftable当中
  2. 对象必须存在(vfptr -> vftable -> 虚函数地址)

构造函数

  1. virtual + 构造函数 NO!
  2. 构造函数中(调用的任何函数,都是静态绑定的),调用虚函数也不会发生静态绑定

派生类对象构造过程

  1. 先调用的是基类的构造函数
  2. 才调用派生类的构造函数
  • 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 ->调用的就是对应的派生类对象的方法

继承的好处是什么?

  1. 可以做代码的复用
  2. 在基类中提供统一的虚函数接口,让派生类重写,然后就可以使用多态了
#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抽象某个实体的类型

  1. string _name; 让所有的动物实体类通过继承Animal直接复用该属性
  2. 给所有的派生类保留统一的覆盖/重写接口

拥有纯虚函数的类,叫做抽象类!(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:

  1. 修饰成员方法是虚函数
  2. 可以修饰继承方式,是虚继承。被虚继承的类,称为虚基类
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;
}
  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值