C++ 学习笔记(4)表达式、运算符、类型转换(static_cast、const_cast、reinterpret_cast、dynamic_cast)
参考书籍:《C++ Primer 5th》
4.1 基础
4.1.1 基本概念
- 左值:对象的身份(在内存所在的位置)。
- 赋值对象。
- 取地址符作用对象是左值,返回结果指针是右值。
- 内置解引用运算符、下表运算符对象。
- 内置类型和迭代器的递增递减运算符对象。
- 右值:对象的值(内容)。
- decltype作用于左值时,得到引用类型。
int a = 0;
int *p = &a;
decltype(*p) b; // int &b 。解引用生成左值,引用类型(未初始化)。
decltype(&p) c; // int **c 。取地址生成右值,指针的指针。
4.1.3 求值顺序
- 优先级规定了运算对象的组合方式,但是运算对象之间没有明确的求值顺序。如果表达式(函数)指向并修改了同一对象,将产生未定义的行为。
int a = f1() * f2(); // 无法判断 f1 和 f2 的调用顺序
int i = 0;
cout << i << " "<< ++i << endl; // 未定义:可能输出 1 1
4.2 算术运算符
- 求余:m % n 如果不等于0,其结果的符号和m相同(与n无关)。
4.4 赋值运算符
- 对于复合运算符(+=、-=等)都等价于(a = a op b)。区别在于复合运算符只求一次值,普通的运算符要求值两次(一次计算、一次赋值)。
4.5 递增和递减运算符
- 前置版本(++a):对象本身作为左值返回。
- 后置版本(a++):对象原始值的副本作为右值返回。
// iter的类型是vector<string>::iterator
*iter++; // 正确。返回 *iter,然后++iter.
(*iter)++; // 错误。*iter 字符串没有递增操作。
*iter.empty() // 错误。'.'运算符优先级高于'*'。
iter->empty(); // 正确。
++*iter; // 错误。*iter 字符串没有递增操作。
iter++->empty(); // 正确。执行函数后自加。
4.8 位运算符
- 左移:右侧加入0。
- 右移:
- 无符号类型:左侧加入0。
- 有符号类型:左侧加入符号的副本或0,如何选择视具体环境而定。
4.9 sizeof运算符
- 两种形式,一种是类型,一种是表达式:
- sizeof( type )
- sizeof expr
- 在sizeof的运算对象中解引用一个无效指针仍然是安全的,sizeof不需要真的解引用指针也能知道他所指对象的类型。
- sizeof运算符的结果部分地依赖于其作用的类型:
- char:1。
- 引用类型:被引用对象所占空间的大小。
- 指针:指针本身所占空间大小。
- 指针解引用:对象所占空间大小。
- 数组:整个数组所占空间的大小。
- string对象或vector对象:返回类型固定部分大小。(不计算元素占用多少空间)
4.11 类型转换
- 隐式转换(implicit conversion)
- 在对大多数表达式中,比int小的整形(包括bool、char)首先提升为较大的整数类型。
- 在条件中,非布尔类型转换成布尔类型(非0 -> true、 0 -> false)。
- 初始化值转换成变量的类型;赋值时,右侧对象转换成左侧运算对象类型。
- 算术运算或关系运算的运算对象,需要转换成同一种类型。
4.11.1 算术转换
- 运算对象一个是无符号类型、另一个是有符号类型时:
- 无符号 ≥ 有符号:带符号的运算对象转换成无符号。如果有符号的值为负,结果无法判断。
- 无符号 < 有符号:转换结果依赖与机器。
- 无符号的值能存在有符号类型中,无符号类型转换成有符号类型。
- 无符号的值不能存在有符号类型中,有符号类型转换成无符号类型。
4.11.3 显示转换
static_cast
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
- 在较大算术类型赋给较小类型时,相当于明确告诉编译器:“我知道且不在乎精度损失”,编译器就不会出现相关警告提示。
- 可用于无法自动执行类型转换的对象。如找回
void*
指针的值:
double d;
void* p = &d;
double *dp = static_cast<double*>(p); // 将void*转回原来的指针类型。如果类型不符,会产生未定义后果。
const_cast
**只能改变运算对象的底层const,通常用于将常量对象转换成非常量。只能用于指针或引用。**常用于函数重载的上下文中,更多:C++标准转换运算符const_cast
- 如果对象本身不是一个常量,这种强制转换是合法的。
- 如果对象本身是一个常量,这种强制转换会产生未定义的后果。
- 不能改变表达式类型。
reinterpret_cast
常用于运算对象的位模式,提供较低层次上的重新解释。本质上依赖于机器。使用这个是非常危险的,如下:pc指针所指的对象实际是一个int而非字符。
int *ip;
char *pc = reinterpret_cast<char*>(ip);
// 等价 char *pc = (char*) ip;
string str(pc); // 运行时错误。pc实质指的是一个int。
dynamic_cast (19.2.1)
将一个基类对象指针(或引用)cast到继承类指针(或引用)。更多:理解C++ dynamic_cast - Todd Wei - 博客园
- 有三种形式(type为类类型,通常含有虚函数):
dynamic_cast<type*>(e)
:e必须为有效的指针。dynamic_cast<type&>(e)
:e必须为一个左值。dynamic_cast<type&&>(e)
:e不能是左值。
- e的类型必须符合以下三个条件中任意一个:
- 是目标type的公有派生类。
- 是目标type的公有基类。
- 是目标type的类型。
- 转换失败时:
- 目标是指针类型,返回0。
- 目标是引用类型,抛出bad_cast异常。
// 指针类型
// bp指针指向基类Base(至少含有一个虚函数),Derived是Base的公有派生类。
if (Derived *dp = dynamic_cast<Derived*>(bp))
{
// 转换成功。dp指向Derived对象。
}
else
{
// 转换失败。使用dp指向的Base对象
}
// ----------------------------------------------------------------------------- //
// 引用类型
// 因为不存在空引用,对于引用失败,应该用捕获异常的方法。
void f(const Base &b)
{
try {
// 使用b引用的Derived对象。
const Derived *d = dynamic_cast<Derived&>(b);
} catch (bad_cast) {
// 转换失败处理。
}
}