本文有两处小细节可以更严谨:
- "静态类型语言"之外的补充:下文里说"在使用基类指针/引用时才会涉及动态类型",这是常见的实用说法。严格按标准,glvalue 的动态类型指它所引用的最派生对象的类型,只是对非多态类来说你无法通过
typeid/dynamic_cast观察到它。 Derived::bar()的写法:它隐藏(hide)了Base::bar,而不是重写。这个例子本来就是想演示"非虚函数按静态类型选择",所以没问题,只是如果想学习,值得明确说一句"这是隐藏,不是覆盖"。
C++ 的静态类型和动态类型
C++ 是静态类型语言:变量、表达式、函数参数等的类型在编译期就确定了,这叫静态类型。
同时,C++ 通过继承 + 虚函数支持运行期多态,所以在使用基类指针/引用操作对象时,才会涉及动态类型:对象在运行时实际是什么类型。
看例子:
#include<iostream>#include<typeinfo>classBase{public:virtual~Base()=default;// 有虚函数,Base 是多态类型virtualvoidfoo()const{std::cout<<"Base::foo\n";}voidbar()const{std::cout<<"Base::bar\n";}};classDerived:publicBase{public:voidfoo()constoverride{std::cout<<"Derived::foo\n";}voidbar()const{std::cout<<"Derived::bar\n";}voidextra()const{std::cout<<"Derived::extra\n";}};intmain(){Derived d;Base*p=&d;// p 的静态类型是 Base*// *p 的静态类型是 Base// *p 的动态类型是 DerivedBase&r=d;// r 的静态类型是 Base&// r 的动态类型是 Derivedp->foo();// 输出 Derived::foo,虚函数按动态类型调用p->bar();// 输出 Base::bar,非虚函数按静态类型调用r.foo();// 输出 Derived::foo// p->extra(); // 编译错误!编译器只看静态类型 Base*,看不到 extraif(typeid(*p)==typeid(Derived)){std::cout<<"p 指向的对象动态类型是 Derived\n";}if(Derived*q=dynamic_cast<Derived*>(p)){q->extra();// 输出 Derived::extra}Base b=d;// 对象切片:b 的静态类型和动态类型都是 Baseb.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 兼容。