前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >c++基础之表达式

c++基础之表达式

作者头像
Masimaro
发布2021-02-05 14:18:36
7600
发布2021-02-05 14:18:36
举报

这次接着更新《c++ primer》 这本书的读书笔记,上一篇博文更新到了书中的第三章,本次将记录书中的第四章——表达式

左值与右值

在理解表达式之前需要先理解c++中左值和右值的概念。 c++ 的表达式要么是右值,要么是左值,这两个名词是从c语言中继承过来的,在c语言中,左值指的是可以位于赋值语句左侧的表达式,右值则不能。在c++中二者的区别就相对复杂一些了。 在c++要区分左值和右值,可以采取一个原则:一般来说当一个对象被用作左值时,用的是对象的地址,也就是在内存中的位置,而右值可以采取排他性原则,只要不是左值的都是右值。 不同运算符对运算对象的要求各不相同,有的要求左值、有的要求右值;返回值也有差异,有的作为左值返回,有的作为右值返回。一个重要的原则是:凡事需要右值的地方可以使用左值来代替,但是不能把左值当成右值来使用。 一般下列运算符需要用到左值

  1. 赋值运算符的左侧需要一个左值。返回的结果也是一个左值
  2. 取地址运算符作用于一个左值运算对象,返回一个指向该对象的指针,结果是一个右值
  3. 内置解引用运算符、下表运算符迭代器解引用运算符、string、vector的下标运算符的求值结果都是左值
  4. 内置类型和迭代器的递增递减运算符作用于左值对象,其前置版本所得到的结果也是左值

优先级与结合律

复合表达式是指含有两个或者多个运算符的表达式,计算复合表达式的值需要将运算符和运算对象合理的组织在一起,优先级与结合律决定了运算对象的组合方式。

表达式中的括号无视运算优先级与结合律的规则,如果表达式中有括号,先运算括号中的内容。

表达式的最终值取决与子表达式的结合方式,在计算表达式的值时,先看运算符的优先级,先处理优先级高的子表达式,而优先级相同的情况下,则由其结合律规则决定

代码语言:javascript
复制
3 + 4 * 5 //根据运算符的优先级,乘法高于加法,所以先计算4 * 5 为20,再计算3 + 20 得到23
20 - 15 - 3 //先看运算符的优先级,都是减法优先级相同,再看结合律,减法的结合律是从左到右,所以先计算20 -15 得到 5,然后再计算5 - 3 得到2
6 + 3 * 4 / 2 + 2 //先看运算符的优先集,乘法除法的优先级大于加法,而乘法除法的结合律都是从左到右结合,所以这个表达式先计算 3 * 4 得到12,再计算 12 / 2 得到 6 ,最后加法的结合律也是从左到右,最后计算 6 + 6 + 2 得到 14

求值顺序

优先级规定了运算对象的组合方式,但是并没有规定运算对象按照什么顺序求值,在大多数情况下不会明确指定求值顺序。例如在表达式 int i = f1() * f2(); 中,先计算函数的返回值,然后再将结果赋值进行乘法运算,最后将结果赋值给i变量,但是究竟是先计算f1函数还是先计算f2函数,这个c++标准没有明确规定。

对于没有指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将会引发错误并产生未定义的行为,例如

代码语言:javascript
复制
int i = 0; 
int j = i + ++i;

根据结合律,会先计算i和 ++i但是不确定是该先计算i还是先计算++i 这里会产生未定义行为。如果先计算i则表达式可以转化为 j = 0 + 1 如果先计算 ++i,则表达式可以转化为 j = 1 + 1;

有4中表达式明确规定了求值顺序

  1. 逻辑与(&&):只有当左侧的结果为真时,才计算右侧的结果
  2. 逻辑或(||):只有当左侧的运算结果为假时,才会计算右侧结果
  3. 三目运算符(?:)当条件为真时,计算:左侧的表达式,否则计算右侧的表达式
  4. 逗号表达式:运算顺序是从左到右,最后返回最右侧的表达式的值

在处理复合表达式时,有下面两条准则:

  1. 在不清楚运算对象的优先级和结合律的时候,按照实际的结合逻辑使用括号
  2. 如果改变了某个运算对象的值,在表达式的其他地方不要使用这个运算对象,但是能明确知道求值顺序的时候这个规则就不适用了

算术运算符

算术运算符的求值对象和求值结果都是右值。 算术运算符的优先级顺序为:单目运算符(+表示取当前值,-表示取相反数) > 乘除法 > 加减法;结合律:采用从左至右结合的方式

算术运算符能作用与所有的算术类型,算术类型的数据在运算前会被转化为精度较大的类型(运算对象只有byte,char, short时会被统一转化为int),在转化为同一类型后执行再进行运算

代码语言:javascript
复制
bool b = false;
int k = 1;
bool i = +k + -b;

在上述代码中,bool类型参与算术运算时,会将true变为1,false变为0,然后针对0和1进行操作,根据优先级得到 i = 1 + 0; 最后再将算术类型转化为bool类型赋值,i最终为true

除法运算中如果除数和被除数符号相同,商为正数,否则为负数,c++11 标准中规定负数商一律向0取整

取余运算,要求除数和被除数都是整数,如果m/n的结果不为0,则m%n的结果符号与m相同

(m/n)*n + m%n = m (-m)/n=m/(-n)=-(m/n) (-m)%n=-(m%n); m%(-n)=m%n

逻辑运算符

逻辑运算符作用与任何能转化为boo类型的运算对象上

优先级为 逻辑非 > 大于/小于/大于等于/小于等于 > 相等/不等 > 逻辑与 > 逻辑非

逻辑运算符一般的语言中都有,而且用法基本类似,这里就不再详细说明了,需要注意的是:

  1. 使用非bool类型来做判断时,不要写成 if(!val) 或者 if(val == true);同样的使用bool类型来判断时,也不要写成 if(val == true) 或者 if(val == 1)
  2. 在进行数值相等的比较时,为了防止少写=,习惯上把常量写在前面例如 if(1 == val)

赋值运算符

赋值运算符一般作用与初始化给对象赋值或者在后续修改对象的值,但是需要注意区分二者的不同,这点在初始化或者给类对象赋初始值的时候尤其重要

赋值运算符的左侧必须是一个可修改的左值。

赋值运算符的结果是它左侧的运算对象,并且是一个左值。结果的类型就是左侧运算对象的类型,如果赋值运算符左右两个运算对象的类型不同,则运算对象将转化成左侧运算对象的类型。

代码语言:javascript
复制
int i, j;
i = j = 10;
const k = 10; //这里是初始化,不是赋值
k = i; //错误,左侧需要可以修改的左值

新的c++ 标准中允许使用初始化列表来给对象进行赋值

代码语言:javascript
复制
i = {3.14}; //错误,使用初始化列表时,不能出现精度丢失
i = 3.14; //正确,值为3
vector<int> vi;
vi = {0, 1, 2, 3, 4, 5};

对于内置类型,初始化列表赋值时,列表中最多只能有一个值,而且值的精度不能大于左侧对象的精度

赋值运算符满足右结合律,对于多重赋值语句中的每一个对象,它的类型或者与右边的对象相同,或者可以又右边对象的类型转化得到

赋值运算符的优先级较低

赋值运算符也包括复合赋值运算符,例如 += 、-=、*= /=

递增和递减运算符

递增和递减运算符为对象的加一和减一提供了一种简洁的书写形式。这两个运算符还可以应用于迭代器。

递增和递减运算符有前置版本和后置版本,前置版本是先加一,然后将改变后对象的值作为求值结果;后置版本是先将对象的结果作为求值结果返回,然后再改变对象的值。

在性能上,在涉及复杂的迭代运算时,前置版本会大大优于后置版本,因此尽量养成使用前置版本的习惯。

代码语言:javascript
复制
auto pbeg = v.begin()
while(pbeg != v.end() && *pbeg >= 0)
{
    cout << *pbeg++ << endl; 
}

这里后置递增运算符的优先级要大于解引用的优先级,所以这里等价于 *(pbeg++),即先进行后置递增运算,但是返回变化之前的迭代器,然后将变化之前的迭代器进行解引用操作,得到具体元素的值

递增和递减运算符可以修改对象的值,而一般的运算符没有严格规定求值的顺序,所以在复合表达式中需要额外注意,不要在可能修改变量值的位置访问该变量

代码语言:javascript
复制
string s = "hello world";
auto beg = s.begin();
while(beg != s.end() && !isspace(*beg))
{
    *beg = toupper(*beg++);
}

上述例子由于赋值运算符未定义两侧运算对象的求值顺序,可能先求值左侧,那么循环中的语句等效于 beg = toupper(beg); 如果先求值右侧,则等效于 (beg + 1) = toupper(beg);

条件运算符

条件运算符也叫做三目运算符。

代码语言:javascript
复制
cond ? expr1:expr2;

条件运算符也可以嵌套使用, 条件运算符满足右结合律。随着嵌套层数的增加,代码的可读性极具下降,因此条件运算的嵌套最好不要超过三层。

条件运算符的优先级非常低,一般使用的时候建议加上括号

代码语言:javascript
复制
cout << ((grade > 60) ? "pass" : "fail"); // 输出pass 或者 fail
cout << (grade > 60)? "pass" : "fail"; // 输出 1或者0,运算结果 是 "pass" 或者 "fail"
cout << grade > 60 ? "pass" : "fail"; // 试图将cout 与 60 进行比较,错误

位运算符

位运算是作用与对象的二进制值的,理论上它可以处理任何对象,但是为了代码安全和可读性,建议只处理整型数据,而且最好是无符号整型

运算符

功能

用法

~

按位求反

~expr

<<

左移

expr << expr2

>>

右移

expr >> expr2

&

位与

expr & expr2

^

位异或

expr ^ expr2

|

位或

expr

sizeof 运算符

sizeof 返回一个类型或者一个表达式所占的字节数。它满足右结合律

针对表达式,sizeof并不计算表达式的值,只返回表达式结果类型的大小

由于sizeof 不计算表达式的值,因此即使在sizeof中解引用指针也不会有什么影响

逗号表达式

逗号运算符含有两个表达式,按照从左至右的顺序依次求值

逗号表达式先对左侧表达式进行求值,然后丢弃返回的结果,然后再对右侧表达式进行求值。逗号表达式的返回值是右侧的表达式的值

类型转换

何时发生隐式转换

  1. 大多数情况下,比int小的整型值会被转化为int
  2. 条件中,非布尔值会被转化为布尔类型
  3. 初始化过程中,初始值转化为变量类型;赋值语句中右侧运算对象转化成左侧运算对象的类型
  4. 如果是算术运算或者关系运算的运算对象有多种类型,需要转化为同一种类型。而且会尽量往精度较大的一方转化
  5. 调用函数时也可能会发生类型转化

算术类型转换

算术转换总是朝着精度更高的一级转换

较小的整型会被转化为int,较大的整型会被转化为long、unsigned long、unsigned longlong 等

其他隐式类型转换

除了算术类型的隐式转换外,还有下面几种

  1. 数组转化为指针:当数组被用作 decltype、sizeof、取地址符一级typeid 等运算符的运算对象时,该转换不会发生
  2. 指针的转化:常量整数0和nullptr可以转化为任意类型的指针,指向任意非常量的指针能转化成void,指向任意对象的指针能转化为const void
  3. 转化为布尔类型: 算术类型或者指针,值为0或者nullptr的被转化为false,其他的值被转化为true
  4. 转化为常量:常量的指针或者引用可以指向非常量对象,反过来则不行;
  5. 类类型定义的转化:由程序员预先定义,在需要转化时,由编译器自动调用进行转化

显式类型转换

显式类型转换绕过了编译器的类型检查,是不安全的一种转化方式

显示类型转换的语法规则如下:

代码语言:javascript
复制
cast-name<type>(express);

其中type是目标类型,express是要转化的值,如果type是引用类型则结果是一个左值。cast-name是 static_cast、dynamic_cast、const_cast 和 reinterpret_cast 中的一种

  1. static_cast 只要不包含底层const,都可以使用static_cast,在对指针进行强制类型转化时,要保证转化前与转化后指针所指向的对象类型相同,用于同类型数据之前的转化,如算术类型之前的相互转化。
  2. const_cast 只能改变运算对象的底层const、与static_const互相补充
  3. reinterpret_cast 重新解释比特位,通常为运算对象的位模式提供较低层次上的重新解释。一般用于指针之间的转化,它没有限制,任何类型间都可以进行转化。但是也十分危险
  4. dynamic_cast 动态类型转化,主要用于多重继承类类型之间的转化

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-02-03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 左值与右值
  • 优先级与结合律
  • 求值顺序
  • 算术运算符
  • 逻辑运算符
  • 赋值运算符
  • 递增和递减运算符
  • 条件运算符
  • 位运算符
  • sizeof 运算符
  • 逗号表达式
  • 类型转换
    • 算术类型转换
      • 其他隐式类型转换
        • 显式类型转换
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档