C++进阶:多态

本文详细介绍了C++中的多态概念,包括多态的实现条件、虚函数、重写、C++11中的`override`和`final`关键字,抽象类以及多态的实现原理,如虚函数表等。通过实例展示了多态在实际代码中的应用和调用过程,帮助读者深入理解C++的多态机制。
摘要由CSDN通过智能技术生成
声明:本次代码和解释都是在vs2013 下的x86程序,涉及的指针都是4bytes的
  • 什么是多态?
    1、多态的概念:同一件事物,在不同场景下,表现出的不同的状态,就是多态,例子:见人说人话,见鬼说鬼话
  • 多态的定义和实现
    1、多态的实现条件:
    (1)必须在继承体系中
    (2)基类中必须有虚函数,派生类必须对基类中的虚函数进行重写
    (3)虚函数调用:通过基类的指针或者引用调用虚函数
    体现:在代码运行时,根据基类的指针指向不同类的对象,调用对应类的虚函数。
    例如:
#include <iostream>
using namespace std;
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "全价票" << endl;
	}
protected:
	string _name;
	string _gender;
	string _job;
	int _age;
};
class Student :public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "半价票" << endl;
	}
protected:
	int _stuID;
	double _score;
};
class soldier :public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "军人优先" << endl;
	}
};
void TestBuyTicket(Person& p)//用基类引用来调用虚函数
{
	p.BuyTicket();
}
int main()
{
	Person p;
	Student s;
	soldier so;
	TestBuyTicket(p);
	TestBuyTicket(s);
	TestBuyTicket(so);
	return 0;
}

分析:定义了三个对象,分别是基类Person对象,子类Student对象,子类soldier对象,通过基类引用引用不同的对象来调用对应的虚函数,最终的结果就是:
在这里插入图片描述
如果多态的条件没有完全满足,调用的就是基类的虚函数,例如:

//如果多态的条件没有完全实现的话,全部调用基类的虚函数
class Person
{
public:
	 void BuyTicket()//基类虚函数没有virtual
	{
		cout << "全价票" << endl;
	}
protected:
	string _name;
	string _gender;
	string _job;
	int _age;
};
class Student :public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "半价票" << endl;
	}
protected:
	int _stuID;
	double _score;
};
class soldier :public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "军人优先" << endl;
	}
};
void TestBuyTicket(Person& p)//用基类引用来调用虚函数
{
	p.BuyTicket();
}
int main()
{
	Person p;
	Student s;
	soldier so;
	TestBuyTicket(p);
	TestBuyTicket(s);
	TestBuyTicket(so);
	return 0;
}

分析:基类中没有virtual修饰函数,最终的结果就是
在这里插入图片描述
就只是调用了基类中的虚函数。
注意:虚函数的概念:被virtual关键字修饰的“成员函数”。
重写的概念:
(1)被重写的函数在基类中必须是虚函数
(2)一个在基类中,一个在派生类中
(3)派生类虚函数必须与基类虚函数的原型完全一致(返回值类型、函数名(参数列表)),但值得注意的是,也有两个例外
1>协变:基类虚函数返回基类的指针或者引用,派生类虚函数返回派生类的指针或者引用;因为基类和派生类虚函数的返回值不同,因此是一个例外。
例如:

class Base
{
public:
	virtual Base* TestFunc()
	{
		cout << "Base::TestFunc()" << endl;
		return this;
	}
};
class Derived :public Base
{
public:
	virtual Derived* TestFunc()
	{
		cout << "Derived::TestFunc()" << endl;
		return this;
	}
};
void TestVirtualFunc(Base* pd)
{
	pd->TestFunc();
}
int main()
{
	Base b;
	Derived d;
	TestVirtualFunc(&b);
	TestVirtualFunc(&d);
	return 0;
}

分析:基类Base中的虚函数返回值是Base*,子类Derived的虚函数返回值是Derived*,但是他们可以构成重写,最终的结果是:
在这里插入图片描述
2>析构函数:基类中的析构函数是虚函数,派生类的析构函数一旦提供,就可以与基类的析构函数构成重写(基类与派生类的析构函数的函数名不同,因此是一个例外)
建议:在继承体系中,如果派生类中涉及到资源管理,最好将基类中的析构函数设置为虚函数。
例如:

class Base
{
public:
	virtual ~Base()
	{
		cout << "Base::~Base()" << endl;
	}
protected:
	int _b;
};
class Derived :public Base
{
public:
	Derived()
	{
		_p = new int[10];
	}
	~Derived()
	{
		if (_p)
		{
			delete[]_p;
		}
		cout << "Derived::~Derived()" << endl;
	}
protected:
	int* _p;
};
int main()
{
	Base* pb = new Derived;
	delete pb;
	return 0;
}

当基类Base中的析构函数是虚函数时,子类Derived的析构函数给出,就能够与基类中的虚函数构成重写。
(4)派生类函数前virtual可加可不加,但一般情况下,最好加上

  • C++11中override和final关键字
    1、override关键字:在有些情况下,一旦没有构成重写,编译器不一定会报错,可以成功编译,但是不能实现多态,比如基类函数前未加关键字virtual,或者派生类虚函数与基类虚函数函数的原型不一致
    例如:
class Base
{
public:
	virtual void TestFunc1()
	{
		cout << "Base::TestFunc1()" << endl;
	}
	void TestFunc2()
	{
		cout << "Base::TestFunc2()" << endl;
	}
	virtual void TestFunc3()
	{
		cout << "Base::TestFunc3()" << endl;
	}
};
class Derived :public Base
{
public:
	virtual void TetsFunc1()//与基类的函数名不同
	{
		cout << "Derived::TestFunc1()" << endl;
	}
	virtual void TestFunc2()//基类TestFunc2不是虚函数
	{
		cout << "Derived::TestFunc2()" << endl;
	}
	virtual void TestFunc3(int)//与基类虚函数参数列表不同
	{
		cout << "Derived::TestFunc3()" << endl;
	}
};
void TestFunc(Base& b)
{
	b.TestFunc1();
	b.TestFunc2();
	b.TestFunc3();
}
int main()
{
	Base b;
	Derived d;
	TestFunc(b);
	TestFunc(d);
	return 0;
}

这样产生的结果就是只调用了基类的虚函数,没有办法实现多态,如图:
在这里插入图片描述
而在C++98中,编译器不能帮助用户进行重写的检测,而在C++11中,关键字override可以让编译器帮助用户检测派生类是否对基类的虚函数进行重写 ,如果是重写,就编译成功,否则代码编译报错。
注意:override只能修饰子类的虚函数(在子类虚函数后加上override)
例如:

class Base
{
public:
	virtual void TestFunc1()
	{
		cout << "Base::TestFunc1()" << endl;
	}
	virtual void TestFunc2()
	{
		cout << "Base::TestFunc2()" << endl;
	}
	virtual void TestFunc3()
	{
		cout << "Base::TestFunc3()" << endl;
	}
};
class Derived :public Base
{
public:
	//virtual void TetsFunc1()override//报错,函数名不同,不能构成重写
	virtual void TestFunc1()override
    {
		cout << "Derived::TestFunc1()" << endl;
	}
	//virtual void TestFunc2()override//基类TestFunc2不是虚函数
	virtual void TestFunc2()override
	{
		cout << "Derived::TestFunc2()" << endl;
	}
	//virtual void TestFunc3(int)override//与基类虚函数参数列表不同
	virtual void TestFunc3()override
	{
		cout << "Derived::TestFunc3()" << endl;
	}
};
void TestFunc(Base& b)
{
	b.TestFunc1();
	b.TestFunc2();
	b.TestFunc3();
}
int main()
{
	Base b;
	Derived d;
	TestFunc(b);
	TestFunc(d);
	return 0;
}

因此是非常方便的。
2、final关键字
如果不想让基类中的虚函数被子类重写,则可以用final修饰虚函数即可
例如:

class Base
{
public:
	virtual void TestFunc()final
	{
		cout << "Base::TestFunc()" << endl;
	}
};
class Derived :public Base
{
public:
	virtual void TestFunc()
	{
		cout << "Base::TestFunc()" << endl;
	}
};

则会报错,如图:
在这里插入图片描述
源代码(github):
https://github.com/wangbiy/C-/tree/master/test_2019_9_25/test_2019_9_25

  • 抽象类
    纯虚函数:在虚函数的后面加上=0,就称为纯虚函数。
    抽象类概念:包含纯虚函数的类称为抽象类,又叫接口类,抽象类不能实例化出对象
    派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯
    虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承.
    例如:
class WashRoom
{
public:
	void GoToManRoom()
	{
		cout << "WashRoom::GoToManRoom()" << endl;
	}
	void GoToWomanRoom()
	{
		cout << "WashRoom::GoToWomanRoom()" << endl;
	}
};
class Person//抽象类
{
public:
	virtual void GoToWc(WashRoom& wc) = 0;//纯虚函数
protected:
	string _name;
	int _age;
};
class Man :public Person//在派生类中必须重写基类的纯虚函数,否则子类也是抽象类
{
public:
	virtual void GoToWc(WashRoom& wc)
	{
		wc.GoToManRoom();
	}
};
class Woman :public Person
{
public:
	virtual void GoToWc(WashRoom& wc)
	{
		wc.GoToWomanRoom();
	}
};
#include <windows.h>
void Test(size_t n)
{
	Person* p;
	WashRoom wc;
	for (size_t i = 0; i < n; ++i)
	{
		if (rand() % 2)
		{
			p = new Man;
		}
		else
		{
			p = new Woman;
		}
		p->GoToWc(wc);
		delete p;
		Sleep(1000);
	}
}
int main()
{
     //Person p;//抽象类不能实例化对象
	Test(10);
	return 0;
}

分析:派生类在继承抽象类后,也不能实例化对象,只有在重写基类的纯虚函数后才能实例化对象,因此如果基类是抽象类,派生类必须重写纯虚函数,否则派生类也是抽象类,不能实例化对象。

  • 多态的实现原理
    1、虚函数表(虚表)
    例如一道笔试题:
class Base
{
public:
	virtual void Func()
	{
		cout << "Base::Func()" << endl;
	}
	int _b;
};
int main()
{
	cout << sizeof(Base) << endl;//8
	Base b;
	return 0;
}

分析:最终类Base的字节大小是8,如果Base类中的成员函数不是虚函数,它的字节大小应为4,但这时因为是虚函数,它的字节多了4个字节,我们也发现b对象除了_b成员,还有一个_vfptr指针放在对象的前面,如图:
在这里插入图片描述
有些平台可能会放到对象的最后面,这个跟平台有关,对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function),它指向一个表格,表格中存放虚函数的地址(注意:在类中定义虚函数,编译器给这个类增加4个字节,虚表在编译阶段就创建好了),占有4个字节,一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中。
2、基类虚表构建规则:按照虚函数在基类中的声明次序依次加载到虚函数表格中
例如:

class Base
{
public:
	virtual void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	int _b;
};
int main()
{
	Base b;
	return 0;
}

分析:其中Base类中虚函数在类中的声明顺序是Func3、Func1、Func2,虚函数地址在虚表中的次序如图:
在这里插入图片描述
则可以得出基类虚表构建规则就是按照虚函数在类中的声明顺序依次加载到虚表中的。
3、派生类虚表构建规则
(1)将基类虚表中的内容拷贝一份到派生类虚表中,例如:

class Base
{
public:
	virtual void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	int _b;
};
class Derived :public Base
{
public:
};
int main()
{
	Derived d;
	return 0;
}

k分析:得出d对象中具有虚表和派生类自己的成员,如图:
在这里插入图片描述
可以得出派生类继承了基类中的成员后,的确是将基类虚表中的内容拷贝一份到派生类虚表中。
(2)如果派生类重写了基类的某个虚函数,用用派生类虚函数地址替换(覆盖)相同偏移量位置的基类虚函数地址,例如:

class Base
{
public:
	virtual void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	int _b;
};
class Derived : public Base
{
public:
	virtual void Func3()
	{
		cout << "Derived::Func3()" << endl;
	}
	virtual void Func2()
	{
		cout << "Derived::Func2()" << endl;
	}
};
int main()
{
	Derived d;
	return 0;
}

分析:派生类Derived对基类中的Func3和Func2进行了重写,用派生类Func3和Func2的地址替换相同偏移量位置的基类虚函数地址,如图:
在这里插入图片描述
派生类重写的Func3地址替换了基类的Func3地址,派生类重写的Func2地址替换了基类的Func2地址。
(3)将派生类新增加的虚函数按照其在派生类中的声明顺序增加到虚表中,例如:

class Base
{
public:
	virtual void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	int _b;
};
class Derived : public Base
{
public:
	virtual void Func5()
	{
		cout << "Derived::Func5()" << endl;
	}
	virtual void Func3()
	{
		cout << "Derived::Func3()" << endl;
	}
	virtual void Func2()
	{
		cout << "Derived::Func2()" << endl;
	}
	virtual void Func4()
	{
		cout << "Derived::Func4()" << endl;
	}
	int _d;
};
typedef void(*PVFT)();//将函数指针变量转为函数指针类型

int main()
{
	cout << sizeof(Base) << endl;
	cout << sizeof(Derived) << endl;
	Base b;
	b._b = 1;
	Derived d;
	d._b = 1;
	d._d = 2;
	PVFT* pVFT = (PVFT*)(*(int*)(&d));
	while (*pVFT)
	{
		(*pVFT)();
		++pVFT;
	}
	return 0;
}

分析:由于在监视窗口看不到派生类新增加的虚函数地址是怎样存放的,使用函数指针来对虚表的内容进行打印,首先每一个虚表定义的都是函数指针类型,要得到虚表中虚函数的地址,先取对象首地址,强转成int*类型得到前4个字节的地址(即虚表的地址),然后解引用将对象前4个字节内容取出,然后再将它转成函数指针类型,得到虚表中第一个虚函数地址,通过++来取下一个虚函数指针,最终的结果就是:
在这里插入图片描述
可以看出派生类中新增加的虚函数按照在类中声明的顺序增加到虚表中。
注意:虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。
那么多态的调用原理到底是怎么样的,现在一一来进行分析:
例如这个代码:

#include <string>
class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	virtual void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
	int _b;
};
class Derived :public Base
{
public:
	virtual void Func1()
	{
		cout << "Derived::Func1()" << endl;
	}
	virtual void Func3()
	{
		cout << "DErived::Func3()" << endl;
	}
	virtual void Func4()
	{
		cout << "DErived::Func4()" << endl;
	}
	virtual void Func5()
	{
		cout << "DErived::Func5()" << endl;
	}
	int _d;
};
void TestVirtual(Base& b)
{
	b.Func1();
	b.Func2();
	b.Func3();
}
int main()
{
	Base b;
	b._b = 1;
	Derived d;
	d._b = 1;
	d._d = 2;
	TestVirtual(b);
	TestVirtual(d);
	return 0;
}

分析:在使用基类引用来调用虚函数时,它的反汇编代码实现如图,具体分析多态的调用过程:
在这里插入图片描述
这是引用基类对象时的汇编代码,先从对象前4个字节获取虚表地址,然后传递this指针,最终取虚函数地址,三个虚函数调用过程是一样的,派生类也是如此;可以看出满足多态的调用,不是在编译时确定的,是运行起来以后到对象中去找的,因为在编译时不能确定基类指针调用的哪个类的对象
4、动态绑定与静态绑定
动态绑定(动态类型多态,晚绑定):编译期间不能确定具体调用哪个函数,只有在代码运行时,根据基类指针或者引用指向不同类的对象,才能确定具体调用哪个类的虚函数。
静态绑定(静态类型多态,早绑定):编译器在编译时就可以确定具体调用哪个类的函数,例如:函数重载(在编译阶段,对所传递实参类型的推演,确定调用哪个函数)、函数模板。
方式

  • 不同继承方式下对象的模型+带有虚函数的继承体系
    1、由于单继承方式的带有虚函数的对象模型已经做过分析,在这里分析多继承方式下带有虚函数的对象模型
    例如:
class B1
{
public:
	virtual void Func1()
	{
		cout << "B1::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "B1::Func2()" << endl;
	}
	int _b1;
};
class B2
{
public:
	virtual void Func3()
	{
		cout << "B2::Func3()" << endl;
	}
	virtual void Func4()
	{
		cout << "B2::Func4()" << endl;
	}
	int _b2;
};
class D :public B1, public B2
{
public:
	virtual void Func1()//对B1中的Func1进行重写
	{
		cout << "D::Func1()" << endl;
	}
	virtual void Func4()//对B2中的Func4进行重写
	{
		cout << "D::Func4()" << endl;
	}
	virtual void Func5()
	{
		cout << "D::Func5()" << endl;
	}
	int _d;
};
typedef void(*PVFT)();
#include <string>
void PrintVFT1(B1& b, const string& str)
{
	cout << str << endl;
	PVFT* pVFT = (PVFT*)(*(int*)(&b));
	while (*pVFT)//虚表中最后一行是00 00 00 00
	{
		(*pVFT)();
		++pVFT;
	}
	cout << endl;
}
void PrintVFT2(B2& b, const string& str)
{
	cout << str << endl;
	PVFT* pVFT = (PVFT*)(*(int*)(&b));
	while (*pVFT)//虚表中最后一行是00 00 00 00
	{
		(*pVFT)();
		++pVFT;
	}
	cout << endl;
}

int main()
{
	cout << sizeof(D) << endl;
	D d;
	d._b1 = 1;
	d._b2 = 2;
	d._d = 3;
	PrintVFT1(d, "override B1");
	PrintVFT2(d, "override B2");
	return 0;
}

分析:以上代码是一个多继承,B1和B2类是D类的基类,D类对B1类中的Func1进行了重写,对B2类的Func4进行了重写,最终这个代码的结果是:
在这里插入图片描述
可以看出子类D中新增加的虚函数按照在类中的声明顺序增加到了B1的虚表中,最终D类的字节大小是20个字节,因为B1的大小是8,B2的大小是8,最终再加上D类自己的成员变量大小即为20,在内存中的分布如图:
在这里插入图片描述
说明派生类D的对象模型由3部分组成,B1、B2和_d,第一行放B1的虚表指针,第二行放_b1,然后第三行和第四行放B2的内容,最后放D类自己的成员变量_d。
那么D类在内存中的对象模型就如图:
在这里插入图片描述
2、菱形虚拟继承
例如:

class B
{
public:
	virtual void Func1()
	{
		cout << "B::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "B::Func2()" << endl;
	}
	int _b;
};
class C1 :virtual public B
{
public:
	virtual void Func1()//对B中的Func1进行重写
	{
		cout << "C1::Func()" << endl;
	}
	virtual void Func3()
	{
		cout << "C1::Func3()" << endl;
	}
	int _c1;
};
class C2 :virtual public B
{
public:
	virtual void Func2()//对B中的Func2进行重写
	{
		cout << "C2::Func2()" << endl;
	}
	virtual void Func4()
	{
		cout << "C2::Func4()" << endl;
	}
	int _c2;
};
class D :public C1, public C2
{
public:
	virtual void Func1()//对C1中的Func1进行重写
	{
		cout << "D::Func1()" << endl;
	}
	virtual void Func3()//对C1中的Func3进行重写
	{
		cout << "D::Func2()" << endl;
	}
	virtual void Func4()//对C2中的Func4进行重写
	{
		cout << "D::Func4()" << endl;
	}
	virtual void Func5()//新增加的
	{
		cout << "D::Func5()" << endl;
	}
	int _d;
};
#include <string>
typedef void(*PVFT)();
void PrintVFT1(C1& b, const string& str)
{
	cout << str << endl;
	PVFT* pVFT = (PVFT*)(*(int*)(&b));
	while (*pVFT)
	{
		(*pVFT)();
		++pVFT;
	}
	cout << endl;
}
void PrintVFT2(C2& b, const string& str)
{
	cout << str << endl;
	PVFT* pVFT = (PVFT*)(*(int*)(&b));
	while (*pVFT)
	{
		(*pVFT)();
		++pVFT;
	}
	cout << endl;
}
void PrintVFT3(B& b, const string& str)
{
	cout << str << endl;
	PVFT* pVFT = (PVFT*)(*(int*)(&b));
	while (*pVFT)
	{
		(*pVFT)();
		++pVFT;
	}
	cout << endl;
}
int main()
{
	cout << sizeof(D) << endl;
	D d;
	d._b = 1;
	d._c1 = 2;
	d._c2 = 3;
	d._d = 4;
	PrintVFT1(d, "override C1");
	PrintVFT2(d, "override C2");
	PrintVFT3(d, "override B");
	return 0;
}

分析:最终的打印结果是:
在这里插入图片描述
从代码中可以看出,D类的字节大小是36,因为C1虚拟继承了B,C2也虚拟继承了B,C1的字节大小应为12(虚基表指针,虚表指针,自己成员),C2也为12(虚基表指针,虚表指针,自己成员),B字节大小是8,再加上D自己的成员_d字节大小,一共是36;
最顶层基类B定义了Func1和Func2两个虚函数,在子类C1中重写了B中的Func1,新增加了虚函数Func3,在子类C2中重写了B中的Func2,新增加了虚函数Func4,D继承了C1和C2,对C1中的Func1和Func3进行重写,对C2中的Func4进行重写,新增加了虚函数Func5,则派生类D的对象模型应由4部分组成:C1、C2、_d、B,其中C1、C2、B中应该都含有3部分:虚表指针、虚基表指针、成员,如图为在内存中的显示结果:
在这里插入图片描述
虚表指针在上,虚基表指针在下,则它的对象模型就是:
在这里插入图片描述

  • 多态面试题
  1. 什么是多态?
    回答多态的概念+例子。
  2. 什么是重载、重写(覆盖)、重定义(隐藏)?
    三者的区别:重载是在同一个作用域中,函数名相同,参数列表必须不同,与返回值类型是否相同是否无关;
    重写(覆盖,最主要是覆盖虚表)是在继承体系下,两个函数分别在基类和派生类的作用域,函数名/参数/返回值必须相同(协变是例外)且两个函数必须是虚函数
    重定义(隐藏)是在继承体系下,两个函数分别在基类和派生类的作用域,函数名相同,两个基类和派生类的同名函数不是重写就是重定义。
  3. 多态的实现原理?
    通过分析代码给出虚表来进行解释。
  4. inline函数可以是虚函数吗?
    内联函数不能是虚函数,因为如果是内联函数,但是编译器已经把inline忽略掉了,根本无法实现多态,内联函数没有地址,无法把地址放到虚函数表中。
  5. 静态成员可以是虚函数吗?
    不能,因为静态成员函数没有this指针,它的调用方式不用通过对象来调用,而虚函数需要对象来访问虚函数表,如果将虚函数给成静态的,就可能无法从对象中找到虚表地址,所以静态成员函数无法放进虚函数表。
  6. 构造函数可以是虚函数吗?
    不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的,如果将构造函数给成虚函数,虚表无法进行初始化,出错。
  7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?
    可以,当基类中的析构函数是虚函数时,派生类的析构函数一旦提供,就可以与基类中的虚函数构成重写,并且建议如果派生类中涉及到了资源管理,最好把基类的析构函数定义成虚函数。
  8. 对象访问普通函数快还是虚函数更快?
    首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
  9. 虚函数表是在什么阶段生成的,存在哪的?
    虚函数表是在编译阶段就生成的,一般情况下存在代码段(静态区)的。
  10. C++菱形继承的问题?虚继承的原理?
    参考继承博客,注意不要把虚函数表和虚基表搞混了。
  11. 什么是抽象类?抽象类的作用?
    含有纯虚函数的类叫做抽象类,抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系。
    源代码(github):
    https://github.com/wangbiy/C-/tree/master/test_2019_9_27/test_2019_9_27
  • 面向对象三大特性总结
    封装、继承、多态,那么如何用C语言来模拟实现这三大特性,
    1、封装:C++中的封装是利用访问权限来实现的,来隐藏一些不想让别人看到的东西,只留一些接口供用户使用,那么在C语言中,有结构体来实现封装和隐藏
    由于结构体中不能定义函数,但是可以定义函数指针(函数指针就指向函数),则:
//C语言模拟实现封装
typedef struct A
{
	void(*Fm)(A* pa,int _a);
	int _a;
}A;
void initA(A* pa,int a)
{
	pa->_a = a;
}
void Construct(A* pa, int a)
{
	pa->_a = a;
	pa->Fm = initA;
}
int main()
{
	A a;
	Construct(&a, 10);
	a.Fm(&a, 100);
	return 0;
}

这样就相当于构造了一个对象,先进行初始化,然后来调用即可
2、继承:C++中的继承是可以在基类的基础上增添新的功能,子类可以拥有基类所有的行为,也能够对原有的行为进行更改,例如:

//C语言模拟实现继承
typedef struct B
{
	void(*func)(B* pb);
	int _b;
}B;
typedef struct D
{
	void(*func)(B* pb);
	struct B b;
	int _d;
}D;
void f1(B*)
{}
void ConstructB(B* pb, int b)
{
	pb->_b = b;
	pb->func = f1;
}
void ConstructD(D* pd, int b,int d)
{
	ConstructB(&pd->b, b);
	pd->_d = d;
	pd->func = pd->b.func;
}

这样先构造B,再构造D,D中将B中的内容继承了下来,所以就形成了以上结果。
3、多态:用同一接口代码处理不同的数据,例如一个通用的结构也不清楚是什么数据,什么类型的处理函数,在处理时只需要调用函数即可,例如:

typedef struct A
{
	void* p;
	void(*_A)(struct A* a);
}A;
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值