
一、C/C 类型转换1.1 C类型转换及缺陷C 语言提供两种类型转换方式:隐式类型转换(编译器自动完成)和显式强制类型转换((type)expr 形式)隐式类型转换编译器在以下场景自动进行隐式转换:算术运算中的类型提升(如 int double double)赋值时的类型转换(如 double d 1;)函数传参时的类型匹配数组名退化为指针非 0 指针转 bool 等显式强制类型转换uintptr_t 能保证整数足够大、完整容纳指针值不会截断C 风格转换的缺陷缺陷1:转换可视性差所有转换都以 (type)expr同一种形式书写,难以区分安全的数值转换和危险的指针重解释,代码审查时难以跟踪错误转换缺陷2:隐式转换可能导致精度丢失或数据截断如 double int截断小数部分,有符号/无符号混合比较时的隐式转换可能产生意外结果缺陷3:显式转换将所有情况混合在一起(int*)p 既可能是合法的 void* 转换,也可能是危险的不相关类型指针转换,编译器无法做针对性检查缺陷4:难以搜索和定位在大型代码库中,(type) 形式的转换难以用 grep 精确查找C 为兼容 C 语言,仍然允许使用 C 风格转换,但 C Core Guidelines 强烈建议避免使用1.2 C四种强制类型转换C 将类型转换细分为四种命名运算符,每种负责一类语义明确的转换场景这种设计的优势在于各司其职意图明确编译器能做更多安全检查代码可读性极高每种转换运算符的名称本身就说明了转换的语义和风险等级static_cast —— 最常用的静态转换最常用最安全的一种,在编译期完成类型检查,无运行时开销它替代了大部分C风格转换,且编译期检查能捕获明显的类型错误合法用途① 基本类型转换:int doubleint charenum int 等② 向上转型(子类指针/引用 父类指针/引用):安全,无需转换也可隐式完成③向下转型(父类指针/引用 子类指针/引用):允许但不检查,存在风险④ void* 与具体类型指针互转⑤ 显式调用单参数构造函数进行转换不能做的事static_cast 不能在多态类层次中做安全向下转型(无运行时检查),需用 dynamic_caststatic_cast 不能用于两个不相关类型的指针互转(如 int* double*),编译报错static_cast 不能去除 const/volatile 限定符,需用 const_castdynamic_cast —— 唯一的运行时检查转换唯一在运行期进行类型检查的运算符,专门用于多态继承结构中的安全向下转型前提条件目标类型(父类)必须含有虚函数(通常是虚析构函数),否则编译报错转换失败的行为性能注意:dynamic_cast 依赖RTTI,有运行时开销在性能敏感的热路径中应谨慎使用,可考虑虚函数多态替代向下转型const_cast —— 常量限定符的增与删用于修改const/volatile限定符的运算符,不能改变变量的基础类型核心规则① 只能增删 const/volatile,不能改变基础类型(如 int* double* 编译报错)② 可用于指针引用this 指针,不能直接转换普通变量③ 操作的对象本身必须是非 const 的安全用法 vs 危险用法对原生 const 变量去除 const 后修改,是未定义行为(UB)编译器可能将 const变量放入只读内存段,修改会触发段错误;也可能因常量传播优化导致读取到的值仍是旧值实战场景:const 成员函数const 成员函数中,this 指针的类型是 const 类名*,默认不能修改任何成员变量const_cast 可去除 this 的 const 属性,但前提是对象本身非 const优雅替代:mutable 关键字对于缓存计数器互斥锁等需要在 const 函数中修改的内部成员,优先使用 mutable,而非 const_castmutable 明确表达了该成员不受 const 约束的语义安全使用总结表reinterpret_cast —— 底层二进制重解释四种转换中最暴力最危险最低级的一种,直接对操作数的二进制比特位重新解释,几乎无安全检查,结果不可移植典型用途① 指针整数(将指针值存储为整数)② 无关类型指针互转(如 int * 转为 char* ,用于逐字节访问)③ 函数指针转换(不同类型函数指针互转)④ 指针到指针的底层重解释(如 uintptr_t 操作)int a 0x12345678; char* p reinterpret_castchar*(a); for (size_t i 0; i sizeof(a); i) { cout hex (static_castunsigned(p[i]) 0xFF) ; }将 char 转换为无符号整数并掩码处理确保输出的是该字节的十六进制值避免符号扩展带来的问题例如如果 char 是有符号的且最高位为 1直接转换可能会得到负数或较大的值在小端机器上输出为78 56 34 12reinterpret_cast 的结果依赖于平台的内存布局字节序对齐方式等,同一份代码在不同架构 上可能产生不同结果仅应用于极少数底层编程场景,如硬件寄存器访问序列化框架的底层实现等日常开发中应避免 reinterpret_cast如果需要逐字节访问对象,应使用 unsigned char* (标准允许通过 char/unsigned char 指针访问任何对象的字节表示),且转换可用 static_cast 完成1.3 RTTI:运行时类型识别RTTI(Run-Time Type Identification)允许程序在运行时获取对象的实际类型信息C 通过以下方式支持 RTTI①typeid运算符:获取类型的std::type_info对象② dynamic_cast 运算符:基于 RTTI 实现安全向下转型③ decltype:编译期类型推导(严格来说不属于 RTTI,但常一起讨论)示例#include iostream #include typeinfo #include cxxabi.h // 用于 GCC/Clang 解码类型名 // 辅助函数获取可读类型名 std::string get_readable_name(const std::type_info ti) { int status 0; char* demangled abi::__cxa_demangle(ti.name(), nullptr, nullptr, status); if (status 0 demangled) { std::string result(demangled); free(demangled); return result; } return ti.name(); // 失败或 MSVC 直接返回原名 } class Base { public: virtual ~Base() {} // 关键必须有虚函数以支持多态 }; class Derived : public Base { public: void specific_func() { std::cout Derived specific function called. std::endl; } }; int main() { Base* pb new Derived; // 1. 安全检查 if (pb) { // 2. 输出类型名称 std::cout Type name: get_readable_name(typeid(*pb)) std::endl; // 3. 类型判断 if (typeid(*pb) typeid(Derived)) { std::cout 确认pb 指向的是 Derived 对象 std::endl; // 4. 安全转型并调用特有方法 Derived* pd static_castDerived*(pb); // 既然已通过 typeid 确认static_cast 即可 pd-specific_func(); } } delete pb; return 0; }关键注意事项1 编译器必须启用 RTTItypeid 的 name() 返回的字符串是编译器实现定义的,不同编译器输出格式不同,不应用于逻辑判断2 typeid(ptr) vs typeid(*ptr)3 空指针风险4 性能考量二、C IO 流2.1 C 语言 IO 与缓冲区机制C 语言通过 cstdio(原 stdio.h)提供标准 IO 函数,最常用的是 scanf() 和 printf()C 语言的 IO 借助缓冲区来协调低速设备与高速 CPU 之间的速度差缓冲区的作用①屏蔽低级 I/O 实现差异:低级 I/O依赖操作系统内核实现,缓冲区层可屏蔽这些差异,便于写出可移植程序②实现行读取的行为:计算机本身没有行的概念,缓冲区可以定义行的概念,解析缓冲区内容后 返回一个行③减少系统调用次数:批量读写比逐字节读写效率高得多C 风格常用 IO 函数sprintf 的安全隐患:sprintf 不检查目标缓冲区大小,可能导致缓冲区溢出应优先使用 snprintf(指定缓冲区大小)2.2 C IO 流体系架构C 的 IO 发生在流(stream)中,流是字节序列从设备流向内存称为输入操作,从内存流向设备称为输出操作C 标准库将 IO 组织为面向对象的类层次结构类层次结构ios_base 是所有流类的根基类,定义了流状态(iostate)打开模式(openmode)等通用枚举ios 继承自 ios_base,持有一个 streambuf 指针istream 和 ostream 继承自 ios,分别提供输入和输出操作iostream 多重继承自 istream 和 ostream底层的 streambuf 负责实际的缓冲区管理和字符传输,filebuf 和 stringbuf 分别是文件和字符串的 streambuf 实现这种流类 缓冲区类的分离设计是 C IO 流的核心架构IO 流分类2.3 流的四种状态(iostate)IO 操作可能发生错误,C 标准库用 iostate 表示流的状态,定义在 ios_base 中不同编译器的 iostate 实现可能不同,但都包含以下四种状态状态查询函数流状态恢复当流进入 failbit 状态后,后续 IO 操作会全部失败需要通过clear() 重置状态位,并通过 ignore() 清空缓冲区中的错误数据,才能继续使用C 中处理标准输入流错误在交互式控制台输入中用 !cin.good() 或 cin.fail() 来捕获格式错误是常见的上述代码主要针对 failbit格式错误如果 cin.bad() 为真表示底层 I/O 错误如磁盘损坏或连接断开通常无法通过 clear() 和 ignore() 恢复程序应终止或进行更高级的错误处理。2.4 标准 IO 流C 标准库定义了四个预定义的全局流对象:cin 的工作机制① 行缓冲:键盘输入的数据保存在缓冲区中,按回车键后才送入输入缓冲区② 提取运算符 :默认跳过空格Tab换行符等空白字符,以它们为分隔符提取数据③ 返回值:cin expr 返回 cin 对象本身,因此可以链式调用(cin num1 num2)④ 布尔转换:cin 对象可隐式转换为 bool,流状态非 fail/bad 时返回 true比如 if (cin x)getline与getcin 以空白字符为分隔符,无法读取包含空格的字符串需要读取整行时使用 getline()