目录
前言
由于之前C语言写的博客很零散,写的也不够好,这里我打算重新整理关于C语言的博客,也方便自己复习C语言,C语言专栏看到旧版本的博客建议移步到新版本相关的博客上
----------------我是分割线---------------
一、操作符分类
操作符可以分为一下几类:
- 算术操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构成员
二、算术操作符
算术操作符有:
+ - * / %
- 加减就不解释了
- 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数
- 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法
- % 操作符的两个操作数必须为整数。返回的是整除之后的余数
- 这几个算术操作符都是由两个操作数的,比如 1 + 2 ,左操作数是 1,右操作数是 2
测试代码:
intmain() { //% 取模操作符的两端必须是整数inta=7%2;//取余,结果是 1intb=7/2; printf("%d\n", a);//1printf("%d\n", b);//3return0;
运行结果:
三、移位操作符
3.1 移位操作符简介
在C语言中,移位运算符有双目移位运算符:<<(左移)和>>(右移)
左移运算是将一个二进制位的操作数按指定移动的位数向左移动,移出位被丢弃,右边移出的空位一律补 0
右移运算是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补 0 ,或者补符号位,这由不同的机器而定。在使用补码作为机器数的机器中,正数的符号位为 0 ,负数的符号位为 1 vvvv
下面且听详解!!!
3.2 原码、反码、补码
在学习移位操作符之前,首先要了解原码、反码、补码
整数在内存中存储的形式是补码的二进制
整数的二进制表示:有3种(原码、反码、补码)(假设在 32 位的平台下)
原码:直接根据数值写出的二进制序列就是原码(32位)
反码:原码的符号位不变,其他位按位取反就是反码
补码:反码加1,就是补码
对于正整数的原码、反码、补码都相同;负数是存放在二进制的补码中,负整数的原码、反码、补码都不相同
例如:1(正整数的原码、反码、补码都相同)
原码:0000000000000000000000000000001反码:0000000000000000000000000000001补码:0000000000000000000000000000001
最高位为0 ,也是符号位(最左边的第一位)
例如:-1(负整数的原码、反码、补码都不相同)
原码:10000000000000000000000000000001反码:11111111111111111111111111111110(按位取反,符号位不变)补码:11111111111111111111111111111111(反码加1)
最高位为1,也是符号位(最左边的第一位)
简单了解到这里,可以继续学习移位操作符了
3.3 << 左移运算符
直接上代码(只演示负整数的,看完正整数的也会了,正整数的比较简单)
intmain() { inta=-5; intb=a<<1; printf("%d\n", a); printf("%d\n", b); return0; }
运行结果是 -10
这是为什么呢,解释如下:
规则:左移运算是将一个二进制位的操作数按指定移动的位数向左移动,移出位被丢弃,右边移出的空位一律补0
简单说就是:左边丢弃,右边补0
先写出 -5 的补码
原码:10000000000000000000000000000101(最高位为1)反码:11111111111111111111111111111010(按位取反,符号位不变)补码:11111111111111111111111111111011(反码加1)
补码向左移动一位,左边去掉,右边补0,如图:
此时得到的是补码,还要反推原码才能打印(正数原、反、补相同,补码就是原码,负数要反推回去)
补码:11111111111111111111111111110110反码:11111111111111111111111111110101(补码-1得到反码)原码:10000000000000000000000000001010(按位取反得到原码)
此时得到的原码就可以转换为十进制打印了,结果就是 -10
注意:%d 意味着打印一个有符号的整数
注:此时的 a 没有改变,依旧是 -5
3.4 >>右移运算符
直接上代码(只演示负整数的,看完正整数的也会了,正整数的比较简单)
intmain() { inta=-5; intb=a>>1; printf("%d\n", a); printf("%d\n", b); return0; }
运行结果是 -3
解释:
右移运算是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补0,或者补符号位,这由不同的机器而定。在使用补码作为机器数的机器中,正数的符号位为 0 ,负数的符号位为 1
简单说就是:(分为 2 种)
- 逻辑右移:左边用0填充,右边丢弃
- 算术右移:左边用原该值的符号位填充,右边丢弃
到底是逻辑右移还是算术右移取决于编译器
我当前使用的编译器 VS2019,它采用的是算术右移
还是一样,先写出 -5 的补码
原码:10000000000000000000000000000101(最高位为1)反码:11111111111111111111111111111010(按位取反,符号位不变)补码:11111111111111111111111111111011(反码加1)
补码向右移动一位,右边丢弃,左边补符号位,如图:
此时得到的是补码,还要反推原码才能打印
补码:11111111111111111111111111111101反码:11111111111111111111111111111100(补码-1得到反码)原码:10000000000000000000000000000011(按位取反得到原码)
此时得到的原码就可以转换为十进制打印了,结果就是 -3
注:此时的 a 没有改变,依旧是 -5,说明左移和右移操作符不改变原操作数
3.5 警告
对于移位运算符,不要移动负数位,这个是标准未定义的;左移和右移运算符只适用于正数,不支持浮点数
例如:
inta=5; intb=a>>-1//errorfloat=1.11; floatd=f<<1;//不支持
四、位操作符
位操作符有:
&//按位与|//按位或^//按位异或//注:他们的操作数必须是整数
运行结果是:4
解释如下:
& 按位与规则:两个二进制操作数对应位同为 1 ,结果位才为 1 ,其余情况为 0
也是先写出 5,-2 的补码
5的补码:00000000000000000000000000000101-2的原码:10000000000000000000000000000010-2的反码:11111111111111111111111111111101-2的补码:11111111111111111111111111111110
按照规则,两个二进制操作数对应位同为 1 ,结果 位 才为 1 ,其余情况为 0
5的补码:00000000000000000000000000000101-2的补码:111111111111111111111111111111105&-2的补码:00000000000000000000000000000100
此时得到的是补码,还要反推原码才能打印
1. 5 &- 2 的原码:00000000 00000000 00000000 00000100 2. (正整数原、反、补相同)
此时得到的原码就可以转换为十进制打印了,结果就是 4
4.2 | 按位或
老样子,先上代码
intmain() { inta=5; intb=-2; intc=a|b; printf("%d\n", c); return0; }
运行结果是:-1
解释如下:
| 按位或规则:两个二进制操作数对应位只要有一个为 1 ,结果 位 就为 1 ,其余情况为 0
也是先写出 5,-2 的补码
5的补码:00000000000000000000000000000101-2的原码:10000000000000000000000000000010-2的反码:11111111111111111111111111111101-2的补码:11111111111111111111111111111110
也是按照规则,两个二进制操作数对应位只要有一个为 1 ,结果 位 就为 1 ,其余情况为 0
5的补码:00000000000000000000000000000101-2的补码:111111111111111111111111111111105|-2的补码:11111111111111111111111111111111
此时得到的是补码,还要反推原码才能打印
5|-2的补码:111111111111111111111111111111115|-2的反码:111111111111111111111111111111105|-2的原码:10000000000000000000000000000001
此时得到的原码就可以转换为十进制打印了,结果就是 -1
4.3 ^ 按位异或
老样子,先上代码
intmain() { inta=5; intb=-2; intc=a^b; printf("%d\n", c); return0; }
运行结果是:-5
原因解释如下:
^ 按位异或 规则:两个二进制操作数对应 位 相同为 0 ,不同为 1
也是先写出 5,-2 的补码
5的补码:00000000000000000000000000000101-2的原码:10000000000000000000000000000010-2的反码:11111111111111111111111111111101-2的补码:11111111111111111111111111111110
也是按照规则来,两个二进制操作数对应 位 相同为 0 ,不同为 1
5的补码:00000000000000000000000000000101-2的补码:111111111111111111111111111111105^-2的补码:11111111111111111111111111111011
此时得到的是补码,还要反推原码才能打印
5^-2的补码:111111111111111111111111111110115^-2的反码:111111111111111111111111111110105^-2的原码:10000000000000000000000000000101
此时得到的原码就可以转换为十进制打印了,结果就是 -5
----------------我是分割线---------------
五、赋值操作符
赋值操作符为:=
赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值
比如:
intweight=120;//体重weight=89;//不满意就赋值doublesalary=10000.0; salary=20000.0;//使用赋值操作符赋值
但是这种代码不建议写,别人看了增加头发负担量
建议这么写:
1. x = y+1; 2. a = x;
赋值操作符也可以写成复合赋值符
- +=
- -=
- *=
- /=
- %=
- >>=
- <<=
- &=
- |=
- ^=
比如:
intx=10; x=x+10; x+=10;//复合赋值,等价于 x = x+10//其他运算符一样的道理,这样写更加简洁
----------------我是分割线---------------
六、单目操作符
6.1 单目操作符介绍
单目操作符意味着只有一个操作数
!逻辑反操作符-负值+正值&取地址sizeof操作数的类型长度(以字节为单位)~对一个数的二进制按位取反--前置、后置--++前置、后置++*间接访问操作符(解引用操作符) (类型) 强制类型转换
6.2 ! 逻辑反操作符
intflag=3; //flag为真,进入ifif (flag) {} //flag为假,进入ifif(!flag) {}
C语言中0表示假,非0表示真
6.3 - +(负值、正值)
//int a = -10;//负号intb=-a;//负负得正intc=+1;//正号一般不写出来
6.4 ~ 取反
直接上代码
intmain() { inta=5; intc=~a; printf("%d\n", c); return0; }
运行结果是:-6
原因解释如下:
~ 取反规则:一个二进制操作数,对应位为 0 ,结果位为 1 ;对应位为 1 ,结果位为 0(作用是将每位二进制取反)
先写出 5 的补码
5 的补码:00000000 00000000 00000000 00000101
按照规则,对应位为 0 ,结果位为 1 ;对应位为1,结果位为 0
1. 5 的补码:00000000 00000000 00000000 00000101 2. 取反:11111111 11111111 11111111 11111010
此时得到的是补码,还要反推原码才能打印
取反后(补码):11111111111111111111111111111010反码:11111111111111111111111111111001原码:10000000000000000000000000000110
此时得到的原码就可以转换为十进制打印了,结果就是 -6
6.5 -- 运算符和 ++ 运算符
-- 分前置--和后置--,++ 也分前置++和后置++
前置++,先++,后使用;后置++,先使用,再++
前置--,先--,后使用; 后置--,先使用,再--
测试代码,-- 同样道理
intmain() { inta=1, b=1; //前置++,先++,后使用//后置++,先使用,再++printf("前置:%d\n", ++a); printf("后置:%d\n", b++); return0; }
运行结果
6.6 & 取地址和 * 解引用
& 取地址操作符用于取变量的起始地址;* 叫做间接访问操作符,也叫解引用操作符,一般都是用于解引用指针,拿到指针所指向的内容
测试代码:
intmain() { inta=10; printf("%p\n", &a);//&取地址, %p是打印地址//取出的地址是变量的起始地址int*p=&a;//把 a的地址拿给指针p,指针p 可以间接使用a*p=20;//p 是a的地址,*p就拿到了地址的内容,即 a的值10,指针p间接修改了a的内容printf("%d\n", a); return0; }
运行结果
指针后面讲,这里简单了解
6.7 sizeof 操作符
sizeof 操作符,可以求变量(类型)所占空间的大小,单位是字节
注意:sizeof是操作符,不是函数,strlen是库函数,是用来求字符串长度
测试代码:
intmain() { inta=1; intarr[10] = { 0 }; printf("%d\n", sizeof(a));//可以计算变量的大小 4printf("%d\n", sizeof(arr));//可以计算数组的大小,4*10printf("%d\n", sizeof(int));//可以计算变量类型的大小 4printf("%d\n", sizeof(char));//可以计算变量类型的大小 1printf("%d\n", sizeofa);//这样写行不行?ok//printf("%d\n", sizeof int);//这样写行不行?errorreturn0; }
运行结果
来看看 sizeof 和 数组的一个面试题
voidtest1(intarr[]) { printf("%d\n", sizeof(arr));//(2)} voidtest2(charch[]) { printf("%d\n", sizeof(ch));//(4)} intmain() { intarr[10] = { 0 }; charch[10] = { 0 }; printf("%d\n", sizeof(arr));//(1)printf("%d\n", sizeof(ch));//(3)test1(arr); test2(ch); return0; }
问:(1)(2)两个地方分别输出多少?(3)(4)两个地方分别输出多少?
(2)(4)本质上是指针,指针在 32位平台下是 4
运行结果
6.8 (类型) 强制类型转换
测试代码:
intmain() { inta= (int)3.14;//把 3.14强制类型转换成 intprintf("%d\n", a); return0; }
运行结果
七、关系操作符
关系操作符有:
>>=<<=!=用于测试“不相等”==用于测试“相等”
这些我们都是很熟悉,不再解释了,要注意一点:在编程的过程中 == 和 = 不小心写错,导致的错误
----------------我是分割线---------------
八、 逻辑操作符
逻辑操作符有:
1. && 逻辑与 2. || 逻辑或
区分逻辑与和按位与
1. 1&2----->0 2. 1&&2---->1
区分逻辑或和按位或
1. 1|2----->3 2. 1||2---->1
看一道面试题
intmain() { inti=0, a=0, b=2, c=3, d=4; i=a++&&++b&&d++; printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d); return0; }
运行结果
说明:&& 左边为假,右边就不计算了
继续看
intmain() { inti=0, a=0, b=2, c=3, d=4; i=a++||++b||d++; printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d); return0; }
运行结果
明:|| 左边为真,右边就不计算了
----------------我是分割线---------------
九、条件操作符
条件操作符也叫三目操作符
exp1 ? exp2 : exp3
表达式1 为真则取 表达式2 的结果,表达式1 为假则取 表达式3 的结果
测试代码
intmain() { inta=3; intb=0; intmax= (a>b?a : b); printf("%d\n", max); /*if (a > 5)b = 3;elseb = -3;*///int max = (a > b ? a : b) 等价于上面的if语句return0; }
运行结果
十、逗号表达式
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果
exp1, exp2, exp3, …expN
测试代码
intmain() { inta=1; intb=2; intc= (a>b, a=b+10, a, b=a+1);//整个表达式的结果是最后一个表达式的结果printf("c=%d\n", c); return0; }
运行结果
再如:
//代码2a=get_val(); count_val(a); while (a>0) { //代码a=get_val(); count_val(a); } 如果使用逗号表达式,改写:while (a=get_val(), count_val(a), a>0) { //代码} //代码3if (a=b+1, c=a/2, d>0)//判断条件为整个表达式的结果是最后一个表达式的结果
----------------我是分割线---------------
十一、下标引用、函数调用和结构成员
11.1 [ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
ntarr[10];//创建数组arr[9] =10;//实用下标引用操作符[ ]的两个操作数是arr和9//arr[9] --> *(arr+9) --> *(9+arr) --> 7[arr]//arr[9] 也可以写成 9[arr],但是创建数组就不可以//arr是数组首元素的地址//arr+9就是跳过9个元素,指向了第10个元素//*(arr+9) 就是第8个元素
11.2 ( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
voidtest1() { printf("hello\n"); } voidtest2(char*str) { printf("%s\n", str); } intmain() { test1(); //实用()作为函数调用操作符test2("hello");//实用()作为函数调用操作符return0; }
11.3 访问一个结构的成员
1. . 结构体.成员名 2. -> 结构体指针->成员名
直接看代码
structStu{ charname[10]; intage; charsex[5]; doublescore; }; voidset_age1(structStustu) { stu.age=18; } voidset_age2(structStu*pStu) { pStu->age=18;//结构成员访问} intmain() { structStustu; structStu*pStu=&stu;//结构成员访问stu.age=20;// . 结构成员访问set_age1(stu); pStu->age=20;// -> 结构成员访问set_age2(pStu); return0; }
----------------我是分割线---------------
文章就到这里,下篇即将更新