【C++】多态

【C++】多态,第1张

文章目录
  • 一、多态定义
    • 1. 构成条件
    • 2. 虚函数
    • 3. 虚函数重写的两个例外
        • 协变
        • 析构函数的重写
  • 二、什么情况下类不能被继承
    • 【C++98解决方式:private】
    • 【C++11解决方式:final】
  • 三、override检查重写
  • 四、抽象类
  • 五、多态的原理
    • 1. 没有继承的虚函数表
    • 2. 单继承中虚函数表
    • 3. 多态当中的虚函数表
    • 4. 不能直接给类切片的原因
    • 5. 同类型的对象,虚函数表一样
    • 6. private不限制虚函数表
    • 7. 多继承中的虚函数

一、多态定义 1. 构成条件

多态:多种形式

静态的多态:函数重载,原理是编译时实现

动态的多态:

  • 必须通过基类的指针或者引用调用虚函数
  • 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写(覆盖)

重写(覆盖)的含义: 当子类和父类(或者只有父类但建议都有、这里可以理解为,子类继承了父类的虚函数属性,再进行重写)中虚函数(有virtual关键字),返回值,函数名,参数一模一样。


#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
using namespace std;

class A
{
public:
	virtual void AB()
	{
		cout << 'A' << endl;
	}

};

class B : public A
{
public:
	virtual void AB()
	{
		cout << 'B' << endl;
	}
};

void test(A* a)
{
	a->AB();
}
int main()
{
	A a;
	B b;
	test(&a);
	test(&b);
	
	return 0;
}



如果只是传类,不会构成多态,必须是指针和引用

构成多态,跟p的类型没有关系,传的哪个类型的对象,调用的就是这个类型的虚函数 – 跟对象有关

b类型:

a类型:(因为父类不能给子类会报错)

ab型:


  • 不构成多态,调用就是A类型的函数 – 跟类型有关


2. 虚函数

只能是类的非静态成员函数 + virtual关键字才是虚函数

class A
{
    virtual void test()
    {}
}

3. 虚函数重写的两个例外 协变

协变(基类与派生类虚函数返回值类型不同) :派生类重写基类虚函数时,与基类虚函数返回值类型不同。

即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

class A
{
public:
	virtual A* AB()
	{
		cout << 'A' << endl;
        return nullptr;
	}

};

class B : public A
{
public:
	virtual B* AB()
	{
		cout << 'B' << endl;
        return nullptr;
	}
};

析构函数的重写

在继承那,子类的析构函数析构时候,会最后调用父类析构函数

且编译会将两个析构函数都变成destructor()调用

在继承中它们构成隐藏,先析构子类再析构父类

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
using namespace std;

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

};

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

int main()
{
	A a;
	B b;

	return 0;
}


如果有这样一个场景的话

复习:

new = operator new + 构造函数

delete = 析构函数 + operator delete

int main()
{
	A* a = new A;
	A* b = new B;

	delete a;
	delete b;

	return 0;
}

当delete b 的时候,如果不构成多态,只会调用父类析构函数,但new B类型确实存在,虽然A* b发生截断了,但这样不释放空间会导致内存泄漏

加上virtual 之后


总结在创建动态申请子类对象的时候,需要重写


二、什么情况下类不能被继承
  • 当父类的构造函数是私有的时候,当子类定义时候,调用不了构造函数
【C++98解决方式:private】
class A
{
	A(int a)
		:_a(a)
	{}

	int _a;
};

class B : public A
{
};


int main()
{
	B b;
	return 0;
}

因为是private被继承不可见


不仅仅子类,父类也是没法对象实例化,同样没法调用构造函数

class A
{
	A(int a)
		:_a(a)
	{}
public:
	static A Create(int a)
	{
		return A(a);
	}

	int _a;
};

class B : public A
{
};


int main()
{
	A a = A::Create(10);
	return 0;
}

这里是static是保证可以从类域里面使用(因为还没实例化的时候Create函数不能使用)


【C++11解决方式:final】
class A final
{
public:
	A(int a)
		:_a(a)
	{}
	int _a;
};

class B : public A
{
};

int main()
{
	B b;
	return 0;
}


final还可以解决重写,防止被重写

class A 
{
public:
	virtual void AB() final
	{
		cout << "a" << endl;
	}

};

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

};

int main()
{
	A a;
	B b;

	A* aa = &a;
	B* bb = &b;

	aa->AB();
	bb->AB();
	
	return 0;
}


三、override检查重写

放在子类重写的虚函数后面,检查是否完成,没有重写报错

class A
{
public:
	void AB()
	{
		cout << "a" << endl;
	}

};

class B : public A
{
public:
	virtual void AB() override
	{
		cout << "b" << endl;
	}

};


总结:


四、抽象类

包含纯虚函数的类叫做抽象类(也叫接口类)

class A
{
public:
	virtual void AB() = 0;
	//virtual void AB() = 0
	//{
	//	cout << "a" << endl;
	//}
};

纯虚函数只声明不实现(但也可以写内容不过没用),因为包含纯虚函数的类不能实例化出对象


class A
{
public:
	//virtual void AB() = 0;
	virtual void AB() = 0
	{
		cout << "a" << endl;
	}
};

int main()
{
	A* a = nullptr;
	a->AB();
	return 0;
}

崩了


派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承
一个类型,如果一般在现实世界中,没有具体的对应实物就定义成抽象类比较好

纯虚函数的类,本质上强制的子类去完成虚函数重写。
override只是在语法上检查是否完成重写。

五、多态的原理 1. 没有继承的虚函数表
  • 没有继承,单独类里面有虚函数,有虚函数表
class A
{
	virtual void f()
	{

	}
	int a;
	char i;
};

int main()
{
	A a;
	return 0;
}

可见虚函数这里是多了个指针,而且这里虚函数表指针是放在前面

如果有两个虚函数?

这里虚函数表就是函数指针数组

2. 单继承中虚函数表

总结:虚函数表放,重写的虚函数和自己虚函数的地址(这里的地址也不是真实的地址,而是封装过的地址(jump指令的地址,jump之后才能到函数的第一句))

虚表存储在常量区并且没有构成重写的虚函数也在虚表中


这里代码运行成功的原因是,类成员函数存在代码区,p->ab只是把指针传给this不会出错

class A
{
public:
	void AB()
	{
		cout << "a" << endl;
	}
};

int main()
{
	A* a = nullptr;
	a->AB();
	return 0;
}

如果把virtual那个函数拿出来,会错误:错误的原因是把指针给了this,this会去找虚函数表的指针,所以空指针会出错(nullptr无法访问数组元素)


3. 多态当中的虚函数表
  • 多态例子,多态时候,子类只有重写和继承的函数地址在监控里可以看到在虚表当中,子类其他虚函数不显示,但可以用虚表地址,访问虚表,看到虚表里面其实是有存储其他虚函数的地址
class A
{
public:
	virtual void f()
	{
		cout << "A" << endl;
	}
};

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

};

void test(A& p)
{
	p.f();
}

int main()
{
	A a;
	B b;
	test(a);
	test(b);
	return 0;
}

二者虚函数表不一样


4. 不能直接给类切片的原因
int main()
{
	A a;
	B b;
	A aa = b;

	return 0;
}

二者地址虚表地址不同


5. 同类型的对象,虚函数表一样

就像类的函数只存一份一样

都存在公共代码区

int main()
{
	A a;
	A aa;

	return 0;
}


6. private不限制虚函数表

尽管有private修饰子类的多态,但仍然能够调用到函数

所以访问限定符对虚函数表没有影响,所以只要有虚表的指针,就能强制访问


7. 多继承中的虚函数

此时会有两个虚函数表,一个a表一个b表

class A
{
public:
	virtual void f()
	{
		cout << "A" << endl;
	}
	virtual void fa()
	{
		cout << "A" << endl;
	}

};

class B 
{
public:
	virtual void f()
	{
		cout << "B" << endl;
	}

	virtual void fb()
	{
		cout << "B" << endl;
	}

};

class C : public A, public B
{

};

int main()
{
	C c;
	A& a = c;
	B& b = c;
	return 0;
}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/727253.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)

保存