C语言学习笔记——常见操作符和表达式求值
C语言学习笔记——常见操作符和表达式求值Hello大家好又是我Mii最近几天Mii又学习了常见操作符和表达式求值所以Mii我又来写一篇文章来分享Mii学习到的知识了话不多说直接开写(・ω・´)上次Mii学习了常亮和变量的知识但是如果要完成一些运算还得掌握常见的操作符下面是C语言中提供的的操作符。操作符也被叫做运算符是不同的翻译意思是一样的。算数操作符、-、*、/、%移位操作符 位操作符 | ^赋值操作符、、-、*、、、、|、^单目操作符、、- -、、*、、-、sizeof类型关系操作符、、、、、!逻辑操作符、||条件操作符逗号表达式下标引用[ ]函数调用结构成员访问.、-这里Mii主要学习了一些常用的操作符~1.算数操作符在C语言中有一组操作符叫算数操作符。分别是 - * / %,这些操作符都是双目操作符。1.1和-和-用来完成加法和减法。和-都是有两个操作数的位于操作符两端的就是它们的操作数这种操作符也叫双目操作符。#includestdio.hintmain(){intx422;inty61-23printf(%d\n,x);printf(%d\n,y);return0;}1.2运算符用来完成乘法。#includestdio.hintmain(){intnum5;printf(%d\n,num*num),//输出25return0;}1.3 /运算符/用来完成除法。除号的两端如果是整数执行的是整数除法得到的结果也是整数。#includestdio.hintmain(){flaot x6/4;inty6/4;printf(%f\n,x);//输出1.000000printf(%d\n,y);//输出1return0;}上面的示例为什么变量x的类型是float(浮点数)但是6/4得到的结果是1.0,而不是1.5这是为什么呢(´ω?)原因就在于C语言里面的整数除法是整数只会返回整数部分丢弃小数部分。如果希望得到浮点的结果两个运算数必须至少有一个浮点数这时C语言就会进行浮点数除法。#includestdio.hintmain(){flaot x6.0/4;//或者写成6/4.0printf(%f\n,x);//输出1.500000return0;}上面示例中6.0/4表示进行浮点数除法得到的结果就是1.5。再看一个例子#includestdio.hintmain(){intscore5;score(score/20)*100;printf(%d\n,score);return0;}上面的代码Mii一开始经过运算以为score会等于25 ᗜˬᗜ可是运行程序后发现score等于0(﹏)这是为什么呢(˘•ω•˘)这是因为score/20是整除会得到一个整数值0所以乘以100后得到的也是0。为了得到预想的结果可以将20改成20.0让整除变成浮点除法。#includestdio.hintmain(){intscore5;score(score/20)*100;printf(%d\n,score);return0;}1.4 %运算符%表示求模余运算即返回两个整数相除的余值。这个运算符只能用于整数不能用于浮点数。#includestdio.hintmain(){intx6%4;//2return0;}负数求模的规则是结果的正负号由第一个运算数的正负号决定。#includestdio.hintmain(){printf(%d\n,11%-5);//1printf(%d\n,-11%-5);//-1printf(%d\n,-11%5);//-1return0;}上面的示例中第一个运算符的正负号11或-11决定了结果的正负号。通过以上的学习Mii写了个计算任意两个整数的平均值结果是小数٩(๑˃̵ᴗ˂̵)۶想要计算任意两个整数的平均值那么我们需要给程序输入两个值这里就需要scanf函数scanf函数是格式化的输入函数。什么是格式化什么是输入呢代码中的%d是占位符对应的是整数类型用在printf函数中表示要打印一个整型类型的数据用在scanf函数中表示从接受从键盘输入的一个整形类型的值。%d//对应int类型%f//输出时对应float和double输入时%f对应float类型%lf对应double%c//对应char类型%s//对应字符串下面是程序的代码#includestdio.hintmain(){inta0;intb0;//输入2个整数scanf(%d%d,a,b);//计算floatavg(ab)/2.0;//输出结果printf(%f\n,avg);return0;}2. 赋值操作符和复合赋值在变量创建的时候给一个初始值叫初始化在变量创建好后再给一个值这叫赋值。inta100;//初始化a100;//赋值这里使用的就是赋值操作符赋值操作符是一个随时可以给变量赋值的操作符。2.1 连续赋值符赋值操作符也可以连续赋值如inta3;intb5;intc0;cba3;C语言虽然支持这种连续赋值但是写出的代码不容易理解Mii在这里更愿意拆开来写这样方便观察代码的执行细节。int a3;int b5;int c0;ba 3;cb;这样写在调试的时候每一次赋值的细节都是可以很方便观察的。2.2 复合赋值符在写代码时我们经常可能对一个数进行自增、自减的操作如下代码inta10;aa3;aa-2;这样的代码C语言给提供了更加方便的写法inta10;a3;a-2;Mii觉得a 3这种写法不光短而且一眼就能看出来是对a自己动手比a a 3少写一遍a也少了一次手滑写错的机会٩(๑˃̵ᴗ˂̵)۶C语言中提供了复合赋值符方便我们编写代码这些赋值符有-*/%//下面的操作符后期讲解|^3.单目操作符、–、、-前面介绍的操作符都是双目操作符有2个操作数的。C语言中还有一些操作符只有一个操作数被称为单目操作符。、–、(正)、-(负)就是单目操作符的。3.1 和–是一种自增的操作符又分为前置和后置。 前置后置都是让它的操作数自增1区别在于表达式的值不同。–是一种自减的操作符也分为前置–和后置–。 前置–后置–都是让它的操作数自减1区别在于表达式的值不同。3.1.1 前置inta10;intba;//的操作数是a是放在a的前面的就是前置printf(a%d b%d\n,a,b);计算口诀先1后使用a原来是10先1后a变成了11再使用就是赋值给bb得到的也是11所以计算结束后a和b都是11相当于这样的代码inta10;aa1;ba;printf(a%d b%d\n,a,b);3.1.2 后置inta10;intba;//的操作数是a是放在a的后面的就是后置printf(a%d b%d\n,a,b);计算口诀先使用后1a原来是10先使用就是先赋值给bb得到了10然后再1然后a变成了11所以结束后a是11b是10相当于这样的代码inta10;intba;aa1;printf(a%d b%d\n,a,b);你看两段代码就差了一个的位置一个写在前面一个写在后面b拿到的值就从11变成了10Mii第一次看的时候还愣了一下(´ω?)3.1.3 前置–如果你听懂了前置那前置–是同理的只是把加1换成了减1计算口诀先-1后使用inta10;intb--a;//--的操作数是a是放在a的前面的就是前置--printf(a%d b%d\n,a,b);//输出的结果是9 93.1.4 后置–同理后置–类似于后置只是把加1换成了减1。计算口诀先使用后-1inta10;intba--;//--的操作数是a是放在a的后面的就是后置--printf(a%d b%d\n,a,b);//输出的结果是9 10这四个Mii整理成了一个小表格方便以后忘了的时候回来瞄一眼假设一开始a都是10写法口诀执行后a表达式本身的值a先1后使用1111a先使用后11110--a先-1后使用99a--先使用后-1910这里要特别注意不管前置还是后置a自己是肯定会变的变的只是“表达式本身的值”也就是赋值给b的那个值别把这两个搞混了(๑•̀ㅂ•́)و✧3.2 和-这里的是正号-是负号都是单目操作符。运算符对正负值没有影响是一个完全可以省略的运算符但是写了也不会报错。inta10;//等价于 int a 10;运算符-用来改变一个值的正负号负数的前面加上-就会得到正数正数的前面加上-会得到负数。inta10;intb-a;intc-10;printf(b%d c%d\n,b,c);//这里的b和c都是-10inta-10;intb-a;printf(b%d\n,b);//这里的b是104.表达式求值学会了上述操作符后我们使用操作符可以写出一些更加复杂的表达式比如34*5;5*6/2;那这些表达式的计算顺序是啥这里就得了解学习一下操作符的属性。操作符的属性有优先级和结合性这两个属性决定了表达式求值的计算顺序。4.1 操作符的属性操作符属性https://zh.cppreference.com/w/c/language/operator_precedence这张表Mii第一次看到的时候头皮都麻了这么多操作符怎么可能记得住(﹏)。其实不用全背下来常用的优先级记住剩下的用的时候回来查表就行了~4.1.1 优先级优先级指的是如果一个表达式包含多个运算符哪个运算符应该优先执行。各种运算符的优先级是不一样的。34*5;上面示例中表达式3 4 * 5里面既有加法运算符又有乘法运算符*。由于乘法的优先级高于加法所以会先计算4 * 5而不是先计算3 4。运算符的优先级顺序很多下面是部分运算符的优先级顺序按照优先级从高到低排列建议大概记住这些操作符的优先级就行其他操作符在使用的时候查看上面表格就可以了。圆括号()自增运算符自减运算符–单目运算符和-乘法*除法/加法减法-关系运算符、等赋值运算符由于圆括号的优先级最高可以使用它改变其他运算符的优先级。4.1.2 结合性如果两个运算符优先级相同优先级没办法确定先计算哪个了这时候就看结合性了则根据运算符是左结合还是右结合决定执行顺序。大部分运算符是左结合从左到右执行少数运算符是右结合从右到左执行比如赋值运算符。5*6/2;上面示例中*和/的优先级相同它们都是左结合运算符所以从左到右执行先计算5 * 6再计算/ 2。优先级解决的是“谁先算”结合性解决的是“优先级一样时往哪边算”这两个是配套使用的缺一不可哦(・ω・´)4.2 类型转换在进行表达式求值的时候有时候会涉及到数据类型的转换类型转换一般分为隐式类型转换和显示类型转换。隐式类型转换又分为整型提升和常规算术转换显示类型转换就是强制类型转换了。隐式类型转换又称自动类型转换由编译器自动完成无需程序员显式写出转换代码。它的目的是使表达式中的操作数类型兼容。整型提升表达式中的char、short等长度小于整型类型的长度在表达式计算的过程中会被自动提升为整型类型int或unsigned int然后参与运算这里的类型提升就是整型提升。charc100;shorts2000;intics;//c和s都被提升为int后再进行加法运算更精确地说C11 §6.3.1.1参与运算的类型char、signed char、unsigned char、short、unsigned short、int位段、枚举类型判定条件若int的取值范围能覆盖原类型的所有值 → 提升为int否则 → 提升为unsigned int提升后的类型始终是int或unsigned int不会提升为long等更大的类型保留值不变提升后的值与原值相等常规算术转换变量的数据类型的长度是大于等于整型类型的长度时如果某个操作符的各个操作数属于不同的类型那么除非其中一个操作数转换为另一个操作数的类型否则操作就无法进行。intn10;floatf5.5f;floatrnf;//n会转换为float类型再和f进行加法运算如果某个操作数的类型在下面这个列表中排名靠后那么首先要转换为另外一个操作数的类型后执行运算。longdoubledoublefloatunsignedlongintlongintunsignedintint这个列表是从“大”到“小”排的越靠下越小小类型要往大类型靠这样才不会丢数据(´ω)强制类型转换C语言的隐式类型转换都是按照既定的规则来转换的但是有时候我们需要更大程度上控制类型的转换所以就提供了强制类型转换。形式如下(类型)表达式代码样例#includestdio.hintmain(){intn3.14;//有警告intm(int)3.14;//将3.14强制类型转换为int类型inta5;intb2;floatc(float)a/b;//这里如果不强制类型转换就是整数除法的printf(%f\n,c);return0;}4.3 问题表达式C语言中没有规定子表达式的计算顺序如果一个表达式比较复杂包含了多个子表达式那么子表达式的计算顺序不确定的情况下就会影响表达式的求值结果。比如(ab)*(c-d)这个表达式无法确定a b和c - d谁先计算。大部分情况下a b和c - d谁先算不影响结果。但是如果子表达式中改变了某个操作数的值的时候产生的值可能就不一致了。比如a10;c(ba3)-(a5);//结果是未定义的如果先执行b a3则c 8如果先执行a 5则c 3。这类包含了子表达式的表达式子表达式的计算顺序就可能引发一些未定义行为这就是问题表达式。1.问题表达式1#includestdio.hintmain(){inti1;intr(i)(i)(i);printf(%d\n,r);return0;}这段代码在不同的编译器上算的结果是不一样的这就是问题表达式。为什么呢VS2019的运行结果mingw64-gcc的运行结果同一个表达式VS2026算出来是12mingw64-gcc算出来是10Mii反复确认了好几遍才相信这是同一段代码(﹏)原因就在于C语言标准只规定了操作符的优先级和结合性并没有规定子表达式的计算顺序。像(i)这种既要取值、又要修改i的子表达式到底谁先算、什么时候真正把i加一标准把决定权交给了编译器所以编译器想怎么算就怎么算这就叫未定义行为。2.问题表达式2#includestdio.hintmain(){inti10;ii-----i*(i-3)*ii;printf(i %d\n,i);return0;}这段代码来自《C和指针》这本书书的作者测试了下面这么多的编译器程序运行结果各不相同。一个表达式居然能算出从-128到42这么多种结果Mii看到这张表的时候是真的震惊了(д)原来“能算出结果”和“结果是唯一的”完全是两回事所以我们以后在编写表达式的时候尽量简化表达式让表达式能够有唯一的计算路径这样的代码才是可靠的。到这里第3讲常见操作符和表达式求值Mii就全部整理完啦。回头看看C语言的表达式看着简单坑是真的不少整数除法和浮点数除法差一个.0求模的正负号跟着第一个操作数走写在前面和后面结果不一样还有那种一个表达式里改好几次同一个变量的写法干脆就是未定义行为(﹏)。所以Mii给自己定了个小规矩表达式宁可多写几行、拆开写也不要为了省事写出看不懂的代码毕竟能看懂、结果唯一的代码才是好代码嘛(๑•̀ㅂ•́)و✧