当代码发生多态 (即父类方法加了 virtual)时,直接通过子类对象或引用调用该方法(如 c.func()),一定会执行子类的版本。这是多态的定义。
如果你此时仍然想要访问父类的版本(方法或变量),方法与非多态情况类似,但有一些细微的语境区别。
1. 访问父类的【方法】 (Virtual Function)
即使发生了多态,你依然可以使用 作用域解析运算符 :: 来强制调用父类版本。这会告诉编译器:"忽略虚函数表(vtable),直接静态绑定到父类的这个函数"。
场景 A:在类外部(如 main 函数)
拥有子类对象/引用时:
cpp
class Parent {
public:
virtual void func() { cout << "Parent Func" << endl; }
};
class Child : public Parent {
public:
void func() override { cout << "Child Func" << endl; }
};
int main() {
Child c;
Child* cPtr = &c;
Child& cRef = c;
// 1. 普通调用 -> 触发多态,执行子类版本
c.func(); // 输出: Child Func
cPtr->func(); // 输出: Child Func
cRef.func(); // 输出: Child Func
// 2. 强制访问父类版本 -> 使用 :: 运算符
c.Parent::func(); // 输出: Parent Func ✅
cPtr->Parent::func(); // 输出: Parent Func ✅
cRef.Parent::func(); // 输出: Parent Func ✅
return 0;
}
原理 :Parent::func() 是一种静态绑定 。它告诉编译器:"不要查虚函数表,直接去 Parent 类的作用域里找 func 的地址并调用"。这完全绕过了多态机制。
场景 B:在子类内部(如 Child 的成员函数中)
如果你在 Child 类的某个方法里,想调用父类的 func(通常用于扩展功能而不是完全替换):
cpp
class Child : public Parent {
public:
void func() override {
cout << "Before Parent logic..." << endl;
// 在内部使用 super 等价物
Parent::func(); // ✅ 调用父类版本
cout << "After Parent logic..." << endl;
}
};
注意:在类内部,Parent::func() 和 super.func() (Java风格概念) 效果一样。但在 C++ 外部,没有 super 关键字,必须用 Parent::。
2. 访问父类的【变量】 (Member Variables)
重要提醒 :变量永远不发生多态 。无论是否加了 virtual(变量不能加 virtual),访问规则都只取决于引用的声明类型。
但是,如果子类隐藏 (Hiding)了父类的变量(定义了同名变量),你想通过子类引用访问父类的那个变量,方法依然是使用 ::。
cpp
class Parent {
public:
int value = 100;
virtual void func() {} // 假设这里有虚函数,但这不影响变量
};
class Child : public Parent {
public:
int value = 200; // 隐藏了父类的 value
};
int main() {
Child c;
Child* cPtr = &c;
// 1. 普通访问 -> 访问子类的变量
cout << c.value << endl; // 输出: 200
cout << cPtr->value << endl; // 输出: 200
// 2. 强制访问父类变量 -> 使用 :: 运算符
cout << c.Parent::value << endl; // 输出: 100 ✅
cout << cPtr->Parent::value << endl; // 输出: 100 ✅
return 0;
}
总结对比表
| 目标 | 操作对象 | 语法 | 结果 | 备注 |
|---|---|---|---|---|
| 访问父类方法 | 子类对象/指针/引用 | obj.Parent::func() |
父类版本 | 绕过虚函数表,强制静态绑定。 |
| 访问子类方法 | 子类对象/指针/引用 | obj.func() |
子类版本 | 正常多态行为。 |
| 访问父类变量 | 子类对象/指针/引用 | obj.Parent::value |
父类变量 | 绕过名字隐藏,直接访问基类作用域。 |
| 访问子类变量 | 子类对象/指针/引用 | obj.value |
子类变量 | 默认行为(名字隐藏)。 |
| 访问父类变量 | 父类 引用 (Parent&) |
ref.value |
父类变量 | 利用引用类型决定可见性(无需 ::)。 |
核心结论
即使发生了多态:
- 方法 :默认走虚函数表(调用子类)。想调父类?用
Parent::func()强行指定。 - 变量 :默认看当前作用域(调用子类隐藏版)。想调父类?用
Parent::value强行指定。 - C++ 的强大之处 :
ClassName::运算符赋予了程序员显式控制作用域的能力,让你可以在任何层级(只要权限允许)精准地调用想要的版本,不受多态或隐藏的自动规则限制。