目录
函数重载和函数重写
函数重载 | 函数重写 |
函数重载是指在同一个作用域内,可以有多个函数名相同但参数列表不同的函数。 | 函数重写是指在派生类中重新定义基类中已经存在的函数,且函数名、参数列表和返回类型都相同。 |
特点:
| 特点:
|
//函数重载
#include <iostream>
using namespace std;
class Calculator {
public:
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
};
int main() {
Calculator calculator;
int result1 = calculator.add(5, 3); // 调用 int add(int a, int b)
double result2 = calculator.add(2.5, 3.7); // 调用 double add(double a, double b)
cout << "Result 1: " << result1 << endl;
cout << "Result 2: " << result2 << endl;
return 0;
}
//函数重写
#include <iostream>
using namespace std;
// 基类 Animal
class Animal {
public:
virtual void makeSound() {
cout << "The animal makes a sound." << endl;
}
};
// 派生类 Dog
class Dog : public Animal {
public:
void makeSound() override {
cout << "The dog barks." << endl;
}
};
// 派生类 Cat
class Cat : public Animal {
public:
void makeSound() override {
cout << "The cat meows." << endl;
}
};
int main() {
// 创建 Animal 类的实例,并调用 makeSound 方法
Animal* animal = new Animal();
animal->makeSound(); // 输出: The animal makes a sound.
// 创建 Dog 类的实例,并调用 makeSound 方法
Dog* dog = new Dog();
dog->makeSound(); // 输出: The dog barks.
// 创建 Cat 类的实例,并调用 makeSound 方法
Cat* cat = new Cat();
cat->makeSound(); // 输出: The cat meows.
// 使用基类指针指向派生类对象,并调用 makeSound 方法
Animal* animalPtr = dog;
animalPtr->makeSound(); // 输出: The dog barks.
animalPtr = cat;
animalPtr->makeSound(); // 输出: The cat meows.
delete animal;
delete dog;
delete cat;
return 0;
}
静态绑定和动态绑定
静态绑定(静态多态性) | 动态绑定(动态多态性) | ||
静态绑定在编译时确定调用哪个函数,根据变量的静态类型进行匹配。 | 动态绑定在运行时确定调用哪个函数,根据变量的实际类型进行动态选择。 | ||
静态绑定是通过函数名和参数类型的匹配来实现的。 | 动态绑定是通过虚函数表(vtable)和虚函数指针(vpointer)来实现的。允许在派生类中重写基类的虚函数并实现多态性。 | ||
静态绑定适用于非虚函数和静态成员函数。 | 动态绑定适用于虚函数和虚基类的成员函数。 |
虚函数表
虚函数表(Virtual Function Table,简称VTable)是用于实现C++中动态多态(即运行时多态)的一种机制。它是一种存储虚函数指针的数据结构,用于支持基类和派生类之间的动态绑定。
在C++中,当一个类中声明了虚函数时,编译器会为该类生成一个虚函数表。虚函数表是一个数组,每个元素存储着一个指向相应虚函数的指针。对于含有虚函数的类,编译器会在该类的对象中插入一个指向虚函数表的指针(通常称为虚函数表指针或虚指针)。这个指针指向了该类的虚函数表。
当通过基类指针或引用调用一个虚函数时,C++会根据对象的实际类型(而不是指针或引用的类型)来确定要调用的函数。通过虚函数表的指针,C++可以在运行时动态地查找正确的函数并进行调用。
#include <iostream>
using namespace std;
class Animal {
public:
void makeSound() {
cout << "动物发出声音" << endl;
}
virtual void eat() {
cout << "动物正在吃东西" << endl;
}
};
class Dog : public Animal {
public:
void makeSound() {
cout << "狗发出汪汪的声音" << endl;
}
void eat() override {
cout << "狗正在吃骨头" << endl;
}
};
int main() {
Animal* animal1 = new Animal();
Animal* animal2 = new Dog();
//
animal1->makeSound(); // 静态绑定,输出: 动物发出声音
animal2->makeSound(); // 静态绑定,输出: 动物发出声音
animal1->eat(); // 动态绑定,输出: 动物正在吃东西
animal2->eat(); // 动态绑定,输出: 狗正在吃骨头
delete animal1;
delete animal2;
return 0;
}
在上述代码中,makeSound() 函数在基类 Animal 中被声明为普通成员函数,并没有使用 virtual 关键字。这意味着它不是一个虚函数,因此不会进行动态绑定。即使在 Dog 类中使用 makeSound() 的重定义,也不会产生多态效果。
const关键字
关键字在 C++ 中用于指定变量或函数的不可修改性。可以在编译时进行静态绑定。
虚函数和纯虚函数
虚函数 | 纯虚函数 |
虚函数是在基类中声明的成员函数,用关键字"virtual"进行修饰。 | 纯虚函数是在基类中声明的虚函数,但没有提供实现,通过在函数声明末尾加上 "= 0" 来指定它为纯虚函数。 |
派生类可以重写虚函数,即在派生类中重新定义和实现基类的虚函数。 | 带有纯虚函数的类被称为抽象类,不能直接实例化抽象类的对象。(但可以通过指针或引用来访问抽象类的派生类对象。) |
在运行时,通过指向派生类对象的基类指针或引用调用虚函数时,将根据对象的实际类型调用相应的函数。 | 派生类必须实现基类中的纯虚函数,才能实例化派生类的对象。 |
虚函数允许基类指针或引用在运行时动态地确定所调用的函数版本。 | 纯虚函数为派生类提供了一个必须实现的接口,但具体的实现方式可以因派生类而异。 |
虚函数可以有实现(非纯虚函数)也可以没有实现(纯虚函数)。 | 如果派生类没有实现基类的纯虚函数,那么派生类仍然是抽象类。 |
虚函数和纯虚函数都用于实现多态性和继承机制。 | |
总而言之,虚函数提供了一种默认的实现,允许派生类进行覆盖,而纯虚函数则强制要求派生类提供自己的实现。 |
什么函数不能声明为虚函数?
- 静态函数(static function):静态函数属于类而不是类的实例,因此不能用于多态。虚函数的主要目的是实现运行时的多态性,而静态函数与特定类的实例无关。
- 构造函数:构造函数没有继承的概念,因此不能被声明为虚函数。此外,虚函数需要在对象完全构造之后才能调用,但构造函数的目的就是创建对象,因此不适合作为虚函数。
- 内联函数(inline function):内联函数是在调用点进行代码展开的函数,它通常在编译时进行展开,不会进行函数调用。虚函数的实现涉及虚函数表等机制,不适合用于内联函数。
- 友元函数(friend function):友元函数是在类外部定义的(类内声明),但具有对类的私有成员的访问权限。由于友元函数不是类的成员函数,它不能被声明为虚函数。