第4章 – C++-运算符

表达式和运算符

在程序中,一个或多个运算对象的组合叫做“表达式”(expression),我们可以把它看成用来做计算的“式子”。对一个表达式进行计算,可以得到一个结果,有时也把它叫做表达式的值。

前面讲到的字面值常量和变量,就是最简单的表达式;表达式的结果就是字面值和变量的值。而多个字面值和变量,可以通过一些符号连接组合在一起,表示进行相应的计算,这就可以得到更加复杂的表达式,比如 a + 1。像“+”这些符号就被叫做“运算符”(operator)。

C++中定义的运算符,可以是像“+”这样连接两个对象,称为“二元运算符”;也可以只作用于一个对象,称为“一元运算符”。另外,还有一个比较特殊的运算符可以作用于三个对象,那就是三元运算符了。

优先级

如果在一个表达式中,使用多个运算符组合了多个运算对象,就构成了更加复杂的“复合表达式”,比如 a + 1 - b。对于复合表达式,很显然我们应该分步来做计算;而计算顺序,是由所谓的“优先级”和“结合律”确定的。

简单来说,就是对不同的运算符赋予不同的“优先级”,我们会优先执行高优先级的运算、再执行低优先级的运算。如果优先级相同,就按照“结合律”来决定执行顺序。这其实跟数学的综合算式是一样的,我们会定义乘除的优先级要高于加减,平级运算从左往右,所以对于算式:

1 + 2 – 3 × 4

我们会先计算高优先级的 3×4,然后按照从左到右的结合顺序计算1+2,最后做减法。另外,如果有括号,那就要先把括起来的部分当成一个整体先做计算,然后再考虑括号外的结合顺序,这一点在C++表达式中同样适用。

总结就是根据数学的规则,先乘除后加减,然后依次从左往右计算,如果有括号则优先级最高,

算术运算符

最简单的运算符,就是表示算术计算的加减乘除,这一类被称为“算术运算符”。C++支持的算术运算符如下:

图片[1]-第4章 – C++-运算符-织秋笔记

这里需要注意的是,同一个运算符,在不同的场合可能表达不同的含义。比如“-”,可以是“减号”也可以是“负号”:如果直接放在一个表达式前面,就是对表达式的结果取负数,这是一元运算符;如果连接两个表达式,就是两者结果相减,是二元运算符。

算术运算符相关规则如下:

  • 一元运算符(正负号)优先级最高;接下来是乘、除和取余;最后是加减;

  • 算术运算符满足左结合律,也就是说相同优先级的运算符,将从左到右按顺序进行组合;

  • 算术运算符可以用来处理任意算术类型的数据对象;

  • 不同类型的数据对象进行计算时,较小的整数类型会被“提升”为较大的类型,最终转换成同一类型进行计算;

  • 对于除法运算“/”,执行计算的结果跟操作数的类型有关。如果它的两个操作数(也就是被除数和除数)都是整数,那么得到的结果也只能是整数,小数部分会直接舍弃,这叫“整数除法”;当至少有一个操作数是浮点数时,结果就会是浮点数,保留小数部分;

  • 对于取余运算“%”(或者叫“取模”),两个操作数必须是整数类型;

赋值

在C++中,用等号“=”表示一个赋值操作,这里的“=”就是赋值运算符。需要注意的是,赋值运算符的左边,必须是一个可修改的数据对象,比如假设我们已经定义了一个int类型的变量a,那么

a = 1;

这样赋值是对的,但

1 = a;

就是错误的。因为a是一个变量,可以赋值;而 1只是一个字面值常量,不能再对它赋值。

    int a, b;
    a = 1;
    //1 = a;    // 错误:表达式必须是可修改的左值
    a = b + 5;
    //b + 5 = a;    // 错误:表达式必须是可修改的左值
    const int c = 10;
    //c = a + b;    // 错误:表达式必须是可修改的左值

所以像变量a这样的可以赋值的运算对象,在C++中被叫做“左值”(lvalue);对应的,放在赋值语句右面的表达式就是“右值”(rvalue)。
赋值运算有以下一些规则:

  • 赋值运算的结果,就是它左侧的运算对象;结果的类型就是左侧运算对象的类型;
  • 如果赋值运算符两侧对象类型不同,就把右侧的对象转换成左侧对象的类型;

  • C++ 11新标准提供了一种新的语法:用花括号{}括起来的数值列表,可以作为赋值右侧对象。这样就可以非常方便地对一个数组赋值了;

    • 赋值运算满足右结合律。也就是说可以在一条语句中连续赋值,结合顺序是从右到左;

    • 赋值运算符优先级较低,一般都会先执行其它运算符,最后做赋值;

a = {2};
int arr[] = {1,2,3,4,5};    // 用花括号对数组赋值
a = b = 20;    // 连续赋值

复合赋值运算符

实际应用中,我们经常需要把一次计算的结果,再赋值给参与运算的某一个变量。最简单的例子就是多个数求和,比如我们要计算a、b、c的和,那么可以专门定义一个变量sum,用来保存求和结果:

int sum = a;    // 初始值是a
sum = sum + b;    // 叠加b
sum = sum + c;    // 叠加c

要注意赋值运算符“=”完全不是数学上“等于”的意思,所以上面的赋值语句sum = sum + b; 说的是“计算sum + b的结果,然后把它再赋值给sum”。

为了更加简洁,C++提供了一类特殊的赋值运算符,可以把要执行的算术运算“+”跟赋值“=”结合在一起,用一个运算符“+=”来表示;这就是“复合赋值运算符”。

复合赋值一般结合的是算术运算符或者位运算符。每种运算符都有对应的组合形式:

图片[2]-第4章 – C++-运算符-织秋笔记

这样上面的代码可以改写为:

int sum = a;    // 初始值是a
sum += b;    // 叠加b
sum += c;    // 叠加c

递增递减运算符

C++为数据对象的“加一”“减一”操作,提供了更加简洁的表达方式,这就是递增和递减运算符(也叫“自增”“自减”运算符)。“递增”用两个加号“++”表示,表示“对象值加一,再赋值给原对象”;“递减”则用两个减号“--”表示。

++a;    // a递增,相当于 a += 1;
--b;    // b递减,相当于 b -= 1;

递增递减运算符各自有两种形式:“前置”和“后置”,也就是说写成“++a”和“a++”都是可以的。它们都表示“a = a + 1”,区别在于表达式返回的结果不同:

前置时,对象先加1,再将更新之后的对象值作为结果返回;

后置时,对象先将原始值作为结果返回,再加1;

这要特别注意:如果我们单独使用递增递减运算符,那前置后置效果都一样;但如果运算结果还要进一步做计算,两者就有明显不同了。

int i = 0, j;
j = ++i;    // i = 1,j = 1
j = i--;        // i = 0, j = 1


在实际应用中,一般都是希望用改变之后的对象值;所以为了避免混淆,我们通常会统一使用前置的写法。

关系和逻辑运算

在程序中,不可缺少的一类运算就是逻辑和关系运算,因为我们往往需要定义“在某种条件发生时,执行某种操作”。判断条件是否发生,这就是一个典型的逻辑判断;得到的结果或者为“真”(true),或者为“假”。很显然,这类运算的结果应该是布尔类型。

关系运算符

最简单的一种条件,就是判断两个算术对象的大小关系,对应的运算符称为“关系运算符”。包括:大于“>”、小于“<”、等于“==”、不等于“!=”、大于等于“>=”、小于等于“<=”。

图片[3]-第4章 – C++-运算符-织秋笔记


这里要注意区分的是,在C++语法中一个等号“=”表示的是赋值,两个等号“==”才是真正的“等于”。

1 < 2;    // true
3 >= 5;    // false
10 == 4 + 6;    // true
(10 != 4) + 6;    // 7

关系运算符的相关规则:

  • 算术运算符的优先级高于关系运算符,而如果加上括号就可以调整计算顺序;

  • 关系运算符的返回值为布尔类型,如果参与算术计算,true的值为1,false的值为0;

逻辑运算符

一个关系运算符的结果是一个布尔类型(ture或者false),就可以表示一个条件的判断;如果需要多个条件的叠加,就可以用逻辑“与或非”将这些布尔类型组合起来。这样的运算符叫做“逻辑运算符”。

  • 逻辑非(!):一元运算符,将运算对象的值取反后返回,真值反转;

  • 逻辑与(&&):二元运算符,两个运算对象都为true时结果为true,否则结果为false;

  • 逻辑或(||):二元运算符,两个运算对象只要有一个为true结果就为true,都为false则结果为false;

1 < 2 && 3 >= 5;    // false
1 < 2 || 3 >= 5;    // true
!(1 < 2 || 3 >= 5);    // false

我们可以把逻辑运算符和关系运算符的用法、优先级和结合律总结如下(从上到下优先级递减):

图片[4]-第4章 – C++-运算符-织秋笔记

这里需要注意的规则有:

  • 如果将一个算术类型的对象作为逻辑运算符的操作数,那么值为0表示false,非0值表示true;
  • 逻辑与和逻辑或有两个运算对象,在计算时都是先求左侧对象的值,再求右侧对象的值;如果左侧对象的值已经能决定最终结果,那么右侧就不会执行计算:这种策略叫做“短路求值”;
i = -1;
1 < 2 && ++i;                 // false
cout << " i = " << i << endl;       // i = 0
1 < 2 || ++i;                 // true
cout << " i = " << i << endl;       // i = 0

条件运算符

C++还从C语言继承了一个特殊的运算符,叫做“条件运算符”。它由“?”和“:”两个符号组成,需要三个运算表达式,形式如下:

条件判断表达式 ? 表达式1 : 表达式2

它的含义是:计算条件判断表达式的值,如果为true就执行表达式1,返回求值结果;如果为false则跳过表达式1,执行表达式2,返回求值结果。这也是C++中唯一的一个三元运算符。

i = 0;
cout << ((1 < 2 && ++i) ? "true" : "false") << endl;
  • 条件运算符的优先级比较低,所以输出的时候需要加上括号

  • 条件运算符满足右结合律

事实上,条件运算符等同于流程控制中的分支语句if...else...,只用一条语句就可以实现按条件分支处理,这就让代码更加简洁。

位运算符

之前介绍的所有运算符,主要都是针对算术类型的数据对象进行操作的;所有的算术类型,占用的空间都是以字节(byte,8位)作为单位来衡量的。在C++中,还有一类非常底层的运算符,可以直接操作到具体的每一位(bit)数据,这就是“位运算符”。

位运算符可以分为两大类:移位运算符,和位逻辑运算符。下面列出了所有位运算符的优先级和用法。

图片[5]-第4章 – C++-运算符-织秋笔记

移位运算符

算术类型的数据对象,都可以看做是一组“位”的集合。那么利用“移位运算符”,就可以让运算对象的所有位,整体移动指定的位数。

移位运算符有两种:左移运算符“<<”和右移运算符“>>”。这个符号我们并不陌生,之前做输入输出操作的时候用的就是它,不过那是标准IO库里定义的运算符重载版本。

下面是移位运算符的一个具体案例:

图片[6]-第4章 – C++-运算符-织秋笔记

  • 较小的整数类型(char、short以及bool)会自动提升成int类型再做移位,得到的结果也是int类型

    • 左移运算符“<<”将操作数左移之后,在右侧补0;

    • 右移运算符“>>”将操作数右移之后,对于无符号数就在左侧补0;对于有符号数的操作则要看运行的机器环境,有可能补符号位,也有可能直接补0;

    • 由于有符号数右移结果不确定,一般只对无符号数执行位移操作;

unsigned char bits = 0xb5;    // 181
cout << hex;    // 以十六进制显示
cout << "0xb5 左移2位:" << (bits << 2) << endl;    // 0x 0000 02d4
cout << "0xb5 左移8位:" << (bits << 8) << endl;    // 0x 0000 b500
cout << "0xb5 左移31位:" << (bits << 31) << endl;    // 0x 8000 0000
cout << "0xb5 右移3位:" << (bits >> 3) << endl;    // 0x 0000 0016

cout << dec;
cout << (200 << 3) << endl;    // 乘8操作
cout << (-100 >> 2) << endl;    // 除4操作,一般右移是补符号

位逻辑运算符

计算机存储的每一个“位”(bit)都是二进制的,有0和1两种取值,这跟布尔类型的真值表达非常类似。于是自然可以想到,两个位上的“0”或“1”都可以执行类似逻辑运算的操作。

位逻辑运算符有:按位取反“~”,位与“&”,位或“|”和位异或“^”。

  • 按位取反“~”:一元运算符,类似逻辑非。对每个位取反值,也就是把1置为0、0置为1;

  • 位与“&”:二元运算符,类似逻辑与。两个数对应位上都为1,结果对应位为1;否则结果对应位为0;

  • 位或“|”:二元运算符,类似逻辑或。两个数对应位上只要有1,结果对应位就为1;如果全为0则结果对应位为0;

  • 位异或“^”:两个数对应位相同,则结果对应位为0;不同则结果对应位为1;

    图片[7]-第4章 – C++-运算符-织秋笔记

    // 位逻辑运算
    cout << (~5) << endl;    // ~ (0... 0000 0101) = 1... 1111 1010,  -6
    cout << (5 & 12) << endl;   // 0101 & 1100 = 0100, 4
    cout << (5 | 12) << endl;   // 0101 | 1100 = 1101, 13
    cout << (5 ^ 12) << endl;    // 0101 & 1100 = 1001, 9

位异或应用案例: 从一组数里找出只出现一次的数

int i1 = 5 , i2 = 12 , i3 =12 , i4 = 9 , i5 = 5 ;
cout << "只出现一次的数为:" << ( i1 ^ i2 ^ i3 ^ i4 ^ i5 ) << endl ;
cin.get();
© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容