✨前言✨
这个系列作为【全网最强】的续作,在内容上不会只是走马灯式的浏览知识点,而会对各个知识点进行精讲精析,达到真正熟练的地步。虽然说博主学习C语言不过两个多月,但也熟知重难点和易错点,会和大家一起分享学习。关注博主🍻,在学习C语言的路上结伴前行吧。
[有问题欢迎留言讨论,作者尽力帮忙]✨
目录
一、优先级和结合性
优先级:优先级的高低决定运算的优先顺序。如最常见的加和乘,3+2*5中乘法优先级高
结合性:C语言中各运算符的结合性分为两种,即左结合性(自左至右)和右结合性(自右至左)。结合性指的是编译器解释的顺序。如赋值操作符的结合性为自右向左,a=b=c实际将c赋值给b,再将b赋值给a,从右往左解释
(操作符也叫运算符)
二、 算数运算符
<第一组> + - *(乘) /(除) %(取余)
解释:+ - * /详细很好理解,解释一下%。取余表示取余数,如30%11=8,而30/11=2。
注意点:
①除号两边数据类型不同时会发生类型转。
举例:如1/2=0,因为除号两边都是整形,所以结果也是整形,1除2等于0余1。1/2.0,除号一边是整数,另一边是浮点数,类型不同,int型转化为double型,因此结果也是浮点数,所以答案是0.5。
自动类型转化原则:将参与运算的操作数转换成其中占用内存大的类型,防止数据丢失
②取余两边必须是整型,不能出现浮点数
<第二组> ++(自增) --(自减)
解释:++等价于a=a+1 --等价于a=a-1
注意点 :
①前置++与后置++问题
前置++,先自增后使用;后置加加,先使用后加加(--同理)
过程分析:为b赋值时,++在a后后置加加,所以现将a的值赋值给b,所以b的值为5,为b赋值后a自增1变成6。为c赋值时,++在a前为前置加加,所以先将a自增再赋值给c,a自增后值为7,所以c的值为7。
为什么将上面的算数运算符分成一二组呢,因为这两组的优先级和结合性不同(在最后总结)
三、关系运算符
<第一组> > < <= >= <=
<第二组> ==(相等) !=(不相等)
(为什么分成两组呢,两组的优先级不同,最后有总结)
注意点:
①大于等于中>与=之间不要留有空格,否则不会被识别为>=,其他同理
②注意:==与!=的优先级低于第一组的关系运算符
举例:if(10>=5 == 4<=3)
>=和<=的优先级高于==,所以最后计算==
10>=5为真,整体的值为1;4<=3为假,整体的值为0。1==0为假,所以if语句不执行
③⭐易错点:注意别把==写成=。
#include<stdio.h>
int main()
{
for (int k = -1; k = 1; k++)
{
printf("*****\n");
}
return 0;
}
分析:写成k=1,而不是k==1,则每次不是进行判断而是将k赋值成1,1表示真,循环还会继续,所以程序的结果是死循环。在设计程序时要特别小心这种错误。
int main()
{
int a = 1;
int b = 2;
int c = 3;
if (a <= b <= c)
{
printf("haha");
}
return 0;
}
④所有表达式都有值。这里if里的逻辑是:a<=b为真,所以a<=b的值为1。1<=c成立,所以if语句执行。注意这里并不是字面看来的判断是不是a<=b<=c。
四、逻辑运算符
<第一组> ! (逻辑非)
<第二组> && (逻辑与)
<第三组> || (逻辑或)
(自上往下优先级降低)
解释:
1.逻辑与:两边都为真整体才为真。如 if(1>0 && 8 >9)左边为真,右边为假,整体为假。
2.逻辑或:两边只要有真整体就位真。如上面的例子用 || if语句会执行
3.逻辑假:真变假,假变真。在vs编译器下,真用1表示,假用0表示
如 !1=0 !6=0 !0=1
注意点:
①逻辑短路⭐:对于&&,只要一边为0,就可以确定整体的值为0,其他的值就不会再运算。对于||,只要一边为1,就可以确定整体的值为1,其他的值就不会再运算。
【总结】只在需要时对右边求值
②注意别把&&写成&,现在分析三种可能造成 &&写成 &程序“没错的原因”
考虑以下代码,其功能是查询表中一个特定元素。
int i = 0;
while (i < tabsize && tab[i] != x)
{
i++;
}
现分析将&&替换成&仍然能"正常工作"的原因。
原因一:只要xy的值都限制在0~1,x&&y和x&y的结果始终相同。
原因二:数组结尾之后的下一个元素,只要不改变他的值而仅仅是读取,没有什么大的危害;并且不同与&&的“逻辑短路”,&要求两边都要被求值,所以会查看tab数组结尾的后一元素(即使不存在)
(&&替换成&问题摘录于博主以前的博客,C经典书籍笔记——C陷阱与缺陷③(语意陷阱))
验证优先级
上面提到优先级 && > ! > ||,我们设计程序来验证
//验证!优先级高于&&
int main()
{
int a = 0;
int b = 1;
if (!a && b)
{
printf("haha");
}
return 0;
}
//验证!优先级高于||
int main()
{
int a = 0;
int b = 0;
if (!a || b)
{
printf("%d",a);
}
}
五、位操作运算符
<第一组> <<(左移) >>(右移)
<第二组> ~(位非) ^(位异或) &(位与) |(位或)
【优先级都不同,这里不重要,所以在最后总结】
解释:位操作符是对二进制位的补码进行操作的,这里涉及数据存储的原码反码补码。原反补不是这里的重点,之后作者也会发文总结,若有不大会的可以参考这篇博客 链接
功能:
①左移:左移位将第一操作数的每一位向左平移第二操作数指定的位数(如-1<<2表示将-1的二进制位每一位向左一两位),右边空位补0,左边移出去的丢弃。
(先以-1左移1位为例图解,-1的补码为 11111111 11111111 11111111 11111111)
②右移:右移位将第一操作数的每一位向右平移第二操作数指定的位数,右边丢弃,左边空 位补值。左边补值有两种情况。
1.有符号数——符号位补符号值
2.无符号数——补0
(先以1右移1位为例图解,1的补码为 00000000 00000000 00000000 00000001)
③位非:按位取反
④位与:对应二进制有0出0,全1出1
⑤位或:对饮二进制位有1出1,全0出0
⑥位异或:对应二进制位上数字相同出0,不同出1
应用:
①用位操作符进行二进制有关的操作极其方便
例一:求一个整数存储在内存中的二进制中1的个数
方法:1.利用%2和/2每次计算一个数字当前二进制位最后一位是0还是1
【缺陷】不能统计负数,局限性很大
2.利用 &1,&10,&100……判断每一位二进制最后一位是1还是。100等用1<<2获取
(不是100而是二进0制的100)
【缺陷】运算次数过多
3.最优解:a & (a-1),每进行一次就可以使原数字二进制少一个1,计数操作多少次使 原数字变成0,即可得出二进制中1的个数
【理解】每次a-1只会改变二进制中最后一位的1,使原来的 10 变成 01,其他不 变,&操作自然会使原二进制少一个1
//方法一:
int main()
{
int n = 0;
int count = 0;
scanf("%d",&n);
while (n)
{
count += n % 2;
n /= 2;
}
printf("二进制序列中1的个数为:%d",count);
return 0;
}
//方法二
int main()
{
int n = 0;
int count = 0;
scanf("%d",&n);
for (int i = 0; i < 32; i++)
{
if (n & 1 << i)
count++;
}
printf("二进制序列中1的个数为:%d",count);
return 0;
}
//方法三
int main()
{
int n = 0;
int count = 0;;
scanf("%d",&n);
while (n)
{
count++;
n = n & (n - 1);
}
printf("二进制序列中1的个数为:%d",count);
return 0;
}
②不使用变量使得两数交换(代码如下)
举个例子就可以理解。可以这样形象记忆a^b是一个宝盒,tmp^a相当于把a作为钥匙打开,就获得了b。但要强调的是这样的代码可读性不佳,最好的还是用三个变量。
int main()
{
int a = 1;
int b = 10;
int tmp = 0;
tmp = a ^ b;
a = tmp ^ a;
b = tmp ^ b;
return 0;
}
六、赋值运算符
<简单赋值符> =
<复合算术赋值符> += -= *= /= %=
<复合位运算赋值符> &= |= ^= >>= <<=
(三种赋值运算符的优先级都相同)
符合操作符如a*=b等价于a=a*b,只是缩写罢了,注意理解。其他以此类推
注意点
赋值运算符的优先级是所有双目操作符中最低的,使用时尤其要注意是否要加括号
练习题: 打印的结果是什么
#include<stdio.h>
int main()
{
int a = 3, b = 5;
int n = 2;
int m;
(m = a <= 3) && a + b < 8;
printf("%d\n", n += n -= n * n);
printf("%d\n", n += n -= n *= n);
printf("%d",m );
return 0;
}
七、条件运算符
<第一组> ?:
(唯一的三目操作符)
作用:
所有表达式都有一个值,包括条件运算符。如果第一个子表达式为真,则条件表达式的值为第二个子表达式的值,否则它就是第三个子表达式的值。
int main()
{
int a = 10;
int b = 1;
int max = 0;
max = a > b ? a : b;
return 0;
}
八、求字节数运算符
想必大家对sizeof都很熟悉了,这里就不赘述了。
注意:sizeof不是函数,而是操作符
九、逗号运算符
作用
逗号表达式的值是最后一个表达式的值。注意使用时要加括号
int main()
{
int a = 0;
int b = 0;
int c = (a + 10, b = a + 10, b + 10);//最后把b+10的值附给c
printf("%d",c);
}
有时候要小心,不经意间就会错。如下面的二维数组实际只有两个元素
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);
return 0;
}
十、强制类型转换运算符
想必大家对()也都很熟悉了,这里就不赘述了。只是注意()里的是类型而不是变量
int main()
{
int a = 1;
int b = 2;
float tmp = 0.0;
tmp = (float)a / b;
printf("%f",tmp);
return 0;
}
十一、词法运算中的“大嘴法”
思考
编译器对连续输入的符号是看成整体还是分别处理
如a/*b,是看成a/ (*b)还是把/*以注释符看待
原则 :大嘴法(也叫贪心法)
一个符号应该尽可能包含更多的字符。即在输入一个可能成为符号的字符后继续读入下一个字符,判断两个字符组成的字符串能否组成一个符号,重复上述过程,直至不能组成一个可能的符号。
练习: a+++++b的含义什么 【原来的解释有误,现纠正】
分析:根据贪心法,++组合后再一次++最后以为((a++)++))+b。然而这个式子在语法上是错误的,因为a++的结果不能作为左值,因此编译器实际不会接受a++后的++,也就会语法报错。在编程实践中的建议就是,尽量避免使用类似的结构
十二、优先级与结合性总结
关于优先级的记忆方法,博主之前做过总结,大家可以参考 C经典书籍笔记——C陷阱与缺陷②(语法陷阱之优先级)
十三、后记
【C语言知识精讲②】出炉啦 【C语言知识精讲②】static修饰局部变量,全局变量,函数区别
这四个没有提到的运算符会在之后关于数组,指针,结构体的专题里细讲。这里先不提了。
还有哦提醒一下,define不是运算符,而是预处理指令。
指针运算符:*和&
分量运算符:. ->
下标运算符:[ ]
函数调用运算符:()
关于C语言操作符博主就不再总结了,因为之前看到过一篇不错的博文,大家可以看看