[C/C++基础--笔试突击] 4.运算符及优先级

概述:

  表达式,由操作数和运算符组成。

  笔试中通常的考点有操作符的优先级、异或等关系运算

 

4.1 赋值语句

赋值运算符"=",操作符左边代表着存储单元的地址,称为左值,右边带表着需要的值,称为右值。

注:赋值操作符的左操作数必须是非const的左值

int const& max(int const& a, int const& b) {
    return a > b ? a : b;
}
int& fun(int& a) {
    a += 5;
    return a;
}
int* fun2(int* a) {  
    return a;
}

int main() {
    int ii = 10, j = 20;
    fun(ii) = 800; // 语句1 正确 执行后 ii = 800
    printf("%d", ii);
    max(ii, j) = 200; // 语句2 错误 表达式 max(ii, j)不是可修改的左值
    printf("%d", ii);
    fun2(&ii) = 200; // 语句3 错误 无法从int转化为int*
    printf("%d", ii);
    *fun2(&ii) = 200; // 语句4 正确
    printf("%d", ii);
    system("pause");
}

其次,赋值操作符具有右结合特性。当表达是含有多个赋值操作符时,从右向左结合。

 

4.2 自增与自减运算符

4.2.1 简单运算

前缀运算时"先变后用",而后缀运算时"先用后变"。

以++为例:

前缀:++a 表示取a的地址,增加它的内容,然后把值放在寄存器中;

后缀:a++ 表示取a的地址,把它的值装入寄存器,然后增加内存a的值。

看下面的代码:

void main(){
    int a, b, c, d;
    a = 5;
    b = 5;
    c = (a++) + (a++) + (a++);
    d = (++b) + (++b) + (++b);
    printf("%d, %d, %d, %d", a, b, c, d);
}

上例中,在VS2010中输出"8, 8, 15, 24",在VC++6.0、Dev-C++以及gcc版本下输出"8, 8, 15, 22"。

在计算c的时候,括号的值都是5,执行c = 15后,a开始自增3次。

在计算d的时候,会受到编译器的影响,在VC++6.0下,由于汇编级只能实现两个数相加,不能实现三个数相加,所以语句"d = (++b) + (++b) + (++b);"相当于"d = (++b) + (++b); d = d + (++b);"拆成了两个语句的组合,这样第一个语句后b = 7,这样d = 7 + 7 = 14,再执行第二个语句,b = 8,d = d + b = 14 + 8 = 22;而在VS2010中进行了一些调整,在计算的时候,三次自增操作都已经执行完毕,故最后d = b + b + b = 8 + 8 + 8 = 24。

注:在实际编程中,应该避免使用这种可能受到不同编译器影响的代码。

4.2.2 作用的对象

自增、自减运算符只能作用于变量,而不能作用于常量表达式。只要是标准类型的变量,不管是整型、实型还是字符型、枚举型都可以作为这两个运算符的运算对象。

1)i+++j++;  //合法
2) ++i+(++j); //合法
3) ++a+b++; //合法
4) ++array[--i]; //合法
5) ++6; // 不合法 6是常量
6) (i + j)++; // 不合法 
7) 'A'++; // 不合法
8) (&p)++; // 不合法

:"++i+++j;"是非法的,与上面的1)、2)进行比较,由于C/C++编译器会从左到右尽可能多将字符组合成一个运算符或标识符,因此i+++j++等效于(i++)+(j++),这个是合法的,而"++i+++j"等效于"++(i++)+j",第一个++作用的是括号中的表达式,因此是非法的。

4.2.3 运算符的结合方向

自增、自减运算符及负号运算符的结合方向是从右向左。

如表达式"k = -i++" 等效于 "k = - (i++)",若 i = 5 则表达式运算后,k = -5,i = 6;

 

4.3 关系与逻辑运算符

关系操作符(<、<=、>、>=)具有左结合性,如下面:

if( i < j < k) ..  

由于i < j 返回的是bool类型,0或者1,因此,只要k大于1,则上述表达式的值为true,而这与我们想要表达的意思不相符,如果想要表示这种递推的逻辑,应用下面的方式:

if(i < j && j < k) ..

这里就引出了逻辑操作符:

expr1 && expr2  // 逻辑与
expr1 ||  expr2  // 逻辑或

都是短路求值,即当expr1不满足条件的时候,才会去执行expr2的表达式。

for(int i = 0 , j = 0 ; !x && (++y) <5 ; x++) {
    ...;
}

上面的结果执行完y = 1。

 

4.4 位运算符

&

按位与

仅当两位都为1,结果为1

|

按位或

仅当两位都为0,结果为0

^

按位异或

仅当两位不相同,结果为1

~

取反

每位取反,单目运算

<< 

左移

将左操作数向左或向右移动有操作数个数的位数,产生新的值,并丢弃移除的位,有时需要补位(补位规则后面会有介绍)

>> 

右移

这里主要讲一下遇到的用法,主要是异或,在做小算法题的时候,一些实用的技巧,下面写一下应用的情况(欢迎补充 *.*)。

1)给定一个整数n,判断它是否为2的正整数次幂

if( n > 1 && ((n & (n-1)) == 0))   // 判断n的二进制位是否仅有一位为1
    cout<<"true";

2)假设一个文件,每行记录了一个数,且每个数都出现了两次,但某一个数不小心删除了,怎么快速找出?

解答:运用异或的知识,A^B^C^D^E^B = A^C^D^E (异或满足交换律,且相同的异或为0),即依次读入文件中的数进行异或,最后得到的数就是所求。

3)找到一个未排序缺失的数(如n-1个1 ~ n的不同整数,缺少一个补齐n个)。

解答:1.求这n-1个数的和sum,然后计算 n*(n + 1)/2 - sum , 比较通俗的做法,但是n很大时会溢出。

     2.用异或,首先求得从1到n共n个数的异或结果A,然后用题中的序列一次与A异或,最后得到的数就是所求。

4)不使用第三方变量,将或两个变量的值。

a = a^b;
b = a^b;
a = a^b;

5) 不使用算术运算符实现两个数的加法。

解答:对于二进制的加法,若不考虑进位,则1+1 = 0,1+0 = 1,0+1 = 1,0+0 = 0,与异或正好类似,即如果排除进位,可以用异或来实现。

这时,再考虑进位,0+0的进位为,1+0的进位为0,只有1+1的进位为1,通过对比发现与位运算的&操作类似。因此可以总结如下:

先不考虑进位,按位异或,得值a;

然后计算进位,并将进位的值左移,得值b,若b为0,则a就是结果;若b不为0,则结果为a+b(递归调用)。下面是代码:

ind add(int a, int b) {
    if(b == 0)
        return a; // 没有进位
    int sum = a^b;
    int carry = (a & b) << 1; // 进位
    
    return add(sum, carry);
}

6)如何实现位操作求两个数的平均值?

(x & y) + ((x^y) >> 1);

如数x:01010,y:010000。x&y 为010000,即取x、y中对应位都为1的位,对于结果来说,相当于取得了都为1的位相加的一半

x^y结果为00110,即取x、y中对应为只有一个为1的位,对于结果来说是二者只有一个位为1的和,最终所求为均值,应右移一位即除以2;

最后,将二者相加即可(同时为1的部分 + 分别为1的部分)。

移位运算符

移位时的补位规则:

类型

左移

右移

int

低位补0

高位补符号位

unsigned int

低位补0

高位补0

由于左移总是在低位补0,高位丢失,因而负数左移后,有可能会变成整数。如:

int x = 0x8FFF0000;
cout<<(x<<1); // 输出536739840

 

4.5 类型转换

较小的类型将会被转换成较大的类型。

需要注意的是:在C++中,有符号数与无符号数转换时,内存中的内容并没有改变,只是对内存中相同的数据解释不同而已。

 

4.6 运算符优先级

表达式的运算顺序主要由一下两种因素决定”

1)运算符的优先级:程序总是先执行优先级较高的运算符;

2)运算符的结合性:当运算符的优先级相同时,运算符的结合性决定运行顺序。

优先级表很长....很长.... 这里从网上找来一个全的...自己打实在是太累了  >.<

优先级

运算符

名称或含义

使用形式

结合方向

说明

1

[]

数组下标

数组名[常量表达式]

左到右

--

()

圆括号

(表达式)/函数名(形参表)

--

.

成员选择(对象)

对象.成员名

--

->

成员选择(指针)

对象指针->成员名

--

 

2

-

负号运算符

-表达式

右到左

单目运算符

~

按位取反运算符

~表达式

++

自增运算符

++变量名/变量名++

--

自减运算符

--变量名/变量名--

*

取值运算符

*指针变量

&

取地址运算符

&变量名

!

逻辑非运算符

!表达式

(类型)

强制类型转换

(数据类型)表达式

--

sizeof

长度运算符

sizeof(表达式)

--

 

3

/

表达式/表达式

左到右

双目运算符

*

表达式*表达式

%

余数(取模)

整型表达式%整型表达式

4

+

表达式+表达式

左到右

双目运算符

-

表达式-表达式

5

<< 

左移

变量<<表达式

左到右

双目运算符

>> 

右移

变量>>表达式

 

6

大于

表达式>表达式

左到右

双目运算符

>=

大于等于

表达式>=表达式

小于

表达式<表达式

<=

小于等于

表达式<=表达式

7

==

等于

表达式==表达式

左到右

双目运算符

!=

不等于

表达式!= 表达式

 

8

&

按位与

表达式&表达式

左到右

双目运算符

9

^

按位异或

表达式^表达式

左到右

双目运算符

10

|

按位或

表达式|表达式

左到右

双目运算符

11

&&

逻辑与

表达式&&表达式

左到右

双目运算符

12

||

逻辑或

表达式||表达式

左到右

双目运算符

 

13

?:

条件运算符

表达式1?

表达式2: 表达式3

右到左

三目运算符

 

14

=

赋值运算符

变量=表达式

右到左

--

/=

除后赋值

变量/=表达式

--

*=

乘后赋值

变量*=表达式

--

%=

取模后赋值

变量%=表达式

--

+=

加后赋值

变量+=表达式

--

-=

减后赋值

变量-=表达式

--

<<=

左移后赋值

变量<<=表达式

--

>>=

右移后赋值

变量>>=表达式

--

&=

按位与后赋值

变量&=表达式

--

^=

按位异或后赋值

变量^=表达式

--

|=

按位或后赋值

变量|=表达式

--

 

15

逗号运算符

表达式,表达式,…

左到右

--

总结如下:

1)括号、下标、->和.(成员)最高;

2)单目的比双目的高;算术双目的比其他双目的高;

3)移位运算高于关系运算;关系运算高于按位运算;按位运算高于逻辑运算;

4)三目的只有一个条件运算,低于逻辑运算;

5)赋值运算仅比','高,且所有的赋值运算符优先级相同,结合访问从右向左。

 

 

到此,这一部分也告一段落了,主要就是多用多积累,只靠死记是不科学的。。文中如有错误请联系我..

希望大家都有所收获 *.*

 

返回目录 -> C/C++基础概述

 

posted @ 2015-08-14 11:17  可爱的波儿胖  阅读(748)  评论(0编辑  收藏  举报

友情链接 : CodeForge源码分享