4.1 基本概念
4.1.1 decltype与左右值
-
表达式结果为左值 →decltype得到引用类型
-
如
decltype(*p)为int&(p是int*)
-
-
表达式结果为右值 →decltype得到普通类型
-
如
decltype(&p)为int*
-
4.1.2 未指定求值顺序的风险
-
大多数运算符不规定运算对象的求值顺序
-
典型问题示例:
cout << i << ++i; // 未定义行为可能输出:
-
0 1(先i后++i) -
1 1(先++i后i) -
或其他结果
-
4.1.3 表达式求值三要素对比
| 要素 | 作用 | 示例 f() + g() * h() + j() |
|---|---|---|
| 优先级 | 决定乘法先于加法 | g()*h()先组合 |
| 结合律 | 决定加法从左到右 | (f()+g()*h())+j() |
| 求值顺序 | 不规定函数调用顺序 | f(),g(),h(),j()调用顺序未定 |
4.1.4 安全编程建议
-
使用括号明确意图
// 明确表达计算顺序 (a + b) * (c - d)
-
避免同一表达式修改并访问变量
// 错误示例 arr[i] = i++; // 未定义行为 // 正确做法 arr[i] = i; i++;
-
例外情况 当修改本身就是子表达式的一部分时是安全的:
*++iter // 安全:先++再解引用
重要结论
-
优先级和结合律只决定表达式如何分组,不决定计算顺序
-
修改变量后在同一表达式中再次使用是未定义行为的主要来源
-
4种特殊运算符是唯一有明确求值顺序的情况
4.2 运算
| 优先级组 | 运算符 | 结合律 |
|---|---|---|
| 最高 | 一元运算符(+、-、*、&等) | 右结合 |
| 中 | 乘法/除法/取模(*、/、%) | 左结合 |
| 最低 | 加法/减法(+、-) | 左结合 |
正号运算符(+)
+ptr // 返回“指针副本” +3.14 // 返回double值3.14
4.3 逻辑和关系运算符
4.3.1 浮点数比较注意事项
// 避免直接相等比较(使用误差范围) double a = 0.1 + 0.2; if (fabs(a - 0.3) < 1e-10) {...}
4.3.2 布尔值比较最佳实践
正确写法
// 测试整数真值 if (val) {...} // val != 0 if (!val) {...} // val == 0 // 明确比较数值 if (val == 1) {...} // 明确测试是否为1
错误写法及问题
if (val == true) {...} // 等价于 if(val == 1),可能非预期 // 当val为2时:true被转为1,2==1为false
4.3.3 类型安全比较建议
-
避免布尔值与数值混用比较
-
使用
static_cast显式转换bool b = static_cast<bool>(val); // 明确转换意图
-
C++17起可用
std::cmp_equal等安全比较函数#include <utility> if (std::cmp_equal(val, 1)) {...} // 类型安全比较
4.3.4 转化符
4.4 四种类型转换运算符
4.4.1 static_cast(静态类型转换)
用途:用于明确定义的类型转换,是最常用的类型转换运算符
特点:
-
编译时完成类型检查
-
不能转换掉const/volatile属性
-
相对安全,但仍需注意精度损失
适用场景:
// 基本数据类型转换
double d = 3.14;
int i = static_cast<int>(d); // 浮点转整型
// 类层次结构中的上行转换(安全)
class Base {};
class Derived : public Base {};
Derived* d = new Derived();
Base* b = static_cast<Base*>(d);
// void*与其他指针互转
int x = 10;
void* p = static_cast<void*>(&x);
int* px = static_cast<int*>(p);
4.4.2 const_cast(常量性转换)
用途:专门用于修改类型的const或volatile属性
特点:
-
唯一能去除const属性的转换
-
不能改变基础类型
-
滥用可能导致未定义行为
适用场景:
// 去除const属性
const char* str = "hello";
char* s = const_cast<char*>(str); // 去const
// 函数重载调用
void func(int&);
void func(const int&);
const int x = 10;
func(const_cast<int&>(x)); // 调用非const版本
注意事项:
const int ci = 5;
int* pi = const_cast<int*>(&ci);
*pi = 10; // 未定义行为!原始对象是const
4.4.3 reinterpret_cast(重新解释转换)
用途:提供低层次的重新解释位模式
特点:
-
最危险的类型转换
-
完全不进行类型检查
-
高度依赖平台和实现
-
应尽量避免使用
适用场景:
// 指针类型间的转换
int* ip = new int(65);
char* cp = reinterpret_cast<char*>(ip);
// 指针与整数间的转换
intptr_t i = reinterpret_cast<intptr_t>(ip);
// 不相关类型指针转换
struct A { int x; };
struct B { int y; };
A a;
B* b = reinterpret_cast<B*>(&a);
危险示例:
float f = 3.14f;
int i = reinterpret_cast<int&>(f); // 直接解释位模式
4.4.4 dynamic_cast(动态类型转换)
用途:用于类层次结构中的安全下行转换
特点:
-
运行时类型检查(RTTI)
-
失败时返回nullptr(指针)或抛出异常(引用)
-
只适用于多态类型(有虚函数)
-
有一定性能开销
适用场景:
class Base { virtual void foo() {} };
class Derived : public Base {};
Base* bp = new Derived();
// 指针转换
Derived* dp = dynamic_cast<Derived*>(bp);
if (dp) { /* 转换成功 */ }
// 引用转换
try {
Derived& dr = dynamic_cast<Derived&>(*bp);
} catch (std::bad_cast& e) {
// 处理转换失败
}
注意事项:
class NonPolymorphic {}; // 无虚函数
NonPolymorphic* np = new NonPolymorphic();
// 错误!不能对非多态类型使用dynamic_cast
// auto p = dynamic_cast<OtherType*>(np);
总结对比表
| 转换类型 | 检查时机 | 安全性 | 主要用途 | 性能开销 |
|---|---|---|---|---|
| static_cast | 编译时 | 高 | 相关类型转换 | 无 |
| const_cast | 编译时 | 中 | 修改const属性 | 无 |
| reinterpret_cast | 编译时 | 低 | 低级重新解释 | 无 |
| dynamic_cast | 运行时 | 高 | 安全下行转换 | 有 |
最佳实践建议:
-
优先考虑设计改进,避免不必要的类型转换
-
使用最严格的能满足需求的转换运算符
-
对每次类型转换添加注释说明必要性
-
避免C风格强制转换
(type)expr,改用C++风格 -
对dynamic_cast的结果总是检查是否成功