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::nonVirtualFunc
    derived.nonVirtualFunc();   // 输出: Derived::nonVirtualFunc
    
    // 情况2:函数重载 - 静态联编
    derived.overloadedFunc(10);     // 输出: Base::overloadedFunc(int): 10
    derived.overloadedFunc(3.14);   // 输出: Base::overloadedFunc(double): 3.14
    derived.overloadedFunc("hello");// 输出: Derived::overloadedFunc(string): hello
    
    // 情况3:通过基类指针只能看到基类的重载
    basePtr->overloadedFunc(20);    // 输出: Base::overloadedFunc(int): 20
    basePtr->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::virtualFunc
    basePtr2->virtualFunc();     // 输出: SecondDerived::virtualFunc
    derivedPtr->virtualFunc();   // 输出: SecondDerived::virtualFunc
    
    // 虚析构函数也是动态联编
    delete basePtr1;  // 先调用Derived::~Derived,再调用Base::~Base
    delete basePtr2;  // 先调用SecondDerived::~SecondDerived,再调用Derived::~Derived,最后Base::~Base
    delete 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 设计原则

  1. 默认使用静态联编:性能更好

  2. 需要多态时使用动态联编:当行为需要根据实际对象类型变化时

  3. 基类析构函数应该是虚函数:确保正确清理资源

  4. 避免在构造/析构函数中调用虚函数:此时不是多态行为


8. 总结

静态联编 vs 动态联编

特性 静态联编 动态联编
确定时间 编译期间 运行期间
决定依据 变量/参数的静态类型 对象的实际类型
实现机制 直接函数调用 通过vtable间接调用
性能 高效 有额外开销
灵活性 较低 很高
适用场景 函数重载、模板、非虚函数 虚函数、多态

关键要点:

  1. 静态联编是默认行为,适用于大多数情况

  2. 动态联编通过虚函数实现,支持运行时多态

  3. 联编方式在调用时确定,不是声明时

  4. 理解两种联编的区别有助于写出高效、正确的代码

相关推荐
WBluuue3 小时前
AtCoder Beginner Contest 430(ABCDEF)
c++·算法
小肖爱笑不爱笑3 小时前
2025/11/5 IO流(字节流、字符流、字节缓冲流、字符缓冲流) 计算机存储规则(ASCII、GBK、Unicode)
java·开发语言·算法
Elias不吃糖3 小时前
第四天学习总结:C++ 文件系统 × Linux 自动化 × Makefile 工程化
linux·c++·学习
手握风云-3 小时前
Java 数据结构第二十八期:反射、枚举以及 lambda 表达式
java·开发语言
熬了夜的程序员3 小时前
【LeetCode】99. 恢复二叉搜索树
算法·leetcode·职场和发展
ᐇ9593 小时前
Java Vector集合全面解析:线程安全的动态数组
java·开发语言
Kent_J_Truman3 小时前
LeetCode Hot100 自用
算法·leetcode·职场和发展
还是码字踏实3 小时前
算法题种类与解题思路全面指南:基于LeetCode Hot 100与牛客Top 101
算法·leetcode
沐怡旸3 小时前
【穿越Effective C++】条款14:在资源管理类中小心copying行为——RAII类的拷贝语义设计
c++·面试