c++多态 virtual 引用传递和值传递
#include <iostream>
using namespace std;
class A
{
public:
virtual void f()
{
cout << 1;
}
void g()
{
cout << 2;
}
};
class B : public A
{
public:
virtual void f()
{
cout << 3;
}
void g()
{
cout << 4;
}
};
void show(A &a)
{
a.f();
a.g();
}
void show2(A a){
a.f();
a.g();
}
int main()
{
B b;
show(b);
cout<<endl;
show2(b);
cout<<endl;
system("pause");
return 0;
}
在回答之前,先梳理一下关键的 C++ 机制和假设:
-
虚函数和多态
-
只要你通过基类指针或引用调用一个
virtual
方法,运行时就会根据对象的 实际类型(dynamic type)去调用对应的重写版本。 -
非虚函数总是根据 静态类型(static type,也就是写在代码里的类型)调用。
-
-
传值时的对象切片(slicing)
-
当你把一个派生类对象传给接受基类 值 的函数时,函数内部会拷贝出一个 纯基类子对象,原来的派生部分就被“切掉”了(切片)。
-
这个拷贝出来的对象,其虚函数表指针指向的是 基类,因此再调用虚函数就只能跑基类的版本。
-
void show(A &a) {
a.f(); // virtual 调用
a.g(); // 非 virtual 调用
}
void show2(A a) {
a.f(); // virtual 调用,但‘a’已经是纯 A 了
a.g(); // 非 virtual 调用
}
int main() {
B b;
show(b);
cout << endl;
show2(b);
cout << endl;
return 0;
}
-
b
的类型是B
。 -
show
接受A&
(引用),不会发生切片; -
show2
接受A
(值),会发生切片。
1. 调用 show(b);
-
形参
a
是对b
的别名——引用,实际保留了派生类型信息。 -
a.f()
:f
是虚函数,运行时看a
真正指向的是B
,调用B::f()
,输出3
。 -
a.g()
:g
不是虚函数,静态类型是A
,调用A::g()
,输出2
。
最终:show(b)
打印32
2. 调用 show2(b);
-
形参
a
拷贝了b
中的 A 子对象,派生对象的B
部分被切掉。 -
拷贝后的
a
的动态类型、静态类型都是A
。 -
a.f()
:虽然f
在A
中是virtual
,但是a
的 vtable 已经指向A
,调用A::f()
,输出1
。 -
a.g()
:非虚,直接调用A::g()
,输出2
。
最终:show2(b)
打印12
总结
调用 | f() (virtual) |
g() (non-virtual) |
原因 |
---|---|---|---|
show(b) |
B::f() → 3 |
A::g() → 2 |
引用保持动态类型,多态 vs 静态 |
show2(b) |
A::f() → 1 |
A::g() → 2 |
值传递切片,只剩基类子对象 |