C++ 虚拟函数、多态实现、静态绑定与动态绑定
虚函数
虚函数是C++中用于实现多态的一种特殊类型的成员函数。通过将函数声明为虚函数,可以在基类中定义一个接口,并允许派生类对该接口进行重写,从而实现运行时的动态绑定。
特点:
- 在基类中声明虚函数时,需要在函数的声明前面加上virtual关键字。
class Base {
public:
virtual void someFunction() {
// 基类中的虚函数的默认实现
}
};
- 派生类可以重写基类中的虚函数,使用相同的函数签名(函数名、参数列表和返回类型),并在函数声明前面加上override关键字,以明确表明该函数是对基类虚函数的重写。
class Derived : public Base {
public:
void someFunction() override {
// 派生类中对虚函数的实现
}
};
- 当通过基类指针或引用调用虚函数时,程序会根据实际对象的类型动态绑定到正确的函数实现。
Base* basePtr = new Derived(); // 使用基类指针引用派生类对象
basePtr->someFunction(); // 调用虚函数,会动态绑定到Derived类中的实现
delete basePtr; // 注意释放内存
虚函数的重写
虚函数的重写是指派生类中重新定义(覆盖)基类中已声明为虚函数的函数。通过重写虚函数,派生类可以提供自己的实现,以满足特定的需求或逻辑。
- 基类中的虚函数必须以
virtual
关键字进行声明。 - 派生类中的函数必须使用相同的函数签名来重写基类的虚函数。
- 在派生类中,使用
override
关键字显式标记重写的函数,以增加代码的可读性和清晰性。 - 访问权限可以是相同的或更宽松的
虚函数表
虚函数表是C++中用于实现动态绑定和多态性的一种机制。每个包含虚函数的类都会在内存中创建一个虚函数表。虚函数表是一个特殊的数据结构,其中存储了该类的虚函数的地址(指针)。每个对象都包含一个指向其所属类的虚函数表的指针,通常称为虚函数表指针。
虚函数表的基本原理和实现方式:
- 对于每个包含虚函数的类,编译器会在编译阶段为其生成一个虚函数表。
- 虚函数表是一个由函数指针组成的数组,每个函数指针对应一个虚函数的实现。
- 虚函数表位于内存的某个固定位置,通常位于类的静态存储区(例如,存储在类对象的元数据中)。
- 每个对象都包含一个虚函数表指针,指向其所属类的虚函数表。vptr通常作为对象的隐藏成员,存储在对象的内存布局中。
- 当通过基类指针或引用调用虚函数时,程序会首先访问对象的虚函数表指针,然后根据指针找到虚函数表。
- 通过虚函数表中的函数指针,程序可以动态地调用正确的虚函数实现,即根据对象的动态类型来确定调用哪个函数。
#include <iostream>
class Shape {
public:
virtual void draw() {
std::cout << "Drawing a shape." << std::endl;
}
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing a circle." << std::endl;
}
};
class Square : public Shape {
public:
void draw() override {
std::cout << "Drawing a square." << std::endl;
}
};
int main() {
Shape* shapePtr;
Circle circle;
Square square;
shapePtr = &circle;
shapePtr->draw(); // 动态绑定到 Circle 的 draw() 函数
shapePtr = □
shapePtr->draw(); // 动态绑定到 Square 的 draw() 函数
return 0;
}
由于
draw()
函数是虚函数,并且基类中声明为虚函数,因此在运行时会根据shapePtr
指向的对象的实际类型动态绑定到正确的函数实现。这意味着,无论shapePtr
指向的是Circle
还是Square
对象,都会调用相应派生类的draw()
函数。
验证虚表指针的方法(_vptr 指针不可访问):
先求一个普通类占的字节数大小sizeof(),然后将类中某一个函数(方法)前加virtual关键字变为虚函数,再求该类占的字节数大小sizeof(),会发现增加4个字节,这就验证了vptr的存在。
多态的实现
定义一个基类,其中包含一个或多个虚函数。虚函数是在基类中声明的函数,使用关键字virtual进行修饰。虚函数可以在派生类中被重写。
只有在虚函数才可以被重写,子类重写父类虚函数的条件是:返回值、函数名、参数都相同,并且是虚函数。
class Base {
public:
virtual void someFunction() {
// 基类中的虚函数的默认实现
}
};
派生一个或多个类从基类,并重写其中的虚函数。在派生类中,虚函数的声明和定义应保持一致,并使用关键字override进行修饰。
class Derived : public Base {
public:
void someFunction() override {
// 派生类中对虚函数的实现
}
};
使用基类指针或引用来引用派生类的对象,并通过这些指针或引用调用虚函数。在运行时,函数调用将根据实际对象的类型动态绑定到正确的函数实现上。
class Derived : public Base {
public:
void someFunction() override {
// 派生类中对虚函数的实现
}
};
在上述代码中,基类指针basePtr引用了派生类Derived的对象。通过调用basePtr->someFunction(),虚函数someFunction()将会根据实际对象的类型,动态地绑定到Derived类中的实现。这就是多态的核心机制。
总结起来,C++多态通过虚函数和动态绑定实现,它允许使用基类指针或引用来调用派生类对象的成员函数,并在运行时动态地选择调用的函数。这种特性使得代码更具灵活性和可扩展性。
静态类型与动态类型
一个对象(变量)的静态类型就是其声明类型,如表达式int a中的int就是对象a的声明类型,即静态类型;而一个对象(变量)的动态类型就是指程序执行过程中对象(指针或引用)实际所指对象的类型,如A* a = new B;其中B继承于A,则指针对象a的静态类型就是A*(声明类型),动态类型就是B*(实际所指对象的类型),又如Bb = new B;此时指针对象b的静态类型和动态类型是相等的,都是B。
静态绑定与动态绑定
静态绑定是指在编译时进行的绑定,也称为早期绑定。在静态绑定中,函数调用或代码执行的目标在编译时就已经确定,根据变量或表达式的静态类型来选择相应的函数或代码。
动态绑定是指在运行时进行的绑定,也称为晚期绑定(Late Binding)。在动态绑定中,函数调用或代码执行的目标是在运行时根据变量或表达式的实际类型来确定的,而不是根据静态类型。
静态绑定和动态绑定的区别:
静态绑定:
- 绑定发生在编译时。
- 目标在编译时已经确定。
- 根据变量或表达式的静态类型来选择函数或代码。
- 面向对象编程中,非虚函数的调用通常是静态绑定。
动态绑定:
- 绑定发生在运行时。
- 目标在运行时根据变量或表达式的实际类型确定。
- 根据变量或表达式的动态类型来选择函数或代码。
- 面向对象编程中,虚函数的调用通常是动态绑定。
动态绑定和静态绑定是编程中两种不同的绑定方式,用于确定将调用哪个函数实现或执行哪段代码。
静态绑定在编译时确定调用的函数或执行的代码,因此在性能上可能更高效。然而,它缺乏动态性和灵活性,无法适应对象类型的变化和多态需求。
#include <iostream>
class Base {
public:
void print() {
std::cout << "Base class." << std::endl;
}
};
class Derived : public Base {
public:
void print() {
std::cout << "Derived class." << std::endl;
}
};
int main() {
Base baseObj;
Derived derivedObj;
Base* ptr = &derivedObj; // 使用基类指针指向派生类对象
baseObj.print(); // 静态绑定到 Base 类的 print() 函数
derivedObj.print(); // 静态绑定到 Derived 类的 print() 函数
ptr->print(); // 静态绑定到 Base 类的 print() 函数
return 0;
}
动态绑定通过虚函数和虚函数表实现多态性,允许在运行时根据对象的实际类型来动态选择正确的函数实现。这使得代码更加灵活、可扩展,并支持面向对象编程的核心概念之一——多态性。
#include <iostream>
class Animal {
public:
virtual void makeSound() {
std::cout << "Animal makes a sound." << std::endl;
}
};
class Dog : public Animal {
public:
void makeSound() override {
std::cout << "Dog barks." << std::endl;
}
};
class Cat : public Animal {
public:
void makeSound() override {
std::cout << "Cat meows." << std::endl;
}
};
int main() {
Animal* animalPtr;
Dog dog;
Cat cat;
animalPtr = &dog;
animalPtr->makeSound(); // 动态绑定到 Dog 类的 makeSound() 函数
animalPtr = &cat;
animalPtr->makeSound(); // 动态绑定到 Cat 类的 makeSound() 函数
return 0;
}
上一篇: 我所理解的 C++ 虚拟函数实现机制
下一篇: C++之虚函数