C语言编程基础-05操作符位操作与运算符优先级

时间:2021-07-06 17:47:44
操作符
位操作符
&操作符
*操作符
运算符优先级

+--------+
| 操作符 |
+--------+
单目操作符+-*/%,=

用来编写表达式的符号统称为操作符
加减乘除是四则运算符(+,-,*和/)
操作符%表示取余操作符
操作符,在c语言程序中可以当操作符使用,操作符可以把两个表达式合并成一个,合并后表达式的计算结果是后一个表达式的计算结果,操作符的优先级非常低

赋值操作符=可以用来改变存储位置的内容
赋值操作符要求左边是一个左值(代表存储位置的东西);
赋值操作符要求右边是一个可以作为数字使用的东西
赋值操作符的优先级非常低,但是比逗号,操作符的优先级要高
优先级高表示如果一条语句中同时存在,先运算优先级高的;

一条语句可以出现多个赋值操作符,先计算右边的赋值操作符然后计算左边的赋值操作符;num = num1 = 3;
赋值语句的计算结果是左边的存储位置里的内容
赋值操作符是从右向左运算

双目操作符

需要两个数字配合才能使用的操作符叫做双目操作符
需要一个数字配合就可以使用的操作符叫单目操作符
需要三个数字配合才能使用的操作符叫三目操作符

绝大多数双目操作符可以和赋值操作符合并形成复合赋值操作符;
复合赋值操作符要求左边必须代表一个存储位置,它可以对这个存储位置中的内容作调整;
只用复合赋值操作符的语句执行速度更快;
复合赋值操作符的优先级和赋值操作符的一样低;

自增(++)和自减(--)是两个单目操作符,他们都需要和一个代表存储位置的东西配合使用;
他们可以对一个存储位置里的内容作加一或减一操作.
这两个操作符都支持前操作和后操作
后操作的优先级非常高,指的是在结合时候的优先级;但改变变量值的时机却要等到语句结束;特别需要注意,深刻理解;
    比如strcpy()函数中 while ((*p_dest++ = *p_src++) != '\0');
    ++的优先级高于*操作,所以先++;等价于((*(p_dest++) = *(p_src++)) != '\0');
    但是++的值改变的时机很晚,要等到一条语句结束时才会发生变化;在取*的时候p_dest和p_src并没有完成自增;等赋值结束并判断是否等于'\0'后,p_dest和p_src才会完成自增;
/* ++i先加后用,i++先用后加 */
int num = num1 = 10;
printf("num = %d, num1 = %d\n", num++, ++num1); /* 10, 11 */
printf("num = %d, num1 = %d\n", num, num1); /* 11, 11 */
后++要等到整个语句结束时才改变变量的值;
i = 5; k = i++; //此时k还是5,但该语句结束时i是6
i = 5; k = ++i; //此时k是6,i的值也是6
i++是一个右值;
++i是一个左值;
在不影响逻辑的前提下,建议优先使用++i;

i++和++i哪个效率更高
在内建数据类型的情况下,效率没有区别;
在自定义数据类型的情况下,++i效率更高;

在C++中++i返回对象的引用;i++总是要创建一个临时对象,在退出函数时还要销毁它,而且返回临时对象的值时还会调用其他拷贝构造函数;

多次在一条语句中对同一个变量做自增或自减计算结果不确定
我们写程序是为了解决问题,不是说所有符合语法的我们都去试,能帮我门解决问题的才是好的方法和思路;
不要总去试一些我们根本就不会用到的问题;

/*
 * 操作符演示
 */
#include <stdio.h>
int num4 = 10;
int main() {
    printf("(4 + 7, 8 - 6) is %d\n", (4 + 7, 8 - 6));
    /* ,合并后表达式的计算结果是后一个表达式的计算结果 */
    /*
     * 逗号操作符的最大作用就是
     * 可以在一条语句中完成多件事
     */

    int num = 0, num1 = 0;
    /* 操作符之间是有优先级的 */
    /* num = 11, 2;
     * 先赋值,后逗号操作 */
    num = 4 + 7, 8 - 6;
    /* 逗号的优先级非常低 */
    /* 用一个小括号提升逗号的优先级 */
    num1 = (4 + 7, 8 - 6);
    printf("num = 4 + 7, 8 - 6 res num = %d\n", num);    /* 11 */
    printf("num1 = (4 + 7, 8 - 6) res num = %d\n", num1);    /* 2 */

    int num2, num3 = 0;
    num2 = num3 = 3;
    printf("num2=num3=3赋值后num2 is %d, num3 is %d\n", num2, num3);
    num += 6;        /* 等价于 num = num + 6; */
    printf("num += 6后num is %d\n", num);
    num *= 2 + 3;        /* 等价于 num = num * (2 + 3) */
    printf("num *= 2 + 3后num is%d\n", num);

    num = num1 = 10;
    num++;
    num1--;
    printf("num++; num1--; num is %d, num1 is %d\n", num, num1);
    /* 11, 9 */

    num = num1 = 10;
    num = ++num1;        /* num1先自增1再赋值 */
    /* num == 11, num1 == 11 */
    printf("num = %d, num1 = %d\n", num, num1);

    num = num1 = 10;
    num = num1++;        /* num1先使用,本语句结束时再自增1 */
    /* num == 10, num1 == 11 */
    printf("num = %d, num1 = %d\n", num, num1);

    num = 10;
    num1 = num++ + ++num;
    /* ++num使num发生了变化,所有的地方都发生了变化 */
    /* num1 == 22, num = 11 + 1 */
    printf("num1 is %d, num is %d\n", num1, num);

    /* num4是全局变量num4=10 */
    num1 = num4++ + ++num4;    /* 结果不确定,因编译器而异 */
    /* ++num4发生了变化,但其他地方的num4并没有变化 */
    /* 在加的时候前面的num4没有变化10 + 11 */
    /* num1 == 21, num4 == 11 */
    printf("num1 is %d, num4 is %d\n", num1, num4);

    return 0;
}


布尔值分为真(true)和假(false)
在C语言中真用整数1表示,假用整数0表示(shell脚本中真为0(正常),假为非零(异常))
逻辑表达式的结果只能是真或假
逻辑表达式使用逻辑操作符编写

双目逻辑操作符包括
==    等于
!=    不等于
>    大于
<    小于
>=    大于等于
<=    小于等于

!是一个单目逻辑操作符,表示求反

在C语言中所有整数都可以当布尔值使用,0当布尔值使用时表示假,其他值当布尔值使用时表示真.
逻辑操作符的优先级比算术操作符符优先级低
逻辑操作符与(&&)和或(||)可以用来合并两个逻辑表达式
如果两个逻辑表达式的计算结果都是真,则用&&合并后的逻辑表达式计算结果也是真
如果两个逻辑表达式中有一个的计算结果是真,则用||合并后的逻辑表达式计算结果也是真

逻辑与(&&)/逻辑或(||)这两个逻辑操作符具有短路特性

/*
 * 短路特性演示
 */
#include <stdio.h>
int main() {
    int num = 0;
    0 && ++num;        //++num不会被执行
    printf("num is %d\n", num);
    1 || ++num;        //++num不会被执行
    printf("num is %d\n", num);
    return 0;
}

如果前一个表达式的结果能决定整个表达式的结果则后一个表达式根本就不会计算或调用,这就是短路特性.
因此最好不要将修改变量内容的语句放在逻辑表达式中,因其具有不确定性;

/*
 * 逻辑表达式练习
 * 我们的午休时间是12:00 - 14:00,判断之
 */
#include <stdio.h>
int main() {
    int  hour, min, a;
    printf("input hour and min:");
    scanf("%d%d", &hour, &min);
    a = (hour >= 12 && hour <14 ) || (hour == 14 && !min);
    printf("是午休时间吗: %d\n", a);
    return 0;
}

+--------+
| 位操作 |
+--------+

位操作符对字节中的二进制位进行操作
双目位操作符把两个数字在字节中的二进制样式对应位进行操作

按位与(&)把对应数位上的数字进行与计算
0 & 0     0
0 & 1     0
1 & 0     0
1 & 1     1

3    0000 0011
5    0000 0101
3&5    0000 0001
按位与可以把一个字节中的某些二进制位清零
任何二进制位和1做按位与结果保持不变
任何二进制位和0做按位与结果一定是0

按位或(|)把对应数位上的数字进行或计算
0 | 0    0
0 | 1    1
1 | 0    1
1 | 1    1

3    0000 0011
5    0000 0101
3|5    0000 0111
按位或可以把一个字节中某些数位设置成1
任何数位和0做按位或保持不变
任何数位和1做按位或结果一定是1

按位异或(^)可以把对应数位上数字进行亦或计算
0 ^ 0    0
1 ^ 1    0
1 ^ 0    1
0 ^ 1    1

3    0000 0011
5    0000 0101
3^5    0000 0110
按位异或可以把字节中某些二进制位求反
任何二进制位和0做按位异或保持不变
任何二进制位和1做按位异或结果一定取反

~(按位求反)是单目位操作符,它可以对一个字节中所有二进制位求反
%hhx表示以十六进制打印一个数字

移位操作可以把一个字节中所有的二进制位统一向左或向右移动指定的位数
左移操作使用<<符号表示
右移操作使用>>符号表示
移位操作符也是双目操作符要求左右两边都是可以当数字使用的东西.左边的数字是要进行移位的数字,右边的数字是要移动的位数.
在移位操作过程中有些数位被丢弃,有些空位置被填充新数字
左移操作中右边空出来的数位一定填充0
右移操作中有符号数字左边空出来的数位一定填充符号位
          无符号数字一定填充0
如果移位操作过程中没有丢失有效数据,则右移n位相当于除以2的n次方,左移n位相当于乘以2的n次方

/*
 *位操作符演示
 */
#include <stdio.h>
int main() {
    char ch = 0, uch = 0;
    printf("3 & 5 is %d\n", 3 & 5);
    printf("5 & 5 is %d\n", 5 & 5);
    printf("3 | 5 is %d\n", 3 | 5);
    printf("3 ^ 5 is %d\n", 3 ^ 5);
    printf("~0x83 is 0x%hhX\n", ~0x83);
    ch = 0x86;
    printf("0x86 ch >> 2 is 0x%hhx\n", ch >> 2);
    uch = 0x86;
    printf("0x86 ch >> 2 is 0x%hhx\n", uch >> 2);
    printf("1 << 31 is %d\n", 1 << 31);
    printf("1 << 31 is 0x%x\n", 1 << 31);
    printf("~(1 << 31) is 0x%x\n", ~(1 << 31));
    return 0;
}

3 & 5 is 1
5 & 5 is 5
3 | 5 is 7
3 ^ 5 is 6
~0x83 is 0x7C
0x86 ch >> 2 is 0xe1
0x86 ch >> 2 is 0xe1
1 << 31 is -2147483648
1 << 31 is 0x80000000
~(1 << 31) is 0x7fffffff


&操作符可以根据一个变量获得对应存储位置的地址
%p是和地址数据配对的占位符
*操作符可以根据地址数据表示对应存储位置

/*
 * 地址相关操作符演示
 */
#include <stdio.h>
int main() {
    int num = 0;
    printf("&num is %p\n", &num); //%p是用来打印地址的占位符
    //printf("&num is 0x%lx\n", &num); //结果于%p相同,但会警告
    *(&num) = 7;
    printf("num is %d\n", num);
    return 0;
}


运算符优先级
    1.初等运算符:() [] -> .
    2.单目运算符:! ~(位取反) 自增自减 * & (类型) sizeof
    3.算术运算符:先乘除取余,后加减,再移位
    4.关系运算符:先大小,再判等(== !=)
    5.位操作符: & ^ |
    6.逻辑运算符:&& ||
    7.三目运算符: ?:
    8.赋值运算逗号运算

#include <stdio.h>
#include <malloc.h>
typedef struct _demo {
    int* pInt;
    float f;
} Demo;
int func(int v, int m) {
    /* return (v & m != 0); //v & (m != 0) */
    return ((v & m) != 0);
}
int main() {   
    Demo* pD = (Demo*)malloc(sizeof(Demo));
    int *p[5]; //int* p[5]
    int *f(); //int* f();
    int i = 0;
    /* i = 1, 2; //i == 1 */
    i = (1, 2);
    /* *pD.f = 0; // *(pD.f) */
    (*pD).f = 0;
    free(pD);
    return 0;
}

易错的优先级
*p.num;
.的优先级高于*
实际上是对p取偏移,作为指针,然后进行取成员操作 <=> *(p.num)
我们常用的是(*p).num;等价于p->num;->操作符可以消除这个问题

int *ap[];
[]的优先级高于*
实际上ap是个元素为int*指针的数组 <=> int *(ap[])
我们有时候会用到int (*ap)[],是一个数组指针;ap指向一个整型数组;

int *fp();
函数()高于*
fp是个函数,返回int* <=> int* (fp())
我们有时会用到int (*fp)();表示一个函数指针,用来指向函数;

(val & mask != 0)
==和!=高于位操作
<=> val & (mask != 0)
我们常会用到(val & mask) != 0;

c = getchar() != EOF;
==和!=高于赋值操作,特别要注意,这个地方的错误最难发现;
<=> (getchar() != EOF)
我们常在程序中用到((c = getchar()) != EOF)类型的;要特别注意优先级问题;

msb << 4 + lsb
算术运算符高于位移运算符
<=> msb << (4 + lsb)
我们常用的形式是(msb << 4) + lsb;

i = 1, 2;
逗号运算符在所有运算符中优先级最低
<=> (i = 1), 2;
我们可能常用i = (1, 2);1,2表示两个表达式;
 
注意()[]的优先级最高,然后是.和->取成员运算符,然后是后++后--;再是其他;
注意后++后--结合的优先级很高,但是要等整个语句运行结束时才会使变量的值生效;


作业
    1.编写逻辑表达式判断用户给定的年份是否是润年
    年份可以被4整除但不能被100整除则年份是润年
    年份可以被400整除则年份是润年

/*
 * 编写逻辑表达式判断用户给定的年份是否是润年
 * 年份可以被4整除但不能被100整除的年份是润年
 * 年份可以被400整除的年份是润年
 */
#include <stdio.h>
int main() {
    int num = 0;
    printf("请输入一个年份:");
    scanf("%d", &num);
    printf("输入的年份是否闰年: %d\n", !(num % 4) && (num % 100)
           || !(num % 400));
    return 0;
}
    2.使用一个char类型的变量ch的最低三个二进制位控制红绿灯,bit0控制绿灯,bit1控制黄灯,bit2控制红灯,1对应灯点亮,否则灯熄灭.
    1)编写语句在不知道变量内容的情况下点亮红灯,熄灭另外两盏灯
    2)已知绿灯亮,另外两盏灯灭;编写语句熄灭绿灯,点亮黄灯.

/*
 * 使用一个char类型的变量ch的最低三个二进制位控制红绿灯;
 * bit0控制绿灯,bit1控制黄灯,bit2控制红灯,1对应灯亮,否则熄灭;
 * 1.编写语句在不知变量内容的情况下点亮红灯,熄灭另外两盏灯
 * 2.已知绿灯亮,两外两盏灯灭,编写语句熄灭绿灯,点亮黄灯
 */
#include <stdio.h>
int main() {
    char ch = 0, bit0, bit1, bit2, bit3;
    ch = (ch | 4) & 4;
    printf("ch is %d\n", ch);
    bit0 = ch % 2;
    ch /= 2;
    bit1 = ch % 2;
    ch /= 2;
    bit2 = ch % 2;
    bit3 = ch / 4;
    printf("lamp: %d%d%d%d\n", bit3, bit2, bit1, bit0);
    return 0;
}