C++ 函数语义学——静动态类型、绑定、坑点与多态体现深谈

静动态类型、绑定、坑点与多态体现深谈

1. 静态类型和动态类型

静态类型在编译期间确定,而动态类型在运行时确定。静态类型决定了编译器如何解析代码,而动态类型决定了在运行时实际调用的函数。

静态类型

静态类型是对象定义时的类型,在编译期间就确定好的。

Base base;       // 静态类型是 Base
Derived derived; // 静态类型是 Derived
Base* pbase;     // 静态类型是 Base*,无论指向什么
Base* pbase2 = new Derived();  // 静态类型是 Base*
Base* pbase3 = new Derived2(); // 静态类型是 Base*
动态类型

动态类型是对象在运行时所指向的实际类型。一般来说,只有指针或引用才有动态类型的说法,通常指向父类的指针或引用。

根据上面的代码行,动态类型如下:

  • BaseDerived 没有动态类型,因为它们既不是指针也不是引用。
  • pbase2 的动态类型是 Derived
  • pbase3 的动态类型是 Derived2

动态类型在执行过程中可以改变。例如:

pbase = pbase2;  // pbase 的动态类型是 Derived
pbase = pbase3;  // pbase 的动态类型是 Derived2
示例代码

以下是一个示例,展示了静态类型和动态类型的工作原理:

#include <iostream>

class Base {
public:
    virtual void myFunction() {
        std::cout << "Base::myFunction" << std::endl;
    }
};

class Derived : public Base {
public:
    void myFunction() override {
        std::cout << "Derived::myFunction" << std::endl;
    }
};

class Derived2 : public Base {
public:
    void myFunction() override {
        std::cout << "Derived2::myFunction" << std::endl;
    }
};

int main() {
    Base base;       // 静态类型是 Base
    Derived derived; // 静态类型是 Derived
    Base* pbase;     // 静态类型是 Base*
    Base* pbase2 = new Derived();  // 静态类型是 Base*
    Base* pbase3 = new Derived2(); // 静态类型是 Base*

    // 动态类型示例
    pbase = pbase2;      // pbase 的动态类型是 Derived
    pbase->myFunction(); // 调用 Derived::myFunction

    pbase = pbase3;      // pbase 的动态类型是 Derived2
    pbase->myFunction(); // 调用 Derived2::myFunction

    return 0;
}
解释
  • 静态类型:在编译期间确定的类型。例如,Base* pbase2 的静态类型是 Base*
  • 动态类型:在运行时确定的实际类型。例如,pbase2 的动态类型是 Derived

2. 静态绑定和动态绑定

静态绑定

静态绑定是指绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期。

动态绑定

动态绑定是指绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期。

示例代码

以下是一个示例,展示了静态绑定和动态绑定的工作原理:

#include <iostream>

class Base {
public:
    void staticFunction() {
        std::cout << "Base::staticFunction" << std::endl;
    }
    virtual void dynamicFunction() {
        std::cout << "Base::dynamicFunction" << std::endl;
    }
};

class Derived : public Base {
public:
    void staticFunction() {
        std::cout << "Derived::staticFunction" << std::endl;
    }
    void dynamicFunction() override {
        std::cout << "Derived::dynamicFunction" << std::endl;
    }
};

int main() {
    Base base;
    Derived derived;#include <iostream>

class Base {
public:
    void staticFunction(int x = 10) {
        std::cout << "Base::staticFunction, x = " << x << std::endl;
    }
    virtual void dynamicFunction(int x = 20) {
        std::cout << "Base::dynamicFunction, x = " << x << std::endl;
    }
};

class Derived : public Base {
public:
    void staticFunction(int x = 30) {
        std::cout << "Derived::staticFunction, x = " << x << std::endl;
    }
    void dynamicFunction(int x = 40) override {
        std::cout << "Derived::dynamicFunction, x = " << x << std::endl;
    }
};

int main() {
    Base base;
    Derived derived;
    Base* pbase = &derived;

    base.staticFunction();       // 静态绑定,调用 Base::staticFunction,输出 x = 10
    derived.staticFunction();    // 静态绑定,调用 Derived::staticFunction,输出 x = 30
    pbase->staticFunction();     // 静态绑定,调用 Base::staticFunction,输出 x = 10

    base.dynamicFunction();      // 动态绑定,调用 Base::dynamicFunction,输出 x = 20
    derived.dynamicFunction();   // 动态绑定,调用 Derived::dynamicFunction,输出 x = 40
    pbase->dynamicFunction();    // 动态绑定,调用 Derived::dynamicFunction,但输出 x = 20

    return 0;
}
    Base* pbase = &derived;

    base.staticFunction();       // 静态绑定,调用 Base::staticFunction
    derived.staticFunction();    // 静态绑定,调用 Derived::staticFunction
    pbase->staticFunction();     // 静态绑定,调用 Base::staticFunction

    base.dynamicFunction();      // 动态绑定,调用 Base::dynamicFunction
    derived.dynamicFunction();   // 动态绑定,调用 Derived::dynamicFunction
    pbase->dynamicFunction();    // 动态绑定,调用 Derived::dynamicFunction

    return 0;
}
解释
  • 静态绑定staticFunction 是普通成员函数,绑定的是静态类型,发生在编译期。例如,pbase->staticFunction() 调用的是 Base::staticFunction,因为 pbase 的静态类型是 Base*
  • 动态绑定dynamicFunction 是虚函数,绑定的是动态类型,发生在运行期。例如,pbase->dynamicFunction() 调用的是 Derived::dynamicFunction,因为 pbase 的动态类型是 Derived
  • 缺省参数(也称为默认参数):缺省参数一般是静态绑定。例如,pbase->dynamicFunction() 调用 Derived::dynamicFunction 时,缺省参数 x 的值是 20,因为 pbase 的静态类型是 Base*,而 Base::dynamicFunction 的缺省参数是 20
结论
  • 普通成员函数是静态绑定,而虚函数是动态绑定。
  • 缺省参数(也称为默认参数)一般是静态绑定。

3. 继承的非虚函数坑

在继承关系中,不应该在子类中重新定义一个继承来的非虚成员函数。这是因为这样做会导致函数的隐藏和意外的行为,破坏代码的可维护性和可读性。

示例代码

以下是一个示例,展示了在子类中重新定义继承来的非虚成员函数所带来的问题:

#include <iostream>

class Base {
public:
    void nonVirtualFunction() {
        std::cout << "Base::nonVirtualFunction" << std::endl;
    }
};

class Derived : public Base {
public:
    void nonVirtualFunction() {
        std::cout << "Derived::nonVirtualFunction" << std::endl;
    }
};

int main() {
    Base base;
    Derived derived;
    Base* pbase = &derived;

    base.nonVirtualFunction();       // 调用 Base::nonVirtualFunction
    derived.nonVirtualFunction();    // 调用 Derived::nonVirtualFunction
    pbase->nonVirtualFunction();     // 调用 Base::nonVirtualFunction

    return 0;
}
解释
  • 隐藏问题:在 Derived 类中重新定义 nonVirtualFunction 会隐藏 Base 类中的同名函数。这意味着通过基类指针或引用调用该函数时,仍然会调用基类的实现,而不是子类的实现。
  • 意外行为:在上面的示例中,pbase->nonVirtualFunction() 调用的是 Base::nonVirtualFunction,而不是 Derived::nonVirtualFunction,因为 nonVirtualFunction 不是虚函数,绑定的是静态类型。
结论
  1. 函数隐藏:在子类中重新定义继承来的非虚成员函数会导致基类中的同名函数被隐藏,破坏代码的可维护性和可读性。
  2. 意外行为:通过基类指针或引用调用该函数时,仍然会调用基类的实现,而不是子类的实现,可能导致意外行为。

为了避免这些问题,建议遵循以下原则:

  • 如果需要在子类中重写基类的函数,应该将该函数声明为虚函数。
  • 如果不希望子类重写某个函数,可以将该函数声明为 final(C++11 及以上版本支持)。

4. 虚函数的动态绑定

虚函数的动态绑定是指在运行时根据对象的动态类型来决定调用哪个函数实现。这种机制允许通过基类指针或引用调用派生类的函数,实现多态性。

示例代码

以下是一个示例,展示了虚函数的动态绑定:

#include <iostream>

class Base {
public:
    virtual void dynamicFunction() {
        std::cout << "Base::dynamicFunction" << std::endl;
    }
};

class Derived : public Base {
public:
    void dynamicFunction() override {
        std::cout << "Derived::dynamicFunction" << std::endl;
    }
};

int main() {
    Base base;
    Derived derived;
    Base* pbase;

    pbase = &base;
    pbase->dynamicFunction();  // 调用 Base::dynamicFunction

    pbase = &derived;
    pbase->dynamicFunction();  // 调用 Derived::dynamicFunction

    return 0;
}
解释
  • 虚函数:在基类中使用 virtual 关键字声明的函数。派生类可以重写该函数。
  • 动态绑定:在运行时,根据对象的动态类型来决定调用哪个函数实现。例如,pbase->dynamicFunction() 会根据 pbase 指向的对象类型来调用相应的函数实现。
结论
  • 多态性:虚函数的动态绑定实现了多态性,使得通过基类指针或引用可以调用派生类的函数。
  • 运行时决策:动态绑定在运行时根据对象的动态类型来决定调用哪个函数实现,而不是在编译期决定。

5. 重新定义虚函数的缺省参数坑

在继承关系中,重新定义虚函数的缺省参数可能会导致意外的行为。这是因为缺省参数是静态绑定的,即在编译期就确定了,而虚函数是动态绑定的,即在运行时根据对象的动态类型来决定调用哪个函数实现。

示例代码

以下是一个示例,展示了重新定义虚函数的缺省参数所带来的问题:

#include <iostream>

class Base {
public:
    virtual void dynamicFunction(int x = 20) {
        std::cout << "Base::dynamicFunction, x = " << x << std::endl;
    }
};

class Derived : public Base {
public:
    void dynamicFunction(int x = 40) override {
        std::cout << "Derived::dynamicFunction, x = " << x << std::endl;
    }
};

int main() {
    Base base;
    Derived derived;
    Base* pbase = &derived;

    base.dynamicFunction();      // 调用 Base::dynamicFunction,输出 x = 20
    derived.dynamicFunction();   // 调用 Derived::dynamicFunction,输出 x = 40
    pbase->dynamicFunction();    // 调用 Derived::dynamicFunction,但输出 x = 20

    return 0;
}
解释
  • 虚函数dynamicFunction 是虚函数,通过基类指针或引用调用时,会根据对象的动态类型来决定调用哪个函数实现。
  • 缺省参数:缺省参数是静态绑定的,即在编译期就确定了。例如,pbase->dynamicFunction() 调用 Derived::dynamicFunction 时,缺省参数 x 的值是 20,因为 pbase 的静态类型是 Base*,而 Base::dynamicFunction 的缺省参数是 20
结论
  • 缺省参数的静态绑定:缺省参数在编译期就确定了,即使虚函数在运行时进行动态绑定,缺省参数的值仍然取决于静态类型。
  • 意外行为:重新定义虚函数的缺省参数可能会导致意外的行为,因为通过基类指针或引用调用时,缺省参数的值不会根据动态类型来决定。
  • 不要在子类中重新定义虚函数缺省参数的值:为了避免意外行为和代码的可维护性问题,不要在子类中重新定义继承来的虚函数的缺省参数。

6. C++ 中的多态性

多态性是面向对象编程的一个重要特性,它允许同一个接口调用不同的实现。在 C++ 中,多态性必须通过虚函数来实现,没有虚函数,就不可能存在多态性。

多态的概念

多态性可以从两个方面来讨论:

  1. 从代码实现上

    • 多态性通过虚函数和继承来实现。基类中声明虚函数,派生类中重写这些虚函数。
    • 在运行时,通过基类指针或引用调用虚函数,根据对象的动态类型来决定调用哪个函数实现。
  2. 从表现形式上

    • 多态性允许通过同一个接口调用不同的实现。具体表现为,通过基类指针或引用调用派生类的函数,实现了动态绑定。
    • 这种机制提高了代码的灵活性和可扩展性,使得代码可以处理不同类型的对象,而无需修改调用代码。
示例代码

以下是一个示例,展示了 C++ 中的多态性:

#include <iostream>

class Base {
public:
    virtual void show() {
        std::cout << "Base::show" << std::endl;
    }
};

class Derived1 : public Base {
public:
    void show() override {
        std::cout << "Derived1::show" << std::endl;
    }
};

class Derived2 : public Base {
public:
    void show() override {
        std::cout << "Derived2::show" << std::endl;
    }
};

void display(Base* base) {
    base->show();
}

int main() {
    Base base;
    Derived1 derived1;
    Derived2 derived2;

    display(&base);      // 调用 Base::show
    display(&derived1);  // 调用 Derived1::show
    display(&derived2);  // 调用 Derived2::show

    return 0;
}
解释
  • 从代码实现上

    • Base 类中声明了虚函数 show
    • Derived1Derived2 类中重写了 show 函数。
    • display 函数中,通过基类指针 base 调用虚函数 show,在运行时根据对象的动态类型来决定调用哪个函数实现。
  • 从表现形式上

    • 有父类有子类(有继承关系)Base 是父类,Derived1Derived2 是子类,且 Base 类中含有虚函数 show,子类重写了该虚函数。
    • 父类指针指向子类对象:在 main 函数中,Base* pbase 可以指向 Derived1Derived2 对象。
    • 调用子类中重写的虚函数:通过 display 函数,基类指针 base 调用虚函数 show,实际调用的是子类的实现。例如,display(&derived1) 调用 Derived1::showdisplay(&derived2) 调用 Derived2::show
    • 通过同一个接口 display,调用了不同的实现,实现了多态性。
结论
  • 多态必须存在虚函数并且调用虚函数:没有虚函数,就不可能存在多态性。
  • 从代码实现上:多态性通过虚函数和继承来实现,基类中声明虚函数,派生类中重写这些虚函数。
  • 从表现形式上:多态性允许通过同一个接口调用不同的实现,提高了代码的灵活性和可扩展性。
  • 16
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值