主页
文章
分类
系列
标签
简历
【C++ Primer(edition 5) 04】表达式
发布于: 2021-9-9   更新于: 2021-9-9   收录于: Cpp
文章字数: 334   阅读时间: 2 分钟   阅读量:

表达式由一个或多个运算对象(operand)组成,对表达式求值将得到一个结果(result )。字面值和变量是最简单的表达式(expression),其结果就是字面值和变量的值。把一个运算符(operator)和一个或多个运算对象组合起来可以生成较复杂的表达式。 ^0c892c

一、表达式基础

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

二、算术运算符

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

三、逻辑运算符

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

四、赋值运算符

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

五、递增递减运算符

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

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

混用解引用和递增运算符

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

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

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

六、成员访问运算符

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

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

七、条件运算符

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

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

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

八、位运算符

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

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

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

应用:

1
2
3
4
5
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返回数组的大小
1
2
3
4
5
int ia[10];
// sizeof(ia)返回整个数组所占空间的大小
// sizeof(ia)/sizeof(*ia)返回数组的大小
constexpr size_t sz = sizeof(ia)/sizeof(*ia);
int arr[sz];

十、逗号运算符

从左向右依次求值。

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

十一、类型转换

1、隐式类型转换

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

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

1.1 算术转换

算术转换(arithmetic conversion) 的含义是把一种算术类型转换成另外一种算术类型

1.1.1 整型提升

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

1.1.2 无符号类型的运算对象

  • 都带符号 or 都不带符号,小类型转大类型
  • 一个带符号,另一个不带符号
    • 带符号>不带符号:无符号能存入带符号类型,则转化为带符号类型,反之则转为无符号类型
    • 带符号≤不带符号:带符号转为不带符号 不同符号的类型转换带来了隐患,比如 unsigned int 类型的变量大于int类型的变量,则有转为为 int->unsigned int , 如果恰巧这个int 是一个负数,则转化为就会产生歧义

1.2 其他转换

p143

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

2.1 static_cast

任何明确定义的类型转换,只要不包含底层const,都可以使用。

1
2
3
int i,j ;
cin >> i >> j;
double slope = static_cast<double>(j)/i;

常用于较大算数运算符转换为较小算数运算符,旨在告诉编译器,不在乎精度的损失。

2.2 dynamic_cast

支持运行时类型识别。

2.3 const_cast

只能改变运算对象的底层const,一般可用于去除const性质。

1
2
const char *pc; 
char *p = const_cast<char*>(pc)

只有const_cast 可以消除底层const,其他类型转换会报错,但是如果被消除的变量本身就是常量对象,通过const_cast消除其底层const后再修改,就是未定义的行为

2.4 reinterpret_cast

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

3、旧式强制类型转换

1
char *pc = (char *) ip; //ip原类型为指向整数的指针

十二、运算符优先级表

《C++ Primer(edition 5)》p166

十三、常量表达式

常量表达式是指不会改变并且在编译过程中就能得到计算结果的表达式,字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。

1
2
const int max_file = 20 ;
const int limit = max_file + 1 ;