本文有两处小细节可以更严谨:
- "静态类型语言"之外的补充 :下文里说"在使用基类指针/引用时才会涉及动态类型",这是常见的实用说法。严格按标准,glvalue 的动态类型指它所引用的最派生对象的类型,只是对非多态类来说你无法通过
typeid/dynamic_cast观察到它。 Derived::bar()的写法 :它隐藏(hide)了Base::bar,而不是重写。这个例子本来就是想演示"非虚函数按静态类型选择",所以没问题,只是如果想学习,值得明确说一句"这是隐藏,不是覆盖"。
C++ 的静态类型和动态类型
C++ 是静态类型语言 :变量、表达式、函数参数等的类型在编译期就确定了,这叫静态类型。
同时,C++ 通过继承 + 虚函数支持运行期多态,所以在使用基类指针/引用 操作对象时,才会涉及动态类型:对象在运行时实际是什么类型。
看例子:
cpp
#include <iostream>
#include <typeinfo>
class Base {
public:
virtual ~Base() = default; // 有虚函数,Base 是多态类型
virtual void foo() const { std::cout << "Base::foo\n"; }
void bar() const { std::cout << "Base::bar\n"; }
};
class Derived : public Base {
public:
void foo() const override { std::cout << "Derived::foo\n"; }
void bar() const { std::cout << "Derived::bar\n"; }
void extra() const { std::cout << "Derived::extra\n"; }
};
int main() {
Derived d;
Base* p = &d; // p 的静态类型是 Base*
// *p 的静态类型是 Base
// *p 的动态类型是 Derived
Base& r = d; // r 的静态类型是 Base&
// r 的动态类型是 Derived
p->foo(); // 输出 Derived::foo,虚函数按动态类型调用
p->bar(); // 输出 Base::bar,非虚函数按静态类型调用
r.foo(); // 输出 Derived::foo
// p->extra(); // 编译错误!编译器只看静态类型 Base*,看不到 extra
if (typeid(*p) == typeid(Derived)) {
std::cout << "p 指向的对象动态类型是 Derived\n";
}
if (Derived* q = dynamic_cast<Derived*>(p)) {
q->extra(); // 输出 Derived::extra
}
Base b = d; // 对象切片:b 的静态类型和动态类型都是 Base
b.foo(); // 输出 Base::foo
}
核心区别
| 概念 | 静态类型 | 动态类型 |
|---|---|---|
| 确定时机 | 编译期 | 运行期 |
| 依据 | 变量/表达式声明类型 | 指针/引用实际指向的对象类型 |
| 用途 | 类型检查、重载解析、模板、非虚函数调用 | 虚函数分派、typeid、dynamic_cast |
| 例子 | Base* p 的静态类型是 Base* |
p 指向 Derived 时,*p 的动态类型是 Derived |
一句话总结:
静态类型是编译器看到的类型;动态类型是运行时对象真正的类型。
在 C++ 中,动态类型主要通过虚函数 和 RTTI 起作用。
小写字母 a 的 ASCII 编码值是:
- 十进制:
97 - 十六进制:
0x61 - 八进制:
141 - 二进制:
01100001
补充:大写字母 A 的 ASCII 值是 65。在常见 C++ 实现中,int('a') == 97;不过 C++ 标准本身不强制一定使用 ASCII,但主流平台基本都是 ASCII/UTF-8 兼容。