• c++基础之表达式


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

    左值与右值

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

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

    优先级与结合律

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

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

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

    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++标准没有明确规定。

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

    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),在转化为同一类型后执行再进行运算

    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)

    赋值运算符

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

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

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

    int i, j;
    i = j = 10;
    const k = 10; //这里是初始化,不是赋值
    k = i; //错误,左侧需要可以修改的左值
    

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

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

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

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

    赋值运算符的优先级较低

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

    递增和递减运算符

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

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

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

    auto pbeg = v.begin()
    while(pbeg != v.end() && *pbeg >= 0)
    {
        cout << *pbeg++ << endl; 
    }
    

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

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

    string s = "hello world";
    auto beg = s.begin();
    while(beg != s.end() && !isspace(*beg))
    {
        *beg = toupper(*beg++);
    }
    

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

    条件运算符

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

    cond ? expr1:expr2;
    

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

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

    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. 类类型定义的转化:由程序员预先定义,在需要转化时,由编译器自动调用进行转化

    显式类型转换

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

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

    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 动态类型转化,主要用于多重继承类类型之间的转化

  • 相关阅读:
    python3安装 MAC
    MacOS三个比较接地气实用的终端命令
    maya界面字体怎么设置大小?
    Mac 下 Android Studio 连 夜神模拟器 调试以及真机调试方法
    [macOS] Mojave10.14 夜神安卓模拟器启动问题
    解决MAC电脑系统设置的安全性与隐私下通用没有任何来源选项
    一个分析“文件夹”选择框实现方法的过程
    windows下nginx+php简单配置
    使用windbg抓取崩溃文件和分析的过程
    解决工作中遇到的一个"打开,保存"文件框的bug的过程
  • 原文地址:https://www.cnblogs.com/lanuage/p/14369883.html
Copyright © 2020-2023  润新知