C++学习笔记——表达式

左值和右值

  1. 当一个对象被用作左值的时候,使用的是对象的身份(在内存中的位置);
    当一个对象被用作右值的时候,使用的是对象的值(内容)。
  2. 需要右值的地方可以使用左值来代替,反之不可以。
  3. 需要使用左值的运算符:
    • 赋值运算符:需要左值作为其左侧运算对象,得到结果也为左值。
    • 取地址符:作用于一个左值运算对象,返回指向该对象的指针,该指针为右值。
    • 内置解引用运算符、下标运算符、迭代器解引用运算符、string & vector的下标运算符:结果均为左值。
    • 内置类型和迭代器的递增递减运算符:作用于左值对象,得到左值。
  4. decltype相关:若decltype( )的参数为左值,则返回引用类型。
优先级、结合律、求值顺序
  1. 表达式按照优先级与结合律计算
  2. 括号无视优先级与结合律
  3. 规定求值顺序的运算符:逻辑与、逻辑或、条件运算符、逗号运算符。
  4. 运算对象的求值顺序与优先级、结合律无关。
  5. 若表达式改变了某对象的值,在此表达式的其他地方避免使用此对象,防止产生undefined behavior。
算术运算符
  1. 算术运算符的运算对象和求值结果均为右值,均满足左结合律。
  2. 表达式求值前,小整数类型的运算对象将被提升,最终转化为同一类型。
  3. 算术表达式的未定义结果:
    • 数学性质本身:除数为0;
    • 溢出;
  4. /:整除运算符,直接弃除商值的小数部分(向0取整)。
  5. %:取余,m = (m / n) + (m % n)
    • 要求运算对象必须是整数类型;
    • m % n的符号与m相同。
逻辑和关系运算符
  1. 逻辑与和逻辑或运算符:短路求值策略。
  2. 关系运算符:不要连写。
赋值运算符
  1. 左侧运算对象要求是左值。
  2. 赋值运算符返回值为左值,类型为左侧运算对象的类型。
  3. 初始化列表作为右侧运算对象:
    • 若左侧运算对象为内置类型,那么初始化列表中最多只能包含一个值,而且该值所占空间不应大于目标类型的空间。
    • 类类型的运算细节由类本身决定。
    • 初始值列表可以为空。
int k; k = {3.14}; //错误:窄化转换 vector vi; vi = {0, 1, 2, 3, 4, 5}; //vi含有6个元素,值从0到6

  1. 赋值运算满足右结合律。
  2. 赋值运算符优先级较低。
  3. 区分相等运算符和赋值运算符。
  4. 复合赋值运算符:影响左侧运算对象的求值次数。
递增递减运算符
  1. 前置版本/后置版本:
    • 前置版本:返回改变后的对象本身(左值);
    • 后置版本:返回对象原始值的副本(右值);
  2. 避免使用后置版本。
//例外情况 //输出一个vector对象内容直至遇到(但不包括)第一个负值为止 auto pbeg = v.begin(); while(pbeg != v.end() && *beg >= 0) cout<< *pbeg++ <

成员访问运算符
  1. 成员访问运算符:点运算符(.)、箭头运算符(->)。
  2. p->mem等价于(*p).mem。
  3. 返回值:
    • 箭头运算符返回值为左值;
    • 点运算符返回成员所属对象的左右值类型;
条件运算符
  1. 形式:cond? expr1 : expr2;
  2. 条件运算符只对expr1和expr2的其中一个表达式求值。
  3. 当条件运算符的两个表达式都是左值或者都能转换成同一种左值类型时,返回左值;
    否则返回右值。
  4. 条件运算符优先级很低,通常需要在两端加上括号。
位运算符
  1. 位运算符作用对象:整数类型、bitset标准库类型。
  2. 小整数类型一般会被提升。
  3. 运算对象最好为无符号型,否则产生未定义行为。
  4. 移位运算符:左结合律。
    • 左移运算符向低位插入0;
    • 右移运算符行为依赖左侧运算对象类型:若为无符号类型,则向高位插入0;若为有符号类型,则向高位插入符号位的副本或0,视具体环境而定。
  5. 使用位运算符:
//一个班级有30名同学,老师对他们进行测验,测验结果只有通过/不通过两种,全班的测验结果可以用一个无符号整数来表示。 unsigned long quiz = 0; //第27名同学通过测试。 quiz |= 1UL << 27; //恢复原来状态。 quiz &= ~(1UL << 27); //检测第27名同学的测验状况。 boolstatus = quiz & (1UL << 27);

sizeof运算符
  1. 形式:sizeof expr或sizeof (type)。
  2. sizeof并不会实际求解运算对象的值。
  3. sizeof的运算结果:
    • char:1;
    • 引用类型:被引用对象所占空间大小;
    • 指针:指针本身所占空间大小;
    • 解引用指针:指针指向对象所占空间大小,指针不需要有效;
    • 数组:整个数组所占空间大小;
    • string、vector对象:该类型固定部分大小,不计算对象中元素占用了多少空间;
  4. sizeof返回值为常量表达式。
类型转换
【C++学习笔记——表达式】1.隐式转换规则:
  • 小整数类型提升为大整数类型;
  • 非布尔类型转化为布尔类型;
  • 初始化过程中,初始值转化为变量的类型;
  • 赋值过程中,右侧运算对象转换成左侧运算对象的类型;
  • 如果算术运算和关系运算的运算对象有多种类型,需要转换成同一类型;
  1. 强制类型转换:
    • 形式:cast-name(expression);
    • cast-name:static_cast、dynamic_cast、const_cast、reinterpret_cast。
    • static_cast:不包含底层const的具有明确定义的类型转换都可以使用static_cast。
double slope = static_cast(j) / i; //不在乎潜在的精度损失 void* p = &d; double *dp = static_cast(p); //该类型转换编译器无法自动完成,但注意不要引发未定义行为

  • const_cast:只能改变运算对象的底层const,即去掉对象的const性质。
const char *pc; char *p = const_cast(pc); //此时通过p写值将引发未定义行为 char *p = static_cast(pc); //错误,static_cast不可去除对象的const性质 char *p = const_cast(pc); //错误,const_cast只可改变常量属性,不能改变表达式类型

  • reinterpret_cast:为运算对象的位模式提供较低层次上的重新解释,极易出错。

    推荐阅读