- 多态性与静态联编、动态联编
- 函数重载
- 重定义
- 重写
- 模板
- 静态联编
编译时确定的函数调用叫静态联编
class A {
public:
void Fun1() {
printf("A\n");
}
};
class B :public A {
public:
void Fun1() {
printf("B\n");
}
};
int main() {
A objA, *pA;
B objB, *pB;
pA = &objB;
pA->Fun1();// A::Fun1
pB = (B*)&objA;
pB->Fun1();// B::Fun1
return 0;
}
- 动态联编
class A {
public:
virtual void Fun1() {
printf("A\n");
}
};
class B :public A {
public:
virtual void Fun1() {
printf("B\n");
}
};
int main() {
A objA, *pA;
B objB, *pB;
int a;
scanf_s("%d", &a);
if (a == 1) {
pA = &objB;
}
else {
pA = &objA;
}
pA->Fun1();// 指向谁调用谁的Fun1
pB = (B*)&objA;
//pB->Fun1();
return 0;
}
如果成员函数不是虚函数,根据指针实际类型来调用相应类的成员函数
- 类与静态联编
- 静态联编
类的普通函数调用,都是静态联编,和指针指向谁没关系,调用的函数取决于指针类型,指针是什么类型就调用该类的函数
- 虚函数定义
- 普通成员函数前加virtual
- 类多了个虚函数,类的大小增加4字节:虚函数表指针
- 虚函数表属于类,不属于类对象,每次有新的类对象产生的时候,把该数组的首地址赋值给该类对象,所以所有的类对象保存的虚函数表指针都是一样的
- 当发生类继承(派生)的时候,会把该数组拷贝一份给新的类,有多少个基类就拷贝多少个数组,派生类的的类对象就有多少个虚函数表指针,派生类自己的虚函数地址放在拷贝过来的第1个虚函数表数组的后面
- 重写
虚函数,返回值类型、函数名、形参与基类函数完全一致的派生类函数构成重写
重写的虚函数,会把派生类中虚函数表中的函数地址换成派生类的虚函数地址
- 动态联编
- 两个要求:(基类)指针和虚函数
- 动态联编调用哪个函数取决于指针指向谁,指向谁调用谁的虚函数
- 调用过程:
- 先确定是不是动态联编:调用函数的指针所在的类中被调函数是不是虚函数,如果是就是动态联编,否则为静态联编
- 去指针指向的对象内存中找虚函数表地址
- 找到虚函数表地址后,找被调函数在数组中的偏移,根据偏移找到函数地址
- 调用该函数
- 哪些函数(不)能定义为虚函数
5.1 构造
不能,虚函数调用要先找虚函数表,构造函数调用前类对象内存中还没有虚函数表地址
5.2 析构
可以,而且最好定义为虚函数,定义为虚析构,会动态联编,基类指针指向派生类对象的时候,delete调用的析构函数就是派生类的,而派生类的析构函数会自动调用基类的析构函数
5.3 普通成员函数
可以
5.4 静态成员函数
不可以
5.5 内联函数
不可以 - 重载、重定义、重写
名称 | 返回值 | 参数 | 作用域 | 函数名 |
---|---|---|---|---|
重载 | 没有要求 | 不同 | 相同 | 相同 |
重定义 | 没有要求 | 没有要求 | 不同 | 相同 |
重写 | 相同 | 相同 | 不同 | 相同(虚函数) |
- 纯虚函数与抽象类
- 纯虚函数定义
virtual void Fun1() = 0;
- 抽象类定义
含有纯虚函数的类就叫抽象类 - 抽象类一般用来定义规范(接口),不具体实现,具体实现由派生类负责
class A {
public:
virtual void Fun1() = 0;
};
class A1 :public A {
public:
// 派生类可以不实现纯虚函数
// 如果不实现,该类也是抽象类
// 不能实例化对象
void Fun2() {
printf("A1\n");
}
};
int main(){
A obj;// 抽象类不能实例化对象
A1 obj1;
return 0;
}