## 1.4 操作符/运算符(Operators)
前面已经学习了变量和常量,我们可以开始对它们进行操作,这就要用到C++的操作符。有些语言,很多操作符都是一些关键字, 比如add, equals等等。C++的操作符主要是由符号组成的。这些符号不在字母表中,但是在所有键盘上都可以找到。这个特点使得C++程序更简洁,也更国际化。运算符是C++语言的基础,所以非常重要。
你不需要背下所有这一小节的内容,这些细节知识仅供你以后需要时参考 。
### 赋值Assignation (=)
赋值运算符的功能是将一个值赋给一个变量。
`a = 5;`
将整数5赋给变量a。= 运算符左边的部分叫做lvalue (left value),右边的部分叫做rvalue (right value)。lvalue 必须是一个变量,而右边的部分可以是一个常量,一个变量,一个运算(operation)的结果或是前面几项的任意组合。
有必要强调赋值运算符永远是将右边的值赋给左边,永远不会反过来。
`a = b;`
将变量b (rvalue)的值赋给变量a (lvalue),不论a当时存储的是什么值。同时考虑到我们只是将b的数值赋给a,以后如果b的值改变了并不会影响到a的值.
例如:如果我们使用以下代码(变量值的变化显示在绿色注释部分):
~~~
// 赋值符号例子
#include <iostream>
using namespace std;
int main ()
{
int a, b; // a:?, b:?
a = 10; // a:10, b:?
b = 4; // a:10, b:4
a = b; // a:4, b:4
b = 7; // a:4, b:7
cout << "a:";
cout << a;
cout << " b:";
cout << b;
return 0;
}
~~~
~~~
a:4 b:7
~~~
以上代码结果是a的值为4, b的值为7。最后一行中b的值被改变并不会影响到a,虽然在此之前我们声明了a = b; (从右到左规则right-to-left rule)。
C++拥有而其他语言没有的一个特性是赋值符 (=) 可以被用作另一个赋值符的rvalue (或rvalue的一部分) 。例如:
`a = 2 + (b = 5);`
等同于:
`b = 5;
a = 2 + b;`
它的意思是:先将5赋给变量b,然后把前面对b的赋值运算的结果(即5)加上2再赋给变量a,这样最后a中的值为7。因此,下面的表达式在C++中也是正确的:
a = b = c = 5; //将5同时赋给3个变量a, b和c。
### 数学运算符Arithmetic operators ( +, -, *, /, % )
C++语言支持的5种数学运算符为:
* `+` 加addition
* `-` 减subtraction
* `*` 乘multiplication
* `/` 除division
* `%` 取模module
加减乘除运算想必大家都很了解,它们和一般的数学运算符没有区别。
唯一你可能不太熟悉的是用百分号(%)表示的取模运算(module)。取模运算是取两个整数相除的余数。例如,如果我们写a = 11 % 3;,变量a的值将会为结果2,因为2是11除以3的余数。
### 组合运算符Compound assignation operators (+=, -=, *=, /=, %=, >>=, <<=, &=, ^=, |=)
C++以书写简练著称的一大特色就是这些组合运算符compound assignation operators (+=, -=, *= 和 /= 及其他) ,这些运算符使得只用一个基本运算符就可改写变量的值:
~~~
value += increase; 等同于 value = value + increase;
a -= 5; 等同于 a = a - 5;
a /= b; 等同于 a = a / b;
price *= units + 1; 等同于 price = price * (units + 1);
~~~
其他运算符以此类推。例如:
~~~
// 组合运算符例子
#include <iostream>
using namespace std;
int main ()
{
int a, b=3;
a = b;
a+=2; // 相当于 a=a+2
cout << a;
return 0;
}
~~~
~~~
5
~~~
### 递增和递减Increase and decrease
书写简练的另一个例子是递增(increase)运算符 (++)和递减(decrease) 运算符(--)。它们使得变量中存储的值加1或减1。它们分别等同于+=1和-=1。因此:
`a++;
a+=1;
a=a+1;
`
在功能上全部等同,即全部使得变量a的值加1。
它的存在是因为最早的C编译器将以上三种表达式的编译成不同的机器代码,不同的机器代码运行速度不一样。现在,编译器已经基本自动实行代码优化,所以以上三种不同的表达方式编译成的机器代码在实际运行上已基本相同。
这个运算符的一个特点是它既可以被用作prefix前缀,也可以被用作后缀suffix,也就是说它既可以被写在变量标识的前面(++a),也可以被写在后面(a++)。虽然在简单表达式如a++或++a中,这两种写法代表同样的意思,但当递增increase或递减decrease的运算结果被直接用在其它的运算式中时,它们就代表非常不同的意思了:当递增运算符被用作前缀prefix (++a) 时,变量a的值线增加,然后再计算整个表达式的值,因此增加后的值被用在了表达式的计算中;当它被用作后缀suffix (a++)时,变量a的值在表达式计算后才增加,因此a在增加前所存储的值被用在了表达式的计算中。注意以下两个例子的不同:
| 例 1 | 例 2 |
|---|---|
| B=3;<br/>A=++B;<br/>// A 的值为 4, B 的值为 4 | B=3;<br/>A=B++;<br/>// A 的值为 3, B 的值为 4 |
在第一个例子中,**B**在它的值被赋给**A**之前增加1。而在第二个例子中**B**原来的值3被赋给 **A**然后**B**的值才加1变为4。
### 关系运算符Relational operators ( ==, !=, >, =, <= )
我们用关系运算符来比较两个表达式。如ANSI-C++ 标准中指出的,关系预算的结果是一个bool值,根据运算结果的不同,它的值只能是真**true**或**false**。
例如我们想通过比较两个表达式来看它们是否相等或一个值是否比另一个的值大。以下为C++的关系运算符:
|||
|---|---|
| == | 相等Equal |
| != | 不等Different |
| > | 大于Greater than |
| < | 小于Less than |
| >= | 大于等于Greater or equal than |
| <= | 小于等于Less or equal than |
下面你可以看到一些实际的例子:
|||
|---|---|
| (7 == 5) | 将返回false. |
| (5 > 4) | 将返回true. |
| (3 != 2) | 将返回true. |
| (6 >= 6) | 将返回true. |
| (5 < 5) | 将返回false. |
当然,除了使用数字常量,我们也可以使用任何有效表达式,包括变量。假设有a=2, b=3和c=6,
|||
|---|---|
| (a == 5) | 将返回false. |
| (a*b >= c) | 将返回true 因为它实际是(2*3 >= 6) |
| (b+4 > a*c) | 将返回false因为它实际是(3+4 > 2*6) |
| ((b=2) == a) | 将返回true. |
**注意:**运算符**=** (单个等号)不同于运算符**==** (双等号)。第一个是赋值运算符(将等号右边的表达式值赋给左边的变量);第二个(==)是一个判断等于的关系运算符,用来判断运算符两边的表达式是否相等。因此在上面例子中最后一个表达式((b=2) == a),我们首先将数值2赋给变量b,然后把它和变量a进行比较。因为变量a中存储的也是数值2,所以整个运算的结果为true。
![](https://box.kancloud.cn/2015-09-06_55ebed7dd527c.jpg)在ANSI-C++标准出现之前的许多编译器中,就像C语言中,关系运算并不返回值为真**true**或假**false**的**bool**值,而是返回一个整型数值最为结果,它的数值可以为**0**,代表"**false**"或一个非**0**数值(通常为**1**)来代表"**true**"。
### 逻辑运算符Logic operators ( !, &&, || )
运算符 ! 等同于**boolean** 运算**NOT** (取非),它只有一个操作数(operand),写在它的右边。它做的唯一工作就是取该操作数的反面值,也就是说如果操作数值为真**true**,那么运算后值变为假**false**,如果操作数值为假**false**,则运算结果为真**true**。它就好像是说取与操作数相反的值。例如:
|||
|---|---|
| !(5 == 5) | 返回false,因为它右边的表达式(5 == 5)为真true. |
| !(6 <= 4) | 返回true因为(6 <= 4)为假false. |
| !true | 返回假false. |
| !false | 返回真true. |
逻辑运算符&&和**||**是用来计算两个表达式而获得一个结果值。它们分别对应逻辑运算中的与运算**AND** 和或运算**OR**。它们的运算结果取决于两个操作数(operand)的关系:
|||
|---|---|---|---|
| 第一个操作数a | 第二个操作数b | 结果 a && b | 结果a \| \| b |
| true | true | true | true |
| true | false | false | true |
| false | true | false | true |
| false | false | false | false |
例如 :
~~~
( (5 == 5) && (3 > 6) ) 返回false ( true && false ).
( (5 == 5) || (3 > 6)) 返回true ( true || false ).
~~~
### 条件运算符Conditional operator ( ? )
条件运算符计算一个表达式的值并根据表达式的计算结果为真true或假false而返回不同值。它的格式是:
condition ? result1 : result2 (条件?返回值1:返回值2)
如果条件condition 为真true,整个表达式将返回esult1,否则将返回result2。
|||
|---|---|
| 7==5 ? 4 : 3 | 返回3,因为7不等于5. |
| 7==5+2 ? 4 : 3 | 返回4,因为7等于5+2. |
| 5>3 ? a : b | 返回a,因为5大于3. |
| a>b ? a : b | 返回较大值,a 或b. |
~~~
// 条件运算符例子
#include <iostream>
using namespace std;
int main ()
{
int a,b,c;
a=2;
b=7;
c = (a>b) ? a : b;
cout << c;
return 0;
}
~~~
~~~
7
~~~
上面的例子中a的值为2,b的值为7,所以表达式(a>b)运算值为假(false),所以整个表达式(a>b)?a:b要取分号后面的值,也就是b的值7。因此最后输出 c 的值为7。
### 逗号运算符 ( , )
逗号运算符 (,) 用来分开多个表达式,并只取最右边的表达式的值返回。
例如有以下代码:
~~~
a = (b=3, b+2);
~~~
这行代码首先将3赋值给变量b,然后将 b+2 赋值给变量 a。所以最后变量a 的值为5,而变量b的值为3。
### 位运算符Bitwise Operators ( &, |, ^, ~, > )
位运算符以比特位改写变量存储的数值,也就是改写变量值的二进制表示:
||||
|---|---|---|
| op | asm | Description |
| & | AND | 逻辑与 Logic AND |
| \| | OR | 逻辑或Logic OR |
| ^ | XOR | 逻辑异或Logical exclusive OR |
| ~ | NOT | 对1取补(位反转)Complement to one (bit inversion) |
| << | SHL | 左移Shift Left |
| >> | SHR | 右移Shift Right |
### 变量类型转换运算符Explicit type casting operators
变量类型转换运算符可以将一种类型的数据转换为另一种类型的数据。在写C++中有几种方法可以实现这种操作,最常用的一种,也是与C兼容的一种,是在原转换的表达式前面加用括号()括起的新数据类型:
~~~
int i;
float f = 3.14;
i = (int) f;
~~~
以上代码将浮点型数字**3.14**转换成一个整数值(**3**)。这里类型转换操作符为(**int**)。在C++中实现这一操作的另一种方法是使用构造函数constructor 的形式:在要转换的表达式前加变量类型并将表达式括在括号中:
~~~
i = int ( f );
~~~
以上两种类型转换的方法在C++中都是合法的。另外ANSI-C++针对面向对象编程(object oriented programming)增加了新的类型转换操作符 (参考[Section 5.4, Advanced class type-casting](http://www.prglab.com/cms/pages/c-tutorial/advanced-concepts/class-type-casting.php)).
### sizeof()
这个运算符接受一个输入参数,该参数可以是一个变量类型或一个变量自己,返回该变量类型(variable type) 或对象(object)所占的字节数:
~~~
a = sizeof (char);
~~~
这将会返回1给a,因为char是一个常为1个字节的变量类型。
sizeof返回的值是一个常数,因此它总是在程序执行前就被固定了。
### 其它运算符
在本教程后面的章节里我们将看到更多的运算符,比如指向指针的运算或面向对象编程特有的运算,等等,我们会在它们各自的章节里进行详细讨论。
### 运算符的优先度 Precedence of operators
当多个操作数组成复杂的表达式时,我们可能会疑惑哪个运算先被计算,哪个后被计算。例如以下表达式:
`a = 5 + 7 % 2`
我们可以怀疑它实际上表示:
a = 5 + (7 % 2) 结果为**6**,还是 a = (5 + 7) % 2 结果为**0**?
正确答案为第一个,结果为6。每一个运算符有一个固定的优先级,不仅对数学运算符(我们可能在学习数学的时候已经很了解它们的优先顺序了),所有在C++中出现的运算符都有优先级。从最从最高级到最低级,运算的优先级按下表排列:
<table >
<tbody><tr><th>优先级<br>Level</th><th>操作符<br>Operator</th><th>说明<br>Description</th><th>结合方向<br>Grouping</th></tr>
<tr><td>1</td><td><tt>::</tt></td><td>范围</td><td>从左到右</td></tr>
<tr><td>2</td><td><tt>() [] . -> ++ -- dynamic_cast static_cast reinterpret_cast const_cast typeid</tt></td><td>后缀</td><td>从左到右</td></tr>
<tr><td rowspan="3">3</td><td><tt>++ -- ~ ! sizeof new delete</tt></td><td>一元(前缀)</td><td rowspan="3">从右到左</td></tr>
<tr><td><tt>* &</tt></td><td>指针和取地址 <br></td></tr>
<tr><td><tt>+ -</tt></td><td>一元符号</td></tr>
<tr><td>4</td><td><tt>(type)</tt></td><td>类型转换 <br></td><td>从右到左</td></tr>
<tr><td>5</td><td><tt>.* ->*</tt></td><td>指向成员的指针<br></td><td>从左到右</td></tr>
<tr><td>6</td><td><tt>* / %</tt></td><td>乘、除、取模 <br></td><td>从左到右</td></tr>
<tr><td>7</td><td><tt>+ -</tt></td><td>加减</td><td>从左到右</td></tr>
<tr><td>8</td><td><tt><< >></tt></td><td>位移<br></td><td>从左到右</td></tr>
<tr><td>9</td><td><tt>< > <= >=</tt></td><td>关系操作符</td><td>从左到右</td></tr>
<tr><td>10</td><td><tt>== !=</tt></td><td>等于、不等于<br></td><td>从左到右</td></tr>
<tr><td>11</td><td><tt>&</tt></td><td>按位与运算<br></td><td>从左到右</td></tr>
<tr><td>12</td><td><tt>^</tt></td><td>按位异或运算</td><td>从左到右</td></tr>
<tr><td>13</td><td><tt>|</tt></td><td>按位或运算</td><td>从左到右</td></tr>
<tr><td>14</td><td><tt>&&</tt></td><td>逻辑与运算</td><td>从左到右</td></tr>
<tr><td>15</td><td><tt>||</tt></td><td>逻辑或运算</td><td>从左到右</td></tr>
<tr><td>16</td><td><tt>?:</tt></td><td>条件运算</td><td>从右到左</td></tr>
<tr><td>17</td><td><tt>= *= /= %= += -= >>= <<= &= ^= |=</tt></td><td>赋值运算</td><td>从右到左</td></tr>
<tr><td>18</td><td><tt>,</tt></td><td>逗号</td><td>从左到右</td></tr>
</tbody></table>
结合方向Grouping定义了当有同优先级的多个运算符在一起时,哪一个必须被首先运算,最右边的还是最左边的。
所有这些运算符的优先级顺序可以通过使用括号parenthesis signs (和)来控制,而且更易读懂,例如以下例子:
`a = 5 + 7 % 2;`
根据我们想要实现的计算的不同,可以写成:
`a = 5 + (7 % 2); 或者
a = (5 + 7) % 2;`
所以如果你想写一个复杂的表达式而不敢肯定各个运算的执行顺序,那么就加上括号。这样还可以使代码更易读懂。