C语言中的操作符详解(含三目表达式和逗号表达式)

📅 2026/7/4 4:46:57
C语言中的操作符详解(含三目表达式和逗号表达式)
操作符一.操作符的介绍1.算术操作符a.和-b.*,/和%2.移位操作符a. 左移操作符b. 右移操作符c.注意事项3.位操作符a.按位与 b.按位或 |c.按位异或 ^d.按位取反 ~e.补充e.1 按位异或操作符的性质4.赋值操作符5.双目与单目操作符a.和 - -b.和-6.逻辑操作符7.关系操作符a.操作符介绍b.注意事项b.1 字符串比较b.2 多个关系运算符不宜连用8.条件操作符三目表达式9.逗号表达式10.下标引用操作符和函数调用操作符11.结构体成员访问操作符**.: 结构体变量.成员****-: 结构体指针-成员**二.操作符的属性1.操作符的优先级2.操作符的结合性一.操作符的介绍1.算术操作符算数操作符有-*/%它们分别对应着加减乘除取余计算下面我们来一一介绍它们包括它们底层的二进制计算原理注意1.这里只讨论整形类型间的算术运算原理而不讨论浮点数的标准二进制浮点数float/double无法精确完成十进制小数的加减乘除运算原因有以下几点计算机以二进制存储数据部分十进制有限小数如 0.1、0.2转二进制后是无限循环小数。而 float/double 尾数位数固定存储时只能截断、舍入存入的数本身就是原数的近似值误差从存储阶段就已产生。浮点数加减必须先对阶阶数小的数尾数要右移低位数据直接丢失运算后规格化、舍入操作会进一步放大误差最终结果偏离真实值。要想对小数进行精准的运算一个可行的方法是将小数统一放大为整数后再进行相应运算运算完后再将整数还原回固定的小数位数这个方法适合固定小数位数的场景。2.且这里只讨论同种整形类型间的算术运算因为实际上不同整型类型间进行算数运算时还会涉及到整型提升与算术转换的问题这将在后续专门讲整型提升与算术转化的文章中讲解~a.和-基本使用如下intmain(){inta5;intb4;intcab;intd5-4;intea-4;return0;}其底层的二进制计算原理可以总结为三步整数转补码-加减进位削位-补码转原码得整数不知啥是补码的看这里~下面以int类型的整数为例inta15;intb-10;计算 ab:第一步整数转补码15-00000000000000000000000000001111-10-11111111111111111111111111110110第二步加减进位削位 因为是加法所以将补码相加等于二的位数进位0000000000000000000000000000111111111111111111111111111111110110100000000000000000000000000000101相加得到的补码有33位而int类型数据只有32位比特位用于编码所以将多出来的第一位削去00000000000000000000000000000101第三步补码转原码得整数 得到的补码第一位符号为0所以为非负数原反补码三码相同所以结果的的原码就是得到的补码00000000000000000000000000000101-5自己试着计算一下a-b吧b.*,/和%*是乘法运算符/是除法运算符%为取余运算符基本使用如下inta7;intb2;doublec2.0;intd1a*b;(结果为14)doubled2a/b;(结果为3.0)doubled3a/c;(结果为3.5)由上面d2的结果是3.0d3的结果是3.5我们可以知道/进行除法的特点为当除数与被除数都是整数时进行整除当有任何一个数是小数时则执行浮点除法保留小数部分和算术转换有关下面说明 * / 和%的底层二进制计算原理*:1.当乘数为2的n次幂时将被乘数补码按照移位规则在后面的移位操作符中会讲到左移n位得到的补码就是原数乘该2的n次幂的结果的补码即X n X × 2ⁿ注意当正数运算结果超出当前位数比特位能表示的最大值或负数运算结果小于当前位数比特位能表示的最小值时移位会发生溢出结果会出错2.当乘数不是2的n次幂时a.乘数从最低位第 0 位向高位逐位遍历若第n位二进制位1则被乘数左移n位等价×2的n次方得到一项累加值b.若第n位二进制位0则跳过该位累加值为0c.遍历完后运算结果各项累加值之和3.若涉及到负数数值计算以该数绝对值的补码形式进行得到的是结果的绝对值而结果的正负即符号位由乘数与被乘数的符号位异或得到符号为相同为0相异为1下面以3*5和(-3)*5为例3*5:int3(被乘数)的补码:000000000000000000000000000000115(乘数)的补码:00000000000000000000000000000101bit01,不移位:00000000000000000000000000000011bit10,不累加:00000000000000000000000000000000bit21,移两位:00000000000000000000000000000011200000000000000000000000000001100各项累加0000000000000000000000000000111115-3*5:int 数值计算用绝对值|-3|*|5|,同上得:0000000000000000000000000000111115符号位异或得符号位:1^01(负)所以最终结果为-3*5-15实际中编译器会进行优化将小常数乘法变成移位 加减例a * 7 a * (8-1) a3 - a比硬件 MUL 更快。/(整数除法):1. 当除数为 2 的 n 次幂时将被除数补码按照移位规则右移n 位无符号数、正数可等价实现原数除以该 2 的 n 次幂负数需额外修正保证向零取整。即正数 X n X ÷ 2ⁿ注意运算超出当前比特位表示范围时会出错负数不可直接单纯右移2. 当除数不是 2 的 n 次幂时a.余数寄存器的值R初始化为被除数的绝对值商寄存器的值Q初始化为0依次将余数左移 1 位后减去除数得R_tmpR_tmp非负则当前商位记 1、RR_tmpR_tmp为负则当前商位记 0、余数R恢复为已完成移位操作但减去除数前的值b.若碰到当前商位计1肯定不符合的情况当前商位计0余数R恢复为已完成移位操作但减去除数前的值这样逐位确定商和余数最终得到除法结果3. 若涉及到负数数值计算以被除数、除数的绝对值进行运算得到商的绝对值商的正负由被除数与除数的符号位异或得到符号相同为 0相异为 1%取余就是在进行 / 运算后直接获得余数寄存器R内的值再按照负数符号规则修正符号最终得到取余结果。余数符号与被除数保持一致下面以7/37%3为例7/3:int a.初始化RQ:R00000000000000000000000000000111Q00000000000000000000000000000000b.逐位确定商和余数:先左移一位一次(R1)-000……0000001100000000000000000000000000000111上面的结果大于0所以当前商位应为1即Q的第一个比特位为1但是若该位计1商就比被除数还大在这里肯定是不合理的所以该商位记0余数R恢复为已完成移位操作但减去除数前的值 即:R00000000000000000000000000001110同上面情况一样左移1~30位都是因为上面的情况而商位计0余数R不变 下面左移1位第31次 R11000000000000000000000000000000(R1)-000……00000011011111111111111111111111111111010所以Q的右数第二位记作1,RR_tmp Q00000000000000000000000000000010,R01111111111111111111111111111101下面左移1位第32次(R1)-000……00000011111111111111111111111111111110100所以Q的右数第一位记作0,R111111111111111111111111111111010所以7/3的商Q2,7%3余数R12.移位操作符移位操作符有两种 左移操作符和右移操作符移位操作符的操作数只能是整数移动的是二进制补码下面介绍具体的移位规则a. 左移操作符移位规则左边抛弃右边补0相当于数值大小乘2b. 右移操作符右移操作就更加复杂了右移运算分为两种逻辑右移和算数右移他们的移位规则不同逻辑右移左边用0补充右边丢弃算术右移左边用该数原来的符号位填充右边丢弃右移具体采用哪种规则取决于编译器大部分编译器为算术右移右移操作相当于除以2c.注意事项对移位操作符不要移动负数位因为标准未定义3.位操作符位操作符有四种按位与按位或|按位异或^和按位取反~位操作符的操作数同样只能是整数运算的是二进制补码下面分别介绍具体的运算机制a.按位与 进行按位与的两数的补码的对应二进制位上同时为1运算结果的对应位上才为1否则为0#includeiostreamusingnamespacestd;intmain(){inta-8,b6;intcab;coutcendl;return0;}//结果为 0b.按位或 |进行按位或的两数的补码的对应二进制位上同时为0运算结果的对应位上才为0否则为1#includeiostreamusingnamespacestd;intmain(){inta-8,b6;intca|b;coutcendl;return0;}//结果为 -2c.按位异或 ^进行按位异或的两数的补码的对应二进制位上若相同运算结果的对应位上为0否则为1#includeiostreamusingnamespacestd;intmain(){inta-8,b6;intca^b;coutcendl;return0;}//结果为 -2d.按位取反 ~按位取反 ~ 和前三个位操作不一样前三个位操作需要两个操作数按位取反只需要一个操作数也叫单目操作符进行按位取反的数的运算结果的对应二进制位上的数是该数每一位上数的反面若原数该位上为1则运算结果该位上数为0#includeiostreamusingnamespacestd;intmain(){inta-1;intb~a;coutbendl;return0;}//结果为 0e.补充e.1 按位异或操作符的性质1.a^a0 (相同两数异或为0)2.0^aa (0和任何数异或结果为原数)3.a ^ a ^ b a ^ b ^ a (异或支持交换律)应用如何在不创建第三个变量的情况下实现两个数的互换#includeiostreamusingnamespacestd;intmain(){inta9;intb7;aa^b;ba^b;aa^b;coutaa bbendl;return0;}4.赋值操作符在变量创建时给变量一个初始值叫初始化而在变量创建好后再给变量值叫做赋值赋值操作符可分为 和 复合赋值符不再赘述下面直接说明复合赋值符平时我们常会对一个数进行自增自减操作如下inta6;aa7;aa-5;c语言中提供了复合赋值符来帮助我们简化上述操作的书写复合赋值符有 , -* , / ,% , , | ,^它们实际上是将 计算 和 赋值 这两步合在一起直接修改变量的值上面的代码可以简化为inta6;a7;a-5;5.双目与单目操作符前面说的大部分是需要有两个操作数的双目操作符有一些操作符只有一个操作数叫做单目操作符下面介绍- -(两个减号,中间无空格)-这四个单目操作符a.和 - -是一种自增操作符a相当于a1,分为前置和后置- -是一种自减操作符a- -相当于a-1,- -分为前置- -和后置- -前置和后置的区别前置后使用先运算后置先使用后计算//看下面代码#includeiostreamusingnamespacestd;intmain(){inta9;intba;couta a b bendl;intca;couta a c cendl;return0;}//后置先使用后计算所以int b a这一条语句b先被初始化为9然后a1,a变成10//前置后使用先运算所以int c a这一条语句a先1,a变成11然后c被初始化为11b.和-表示正号-表示负号对操作数的正负没有影响-会改变操作数的正负号#includeiostreamusingnamespacestd;intmain(){inta9;intb-a;couta a b bendl;return0;}//这里b-96.逻辑操作符逻辑运算符提供逻辑判断功能用于构建更复杂的表达式主要有下面三个运算符!逻辑取反运算符(改变单个表达式的真假)。逻辑与运算符就是并且的意思两侧的表达式都为真则为真否则为假||逻辑或运算符就是或者的意思两侧至少有一个表达式为真则为真否则为假7.关系操作符a.操作符介绍c语言中的用于比较的表达式叫做“关系表达式”其中使用的运算符叫做“关系运算符”主要有下面6个 大于运算符 小于运算符 大于等于运算符 小于等于运算符 相等运算符! 不等运算符例如2 9 , a ! i等表达式就为关系表达式关系表达式通常返回0或1表示真假0为假非0为真b.注意事项b.1 字符串比较字符串的比较规则是从头到尾依次比较字符的ASCLL码值比出大小就停止若从头到尾都是相等两字符串才相等而c语言中字符串的比较不能用以上的操作符进行比较因为它比较的是字符串的内存地址即首字符的地址#includestdio.hintmain(){chara[]123;charb[]123;printf(%d,ab);return0;}//结果为0因为a和b为两个独立的字符数组所以内存地址不一样,尽管字符串内容一样c语言中字符串的比较应该用strcmp需包含头文件string.hstrcmp返回值为0表示比较的两字符串相等返回0的数表示str1str2 , 0表示str1str2在c中字符串可以用类string表示该类里对以上的比较符号进行了重载所以可以直接用以上符号比较string类的字符串b.2 多个关系运算符不宜连用请看下面例子ijk上面的代码看似实现了 i 小于 j 小于 k 的比较但实际上关系表达式从左到右执行的是下面的式子(ij)k(i j)返回0或1然后0或1再和k比较大小这显然不是我们想要的要想实现数学中i j k这样的效果需要利用逻辑运算符(ij)(jk)//i j 并且 j k//类似的还有(ij)||(jk)(ij)8.条件操作符三目表达式条件操作符也叫三目操作符需要接受三个操作数的形式如下exp1 ? exp2 : exp3条件操作符的计算逻辑是如果 exp1 为真exp2 计算计算的结果是整个表达式的结果如果 exp1 为假exp3 计算计算的结果是整个表达式的结果下面使用条件操作符找到两个数中较大的那个#includeiostreamusingnamespacestd;intmain(){inta5;intb8;cout更大的数为(ab?a:b)endl;return0;}9.逗号表达式逗号表达式就是用逗号隔开的多个表达式exp1, exp2, exp3, …expN逗号表达式从左向右依次执行整个表达式的结果是最后一个表达式的结果#includeiostreamusingnamespacestd;intmain(){inta1;intb2;intc(ab,ab10,a,ba1);coutc cendl;return0;}10.下标引用操作符和函数调用操作符在访问数组中特定位置的数据时会用到[ ],里面填对应的数组下标就可以访问到数组对应位置的数据[ ]就是下标引用操作符而我们在使用printf函数打印内容时如 printf(“hehe\n”) ,函数名后的括号()就是函数调用操作符printf 和 hehe\n是它的两个操作数易知()至少有一个操作数即函数名如 test()11.结构体成员访问操作符在访问结构体内的成员时可以用到两个结构体成员访问操作符.和-.: 结构体变量.成员-: 结构体指针-成员#includestdio.h// 定义结构体类型structStudent{charname[20];intage;};intmain(){// 1. 用 . 访问结构体变量的成员structStudentstu{张三,18};printf(结构体变量访问姓名%s年龄%d\n,stu.name,stu.age);// 2. 用 - 访问结构体指针的成员structStudent*p_stustu;printf(结构体指针访问姓名%s年龄%d\n,p_stu-name,p_stu-age);return0;}二.操作符的属性C 语言的操作符有 2 个重要的属性优先级、结合性这两个属性决定了表达式求值的计算顺序。1.操作符的优先级优先级指的是如果一个表达式包含多个运算符哪个运算符应该优先执行各种运算符的优先级是不一样的但实际上操作符的运算顺序可以通过()来自主地规定,因为括号在 C 语言里拥有最高的运算优先级能够强行改变表达式原本默认的运算先后顺序就像数学的结合律与其死记运算符之间谁先谁后用()显然更加容易如(ab)*72.操作符的结合性如果两个运算符优先级相同优先级没办法确定先计算哪个了这时候就看结合性了根据运算符是左结合还是右结合决定执行顺序大部分运算符是左结合从左到右执行少数运算符是右结合从右到左执行比如赋值运算符。同样地可以用()来自主决定执行顺序所以操作符的结合性也不用死记