C++笔记(面向对象)静态联编和动态联编
1. 基本概念
联编(Binding) 指的是将函数调用与函数实现关联起来的过程。
静态联编(Static Binding)
也称为早期绑定(Early Binding)
在编译期间确定调用哪个函数
基于变量的静态类型(声明类型)
C++语言中,使用对象名加点“.”成员选择运算符,去调用对象虚函数,则被调用的虚函数是在编译和链接时确定。(称为静态联编)。
动态联编(Dynamic Binding)
也称为晚期绑定(Late Binding)
在运行期间确定调用哪个函数
基于对象的实际类型(动态类型)
C++语言中,使用类类型的引用或指针调用虚函数(成员选择符“->”),则程序在运行时选择虚函
数的过程,称为动态联编
2. 静态联编详解
2.1 静态联编的几种情况
cpp
#include <iostream>
using namespace std;class Base {
public:void nonVirtualFunc() {cout << "Base::nonVirtualFunc" << endl;}void overloadedFunc(int x) {cout << "Base::overloadedFunc(int): " << x << endl;}void overloadedFunc(double x) {cout << "Base::overloadedFunc(double): " << x << endl;}
};class Derived : public Base {
public:// 隐藏基类的nonVirtualFunc,不是重写!void nonVirtualFunc() {cout << "Derived::nonVirtualFunc" << endl;}// 添加新的重载版本void overloadedFunc(const string& s) {cout << "Derived::overloadedFunc(string): " << s << endl;}
};void testStaticBinding() {cout << "=== 静态联编演示 ===" << endl;Derived derived;Base* basePtr = &derived; // 基类指针指向派生类对象// 情况1:非虚函数调用 - 静态联编basePtr->nonVirtualFunc(); // 输出: Base::nonVirtualFuncderived.nonVirtualFunc(); // 输出: Derived::nonVirtualFunc// 情况2:函数重载 - 静态联编derived.overloadedFunc(10); // 输出: Base::overloadedFunc(int): 10derived.overloadedFunc(3.14); // 输出: Base::overloadedFunc(double): 3.14derived.overloadedFunc("hello");// 输出: Derived::overloadedFunc(string): hello// 情况3:通过基类指针只能看到基类的重载basePtr->overloadedFunc(20); // 输出: Base::overloadedFunc(int): 20basePtr->overloadedFunc(2.71); // 输出: Base::overloadedFunc(double): 2.71// basePtr->overloadedFunc("world"); // ❌ 错误:基类没有string版本
}2.2 静态联编的原理
编译器的处理过程:
cpp
// 源代码 Base* ptr = new Derived(); ptr->nonVirtualFunc();// 编译器的处理(概念上): // 1. 查看ptr的声明类型:Base* // 2. 在Base类中查找nonVirtualFunc // 3. 生成调用Base::nonVirtualFunc的代码 // 4. 函数地址在编译时就已经确定
汇编级别看静态联编:
assembly
; ptr->nonVirtualFunc() 的编译结果 call Base::nonVirtualFunc ; 直接调用,地址在编译时确定
3. 动态联编详解
3.1 动态联编的实现:虚函数
cpp
class Base {
public:// 虚函数 - 支持动态联编virtual void virtualFunc() {cout << "Base::virtualFunc" << endl;}virtual void anotherVirtual() {cout << "Base::anotherVirtual" << endl;}// 虚析构函数 - 重要!virtual ~Base() {cout << "Base destructor" << endl;}
};class Derived : public Base {
public:// 重写虚函数void virtualFunc() override {cout << "Derived::virtualFunc" << endl;}void anotherVirtual() override {cout << "Derived::anotherVirtual" << endl;}~Derived() override {cout << "Derived destructor" << endl;}
};class SecondDerived : public Derived {
public:void virtualFunc() override {cout << "SecondDerived::virtualFunc" << endl;}
};void testDynamicBinding() {cout << "\n=== 动态联编演示 ===" << endl;Base* basePtr1 = new Derived();Base* basePtr2 = new SecondDerived();Derived* derivedPtr = new SecondDerived();// 动态联编:根据实际对象类型调用函数basePtr1->virtualFunc(); // 输出: Derived::virtualFuncbasePtr2->virtualFunc(); // 输出: SecondDerived::virtualFuncderivedPtr->virtualFunc(); // 输出: SecondDerived::virtualFunc// 虚析构函数也是动态联编delete basePtr1; // 先调用Derived::~Derived,再调用Base::~Basedelete basePtr2; // 先调用SecondDerived::~SecondDerived,再调用Derived::~Derived,最后Base::~Basedelete derivedPtr;
}3.2 动态联编的原理
编译器的处理过程:
cpp
// 源代码 Base* ptr = new Derived(); ptr->virtualFunc();// 编译器的处理(概念上): // 1. 发现virtualFunc是虚函数 // 2. 生成通过vtable间接调用的代码: // - 通过ptr找到vptr // - 通过vptr找到vtable // - 在vtable中找到virtualFunc的地址 // - 调用该地址对应的函数
汇编级别看动态联编:
assembly
; ptr->virtualFunc() 的编译结果 mov rax, qword ptr [ptr] ; 获取对象地址 mov rax, qword ptr [rax] ; 获取vptr(对象的前8字节) mov rax, qword ptr [rax] ; 获取vtable中第一个函数的地址 call rax ; 间接调用,地址在运行时确定
4. 对比实验:区分两种联编
cpp
class Calculator {
public:// 静态联编:函数重载int calculate(int a, int b) {cout << "Calculator::calculate(int, int)" << endl;return a + b;}double calculate(double a, double b) {cout << "Calculator::calculate(double, double)" << endl;return a * b;}// 动态联编:虚函数virtual double compute(int a, int b) {cout << "Calculator::compute" << endl;return a * b;}
};class ScientificCalculator : public Calculator {
public:// 隐藏基类的calculate(静态联编)double calculate(double a, double b) {cout << "ScientificCalculator::calculate(double, double)" << endl;return a / b;}// 重写虚函数(动态联编)double compute(int a, int b) override {cout << "ScientificCalculator::compute" << endl;return pow(a, b);}
};void contrastExperiment() {cout << "=== 联编方式对比实验 ===" << endl;ScientificCalculator sciCalc;Calculator* calcPtr = &sciCalc;cout << "\n1. 静态联编(非虚函数):" << endl;sciCalc.calculate(10, 20); // 调用基类版本(静态联编)sciCalc.calculate(10.0, 20.0); // 调用派生类版本(静态联编)calcPtr->calculate(5, 3); // 调用基类版本(静态联编)cout << "\n2. 动态联编(虚函数):" << endl;sciCalc.compute(2, 3); // 调用派生类版本calcPtr->compute(2, 3); // 调用派生类版本(动态联编!)
}5. 联编方式的决定因素
5.1 影响联编方式的因素
cpp
class Test {
public:void normalMethod() {} // 静态联编virtual void virtualMethod() {} // 动态联编
};void analyzeBindingFactors() {Test obj;Test* ptr = &obj;Test& ref = obj;cout << "=== 联编方式分析 ===" << endl;// 情况1:通过对象调用 - 总是静态联编obj.normalMethod(); // 静态联编obj.virtualMethod(); // 静态联编!(编译器可以确定对象类型)// 情况2:通过指针调用 - 根据函数类型决定ptr->normalMethod(); // 静态联编ptr->virtualMethod(); // 动态联编// 情况3:通过引用调用 - 根据函数类型决定ref.normalMethod(); // 静态联编ref.virtualMethod(); // 动态联编// 情况4:在构造函数中调用// 情况5:在析构函数中调用// (构造函数和析构函数中总是静态联编)
}5.2 构造函数和析构函数中的联编
cpp
class Base {
public:Base() {cout << "Base构造函数中: ";show(); // 静态联编!在构造期间对象还不是派生类}virtual ~Base() {cout << "Base析构函数中: ";show(); // 静态联编!在析构期间对象已不是派生类}virtual void show() {cout << "Base::show" << endl;}
};class Derived : public Base {
public:Derived() {cout << "Derived构造函数中: ";show(); // 静态联编}~Derived() override {cout << "Derived析构函数中: ";show(); // 静态联编}void show() override {cout << "Derived::show" << endl;}
};void testConstructorBinding() {cout << "\n=== 构造/析构函数中的联编 ===" << endl;Derived derived;cout << "--- 对象创建完成 ---" << endl;// 正常使用时的动态联编Base* ptr = &derived;cout << "正常使用: ";ptr->show(); // 动态联编:Derived::show
}输出结果:
text
Base构造函数中: Base::show Derived构造函数中: Derived::show --- 对象创建完成 --- 正常使用: Derived::show Derived析构函数中: Derived::show Base析构函数中: Base::show
6. 性能对比分析
6.1 性能测试代码
cpp
#include <chrono>
using namespace std::chrono;class PerformanceTest {
public:void staticMethod() {// 一些简单工作volatile int result = 0;for (int i = 0; i < 100; ++i) {result += i;}}virtual void virtualMethod() {// 同样的工作volatile int result = 0;for (int i = 0; i < 100; ++i) {result += i;}}
};void performanceComparison() {const int iterations = 100000000; // 1亿次调用PerformanceTest obj;PerformanceTest* ptr = &obj;cout << "=== 性能对比 ===" << endl;// 测试静态联编性能auto start1 = high_resolution_clock::now();for (int i = 0; i < iterations; ++i) {obj.staticMethod(); // 静态联编}auto end1 = high_resolution_clock::now();// 测试动态联编性能auto start2 = high_resolution_clock::now();for (int i = 0; i < iterations; ++i) {ptr->virtualMethod(); // 动态联编}auto end2 = high_resolution_clock::now();auto static_time = duration_cast<milliseconds>(end1 - start1);auto virtual_time = duration_cast<milliseconds>(end2 - start2);cout << "静态联编: " << static_time.count() << "ms" << endl;cout << "动态联编: " << virtual_time.count() << "ms" << endl;cout << "性能差异: " << (virtual_time.count() - static_time.count()) << "ms" << endl;
}7. 实际应用建议
7.1 选择联编方式的准则
cpp
// 适合静态联编的情况:
class MathUtils {
public:// 工具函数,行为固定static double sqrt(double x) { return std::sqrt(x); }static double sin(double x) { return std::sin(x); }// 重载函数,编译时就能确定int process(int x) { return x * 2; }double process(double x) { return x * 1.5; }
};// 适合动态联编的情况:
class Document {
public:virtual void save() = 0; // 不同文档格式保存方式不同virtual void print() = 0; // 打印行为可能不同virtual void close() = 0; // 关闭前的清理工作不同virtual ~Document() = default;
};class PdfDocument : public Document {void save() override { /* PDF保存逻辑 */ }void print() override { /* PDF打印逻辑 */ }void close() override { /* PDF关闭逻辑 */ }
};class WordDocument : public Document {void save() override { /* Word保存逻辑 */ }void print() override { /* Word打印逻辑 */ }void close() override { /* Word关闭逻辑 */ }
};7.2 设计原则
默认使用静态联编:性能更好
需要多态时使用动态联编:当行为需要根据实际对象类型变化时
基类析构函数应该是虚函数:确保正确清理资源
避免在构造/析构函数中调用虚函数:此时不是多态行为
8. 总结
静态联编 vs 动态联编
| 特性 | 静态联编 | 动态联编 |
|---|---|---|
| 确定时间 | 编译期间 | 运行期间 |
| 决定依据 | 变量/参数的静态类型 | 对象的实际类型 |
| 实现机制 | 直接函数调用 | 通过vtable间接调用 |
| 性能 | 高效 | 有额外开销 |
| 灵活性 | 较低 | 很高 |
| 适用场景 | 函数重载、模板、非虚函数 | 虚函数、多态 |
关键要点:
静态联编是默认行为,适用于大多数情况
动态联编通过虚函数实现,支持运行时多态
联编方式在调用时确定,不是声明时
理解两种联编的区别有助于写出高效、正确的代码
