1. 多态性概述
(1)多态的定义
多态指操作接口具有多种表现形态的能力,同一接口根据操作环境的不同采用不同的处理方式
(2)多态的分类
a. ->编译时多态:如运算符重载
编译时多态属于静态时多态,优点是函数调用速度快、效率较高,缺点是编程不够灵活
- 运行时多态:如虚函数(代码无法体现的)
运行时多态属于动态时多态,动态时多态的优点是提供了更好的编程灵活性、问题抽象性和程序易维护性动态时多态的缺点是:与编译时多态相比,函数调用速度慢。
b. ->重载多态:函数重载、运算符重载(编译时多态)
- 强制多态:变量强制类型转换(编译时多态)
- 包含多态:同样的操作可用于一个类型及其子类型。包含多态一般需要进行运行时类型检查,主
要通过虚函数来实现(运行时多态)
函数重载和虚函数有哪些相同点和不同点?相同点:都与多态性有关,提高了代码的灵活性不同点: (1)实现机制:编译时多态;运行时多态
(2)调用方式:函数重载 由编译器根据参数匹配情况确定;虚函数 动态绑定调用的函数版
本在运行时确定,依赖于对象的真实类型
(3)实现目的:函数重载处理参数的不同类型 虚函数让派生类以不同方式实现基类的函数
接口
- 参数多态:
采用参数化模板,使得一个结构可以适用于多种数据类型。例如,函数模板、类模板在C++的模版中可以很好地体现参数多态:
template<typename T>
void swap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
📌 这段代码中,T 是一个类型参数。在调用 swap 函数时,可以传入不同类型的参数,如 int、float、double 等基本类型,甚至是自定义的类类型(只要该类类型支持赋值操作),编译器会根据传入的实际参数类型生成相应的函数实例。作用
- 提高代码的复用性,减少代码冗余。因为可以用一套代码处理多种类型的数据,而不是针对每种类
型都重复编写相似的代码。
- 增强程序的可扩展性。当需要处理新的数据类型时,只要该类型满足模板中对参数的要求,就可以
直接使用现有的代码结构,而不需要对原有代码进行大规模修改。
2. 运算符重载
(1)不能重载的运算符
:“.” “*”“::”“?:”“sizeof”
(2)重载的原则
a. 不能改变原有运算符的运算顺序和结合性
b. 要保持原运算符的意义。例如,单目运算符不能重载为双目运算符
(3)两种重载方式
类的非静态成员函数:类内实现非成员函数:类外实现(可声明友元)主要区别:成员函数具有this指针; 友元函数没有this指针()和【】只能用成员函数不能用友元函数重载
(4)对this调用的理解
一元、二元运算符函数operator@都需要一个操作数由对象aa通过this指针隐含地传递,因此参数表为空。这时,运算符函数用类的成员函数表示。
(5)前置和后置++的区别
class MyInteger{
通过友元实现输出流重
friend ostream& operator<<(ostream &out,MyInteger myint);//
载
public:
MyInteger(){m_Num=0;}
MyInteger& operator++(){
m_Num++;
return *this;
}
占位参数,避免重定义以实现重载
MyInteger& operator++(int){//int
int temp=*this;
m_Num++;
return temp;
}
private:
int m_Num;
};
(6)函数运算符重载
重载的()运算符,也称为仿函数
class MyPrint
{
public:
void operator()(string text)
{
cout<<text<<endl;
}
}
void test01()
{
Myprint func;
func("Helloworld");
}
(7)operator= 的特殊性
a. 重载的运算符函数operator=不能被继承
b. 重载的运算符函数operator=必须是成员函数
(8)没有声明类重载赋值运算符的情况
如果用户没有为类重载赋值运算符函数,编译程序将生成一个缺省的赋值运算符。默认(缺省)赋值运算符的行为:
a. 默认的赋值运算符会逐个复制类中的每个成员变量。对于基本类型(如 int 、 double 等),这
相当于直接复制它们的值。
b. 对于指针类型的成员,这相当于复制指针本身,而不是指针所指向的数据。(浅拷贝警告)
拷贝函数用于创建一个新对象,赋值运算符是改变一个已存在的对象的值深拷贝是一种特殊的运算符重载
3. 虚函数
(1)虚函数的意义
虚函数是实现多态性的基础,必须是非静态的成员函数,经过派生之后,就可以实现运行过程中的多态C++中虚函数是动态绑定的函数,即编译的时候不指明哪一个重载函数,只有在运行时根据实际的地址确定调用哪一个函数
(2)虚表
在C++中,虚表(Virtual Table,简称vtable)是一种实现多态机制的底层数据结构。
a. 存储内容
虚表本质上是一个函数指针数组,数组中的每个元素指向一个虚函数的入口地址。当一个类中包含虚函数时,编译器会为该类生成一个虚表。例如:
class Shape {
public:
virtual float area() = 0;
};
📌 Shape类中有虚函数,编译器会为其构建虚表。
b. 虚表指针
类的每个对象中都包含一个虚表指针(vptr),它指向该类的虚表。在对象的内存布局中,虚表指针通常位于对象的开头部分。
c. 多态的实现
当通过基类指针或引用调用虚函数时,程序会根据对象的虚表指针找到对应的虚表,然后在虚表中查找并调用正确的虚函数版本。例如:
class Circle : public Shape {
public:
float area() override {
// 计算圆面积的代码
}
};
Shape* shapePtr = new Circle();
shapePtr->area();
📌 上述代码中,shapePtr虽然是Shape*类型,但因为虚表机制,实际调用的是Circle类中的area函数实现多态。
(3)virtual关键字
a. 派生类可以不显式地用virtual声明虚函数,这时系统就会用以下规则来判断派生类的一个函数成员是
不是虚函数:该函数是否与基类的被覆盖的虚函数有相同的名称、参数个数及对应参数类型、cv限定符(是否const)、引用限定符(&)该函数的返回值类型是否与基类被覆盖的虚函数返回值类型相同,或者可以隐含转换为基类被覆盖的的虚函数的返回值类型;如果派生类的函数满足上述条件,就会自动确定为虚函数。这时,派生类的虚函数便覆盖了基类的虚函数。派生类中的虚函数还会隐藏基类中同名函数的所有其它重载形式。
(4)虚函数设定规范
虚函数必须是非静态的成员函数(所有对象共用,无法实现动态绑定),不能成为虚函数的函数:
a. 普通函数(非成员函数):只能overload(并列关系) 不能override(覆盖关系)
b. 构造函数
c. 内联成员函数:inline函数在编译时被展开,虚函数在运行时才能动态地绑定函数
d. 静态成员函数:对于每个类都只有一份代码,所有的对象都共享这一份代码,没有动态绑定的必要
性
e. 友元函数:没有继承特性的函数,就无虚函数之称
(5)*override与final
--关键字override:上下文关键字,用于修饰派生类的虚函数主要用来增强代码可读性 或者 检查派生过程中的错误--关键字final:a.final修饰类表示类不能被继承
class OVER final{
};
b. final修饰虚函数使虚函数不能在派生类中被重写
virtual 函数类型 函数名() final{}
(6)*动态绑定的实现原理
--动态选择被执行的函数函数的调用,需要通过函数代码的入口地址把函数入口地址作为变量,在不同情况下赋予不同的值,通过该变量调用函数,就可动态选择被执行的函数--虚表每个多态类有一个虚表(virtualtable)虚表中有当前类的各个虚函数的入口地址每个对象有一个指向当前类的虚表的指针(虚指针vptr)--动态绑定的实现构造函数中为对象的虚指针赋值通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到所调用的虚函数的入口地址通过该入口地址调用虚函数
4. 虚析构virtual 函数类型 函数名() final{}函数
(1)认识
(2)作用
a. 通过基类指针删除派生类对象
b. 若允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就要让基类的析构
函数成为虚函数,否则delete的结果是不确定的
5. 纯虚函数
(1)定义
纯虚函数是一个在基类中声明的虚函数,在基类中没有定义具体的操作内容,需要各个派生类根据实际需要定义自己的版本
(2)抽象类
a. 带有纯虚函数的类称为抽象类
b. 抽象类不能定义对象
c. 抽象类只能作为基类使用
(3)语法
virtual 函数类型 函数名()=0;