• 表达式由一个或多个运算对象组成,对表达式求值返回结果。

  • 字面值和变量是最简单的表达式

  • 运算符运算对象组合可得到复杂表达式。

4.1 基础

4.1.1 基本概念

  • 一元运算符作用于一个对象,如取地址符&解引用符*

  • 二元运算符作用于两个对象,如==*

  • 三元运算符?:

  • 函数调用也是特殊的运算符,它运算对象数量没有限制。

组合运算符和运算对象

理解含有多个运算符的复杂表达式,要先理解运算符的优先级结合律,以及运算对象的求值顺序

运算对象转化

常见的是整型提升,如小整型(bool、char、short)被提升为int

重载运算符

运算符作用于类类型的运算对象时,用户可以自行定义其含义。

左值右值

当对象被用作右值时,用的是对象的值(内容)。当对象被用作左值时,用的是对象的身份(在内存中的位置)。

  • 赋值符=需要非常量左值作为左侧对象,返回结果也是左值

  • 取地址符&作用于左值对象,返回指向该对象的指针,该指针是右值

  • 内置解引用、内置下标`[]`、迭代器解引用、string和vector的下标[],它们返回的结果都是左值

  • 内置和迭代器的递增++递减-作用于左值对象,其前置版本返回左值

4.1.2 优先级和结合律

  • 复合表达式是含有两个或多个运算符的表达式

  • 优先级结合律决定了运算对象的组合方式。先看优先级,一致时看结合律

  • 括号()无视优先级和结合律

  • 算术运算符和IO运算符都满足左结合律

4.1.3 求值顺序

  • 求值顺序定义了多个运算对象哪个先被求值,如f1()*f2()中哪个函数先被调用

  • 对于未指定求值顺序的运算符,若表达式指向并修改了同一个对象,则行为未定义。如cout<<i<<i++;

  • 明确规定求值顺序的4种运算符:逻辑与&&、逻辑或||、条件?:、逗号,

  • 最佳实践:

    • 拿不准优先级和结合律时,用括号。
    • 若表达式某处改变了某对象的值,则其他地方不要使用它。例外:++iter

4.2 算术运算符

  • 一元运算符优先级最高,其次乘除和求余,最后加减。

  • 上述算术运算符都满足左结合律

  • 算术运算符的运算对象和结果都是右值。

  • 算术表达式求值前,小整型都会被提升。所有对象最终都转换成同一种类型

  • 一元正号、加减都可用于指针。一元正号作用于指针或算术值时,返回(提升后的)副本,一元负号对对象的值取负后,返回(提升后的)副本。

  • 算术运算符的结果可能溢出,其结果与机器相关,不可预知

  • 整数相除还是整数

  • 参与取余%的运算对象必须都是整型,不可用浮点做转换

  • (-m)/nm/(-n)都等于-(m/n)m%(-n)等于m%n(-m)%n等于-(m%n)

21%6;   /*结果是3*/     21/6;   /*结果是3*/
21%7; /*结果是0*/ 21/7; /*结果是3*/
-21%-8; /*结果是-5*/ -21/-8; /*结果是2*/
21%-5; /*结果是1*/ 21/-5; /*结果是-4*/

4.3 逻辑和关系运算符

  • 关系运算符作用于算术或指针类型,逻辑运算符作用于任何能转换为bool的类型。它们的返回类型都是bool型右值

  • 逻辑与&&、逻辑或||都是短路求值,先求左侧,仅由左侧无法确定表达式结果时再求右侧。

    • 逻辑与&&仅当左侧为时才求右侧
    • 逻辑或||仅当左侧为时才求右侧
  • 逻辑非运算符将运算对象的值取反后返回

  • 关系运算符都满足左结合律,因此不能出现i<j<k这种写法。

  • 进行比较时除非比较对象都是严格的bool类型, 否则不要用true等字面值,因为true提升为整型时是1,不是任何非零值都能true


4.4 赋值运算符

  • 赋值运算符的左侧对象必须是可修改的左值,其返回结果就是左侧对象,也是左值

  • 如果左右类型不匹配,将右侧转为左侧类型。

  • 类类型的赋值运算符由类本身决定,如vector模板重载了赋值运算符使其可接收花括号列表作为初值。

  • 无论左侧对象的类型是什么,初始值列表都可为空。此时编译器创建一个值初始化的临时量来初始化。

  • 赋值运算满足右结合律,即多重赋值语句a=b=c;解读为a=(b=c);,前面所有类型或者和最右侧类型相同,或者可由最右侧类型转换得到。

  • 赋值运算符优先级较低

  • C++允许赋值运算的结果作为条件,所以===要分清。

  • 复合赋值运算符:

    • 算术运算符: += = = /= %=
    • 位运算符: <<= >>= &= |= ^=
  • 复合运算符更快:复合运算符a+=b仅求值一次,普通运算符a=a+b求值两次,一次加法一次赋值。


4.5 递增和递减运算符

  • ++i被称为前置版本i++被称为后置版本

  • 前置版本和后置版本的区别

    • 前置版本将对象+1或-1后,将对象本身作为左值返回
    • 后置版本将对象+1或-1后,将对象原始值副本作为右值返回
  • 混用解引用和递增可实现简洁性

    cout<<*iter++;等价于cout<<*(iter++);cout<<*iter; ++iter;

  • 由于求值顺序经常未指定,所以不要在一条语句中出现a和a++

  • *beg=toupper(*beg++);未定义,可被解读为beg=toupper(*beg)(beg+1)=toupper(*beg)


4.6 成员访问运算符

  • ptr->mem等价于(*ptr).mem 。括号不可省略,因为点优先级比解引用高。

  • 箭头运算符作用于指针,得到对象的成员,结果是左值。(因为解引用得到的一定是引用,引用是左值)

  • 点运算符取决于对象:对象是左值就返回左值,对象是右值就返回右值。


4.7 条件运算符

  • cond?expr1:expr2

  • 如果expr1和expr2都是左值或能转换为同一种左值类型,则运算结果是左值。否则是右值。

  • 允许在条件运算符的内部嵌套另外一个条件运算符

  • 条件运算符优先级非常低,因此在长表达式之中使用时,需要在两端加上括号


4.8 位运算符

  • 移位运算符满足左结合律
unsigned char bits=0233;    //10011011
bits<<8; //提升为int大小,再左移8位,00000000 00000000 10011011 00000000
bits<<31; //提升为int大小,再左移31位,10000000 00000000 00000000 00000000
bits>>3; //提升为int大小,再右移3位,00000000 00000000 00000000 00010011

4.9 sizeof 运算符

  • sizeof运算符返回一条表达式或一个类型所占的字节数,满足右结合律,返回值是size_t类型的常量表达式
  • 两种形式:
    • sizeof(type)

    • sizeof expr

  • sizeof不会计算运算对象的值,所以可以:
    • 可在sizeof里解引用无效指针没有影响,是一种安全的行为。

    • 可在sizeof里用域操作符::获取类成员大小,而不需要对象和成员。

  • sizeof的结果取决于运算对象的类型:
    • char型表达式返回1

    • 引用做sizeof返回被引用对象所占空间大小

    • 指针做sizeof返回指针本身所占空间大小

    • 解引用指针做sizeof返回指向对象所占空间大小

    • 数组做sizeof得到整个数组所占空间大小,(attention:sizeof不会把数组当指针处理)

    • string或vector求sizeof只返回固定部分的大小,不会计算对象实际占用空间

  • 常用于计算数组长度的方法是:sizeof(ia)/sizeof(*ia),sizeof返回的是常量表达式,可用于声明新数组。

4.10 逗号运算符

  • 含有两个运算对象,遵循从左往右的求值顺序
  • 实质上是将多个顺序执行的表达式写为一行的手段。
for (vector<int>::size_type ix = 0; ix != ivec.size(); ix++, cnt--)
ivec[ix] = cnt; someValue ? ++x, ++y : --x, --y;

4.11 类型转换

  • 隐式转换自动执行,不需程序员介入。算术类型的隐式转换被设计为尽量避免损失精度。
  • 隐式转换发生的情形:
    • 整型提升
    • 条件中非bool转bool
    • 初始化时初始值转为变量类型,赋值时右侧对象转为左侧类型
    • 算术运算或关系运算中有多种类型,最终会统一
    • 函数调用时也会有类型转换

4.11.1 算术转换

  • 算术转换:把运算对象(算术类型)转为最宽的类型,同时有整型和浮点时将整型转浮点。

  • 整型提升:把小整型转为大整型。

    • 小整型(bool、char、signed char、unsigned char、short、unsigned short等),只要值能放进int就转为int,放不进int就放进unsigned int
    • 宽字符(wchar_t、char16_t、char32_t)提升为int、unsigned int、long、unsigned long、long long、unsigned long long中能装进去的最小者
  • signed和unsigned的转换

    • 若unsigned类型不小于signed类型,直接将signed转为unsigned
    • 若unsigned类型小于signed类型,且该unsigned类型的值都能装进该signed类型,则unsigned转为signed
    • 若unsigned类型小于signed类型,且该unsigned类型的值不都能装进该signed类型,则signed转为unsigned
    bool    flag;   char            cval;
    short sval; unsigned short usval;
    int ival; unsigned int uival;
    long lval; unsigned long ulval;
    float fval; double dval;
    3.14159L+'a'; //'a'提升为int,再转为long double
    dval+ival; //ival转double
    dval+fval; //fval转double
    ival=dval; //dval切除小数部分转int
    flag=dval; //dval是0则false,否则true
    cval+fval; //cval提升为int,再转float
    sval+cval; //sval和cval都提升为int
    cval+lval; //cval转long
    ival+ulval; //ival转unsigned long
    usval+ival; //未定义,根据unsigned short和int所占空间大小做转换
    uival+lval; //未定义,根据unsigned int和long所占空间大小做转换

    4.11.2 其他隐式类型转换

    • 数组转指针:

      • 大多数用到数组的表达式中,数组自动转为指向首元素的指针

      • 在表达式中使用函数类型也会转为函数指针

      • 例外:decltype、取地址&sizeoftypeid运算符不会将数组转指针

      • 例外:用引用初始化数组时也不会转指针

    • 指针的转换:

      • 0nullptr可转为任意指针类型

      • 指向任意非常量的指针能转为void *

      • 指向任意对象的指针能转为const void *

    • 转成bool类型

    • 转换为常量

      • 允许将指向非常量的指针或引用转为指向常量的指针或引用。但反之不可,底层const不可删除。
    • 类类型的转换:类类型可定义转换,但编译器只能执行一种类类型转换。

4.11.3 显示转换

  • 强制类型转换

    手动指定要转换的变量和要转换为的类型,经常是很危险的。

命名的强制类型转换

cast-name<type>(expression)

  • expression 是要转换的值

  • type是转换的目标类型,若type是引用类型,则返回左值。

  • cast-name是一下一种

    • static_cast

      只要不包含底层const,都可使用。例如将大算术类型转为小算术类型、浮点转整型、编译器无法自动执行的类型转换
    double d=3.14;
    void *p=&d; //任何非常量对象的地址都能放进void *指针
    double *dp=stataic_cast<double *>(p); //将指针转回指向double型
    • dynamic_cast

    • const_cast

      • 只能改变运算的底层const
      • 如果对象是常量,用const_cast去掉常量后执行写操作是未定义行为。
      • const_cast能改变表达式的常量属性,但不能用const_cast改变表达式类型
      const char *pc;
      char *p = const_cast<char*>(pc); //正确,但是通过p写值是未定义的行为 const char *cp;
      char *q = static_const<char*>(cp); //错误,static_cast 不能转换掉const性质
      static_cast<string>(cp); //正确,字符串字面值转成string类型
      const_cast<string>(cp) //错误,const只改变常量属性
    • reinterpret_cast中的一种。

      • 为运算对象的位模式提供较低层次上的重新解释,即内存中的bits不变,改变解读方式。它依赖于机器,非常危险。

建议避免使用强制类型转换,尤其是reinterpret_cast

早期C++中,显式类型转换的形式为:

type(expr)

(type)expr

在某处使用旧式强制转换时,若换为const_cast和static_cast也合法,就当作const_cast和static_cast,否则当作reinterpret_cast。因为指代不明,故建议不使用。


4.12 运算符优先级表



【c++ Prime 学习笔记】第4章 表达式的更多相关文章

  1. The C++ Programming Language 学习笔记 第6章 表达式和语句

    1.关于strcpy函数. 书中说c风格的字符串尽量少用,strcpy这样的函数应该也要少用.这里讲这个函数主要是要通过本章课后练习第十题来讲一下前面提及的要点.巩固一下前几章的知识.写了一段,本来感 ...

  2. Programming Entity Framework-dbContext 学习笔记第五章

    ### Programming Entity Framework-dbContext 学习笔记 第五章 将图表添加到Context中的方式及容易出现的错误 方法 结果 警告 Add Root 图标中的 ...

  3. 《Java核心技术·卷Ⅰ:基础知识(原版10》学习笔记 第5章 继承

    <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 目录 <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 5.1 类.超类和子类 5.1 ...

  4. Stealth视频教程学习笔记(第二章)

    Stealth视频教程学习笔记(第二章) 本文是对Unity官方视频教程Stealth的学习笔记.在此之前,本人整理了Stealth视频的英文字幕,并放到了优酷上.本文将分别对各个视频进行学习总结,提 ...

  5. Stealth视频教程学习笔记(第一章)

    Stealth视频教程学习笔记(第一章) 本文是对Unity官方视频教程Stealth的学习笔记.在此之前,本人整理了Stealth视频的英文字幕,并放到了优酷上.本文将分别对各个视频进行学习总结,提 ...

  6. 20145330《Java学习笔记》第一章课后练习8知识总结以及IDEA初次尝试

    20145330<Java学习笔记>第一章课后练习8知识总结以及IDEA初次尝试 题目: 如果C:\workspace\Hello\src中有Main.java如下: package cc ...

  7. java JDK8 学习笔记——第16章 整合数据库

    第十六章 整合数据库 16.1 JDBC入门 16.1.1 JDBC简介 1.JDBC是java联机数据库的标准规范.它定义了一组标准类与接口,标准API中的接口会有数据库厂商操作,称为JDBC驱动程 ...

  8. CSS3秘笈第三版涵盖HTML5学习笔记1~5章

    第一部分----CSS基础知识 第1章,CSS需要的HTML HTML越简单,对搜索引擎越友好 div是块级元素,span是行内元素 <section>标签包含一组相关的内容,就像一本书中 ...

  9. 《DOM Scripting》学习笔记-——第三章 DOM

    <Dom Scripting>学习笔记 第三章 DOM 本章内容: 1.节点的概念. 2.四个DOM方法:getElementById, getElementsByTagName, get ...

  10. The Road to learn React书籍学习笔记(第三章)

    The Road to learn React书籍学习笔记(第三章) 代码详情 声明周期方法 通过之前的学习,可以了解到ES6 类组件中的生命周期方法 constructor() 和 render() ...

随机推荐

  1. 「山东省队集训2021 Round 1」 半夜

    考虑将 \(X\) 复制一次放到后面再对其长度为 \(n\) 的连续子串和 \(Y\) 求一波 \(\rm{Longest\ Common\ Subsequence}\) 就能得到 \(\Theta( ...

  2. 20210807 Smooth,Six,Walker

    考场 开题,感觉 T1 很像 dky 讲过的一道中北大学 ACM 题,T3 一看就是随机化,具体不知道怎么做. T1 sb 题,直接取当前最小的光滑数,把它乘一个质因子放入候选集.类似<蚯蚓&g ...

  3. 最详尽的 JS 原型与原型链终极详解(1)(2)(3)===转载

    转载===方便以后复习 原文网址:https://www.jianshu.com/p/dee9f8b14771 一. 普通对象与函数对象 JavaScript 中,万物皆对象!但对象也是有区别的.分为 ...

  4. ICCV2021 | MicroNet:以极低的 FLOPs 改进图像识别

    ​前言:这篇论文旨在以极低的计算成本解决性能大幅下降的问题.提出了微分解卷积,将卷积矩阵分解为低秩矩阵,将稀疏连接整合到卷积中.提出了一个新的动态激活函数-- Dynamic Shift Max,通过 ...

  5. 第09课:GDB 实用调试技巧(下)

    本节课的核心内容: 多线程下禁止线程切换 条件断点 使用 GDB 调试多进程程序 多线程下禁止线程切换 假设现在有 5 个线程,除了主线程,工作线程都是下面这样的一个函数: void thread_p ...

  6. Node.js躬行记(9)——微前端实践

    后台管理系统使用的是umi框架,随着公司业务的发展,目前已经变成了一个巨石应用,越来越难维护,有必要对其进行拆分了. 计划是从市面上挑选一个成熟的微前端框架,首先选择的是 icestark,虽然文档中 ...

  7. Selenium系列4-元素定位

    前言 说起元素定位,一定是学习自动化测试绕不开的第一道关,无论是web端的UI自动化还是移动端的自动化,在需要首先对元素进行定位才可以完成对元素的操作已达成测试目的,在Selenium中,可以使用fi ...

  8. JetBrains 系列软件汉化包 2017.3-2018.1

    JetBrains 系列软件汉化包 关键字: Android Studio 3.0-3.1.3 汉化包 CLion 2018.1-2018.2 汉化包 GoLand 2017.3.2-2018.2 汉 ...

  9. PTA面向对象程序设计6-3 面积计算器(函数重载)

    实现一个面积计算器,它能够计算矩形或长方体的面积. 函数接口定义: int area(int x, int y); int area(int x, int y, int z); 第一个函数计算长方形的 ...

  10. Spring Cloud Hystrix 学习(一)

    在学习Hystrix之前,首先引入一个问题场景,服务雪崩.如下图所示: 可以看到,三个入口服务A.B.C最终都会请求到服务T.当服务T的请求过载,打满CPU都无法匹配请求的频率时,同步调用的上级服务就 ...