1. 操作符的分类
- 算术操作符: + , - , * , / , %
- 移位操作符: << >>
- 位操作符: & | ^
- 赋值操作符: = , += , *= ,/= , <<= , >>= , &= , |= , ^=
- 单目操作符: ! , ++ , --, & , * , + ,- , ~ , sizeof ,(类型)
- 关系操作符: > ,>= , < ,<= ,== , !=
- 逻辑操作符:&& ,||
- 条件操作符: ? :
- 逗号表达式: ,
- 下标引用: [ ]
- 函数调用: ()
- 结构成员访问:. 、->
2. 二进制和进制转换
在日常生活和学习中我们早已听过数制的概念:
数制也称为计数制,是一种计数的方法,是用一组固定的符号和统一的规则来表示数值的方法。在计数过程中采用进位的方法称为进位计数制(进制),包括数位、基数和位权三个要素。
数位:
数位,指一个数中每一个数字所占的位置。整数部分的数位从右起,每4个数位是一级,个级包括个位、十位、百位和千位,表示多少个一;万级包括万位、十万位、百万位和千万位,表示多少个万;亿级包括亿位、十亿位、百亿位和千亿位,表示多少个亿……小数部分的数位从左往右依次为十分位、百分位、千分位……表示多少个十分之一、百分之一、千分之一……
基数:
数制所使用数码的个数。例如,二进制的基数为2,十进制的基数为10。
位权:
数制中某一位上的1所表示数值的大小(所处位置的价值)。例如,十进制的123,1的位权是100,2的位权是10,3的位权是1。二进制中的 1011 (一般从左向右开始),第一个1的位权是8,0的位权是4,第二个1的位权是2,第三个1的位权是1。
在日常生活中,我们经常要和数字打交道,我们常见的数字都是十进制,例如:5,但在计算机语言中,二进制数字则是最常见的。
在十进制中数字都是满十进一,列如当数字九再加上1时就不在个位上加减,而是进一在十位上加一;二进制则是满2进一,如数字2在二进制中也写做2,但当加一时就要进一位,写作10。
诸如此类还有8进制,16进制,原理都是一样的。
2.1 2进制转10进制
2.1.1 二进制转十进制数字
2.2 二进制转8进制和16进制
2.2.1 二进制转8进制
8进制的数字都是从0~7的,写成二进制,相当于二进制三位,所以二进制转8进制时,从右到左三位转化为8进制1位,位数不够则直接换算。
如:二进制的01101011,换成8进制:0153。0开头的数字,会被当成8进制。
2.2.2 二进制转换16进制
16进制的每一位都是0~7,a~f的数字,各自写成2进制,最多相当于4个二进制位。从二进制序列中右边到左边每4位组成一个16进制位,剩余不够4位直接进行换算。
如:二进制位的01101011,换成16进制:0x6b。16进制表示的时候加上0x。
3. 原码,反码,补码
整数的二进制表示方法有三种,即原码,反码,补码。
有符号整数的三种表示方法均有符号位和数值位两部分,2进制序列中,最高位的1位是被当做符号位,剩余的都是数值位。
符号位都是用0表示“正”,1表示“负”。
正整数的原码,反码,补码都相同。
负整数的三种表示方法各不相同。
原码:直接将数制按照正负数的形式翻译成二进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
补码得到原码也是可以使用:取反,+1的操作。
对于整形来说:数据存放内存中其实存放的是补码。
原因:
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码和原码也可以相互转换,其运算过程是相同的,不需要额外的硬件电脑。
4. 移位操作符
<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是整数。
4.1 左移操作符
移位原则:左边抛弃,右边补零
1. #include<stdio.h>
2. int main()
3. {
4. int num = 10;
5. int n = num << 1;
6. printf("n=%d\n", n);
7. printf("num=%d\n", num);
8. return 0;
9. }
4.2 右移操作符
移位原则:右移原则运算分两种。
1. 逻辑右移:左边用0填充,右边丢弃;
2. 算术右移:左边用原该值的符号位填充,右边丢弃。
1. #include<stdio.h>
2. int main()
3. {
4. int num = 10;
5. int n = num >> 1;
6. printf("n = %d\n",n);
7. printf("num=%d\n", num);
8. return 0;
9. }
注:对于移位运算符,不移动负数位,这个是标准未定义的。
例:
1. int num = 10;
2. num >> -1;//error
5. 位操作符
位操作符有:
1. & //按位与
2. | //按位或
3. ^ //按位异或
4. ~ //按位取反
注:操作数必须为整数。
5.1 按位与(&)
与(&)运算符:两个操作数对应二进制位同样为1结果位才为1,否则为0;
例:
如 3&4
3的原码是:00000011
4的原码是:00000100
所以 3&4为:00000000
故 3&4=0
注:负数的运算用补码。
用途:
1. 判断奇偶
只要根据最末位是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数。
2. 取一个数的指定位
比如取数 X=1010 1110 的底4位,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位与运算(X&Y=0000 1110)即可得到X的指定位。
3. 清零
如果想将一个单元清零,即使其全部二进制位为0,只要与一个各位都为零的数值相与,结果为零。
5.2 按位或(|)
或(|)运算符:两个操作数对应二进制位同样为0结果位才为0,否则为1;
如 3|4
3的原码是:00000011
4的原码是:00000100
所以3|4为:00000111
故3|4 = 7
注:负数的运算用补码。
用途:
1. 常用来对一个数据的某些位设置为1
比如将数 X=1010 1110 的低4位设置为1,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位或运算(X|Y=1010 1111)即可得到。
5.3 按位异或(^)
异或(^)运算符:两个操作数对应二进制位相同则结果位为0,不同则为1;
如 3^4
3的原码是:00000011
4的原码是:00000100
所以3^4 = 00000111 = 7
注:负数的运算用补码。
性质:
- 1、交换律
- 2、结合律 (a^b)^c == a^(b^c)
- 3、对于任何数x,都有 x^x=0,x^0=x
- 4、自反性: a^b^b=a^0=a;
用途:
1. 翻转指定位
比如将数 X=1010 1110 的低4位进行翻转,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行异或运算(X^Y=1010 0001)即可得到。
2. 与零相异或值不变
例如:1010 1110 ^ 0000 0000 = 1010 1110
3. 交换两个数
1. void Swap(int &a, int &b){
2. if (a != b){
3. a ^= b;
4. b ^= a;
5. a ^= b;
6. }
7. }
5.4 按位取反运算符(~)
非(~,按位取反)运算符:一个二进制操作数,对应位为0,结果位为1;对应位为1,结果位为0;
用途:
1. 使一个数的最低位为零
使a的最低位为0,可以表示为:a & ~1。~1的值为 1111 1111 1111 1110,再按"与"运算,最低位一定为0。因为" ~"运算符的优先级比算术运算符、关系运算符、逻辑运算符和其他运算符都高。
不同长度的数据进行位运算:如果两个不同长度的数据进行位运算时,系统会将二者按右端对齐,然后进行位运算。
以"与运算"为例说明如下:我们知道在C语言中long型占4个字节,int型占2个字节,如果一个long型数据与一个int型数据进行"与运算",右端对齐后,左边不足的位依下面三种情况补足,
- 1)如果整型数据为正数,左边补16个0。
- 2)如果整型数据为负数,左边补16个1。
- 3)如果整形数据为无符号数,左边也补16个0。
- 如:long a=123;int b=1;计算a& b。
如:long a=123;int b=-1;计算a& b。
如:long a=123;unsigned intb=1;计算a & b。
6. 逗号表达式
逗号表达式,就是逗号隔开多个表达式。
逗号表达式,从左到右依次执行,整个表达式的结果是最后一个表达式的结果。
1. #include<stdio.h>
2. int main()
3. {
4. int a = 10;
5. int b = 20;
6. int c = (a > b, a = b + 10, a, b = a + 1);
7. printf("%d", c);
8. return 0;
9. }
由以上逗号表达式代码可推:a>b不成立,但并未进行赋值,往后看,a=b+10,此时a=30,然后是a,并没任何意义,接着是b=a+1,现在b为31.逗号表达式结束,整个表达式的结果是最后一个表达式的结果,故c=31.
代码一:
1. #include<stdio.h>
2. int main()
3. {
4. int a = 10 = get_val();
5. count_val(a);
6. while (a > 0)
7. {
8. int a = get_val();
9. count_val(a);
10. }
11.
12. return 0;
13. }
代码二:
1. #include<stdio.h>
2. int main()
3. {
4. int a = 10;
5. while (a = get_val(),count_val(a), a > 0)
6. {
7. int a = get_val();
8. count_val(a);
9. }
10.
11. return 0;
12. }
由以上两个代码可知:代码二应用了逗号表达式,相较代码一代码更加整洁,同时也可知:while表达式括号中可以有多个表达式。
7. 下标访问[],函数调用()
7.1 下标引用操作符
操作数:一个数组名 + 一个索引值(下标)
1. int arr[10];//创建数组
2. arr[9] = 10;//使用下标引用操作符
3. [ ] 的两个操作数是arr和9.
7.2 函数调用操作符
接受一个或多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
1. #include<stdio.h>
2. void test1()
3. {
4. printf("hehe\n");
5. }
6. void test2(const char *str)
7. {
8. printf("%s\n", str);
9. }
10. int main()
11. {
12. test1(); //这里的()就是作为函数调用操作符
13. test2("hello bit.");//这里的()就是函数调用操作符
14. return 0;
15. }
8. 结构成员访问操作符
8.1 结构体成员的直接访问
结构体成员直接访问是通过(.)访问的。点操作符接受两个操作数。如下所示:
1. #include<stdio.h>
2. struct Point
3. {
4. int x;
5. int y;
6. }p={1,2};
7. int main()
8. {
9. printf("x:%d y:%d\n", p.x, p.y);
10. return 0;
11. }
使用方式:结构体变量.成员名
8.2 结果体成员的间接访问
有时我们得到的不是一个结构体变量,而是得到一个指向结构体的指针。如下所示:
1. #include<stdio.h>
2. struct Point
3. {
4. int x;
5. int y;
6. };
7. int main()
8. {
9. struct Point p = { 3,4 };
10. struct Point* ptr = &p;
11. ptr->x = 10;
12. ptr->y = 20;
13. printf("x=%d y=%d\n", ptr->x, ptr->y);
14. return 0;
15. }
使用方式:结构体指针->成员名
9. 操作符的属性:优先级,结合性
C语言的操作符有两个重要的属性:优先级,结合性,这两个属性决定了表达式求值的计算顺序。
优先级
优先级指的是,如果一个表达式包含多个运算符,哪个运算符应该优先执行。各种运算符的优先级是不一样的。
结合性
如果两个运算符优先级相同,优先级没办法确定先算哪个了,这时候就看结合性了,则根据运算符是左结合还是右结合,决定执行顺序。大部分运算符是左结合,少数运算符是右结合。
部分操作符由高到低排序:
- 圆括号()
- 自增运算符(++),自减运算符(- -)
- 单目操作符(+和-)
- 乘法(*),除法(/)
- 加法(+),减法(-)
- 关系运算符(<,>等)
- 赋值运算符(=)
由于圆括号的优先级最高,可以使用它改变其他运算符的优先级。