C++ 中有两种不同的函数调用方式:静态绑定和动态绑定。
静态绑定
静态绑定是指在编译时确定调用哪个函数。也就是说,编译器会根据函数调用的名称和参数类型来确定要调用的函数。这种方式也被称为静态多态或编译时多态。
静态绑定适用于以下情况:
- 在编译时已经知道调用哪个函数。
- 函数的名称和参数类型是确定的。
- 函数调用的目标是一个静态类型的对象。
例如:
class Shape {
public:
void draw() {
cout << "Drawing shape" << endl;
}
};
class Circle : public Shape {
public:
void draw() {
cout << "Drawing circle" << endl;
}
};
int main() {
Circle c;
Shape* s = &c;
s->draw(); // 静态绑定,输出 Drawing shape
return 0;
}
上述代码中,调用s->draw()函数时,由于s的静态类型是Shape*,因此编译器会使用静态绑定来调用Shape::draw()函数,而不是Circle::draw()函数。
动态绑定
动态绑定是指在运行时确定调用哪个函数。也就是说,编译器不确定要调用哪个函数,而是将函数调用委托给运行时系统来决定。这种方式也被称为动态多态或运行时多态。
动态绑定适用于以下情况:
- 在运行时才知道调用哪个函数。
- 函数的名称和参数类型不确定,只有在运行时才能确定。
- 函数调用的目标是一个动态类型的对象。
例如:
class Shape {
public:
virtual void draw() {
cout << "Drawing shape" << endl;
}
};
class Circle : public Shape {
public:
void draw() {
cout << "Drawing circle" << endl;
}
};
int main() {
Circle c;
Shape* s = &c;
s->draw(); // 动态绑定,输出 Drawing circle
return 0;
}
上述代码中,调用s->draw()函数时,由于s的动态类型是Circle,因此运行时系统会使用动态绑定来调用Circle::draw()函数,而不是Shape::draw()函数。这就是C++中的虚函数机制,通过在函数前加上virtual关键字来实现。
空指针调用函数
#include <iostream>
using namespace std;
class test {
public:
void show() { cout << "I'm test" << endl; }
};
int main()
{
test* t = nullptr;
t->show(); // 输出 I'm test
}
上面的代码是可以正常运行的。
在 c++ 中,类的成员函数并不与特定对象绑定,所有成员函数共用一份成员函数体,当程序编译后,成员函数的地址即已经确定。(静态绑定)
当调用 t->show()
时,实际上执行的代码是 test::show(this)
, 成员函数 show() 的地址在编译器就已经确定。test::show(this)
操作相当于执行了一个函数,传入的参数为 nullptr。而在 show() 函数中没有用到 this 指针,所以程序正常运行,不会报错。
如果把代码改成下面这样,程序就会 crash
#include <iostream>
using namespace std;
class test {
public:
void show() {
a = 2;
}
private:
int a;
};
int main()
{
test* t = nullptr;
t->show();
}
在 show() 函数里会执行 this->a = 2;
, 而 this 指针是 nullptr, 所以会报错。
所以,空指针也不能调用虚函数,因为虚函数的地址是动态绑定的,在运行时要通过虚函数表指针 this->vptr
找到对象对应的类的虚函数表(vtbl)。此时 this 为空,就会报错