C++虚.

本文详细探讨了C++中的多态特性,包括虚函数、虚析构函数、纯虚函数、抽象类以及运行时类型信息(RTTI)和动态类型转换(dynamic_cast)。通过实例展示了如何利用虚函数实现最大面积图形的查找,并解释了重载、覆盖和隐藏的区别。此外,还深入解析了虚函数表和虚表指针的概念,以及它们在内存布局中的影响。最后,讨论了动态绑定对程序性能的影响和使用场景。
摘要由CSDN通过智能技术生成


什么时候必须用初始化列表
1.常属性和引用属性
2.显示调用父类或者成员属性的有参构造函数

即使父类中为私有的虚函数,子类版本中也可以覆盖;这点在很多地方没有提及

计算10个图形的面积,返回面积最大的那个图形

要存储所有的图形
只能用基类类型 * 或者 &

在基类中:
static Shape* maxArea(Shape* srr[],size_t size){
	Shape *pm = srr[0];
	for(int i=1;i<size;i++){
		cout << "-------------" << endl;
		if(srr[i]->area() > pm->area()){
			pm = srr[i];	
		}
	}
	return pm;
}
#include <iostream>
using namespace std;

class Shape{
private:
	double x;
	double y;
public:
	Shape(double x=0,double y=0):x(x),y(y){}
	virtual double len(){
		cout << "shape::len()" << endl;
		return 0.0;
	}
	virtual double area(){
		cout << "shape::area()" << endl;	
	}
	void draw(){
		cout << "shape::draw()   position:<" << x << "," << y << ")" << endl; 	
	}
	static Shape* maxArea(Shape* srr[],size_t size){
		Shape *pm = srr[0];
		for(int i=1;i<size;i++){
			cout << "-------------" << endl;
			if(srr[i]->area() > pm->area()){
				pm = srr[i];	
			}
		}
		return pm;
	}
};

class Rect:public Shape{
private:
	double l;
	double w;
public:
	Rect(double x,double y,double l,double w):Shape(x,y),l(l),w(w){
		
	}
	double len(){
		cout << "Rect::len()" << endl;
		return 2*(l+w);
	}
	double area(){
		cout << "Rect::area()" << endl;
		return l*w;
	}
};

class Circ:public Shape{
private:
	double r;
	static double PI;
public:
	Circ(double x,double y,double r):Shape(x,y),r(r){
		
	}
	double len(){
		cout << "Circ::len()" << endl;
		return PI*2*r;
	}
	double area(){
		cout << "Circ::area()" << endl;
		return PI*r*r;
	}
};

double Circ::PI = 3.14159;

int main(){
	Rect r(0,0,2,4);
	Circ c(0,0,3);

	cout << r.len() << endl;
	cout << c.len() << endl;
	cout << r.area() << endl;
	cout << c.area() << endl;


	Shape* ps = &r;
	ps->len();
	ps->area();

	ps = &c;
	ps->len();
	ps->area();

	Shape& rs = r;
	rs.len();
	rs.area();

	Shape& rc = c;
	rc.len();
	rc.area();



	Shape* arr[5] = {
		new Rect(0,0,4,1),new Circ(0,0,3),new Rect(0,0,3,3),
		new Circ(0,0,2),
		new Rect(0,0,2.5,4)
	};

	ps = Shape::maxArea(arr,5);

	cout << ps->area() << endl;
	return 0;	
}

重载 覆盖 隐藏的区别

重载:
	在同一个作用域下,函数名相同,参数列表不同即构成重载
	条件:
		必须在同一个作用域(类)下
		函数名相同
		参数列表不同: 参数类型和参数的个数不一样  对于指针和引用常属性不一样也构造重载
		与返回值类型无关
		与函数是否是虚函数无关
		在编译时,根据调用时传递的实参类型和个数来绑定调用的函数,静态绑定
		
覆盖(重写):
	子类重写父类同型的虚函数
	条件:
		分布在父子类中
		函数名相同
		参数列表必须相同:参数类型和个数必须相同 函数的常属性必须相同  参数是指针和引用 常属性也必须相同
		与返回值类型有关  如果不是类类型指针或者类类型引用,必须完全一致,否则可以有父子关系
		基类必须是虚函数  重写的版本也一定是虚函数
		在运行时,根据指针所指向的目标类型 或者 根据引用所引用的目标类型,来决定调用哪个版本的方法
		动态绑定  在运行时才决定调用哪个函数 
		
隐藏:
	子类隐藏父类同名的标识符(只讨论函数)
	条件:
		分布在父子类中
		函数名相同
		如果参数不同,不管是否有virtual关键字,都构成隐藏
		如果参数一致,基类函数没有virtual关键字,构成隐藏,否则构成覆盖
#include <iostream>
using namespace std;

class A{};
class B:public A{};

class F{
public:	
	void f1(){
		cout << "F::f1()" << endl;	
	}
	void f1(int x){
		cout << "F::f1(int)" << endl;	
	}
	virtual void f2(){
		cout << "F::f2()" << endl;	
	}
	void f2(int x){
		cout << "F::f2()" << endl;	
	}
	virtual int f3(){
		
	}
	int f4(){
		
	}
	void f5()const{
		cout << "F::f5()const" << endl;		
	}
	virtual void f6(){
		cout << "F::f6()" << endl;	
	}
	virtual void f7(){
		cout << "F::f7()" << endl;	
	}
private:
	virtual void f8(){//子类不可见  子类依然可以覆盖
		cout << "F::f8()" << endl;	
	}
public:
	void bar(F *pf){
		pf->f8();//如果f8没有被覆盖 一定是调用父类的	
	}

	virtual A f9(){
		return A();	
	}

	virtual A* f10(){
		return new A();	
	}
	virtual A& f11(A& a){
		cout << "F::f11" << endl;
		return a;	
	}
	virtual void f12(int x){
		cout << "F::f12" << endl;	
	}
};


class S:public F{
public:
	void f1(){//隐藏  f1()   f1(int x)
		cout << "S::f1()" << endl;	
	}
	void f2(){//重写
		cout << "S::f2()" << endl;	
	}
	/*
	void f3(){//隐藏  覆盖  父类中的f3    虚函数+参数列表一致 覆盖 要求返回值类型
		
	}
	*/
	void f3(int){
		
	}
	virtual void f4(){//隐藏
		
	}
	void f5(){
		cout << "S::f5()" << endl;
	}
	void f6()const{//常属性不一致 不会构成覆盖
		cout << "S::f6()" << endl;	
	}
private:
	virtual void f7(){
		cout << "S::f7()" << endl;
	}
public:
	void f8(){
		cout << "S::f8()" << endl;	
	}
	/*
	B f9(){//函数名一样 参数列表一样  基类函数有virtual 返回值类型有要求
		return B();	
	}
	*/

	B* f10(){
		return new B();	
	}
	virtual B& f11(B& b){//隐藏
		cout << "S::f11(B& b)" << endl;	
		return b;
	}
	virtual B& f11(A& a){//覆盖
		cout << "S::f11(A& a)" << endl;
		return static_cast<B&>(a);
	}
	void f12(const int x){
		cout << "S::f12" << endl;
	}
};


int main(){
	S s;
	s.f1();
	s.F::f1();//通过显示的方式 访问 被隐藏的标识符

	s.F::f1(100);

	cout << "--------" << endl;
	s.f2();//重写
	s.F::f2();//被重写的方法也可以 显示 调用
	
	F *pf = &s;
	pf->f1(); //父类
	pf->f2(); //子类
	
	s.F::f2(100);//被子类同名的f2隐藏了
	
	s.f5();
	const S s1;
	//s1.f5();
	s.f6();
	pf->f6();
	pf->f7();//调用到了子类private方法
	//s.f7();
	//pf->f8();
	s.f8();
	pf->bar(pf);
	A a; B b;
	pf->f11(a);
	s.f11(b);
	pf->f11(b);
	s.f12(1);
	pf->f12(1);
	return 0;	
}

虚析构函数

将基类的析构函数声明为virtual虚函数,delete一个指向子类对象的基类指针时
实际被执行的将是子类的析构函数,而子类的析构函数可以自动调用基类的析构函数
进而保证子类特有的资源和基类子对象中的资源都能够得到释放,从而防止内存泄漏

如果一个类存在虚函数,那么就有必要为其定义一个虚析构,即使析构函数中啥也不干

虚析构的作用:
	防止内存泄漏
	
虚函数可以是声明为内联函数吗?      不可以
	指针/引用 调用虚函数时 只有到运行时才能确定调用哪个函数
	内联函数 在编译时 用函数的二进制代码替换掉调用指令
一个构造函数可以被定义为虚函数吗?  不可以的
一个类的静态成员函数可以被定义为虚函数吗?   不可以
一个全局函数可以被定义为虚函数吗?     不可以
一个类的成员操作符函数可以被定义为虚函数吗?    可以的
#include <iostream>
using namespace std;


class A{
public:
	A(){
		cout << "A()构造" << endl;
	}
	~A(){
		cout << "~A()析构" << endl;	
	}
};

class B:public A{
public:
	B(){
		cout << "B()构造" << endl;	
	}
	~B(){
		cout << "~B()析构" << endl;	
	}
};

class F{
public:
	virtual ~F(){
		cout << "~F()" << endl;	
	}
};

class S:public F{
public:
	~S(){
		cout << "~S()" << endl;	
	}
};

int main(){
	A *pa = new B();
	//delete pa;//只调用父类的析构函数 内存泄漏
	delete static_cast<B*>(pa);//这样是能都调用析构

	F *pf = new S();
	delete pf;
	return 0;	
}

纯虚函数 抽象类 纯抽象类

有的时候基类的虚函数实现没有任何意义,只是为了提供一个接口(函数的声明)让子类去重写
就可以把虚函数不实现函数体,从而定义为纯虚函数
virtual RET_TYPE func_name(arg list,...) [const] = 0;
纯虚函数:没有函数体(函数体用 = 0替换掉)的虚函数
一个拥有纯虚函数的类,称为抽象类
抽象类:拥有纯虚函数的类
	抽象类也称为不完全类
	抽象类不能实例化对象
	  抽象类可以允许拥有自己的成员属性和构造函数,但就是不允许实例化对象
class Shape{
private:
	double x;
	double y;
public:
	Shape(double x=0,double y=0):x(x),y(y){}
	virtual double len() = 0;
	virtual double area() = 0;
	/*
	virtual double len(){
		cout << "shape::len()" << endl;
		return 0.0;
	}
	virtual double area(){
		cout << "shape::area()" << endl;	
		return 0.0;
	}
	*/
	void draw(){
		cout << "shape::draw()   position:<" << x << "," << y << ")" << endl; 	
	}
	static Shape* maxArea(Shape* srr[],size_t size){
		Shape *pm = srr[0];
		for(int i=1;i<size;i++){
			cout << "-------------" << endl;
			if(srr[i]->area() > pm->area()){
				pm = srr[i];	
			}
		}
		return pm;
	}
};
class Rect:public Shape{
private:
	double l;
	double w;
public:
	Rect(double x,double y,double l,double w):Shape(x,y),l(l),w(w){
		
	}
	double len(){
		cout << "Rect::len()" << endl;
		return 2*(l+w);
	}
	double area(){
		cout << "Rect::area()" << endl;
		return l*w;
	}
};
子类继承的父类的纯虚函数,一定要在子类中写实现

如果一个类继承自抽象类,但是没有为抽象基类中的全部纯虚函数提供覆盖,
那么该类也是一个抽象类

纯抽象类:class B
除了构造函数和析构函数以外,所有的成员函数都是纯虚函数的类称为纯抽象类
	
抽象类既然不能实例化对象,那么抽象类的意义在哪:
1.为子类提供一个公共的基类类型   可以定义抽象类的引用 和 指针 引用子类对象 指向子类对象
2.定义统一的接口(虚函数) 为子类重写提供一个模型
3.封装子类共同拥有的属性和方法   提高代码的复用
4.虽然不能实例化对象 但依然能够实现多态的效果
#include <iostream>
using namespace std;

class B{//抽象类   纯抽象类
public:
	virtual void show() = 0;//纯虚函数
	virtual ~B(){
	
	}
};

class C{
public:
	virtual ~C()=0;
};

class D:public B{
	
};

class E:public B{
	void show(){}	
};

class A{
public:
	virtual void bar() = 0;
};

//java中纯虚函数 默认是 public 
class F:public A,public B{
	void show(){}
	void bar(){}
};
int main(){
	//B b;
	//D d;
	E e;
	F f;
	//f.show();
	return 0;	
}

面向对象三大特征:
封装、继承、多态

typeid 运行时类型信息(RTTI)

typeid(对象/类型)  返回的是一个typeinfo类型对象的常引用
#include <typeinfo>
typeinfo成员方法:
	name();  //以字符串的形式返回类型名称
	operator==() 类型比较  类型一致为true
	operator!=() 类型比较  类型不一致为true;
如果没有多态,那么,typeid求到的是固定的(变量定义的类型)的类型
	如果存在多态,去求得基类指针或者引用的目标类型的类型
在存在多态关系中:
	如果需要把基类指针、基类引用转换成子类指针或者子类引用时,可以用typeid来判断
	如果目标类型和指针所指向的类型 或者 引用类型一致时,可以用static_cast转换
#include <iostream>
#include <typeinfo>
using namespace std;


class Father{
public:
	virtual void f(){};
};
class Son1:public Father{
public:
	virtual void f(){};
};

class Son2:public Father{
public:
	virtual void f(){}
};
//typeid
int main(){
	int a = 10;
	cout << typeid(a).name() << endl;
	double d;
	cout << typeid(d).name() << endl;
	cout << typeid(Father).name() << endl;
	Father *pf = new Son1();
	cout << typeid(*pf).name() << endl;
	if(typeid(*pf) == typeid(Son2)){//*pf
		Son2 *ps = static_cast<Son2*>(pf);	
		cout << "-------" << endl;
	}else if(typeid(*pf) == typeid(Son1)){
		Son1 *ps = static_cast<Son1*>(pf);	
		cout << "=======" << endl;
	}
	return 0;	
}

动态类型转换 dynamic_cast

用在有多态的父子指针或者引用之间的转换
	如果没有多态,编译报错
一定会用这个,因为dynamic_cast会校验是否可以转换 ,用typeid进行校验

当基类指针所指向的对象和目标类型对象不一致时,后续操作都将导致核心段错误 
只有当指针所指向的目标或者引用所引用的目标和目标类型一致时,才能转换成功
能用dynamic_cast就不会用static_cast;
能用static_cast就不用reinterpret_cast;    static_cast会进行地址偏移(基类子对象查找)
能用reinterpret_cast就不用强制


S2 *ps2=dynamic_cast<S2*>(p);//    编译虽然不会报错,但是调用就会错
//当基类指针所指向的对象和目标类型对象不一致时,后续操作都将导致核心段错误   
//转换失败 返回NULL

S2 *ps2=static_cast<S2*>(p);//一定能成功,但是后续的访问不确定
#include <iostream>
using namespace std;

class F{
private:
	string name;
public:
	virtual void f(){}
};


class S1:public F{
public:
	int x;
};

class S2:public F{
public:
	double d;
public:
	virtual void f(){
		cout << "S2::f()" << endl;	
	}
};

int main(){
	S1 s;
	F *p = &s;

	S1 *ps1 = dynamic_cast<S1*>(p);
	S2 *ps2 = dynamic_cast<S2*>(p);//当基类指针所指向的对象和目标类型的对象不一致时,后续操作都将导致核心段错误
	cout << ps2 << endl;//NULL
	S2 *ps3 = static_cast<S2*>(p);//一定能成功  后续的访问不确定
	ps2->f();
	return 0;	
}

虚函数和sizeof

如果一个类中有虚函数(个数只要大于0),
那么该类或者对象在用sizeof求字节数时,比没有这个虚函数时大4个字节(byte)/8个字节  
后面再加虚函数,类大小不变
多出的4/8个字节是个指针

class E{};//空类大小1

补了成员函数,大小还是1
但是第一次加了virtual,大小是4   dev中8

hex << *(int *)(&f1)   ;//大概在代码区的位置 
#include <iostream>
using namespace std;


class E{};

class F{
public:	
	virtual void show(){}
	virtual void f1(){}
};


int main(){
	cout << sizeof(E) << endl;
	cout << sizeof(F) << endl;
	F f1,f2,f3;//指针 4byte
	cout << hex << *(int*)(&f1) << endl;
	cout << hex << *(int*)(&f2) << endl;
	cout << hex << *(int*)(&f3) << endl;
	cout << &f1 << endl;
	return 0;	
}

虚函数表与虚表指针

虚函数表:
	如果一个类中存在虚函数,那么在编译该类时,编译器会为该类生成一个虚函数表
	所谓虚函数表,即存储虚函数地址的数组  (函数指针数组)  environ环境列表
	数组中的每一项都存储一个虚函数的地址(指针)
	如果存在继承关系,那么在生成子类的虚函数表时,会把父类的虚函数表拷贝过来
	然后,子类提供了覆盖版本的函数,则修改相对应版本的函数的地址
	如果子类没有提供覆盖版本,则是父类函数对应的地址
	而且在虚函数表中添加子类自己定义的虚函数地址
虚表指针:
	拥有虚函数类的对象,都会有4/8个字节byte的一个指针,该指针指向该类的虚函数表
	一个类只有一个虚函数表  一个类所有对象的虚表指针的值都是一样的
虚表指针在前面,成员属性在后面
	如果是多继承(基类都是有虚函数)会有多个虚表指针,从不同的基类继承拷贝一份进行覆盖

通过类型转换,获得虚表中的元素

#include <iostream>
using namespace std;


class A{
public:
	virtual void f1(){
		cout << "A::f1" << endl;	
	}
	void f2(){}
	virtual void f3(){
		cout << "A::f3" << endl;	
	}
	virtual void f4(){
		cout << "A::f4" << endl;	
	}
};

class B:public A{
public:	
	virtual void f1(){
		cout << "B::f1()" << endl;		
	}
	virtual void f2(){
		cout << "B::f2()" << endl;
	}
};


int main(){
	A a;
	int x = *(int*)(&a);//虚函数表的地址
	int y = *(int*)(x+4);
	((void (*)(void))y)();
	//cout << hex << y << endl;
	//((void (*)(void))(*(int*)(*(int*)&a)))();
	//void (*)(void)  函数指针     
	//void (**)(void)   函数指针数组
	void (**vft)(void) = *(void (***)(void))&a;
	cout << (void*)vft[0] << endl;
	cout << (void*)vft[1] << endl;
	cout << (void*)vft[2] << endl;
	vft[0]();
	vft[1]();
	vft[2]();
	B b;
	vft = *(void (***)(void))&b;
	cout << (void *)vft[0] << endl;
	cout << (void *)vft[1] << endl;
	cout << (void *)vft[2] << endl;
	cout << (void *)vft[3] << endl;
	
	vft[0]();
	vft[1]();
	vft[2]();
	vft[3]();

	return 0;	
}

这里关于虚表指针,他是放在每个类存储的第一位,因为是一个指针,所以要转换为(void*)
因为大小为一个指针,所以+1 后通过类型转换会得到第一个成员属性

#include <iostream>
using namespace std;

class A{
public:
	int x;
public:
	A():x(1024){}
	virtual void f1(){
		cout << "A::f1()" << endl;	
	}
};

int main(){
	cout << sizeof(A) << endl;
	
	A a;
	cout << (void*)*(int*)&a << endl;//虚函数表的地址 
	cout << *(int*)((int*)&a+1) << endl;


	return 0;	
}

如果是多继承(基类都是有虚函数)会有多个虚表指针,从不同的基类继承拷贝一份进行覆盖

#include <iostream>
using namespace std;


class A{
	//int x;
public:
	//A():x(1024){}
	virtual void f1(){
		cout << "A::f1" << endl;	
	}	
};

class B{
	//int y;
public:	
	//B():y(9527){}
	virtual void f2(){
		cout << "B::f2" << endl;	
	}
};
//从父类中继承虚函数表  
class C:public A,public B{
public:
	void f1(){
		cout << "C::f1" << endl;	
	}
	void f2(){//虚函数
		cout << "C::f2" << endl;	
	}
	virtual void f3(){
		cout << "C::f3" << endl;	
	}
};


int main(){
	cout << sizeof(C) << endl;
	C c;
	A* pa = &c;
	B* pb = &c;
	pa->f1();
	pb->f2();
	cout << (void*)*(int*)&c << endl;
	cout << (void*)*((int*)&c+1) << endl;
	void (**vft)(void) = *(void (***)(void))&c;
	vft[0]();
	vft[1]();
	vft[2]();
	//vft[2]();
	vft = *(void (***)(void))((int*)&c+1);
	vft[0]();
	//vft[1]();
	C c1;
	cout << (void*)*(int*)&c1 << endl;
	cout << (void*)*((int*)&c1+1) << endl;

	return 0;
}



多态 动态绑定

当编译器看到通过指向子类对象的基类指针或者引用子类对象的基类引用,
调用基类中的虚函数时,并不会直接生成函数的调用代码
相反,会在该函数调用的地方生成若干指令,这些指令在程序运行时被执行,完成如下功能:
1.根据指针或者引用的目标对象找到对应的虚函数表的指针(虚表指针)
2.根据虚函数表指针找到该类对应的虚函数表,找到虚函数的地址
3.根据虚函数的地址,指向对应虚函数的代码
4.执行虚函数的代码指令
	故,多态是运行时绑定,也叫动态绑定
	指的是只有当运行阶段才能确定调用的函数
#include <iostream>
#include <cstring>
using namespace std;


void f1(){
	cout << "f1" << endl;
}

void f2(){
	cout << "f2" << endl;	
}

void f3(){
	cout << "f3" << endl;	
}

void f4(){
	cout << "f4" << endl;	
}

class A{
int x;	
};
int main(){
	cout << (void*)f1 << endl;
	cout << (void*)&f1 << endl;
	void (*arr[4])(void) = {f1,f2,&f3,&f4};//	模拟了虚函数表
	for(int i=0;i<4;i++){
		cout << (void*)arr[i] << endl;	
	}
	A a;
	int *p = (int*)&arr;
	memcpy(&a,&p,4);//按字节拷贝  虚表指针  memcpy(&a,&arr,4);

	//((void (*)(void))(*(int*)(*(int*)&a)))();
	cout << (void*)((void (*)(void))(*(int*)(*(int*)&a))) << endl;;

	((void (*)(void))(*(int*)(*(int*)&a)))();
	
	void (**vft)(void) = *(void (***)(void))&a;
	vft[0]();
	vft[1]();
	vft[2]();
	vft[3]();

	return 0;	
}
动态绑定对程序的性能会造成一定的影响,(让程序的运行效率变慢)
如果没有必要实现多态时,就不要用虚函数

int *p=(int *)&arr;
memcpy(&a,&p,4);//按字节拷贝 虚表指针 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值