表达式

表达式基础

  • 运算对象转换:小整数类型会被提升为较大的整数类型
  • 重载运算符:当运算符作用在类类型的运算对象时,用户可以自行定义其含义。
  • 左值和右值
    • C中原意:左值可以在表达式左边,右值不能。
    • C++:当一个对象被用作右值的时候,用的是对象的(内容);
    • 被用做左值时,用的是对象的身份(在内存中的位置)。
  • 求值顺序int i = f1() + f2()
    • 先计算f1() + f2(),再计算int i = f1() + f2()。但是f1和f2的计算先后不确定
    • 但是,如果f1、f2都对同一对象进行了修改,因为顺序不确定,所以会编译出错,显示未定义
    • 有4种运算符明确规定了运算对象的求值顺序。
      • 逻辑与&&
      • 逻辑或||
      • 条件运算符?:
      • 逗号运算符,

算术运算符

  • 溢出:当计算的结果超出该类型所能表示的范围时就会产生溢出。
  • bool类型不应该参与计算
    bool b=true;
    bool b2=-b;   //仍然为true
    //b为true,提升为对应int=1,-b=-1
    //b2=-1≠0,所以b2仍未true
    
  • 取余运算m%n,结果符号与m相同

逻辑运算符

  • 短路求值:逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。先左再右
    • 对于逻辑与运算符来说,当且仅当左侧运算对象为真时才对右侧运算对象求值。
    • 对于逻辑或运算符来说,当且仅当左侧运算对象为假时才对右侧运算对象求值。
  • 小技巧,声明为引用类型可以避免对元素的拷贝,如下,如string特别大时可以节省大量时间。
vector<string> text;
for(const auto &s: text){
  cout<<s;
}

相等性测试

  • 测试一个算法对象或指针对象的真值,最直接的方法就是将其作为if语句的条件;
    • if(val) {} // 如果val是任意的非0值,条件为真
    • if(!val) {} // 如果val是0,条件为真

赋值运算符

  • 赋值运算的返回结果时它的左侧运算对象,且是一个左值。类型也就是左侧对象的类型。
  • 如果赋值运算的左右侧运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型。
  • 赋值运算符满足右结合律,这点和其他二元运算符不一样。 ival = jval = 0;等价于ival = (jval = 0);
  • 赋值运算优先级比较低,使用其当条件时应该加括号。
  • 复合赋值运算符,复合运算符只求值一次,普通运算符求值两次。 任意复合运算符op等价于a = a op b;

递增递减运算符

  • 前置版本j = ++i,先加一后赋值
  • 后置版本j = i++,先赋值后加一

优先使用前置版本,后置多一步储存原始值。(除非需要变化前的值)

混用解引用和递增运算符

*iter++等价于*(iter++),递增优先级较高

auto iter = vi.begin();
while (iter!=vi.end()&&*iter>=0)
	cout<<*iter++<<endl;	// 输出当前值,指针向前移1

简介是一种美德,追求简洁能降低程序出错可能性

成员访问运算符

ptr->mem等价于(*ptr).mem

注意.运算符优先级大于*,所以记得加括号

条件运算符

  • 条件运算符(?:)允许我们把简单的if-else逻辑嵌入到单个表达式中去,按照如下形式:cond? expr1: expr2

  • 可以嵌套使用,右结合律,从右向左顺序组合

    • finalgrade = (grade > 90) ? "high pass"
          : (grade < 60) ? "fail" : "pass";
      //等价于
      finalgrade = (grade > 90) ? "high pass"
          : (grade < 60) ? "fail" : "pass";
      
  • 输出表达式使用条件运算符记得加括号,条件运算符优先级太低。

位运算符

用于检查和设置二进制位的功能。

  • 位运算符是作用于整数类型的运算对象。
  • 二进制位向左移(<<)或者向右移(>>),移出边界外的位就被舍弃掉了。
  • 位取反(~)(逐位求反)、与(&)、或(|)、异或(^

有符号数负值可能移位后变号,所以强烈建议位运算符仅用于无符号数

应用:

unsigned long quiz1 = 0;    // 每一位代表一个学生是否通过考试
1UL << 12;  // 代表第12个学生通过
quiz1 |= (1UL << 12);   // 将第12个学生置为已通过
quiz1 &= ~(1UL << 12);  // 将第12个学生修改为未通过
bool stu12 = quiz1 & (1UL << 12);   // 判断第12个学生是否通过

位运算符使用较少,但是重载cout、cin大家都用过

位运算符满足左结合律,优先级介于中间,使用时尽量加括号。

sizeof运算符

  • 返回一条表达式或一个类型名字所占的字节数
  • 返回的类型是 size_t的常量表达式。
  • sizeof并不实际计算其运算对象的值。
  • 两种形式:
    1. sizeof (type),给出类型名
    2. sizeof expr,给出表达式
  • 可用sizeof返回数组的大小

  • sizeof运算符的结果部分地依赖于其作用的类型:
    • 对char或者类型为char的表达式执行sizeof运算,结果得1。
    • 对引用类型执行sizeof运算得到被被引用对象所占空间的大小。
    • 对指针执行sizeof运算得到指针大本身所占空间的大小。
    • 对解引用指针执行sizeof运算得至到指针指向的对象所占空间的大小,指针不需有效。
    • 对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有的元素各执行一次sizeof运算并将所得结果求和。注意,sizeof运算不会把数组转换成指针来处理。
    • 对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。

因为执行sizeof运算能得到整个数组的大小,所以可以用数组的大小除以单个元素的大小得到数组中元素的个数:

int ia[10];
// sizeof(ia)返回整个数组所占空间的大小
// sizeof(ia)/sizeof(*ia)返回数组的大小
constexpr size_t sz = sizeof(ia)/sizeof(*ia);
int arr[sz];

逗号运算符

从左向右依次求值。

左侧求值结果丢弃,逗号运算符结果是右侧表达式的值。

类型转换

隐式类型转换

设计为尽可能避免损失精度,即转换为更精细类型。

  • int类型小的整数值先提升为较大的整数类型。
  • 条件中,非布尔转换成布尔。
  • 初始化中,初始值转换成变量的类型。
  • 算术运算或者关系运算的运算对象有多种类型,要转换成同一种类型。
  • 函数调用时也会有转换。

算术转换

整型提升
  • 常见的char、bool、short能存在int就会转换成int,否则提升为unsigned int
  • wchar_t,char16_t,char32_t提升为整型中int,long,long long ……最小的,且能容纳原类型所有可能值的类型。

其他转换

p143

显式类型转换(尽量避免)

  • static_cast:任何明确定义的类型转换,只要不包含底层const,都可以使用。 double slope = static_cast<double>(j);

  • dynamic_cast:支持运行时类型识别。

  • const_cast:只能改变运算对象的底层const,一般可用于去除const性质。 const char *pc; char *p = const_cast<char*>(pc)

    只有其可以改变常量属性

  • reinterpret_cast:通常为运算对象的位模式提供低层次上的重新解释。

旧式强制类型转换

type expr

运算符优先级表

20220522224008-2022-05-22-22-40-08

20220522224021-2022-05-22-22-40-22