C++ 学习笔记(4)表达式、运算符、类型转换(static_cast、const_cast、reinterpret_cast、dynamic_cast)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/l773575310/article/details/79039680

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) {
        // 转换失败处理。
    }
}

运算符优先级表

猜你喜欢

转载自blog.csdn.net/l773575310/article/details/79039680
今日推荐