C++基础与深度解析 | 表达式 | 操作符

文章目录

  • 一、表达式基础
    • 1.表达式的值类别
    • 2.表达式的类型转换
  • 二、表达式详述
    • 1.算术操作符
    • 2.逻辑与关系操作符
    • 3.位操作符
    • 4.赋值操作符
    • 5.自增与自减运算符
    • 6.其他操作符
    • 三、C++17对表达式的求值顺序的限定

一、表达式基础

  表达式由一到多个操作数组成,可以求值并 ( 通常会 ) 返回求值结果。

  • 最基本的表达式:变量、字面值

  • 函数调用也是表达式

  • 通常来说,表达式会包含操作符(运算符),

    操作符的特性

    • 接收几个操作数:一元、二元、三元(3个操作数)

    • 操作数的类型—类型转换

      需要考虑操作符接收什么类型的操作数,当操作数类型不同或不满足要求时,可能涉及到操作数类型转换

    • 操作数是左值还是右值

      如:x = 3;x是左值,3是右值

    • 表达式求值结果的类型

    • 表达式求值结果结果是左值还是右值

    • 优先级与结合性 ,可以通过小括号来改变运算顺序

      操作符有优先级,如*的优先级比+的优先级高

      具体可查看C++运算符优先级表

      结合性:如果在一个表达式中包含多个操作符,且这些操作符有相同优先级

        在C++中,表达式的结合性(associativity)决定了在没有括号指明操作顺序时,运算符如何应用到操作数上。C++中的运算符按照结合律可以分为左结合(left-associative)、右结合(right-associative)和非结合(non-associative)三类。

      左结合(Left-Associative):

        左结合运算符在表达式中,从左到右依次与操作数结合。例如:

      • 乘法和除法(*, /)是左结合的。
      • 加法和减法(+, -)也是左结合的。
      int a = 1 - 3 - 5; // (1-3)-5
      

      右结合(Right-Associative):

        右结合运算符在表达式中,从右向左依次与操作数结合。在C++中,大多数右结合的运算符与赋值有关:

      • 赋值运算符(=, +=, -=, *=, /=, %=)是右结合的。
      int a = 10;
      a += 20 += 30; // (20 += 30) += 10,先计算右侧的表达式,再赋值给左侧
      

      非结合(Non-Associative):

        非结合运算符不能应用结合律,它们在表达式中不能省略括号。C++中大多数比较和逻辑运算符是非结合的:

      • 逻辑AND(&&)和逻辑OR(||)是非结合的。
      • 比较运算符(<, <=, >, >=, ==, !=)也是非结合的。
      bool result = a < b && c < d; // a < b 必须先于 c < d 进行计算
      

      注意事项

      • 在复杂的表达式中,为了清晰和避免歧义,推荐使用括号来明确指定运算的顺序。

      • 运算符的优先级和结合性共同决定了表达式的求值顺序。当运算符优先级相同时,结合性决定求值顺序(同样优先级的运算符的结合性相同);当优先级不同时,优先级高的运算符先进行计算。

      • 赋值运算符的右结合性意味着连续赋值需要从右向左进行。

    • 操作符的重载–不改变接收操作数的个数、优先级与结合性

      操作符的重载重点应用在类上,同样的操作符可以处理更多的类型,为操作符引入不同的含义,类章节会具体讨论

      具体可参考operator overloading

  • 操作数求值顺序的不确定性

    在C++中,操作数求值顺序的不确定性是指在某些表达式中,标准并没有定义操作数被求值的顺序。这意味着编译器可以自由地选择在计算过程中先求值哪个操作数,这可能导致未定义行为(undefined behavior),特别是当表达式中包含对同一个地方的多次修改时。

    安全的做法

    为了避免由于求值顺序不确定性带来的问题,C++程序员应该遵循以下安全做法:

    1. 避免在同一表达式中对同一个对象多次赋值。如果需要对同一对象赋值多次,应该使用不同的表达式。
    2. 使用括号明确求值顺序。虽然在大多数表达式中,括号内的表达式会先于其他操作数被求值,但在涉及不确定性求值顺序的情况下,这并不能完全保证安全。
    3. 使用序列点。C++中的序列点(sequence point)是程序执行中的一个点,在该点之前的所有操作都必须完成,之后的操作才开始。赋值操作、函数调用等都是序列点。
    4. 避免写依赖于求值顺序的代码。依赖于特定求值顺序的代码通常难以阅读和维护,并且可能在不同的编译器或不同的优化级别下有不同的行为。

1.表达式的值类别

  所有的划分都是针对表达式的,不是针对对象或数值。

image-20240505193954680
  • glvalue(泛左值):标识一个对象、位或函数

  • prvalue(纯右值):用于初始化对象或作为操作数

    prvalue是传统意义上的右值,它们是不可修改的,且没有自己的存储期。字面量和大多数表达式的计算结果都是prvalue

  • xvalue (将亡值):表示其资源可以被重新使用

    C++11引入了xvalue,表示即将被移动的右值。xvalue通常由使用 std::move 函数的表达式产生,它们可以绑定到非const引用上,并且可以用作移动语义的来源。

    std::unique_ptr<int> p1(new int(10));
    std::unique_ptr<int> p2 = std::move(p1); // p1 现在是一个xvalue
    
  • lvalue(左值):左值是可以取得地址的表达式,它们代表内存中的具体位置。左值可以出现在赋值表达式的左侧,也可以出现在需要具体存储位置的上下文中,如函数参数或数组索引。

    • 变量的名称
    • 数组的名称
    • 函数调用的结果(如果函数返回一个对象,而非按值返回)
    • 通过解引用指针获得的值
  • rvalue(右值):右值是不具备存储持续时间的临时对象或值,它们不能有名称,也不能出现在赋值表达式的左侧。右值通常用在赋值表达式的右侧,或作为函数参数传递(在C++11及以后的版本中,通过右值引用可以改变这一限制)。

    • 字面量(如整数、浮点数、字符)
    • 表达式的临时结果(如 (a + b)
    • 被创建和使用在同一个表达式中的临时对象

具体参考:Value categories

注意事项

  • 在 C++ 中,左值也不一定能放在等号左边;右值也可能放在等号左边
  • 左值和右值的概念在C++中非常重要,特别是在涉及到赋值、函数参数传递和返回类型时。
  • 右值引用(T&&)允许程序员安全地利用临时对象,通过移动语义而不是复制语义来提高效率。
  • std::move 函数可以将左值强制转换为xvalue,使其可以被用作移动赋值的源。
  • static_cast<T&&> 可以将一个表达式显式转换为xvalueprvalue,这在模板编程中非常有用

左值与右值的转换

  • 左值转换为右值( lvalue to rvalue conversion )(在放置右值处放入左值,编译器自动转换为右值)

    int x = 3;	//x为左值
    int y;
    y = x;	//此时,x为右值
    
  • 临时具体化( Temporary Materialization )

    prvalue到xvalue的转化

    #include <iostream>
    
    void fun(const int& par)
    {
    
    }
    
    int main()
    {
        fun(3);	 //prvalue到xvalue的转化
    }
    

再讨论decltype:https://zh.cppreference.com/w/cpp/language/decltype

如果实参是类型为 T 的任何其他表达式,且

a) 如果 表达式 的值类别是xvalue,则 decltype 产生 T&&(右值引用);

b) 如果 表达式 的值类别是lvalue,则 decltype 产生 T&

c) 如果 表达式 的值类别是prvalue,则 decltype 产生 T

2.表达式的类型转换

  一些操作符要求其操作数具有特定的类型,或者具有相同的类型,此时可能产生类型转换。

  • 隐式类型转换

    • 自动发生

      并不是给一个源类型与一个目标类型,两者之间就会自动转换,转换是有限制的。比如:int类型可以自动转换成double类型,但字符串类型就不能隐式转换成double类型。

      这部分内容官网很复杂,这里主要叙述数值提升与数值转换

      • 数值提升包括整型提升与浮点提升

        • 整型提升:将小整型类型(如:char)的纯右值转换为较大整型类型(如:int)的纯右值
        • 浮点提升:float类型纯右值转换为double类型的纯右值
      • 数值转换:不同于数值提升,数值转换可以更改值,而且有潜在的精度损失

    • 实际上是一个(有限长度的)转型序列

  • 显式类型转换(也称为强制类型转换,少使用显式类型转换)

    在C++中,显式类型转换(也称为强制类型转换)是程序员明确指示的类型转换,与隐式类型转换(自动进行的转换)相对。显式类型转换允许你将一个表达式从一种类型转换为另一种类型,即使这种转换不是显而易见的或者编译器不会自动进行的。显式类型转换同样是有限制的,并不是任意类型之间都能相互转换。

    • static_cast(编译期完成,不会对运行期产生性能的影响)

      语法:

      static_cast<Type>(expression)
      

      用于非多态类型之间的转换。它不允许包含访问权限改变或存在继承关系的转换。

      double d = 3.14;
      int i = static_cast<int>(d); // 安全地转换为int类型
      

      从static_cast中知道其支持的转换:

      • 如果存在从表达式到目标类型 的隐式转换序列
      • 如果存在从目标类型 到表达式 类型的标准转换序列,且它不包含左值到右值、数组到指针、函数到指针、空指针、空成员指针、函数指针 (C++17 起)或布尔转换,那么 static_cast 能进行该隐式转换的逆转换。
      • 如果从表达式 到目标类型 的转换涉及左值到右值、数组到指针或函数到指针转换,那么 static_cast 可以显式执行该转换。
      • 有作用域枚举类型能转换到整数或浮点类型
      • 整数或枚举类型值可转换到任何完整的枚举类型。
      • 浮点类型的纯右值可转换到任何其他浮点类型。
      • 指向void 的指针类型的纯右值可以转换成指向对象类型 T 的另一指针。
    • dynamic_cast(运行期完成,安全性高。性能相比static_cast会差一些)

      dynamic_cast<Type>(expression)
      

      dynamic_cast 主要用于处理多态性,允许你在存在继承关系的对象之间进行安全的向下转型。

      Base* basePtr = &derivedObj;
      Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
      
    • const_cast(改变变量的常量性,比较危险)

      语法:

      const_cast<Type>(expression)
      

      const_cast 用于移除或添加constvolatile限定符。这种转换不涉及值的修改,仅用于改变表达式的值类别。

      const int* ci = new int(10);
      int* modifiable = const_cast<int*>(ci); // 移除const限定符
      
      //在不同编译器中打印的结果可能不同(行为不确定),可能为3也可能为4
      const int x = 3;
      const int& ref = x;
      int& ref2 = const_cast<int&>(ref);
      ref2 = 4;
      std::cout << x << std::endl;
      
    • reinterpret_cast(重新解释)

      语法:

      reinterpret_cast<Type>(expression)
      

      reinterpret_cast 提供了最低级别的类型转换,它允许几乎任意的类型转换,包括指针和整型之间的转换。

      //转换之后可能导致值完全看不懂
      int* p = new int(65);
      char* ch = reinterpret_cast<char*>(p); // 转换为char指针
      
    • C 形式的类型转换(C++中不建议使用这种类型转换,比较危险,建议在上述四种转换中选择)

      语法:

      (Type)expression
      

      这是C语言中的类型转换方式,C++同样支持。它对于基本数据类型之间的转换是有效的,但不推荐使用,因为它缺乏类型安全。

      double pi = 3.14159;
      int intPi = (int)pi; // C风格转换为int类型
      

      遇到C 风格转换表达式 时,编译器会尝试按以下顺序将它解释成下列转换表达式:

      a) const_cast<目标类型>(表达式);

      b) static_cast<目标类型>(表达式),带扩展:额外允许将到派生类的指针或引用转换成到无歧义基类的指针或引用(反之亦然),纵使基类不可访问也是如此(即此转换忽略 private 继承说明符)。同样适用于将成员指针转换到指向无歧义非虚基类的成员的指针;

      c) static_cast(带扩展)后随 const_cast;

      d) reinterpret_cast<目标类型>(表达式);

      e) reinterpret_cast 后随 const_cast。

      选择首个满足相应转换运算符要求的方式,即便它无法编译(见示例)。如果该转换能解释成多于一种 static_cast 后随 const_cast 的方式,那么它无法编译。

二、表达式详述

1.算术操作符

  • 共分为三个优先级

    • + , - (一元)
    • * , / , %
    • + , - (二元)
  • 这些操作符均为左结合

      在C++中,操作符的结合性(associativity)描述了在没有括号的情况下,多个相同优先级的操作符如何应用到它们的操作数上。操作符可以是左结合(left-associative)或右结合(right-associative),也可以是既不左结合也不右结合(non-associative)。

    左结合(Left-Associative):

      左结合操作符意味着当多个相同类型的操作符连续出现时,它们从左到右依次与它们的操作数结合。在C++中,大多数二元操作符(如算术操作符、比较操作符、位操作符等)都是左结合的。

    例如,加法操作符(+)是左结合的:

    int a = 1 + 2 + 3; // 等同于 (1 + 2) + 3
    

    右结合(Right-Associative):

      右结合操作符意味着当多个相同类型的操作符连续出现时,它们从右向左依次与它们的操作数结合。在C++中,大多数赋值操作符和条件表达式的操作符(如 =, +=, ? : 等)是右结合的。

    例如,赋值操作符(=)是右结合的:

    int a = 1;
    a = 2 = 3; // 等同于 a = (2 = 3)
    

    既不左结合也不右结合(Non-Associative):

      既不左结合也不右结合的操作符意味着连续出现时不能省略括号,因为它们不符合结合律。在C++中,大多数比较操作符(如 <, >, ==, != 等)和逻辑操作符(如 &&, || 等)都是既不左结合也不右结合的。

    例如,逻辑与操作符(&&)是既不左结合也不右结合的:

    bool a = true;
    bool b = false;
    bool c = a && b; // 错误:缺少括号,意图是 (a && b)
    bool c = (a && b); // 正确
    

    注意事项

    • 在编写代码时,为了提高可读性和避免歧义,建议总是使用括号来明确表达你的意图,即使操作符是左结合或右结合的。
    • 了解操作符的结合性对于正确理解复杂表达式的行为非常重要。
    • 在C++中,= 是右结合的,这意味着连续赋值是从右向左进行的。
  • 通常来说,操作数与结果均为算数类型的右值;但加减法与一元 + 的操作数可接收指针

    #include <iostream>
    
    int main()
    {
        int x[3] = {1, 2, 3};
        int* ptr = x;
        ptr = ptr + 1;
    
        const auto& a = +x; //仅用于获取指针当前指向的值,+x == x[0]
    }
    
  • 一元 + 操作符会产生 integral promotion(整型提升)

    image-20240507001808478

  • 整数相除会产生整数,向 0 取整

    4 / 3 == 1
    -4 / 3 == -1
    
  • 求余只能接收整数类型操作数,结果符号与第一个操作数相同

    求余满足
    ( m / n ) ∗ n + m % n = = m (m / n) * n + m \% n == m (m/n)n+m%n==m

2.逻辑与关系操作符

  • 优先级划分,可查看C++ Operator Precedence

    • !(逻辑非,优先级3)

    • <=>(关系操作符-三向比较符,优先级8,C++20引入)

    • <、<=、>、>=(关系操作符,优先级9)

    • ==、!=(关系操作符,优先级10)

    • &&(逻辑与,优先级14)

      C++中的逻辑与运算符具有短路(short-circuit)特性,即如果第一个操作数为假,那么整个表达式的结果已经确定为假,编译器将不会对第二个操作数求值,因为无论第二个操作数的值是什么,都不影响最终结果。

      与位与运算符的区别

      逻辑与运算符 && 与位与运算符 & 不同。位与运算符 & 是按位操作,对整数类型的两个操作数的每一位进行与操作,而逻辑与运算符 && 是布尔操作,只用于布尔值的逻辑运算

      int x = 0b1100; // 二进制 12
      int y = 0b1010; // 二进制 10
      
      int bitAnd = x & y;  // 按位与,结果为 0b1000 (8)
      bool logicAnd = x && y; // 逻辑与,结果为 true
      
    • ||(逻辑或,优先级15)

      短路特性:

      C++中的逻辑或运算符 || 具有短路(short-circuit)特性,即如果第一个操作数为真,那么整个表达式的结果已经确定为真,编译器将不会对第二个操作数求值,因为无论第二个操作数的值是什么,都不影响最终结果。

      bool a = true;
      bool b = false;
      
      if (a || someCondition()) {
          // 这段代码会执行,因为 a 为 true,所以不会调用 someCondition()
      }
      

      在上面的例子中,由于 a 为真,someCondition() 将不会被调用,因为整个 if 条件的结果已经确定为真。

      与位或运算符的区别

      逻辑或运算符 || 与位或运算符 | 不同。位或运算符 | 是按位操作,对整数类型的两个操作数的每一位进行或操作,而逻辑或运算符 || 是布尔操作,只用于布尔值的逻辑运算

      int x = 0b1100; // 二进制 12
      int y = 0b1010; // 二进制 10
      
      int bitOr = x | y;   // 按位或,结果为 0b1110 (14)
      bool logicOr = x || y; // 逻辑或,结果为 true,因为至少有一个操作数为非零值
      
  • 关系操作符接收算术或指针类型操作数;逻辑操作符接收可转换为 bool 值的操作数

  • 操作数与结果均为右值(通常结果类型为 bool,除了<=>操作符 )

  • 除逻辑非外,其它操作符都是左结合的

  • 逻辑与、逻辑或具有短路特性

  • 逻辑与的优先级高于逻辑或

  • 通常来说,不能将多个关系操作符串连

    #include <iostream>
    
    int main()
    {
        int a = 3;
        int b = 4;
        int c = 5;
        std::cout << (c>b>a) << std::endl;  //这里实际结果为0,c>b为1, 1>3为0;但我们实际想要的是(c>b)&&(b>a)
    }
    
  • 不要写出 val == true 这样的代码

    #include <iostream>
    
    int main()
    {
        int a = 3;
        if(a)   //不要写成if(a==true),该表达式隐式转化为if(a == 1),不是我们想要的
        {
    
        }
    }
    
  • Spaceship operator: <=>(C++20引入)

    当比较的两个对象比较复杂时,使用该操作符

    这个运算符的结果是一个 std::strong_orderingstd::weak_orderingstd::partial_ordering 类型的对象,分别对应于两个操作数之间的严格弱序、弱序或部分序关系。

    语法

    auto result = lhs <=> rhs;
    

3.位操作符

  • 优先级划分,可查看C++ Operator Precedence

    • ~(按位取反,优先级3)

      在C++中,当你对一个整数按位取反(使用 ~ 运算符)后,得到的结果是一个补码表示的整数。在大多数现代计算机系统中,整数是以补码形式存储的

      #include <iostream>
      
      int main()
      {
          //二进制表示为00000011
          signed char x = 3;  
          //按位取反结果为:11111100,得到的是一个补码表示的整数,
          //其对应原码表示的整数为:10000100 == -4
          std::cout<< ~x << std::endl;	//-4
      }
      

      原码、反码、补码的关系

      • 正数的反码、补码与原码相同
      • 负数的反码是其绝对值原码按位取反,负数的补码为其反码的末尾加1

      转换补码到原码

      • 确定位数:确定补码表示的位数(例如,32位或64位)
      • 识别符号:最左边的一位是符号位,0表示正数,1表示负数
      • 取反并加1:对于负数,将除了符号位之外的所有位取反,然后加1
    • << >> (移位操作,优先级7)

      左移操作符 << 将操作数的二进制表示向左移动指定的位数。移入的新位(最左边的位)通常是0。且无论时有符号整数还是无符号整数,最高位(有可能是符号位)也正常移动,因此,左移不能保证符号

      #include <iostream>
      
      int main()
      {
          unsigned int y = 0x80000001;(正数)
          std::cout << (y<<1) <<std::endl;  //2
      
          signed int y1 = 0x80000001;(负数)
          std::cout << (y1<<1) <<std::endl; //2
      }
      

      右移操作符 >> 将操作数的二进制表示向右移动指定的位数。移入的新位(最右边的位)取决于编译器的实现,但通常遵循以下规则:

      • 对于无符号整数,移入的新位是0。
      • 对于有符号整数,移入的新位与原来的最左边的位(符号位)相同。
      #include <iostream>
      
      int main()
      {
          signed int x = -4;  
          //-4的补码为11111100(内存中存储形式)
          //移码后为11111000 -->其对应原码为-8
          std::cout << (x<<1) << std::endl;
          //移码后为11111110 --> 其对应原码为-2
          std::cout << (x>>1) <<std::endl;
      }
      
    • &(按位与,优先级11)

    • ^(按位异或,优先级12)

      在C++中,^ 是按位异或运算符(bitwise XOR operator)。按位异或运算符对两个操作数的对应位进行逐位比较,并根据以下规则进行运算:

      • 如果对应的两位相同,则结果为0。
      • 如果对应的两位不同,则结果为1。

      基本用法

      int a = 0b1100; // 二进制表示的12
      int b = 0b1010; // 二进制表示的10
      
      int result = a ^ b; // 按位异或,结果为 0b0110 (6)
      

      在这个例子中,ab 的二进制表示分别为 11001010,进行按位异或操作后得到 0110,这是二进制表示的 6

    • |(按位或,优先级13)

  • 接收右值,进行位运算,返回右值

  • 除取反外,其它运算符均为左结合的

  • 注意:计算过程中可能会涉及到 integral promotion

    image-20240507214520847

    image-20240507214702493

    为什么会涉及到 integral promotion呢?

      因为int是对应到硬件系统中最经常使用的数据类型,将char提升到int在操作时,可能只需要一个指令就能完成位操作运算。

  • 注意这里没有短路逻辑,不同于逻辑与于逻辑或

  • 移位操作在一定情况下等价于乘(除) 2 的幂,但速度更快

  • 注意整数的符号与位操作符的相关影响

    • integral promotion 会根据整数的符号影响其结果

      #include <iostream>
      
      int main()
      {
          unsigned char x = 0xff; //11111111
          //提升:0000...0000 1111 1111
          //取反:1111...1111 0000 0000(补码表示)
          //其对应原码为:1000...0001 0000 0000 等于-256
          auto y = ~x;
          std::cout << y << std::endl; //-2^8 == -256
        
        	signed char x1 = 0xff; //11111111,第一位为符号位
          //提升:1111...1111 1111 1111
          //补码:0000...0000 0000 0000
          //其原码为0000...0000 0000 0000 等于0
          auto y1 = ~x1;
          std::cout << y1 << std::endl; //0
      }
      
    • 右移保持符号,但左移不能保证

4.赋值操作符

  • 优先级划分,可查看C++ Operator Precedence

    • =(优先级16)

      int x = 5; //这里的=为初始化操作符
      x = 6;		//这里的=为赋值操作符
      
  • 赋值操作符的左操作数为可修改左值;右操作数为右值,可以转换为左操作数的类型

  • 赋值操作符是右结合的,求值结果为左操作数

    x = y = 3;  //等价于(y=3) == y; x = y;
    (x = 5) = 2;//等价于(x=5) == x; x = 2;
    
  • 可以引入大括号(初始化列表)以防止收缩转换( narrowing conversion )

    收缩转换:大的类型转换成小的类型。引入大括号后,如果发生收缩转换,编译器会报错

    image-20240507234423810

  • 小心区分 = 与 ==

  • 复合赋值运算符(右结合)

    • +=、-=、*=、/=、%=、<<=、>>=、&=、^=、|=(优先级均为16,与=一样)
    #include <iostream>
    
    int main()
    {
        int x = 2;
        int y = 3;
        x^=y^=x^=y;  //x与y交换 
    }
    

5.自增与自减运算符

  • 优先级划分,可查看C++ Operator Precedence

    • a++、a–(后缀,从左到右优先级为2)

    • ++a、–a(前缀,优先级3)

      ++a 等价于 a = a + 1;
      --a 等价于 a = a - 1;
      
  • 分前缀与后缀两种情况

  • 操作数为左值;前缀时返回左值(返回更新后的值);后缀时返回右值(返回更新前的值,理论会用到临时变量,编译器有可能会进行优化)

  • 建议使用前缀形式

    因为后缀形式会用到临时变量,且还会有拷贝成本。

6.其他操作符

  • 成员访问操作符: . ->

    • -> 等价于 (*).
    • . 的左操作数是左值(或右值),返回左值(或右值 xvalue
    • -> 的左操作数指针,返回左值
  • 条件操作符

    • 唯一的三元操作符

      三元操作符的基本语法如下:

      condition ? expr_true : expr_false
      

      这里,condition 是一个布尔表达式,expr_true 是当条件为真(true)时返回的表达式,而 expr_false 是当条件为假(false)时返回的表达式。

    • 接收一个可转换为 bool 的表达式与两个类型相同的表达式,只有一个表达式会被求值

    • 如果表达式均是左值,那么就返回左值,否则返回右值

    • 右结合

      #include <iostream>
      
      int main()
      {
          int score = 100;
          //右结合,先算(score == 0) ? 0 : -1;再算(score > 0) ? 1 : 结果
          int res = (score > 0) ? 1 : (score == 0) ? 0 : -1;
          std::cout << res << std::endl;
      }
      
  • 逗号操作符

    • 确保操作数会被从左向右求值
    • 求值结果为右操作数
    • 左结合
  • sizeof 操作符

    • 操作数可以是一个类型或一个表达式
    • 并不会实际求值,而是返回相应的尺寸(产生类型的对象表示的字节数)
  • 其它操作符

    • 域解析操作符 ::
    • 函数调用操作符 ()
    • 索引操作符 []
    • 抛出异常操作符 throw

三、C++17对表达式的求值顺序的限定

  以下表达式在 C++17 中,可以确保 e1 会先于 e2 被求值

  • e1[e2]
  • e1.e2
  • e1.*e2
  • e1→*e2
  • e1<<e2
  • e1>>e2
  • e2 = e1 / e2 += e1 / e2 *= e1…(赋值及赋值相关的复合运算,等号右边的先求值)

new Type(e) 会确保 e 会在分配内存之后求值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/627064.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年5月面试准备

2024年5月面试准备 资料来源Java基础泛型注解异常反射SPI机制Java集合CollectionMap 并发基础线程并发关键字并发集合Lock核心类并发集合核心类原子类核心类线程池核心类ScheduledThreadPoolExecutorForkJoinPoolFokJoinTask JUC原子类: CAS, Unsafe和原子类详解JUC 工具类 Jav…

Nginx 生产环境部署的最佳实践

你好呀&#xff0c;我是赵兴晨&#xff0c;文科程序员。 最近一段时间&#xff0c;我一直在和大家一起探讨Nginx的相关话题。期间&#xff0c;我收到了很多小伙伴的私信&#xff0c;他们好奇地问我&#xff1a;在生产环境中&#xff0c;Nginx应该如何配置&#xff1f; 他们在…

LeetCode题练习与总结:不同的二叉搜索树--96

一、题目描述 给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#xff1a;n 1 输出&…

平衡三进制小数详解与进制转换

标准三进制是“逢三进一&#xff0c;退一还三”的机制&#xff0c;平衡三进制与之类似&#xff0c;但就是偏移了一下变得对称了&#xff0c;平衡三进制与标准三进制可以相互转换&#xff0c;但这样显得有点多余了&#xff0c;所以这里只讲平衡三进制与十进制的转换。 数字系统的…

_pickle.UnpicklingError: STACK_GLOBAL requires str

导致这个报错的原因是我跑yolo的时候修改数据集了&#xff0c;里面的label.cache没有删除&#xff0c;咱只要删除掉缓存就行&#xff01;&#xff01; 我这里是已经删除掉了&#xff0c;所以图片里面没有&#xff0c;一般就是在箭头所示位置有.cache文件的

Python 全栈体系【四阶】(四十三)

第五章 深度学习 九、图像分割 3. 常用模型 3.4 DeepLab 系列 3.4.1 DeepLab v1(2015) 3.4.1.1 概述 图像分割和图像分类不一样&#xff0c;要对图像每个像素进行精确分类。在使用CNN对图像进行卷积、池化过程中&#xff0c;会导致特征图尺寸大幅度下降、分辨率降低&…

windows驱动开发-PCI和中断(二)

谈到中断使用PCI总线来作为例子是最合适的&#xff0c;在Windows发展过程中&#xff0c;PCI作为最成功的底层总线&#xff0c;集成了大量的外设&#xff0c;不夸张的说&#xff0c;目前PCI几乎是唯一的总线选择&#xff0c;故大部分情况下&#xff0c;只有PCI设备驱动程序会遇到…

【回溯】1240. 铺瓷砖

本文涉及知识点 回溯 LeetCode1240. 铺瓷砖 你是一位施工队的工长&#xff0c;根据设计师的要求准备为一套设计风格独特的房子进行室内装修。 房子的客厅大小为 n x m&#xff0c;为保持极简的风格&#xff0c;需要使用尽可能少的 正方形 瓷砖来铺盖地面。 假设正方形瓷砖的…

【C++小语法】引用和内联函数(完结篇)

在使用C语言编程过程中&#xff0c;C语言的要求之严格&#xff0c;编程过程之繁琐&#xff0c;大同小异的重复性工作&#xff0c;令C之父使用C语言编程时也深受其扰&#xff0c;于是乎C兼容C小语法诞生了 一、引用 1.引用概念 在C中&#xff0c;引用&#xff08;Reference&am…

SpringCloud------Feign,Geteway

Feign 所以我们使用一门新的技术&#xff1a;声明式的http客户端Feign 第一步&#xff1a;引入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency> …

C++ | Leetcode C++题解之第90题子集II

题目&#xff1a; 题解&#xff1a; class Solution { public:vector<int> t;vector<vector<int>> ans;vector<vector<int>> subsetsWithDup(vector<int> &nums) {sort(nums.begin(), nums.end());int n nums.size();for (int mask …

C++青少年简明教程:赋值语句

C青少年简明教程&#xff1a;赋值语句 赋值语句是编程中最基本也是最常用的概念之一&#xff0c;它用于将一个值分配给一个变量。 使用等号&#xff08; 称为赋值运算符&#xff09;来给变量赋值&#xff0c;赋值语句的左边是要赋值的变量&#xff0c;右边是要赋给变量的值。C…

PHP 自提时间

前端: 后台设置: 代码: public function getBusinessHour(){// 需求单门店$data (new StoreModel())->limit(1)->select()->toArray();$days explode(,, $data[0][shop_hours]);$businessHours $days[1];// 使用 explode 分割字符串&#xff0c;获取开始和结束时…

Nodejs 第七十章(OSS)

OSS OSS&#xff08;Object Storage Service&#xff09;是一种云存储服务&#xff0c;提供了一种高度可扩展的、安全可靠的对象存储解决方案 OSS 对象存储以对象为基本存储单元&#xff0c;每个对象都有唯一的标识符&#xff08;称为对象键&#xff09;和数据。这些对象可以…

【教程】Jetson安装PyQt5和CUDA版OpenCV

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;请不吝给个[点赞、收藏、关注]哦~ 安装PyQt5 注意目前似乎只支持Python3.6&#xff01;&#xff01;&#xff01; sudo apt install pyqt5* -y sudo apt-get install python3-pyqt…

基于HTTP GET方式获取网络时间的实现

上一节&#xff0c;我们介绍了基于NTP服务器获取网络时间的例子&#xff0c;但在有些情况下&#xff0c;比如我最近在使用RNDIS协议通过4G模块上网&#xff0c;这个协议不支持UDP协议&#xff0c;所以就用不了NTP服务器。或者有时候我们需要有更多的网络时间获取方式&#xff0…

python数据分析——seaborn绘图2

参考资料&#xff1a;活用pandas库 # 导入库 import pandas as pd import matplotlib.pyplot as plt import seaborn as sns tipspd.read_csv(r"...\seaborn常用数据案例\tips.csv") print(tips.head()) 1、成对关系表示 当数据大部分是数据时&#xff0c;可以使用…

AI图像生成-调整

一、两张图画风不相似 2、在两张图的共同输出口新添加一个空白正面提示词板块和条件合并板块 二、预处理插件&#xff08;提取人物姿态&#xff09; 1、新建节点-》ControlNet预处理器-》面部与姿态-》Openpose姿态预处理器 2、添加上传图片板块与预览图片板块 3、提取姿态 右…

数据库学习之select语句练习

目录 素材 练习 1、显示所有职工的基本信息。 结果 2、查询所有职工所属部门的部门号&#xff0c;不显示重复的部门号。 结果 3、求出所有职工的人数。 结果 4、列出最高工和最低工资。 结果 5、列出职工的平均工资和总工资。 结果 6、创建一个只有职…

【全开源】房屋出租出售预约系统支持微信小程序+H5+APP

一款基于FastAdminThinkPHPUniapp开发的房屋出租出售预约系统&#xff0c;支持小程序、H5、APP&#xff1b;包含房客、房东(高级授权)、经纪人(高级授权)三种身份。核心功能有&#xff1a;新盘销售、房屋租赁、地图找房、小区找房&#xff0c;地铁找房等方式。 特色功能&#…