zoukankan      html  css  js  c++  java
  • [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++基础概述

  • 相关阅读:
    Mybatis与Hibernate概述
    Linux命令中:rsync和scp之间的区别
    更改了ssh文件下,还没有权限
    karaf 控制台 常用linux指令(2)
    karaf 控制台 常用linux指令(1)
    POM文件详解(2)
    POM文件详解(1)
    maven配置parent pom查找策略
    排序算法性能比较
    Eclipse下用NDK编译生成so文件
  • 原文地址:https://www.cnblogs.com/TinyBobo/p/4728366.html
Copyright © 2011-2022 走看看