彻底搞懂操作符:C语言表达式核心手册
C语言操作符一.进制转换1.有哪些进制2.如何转换二.原码、反码、补码(二进制)1.有符号2.无符号三.位移操作符( )1.左移操作符( )2.右移操作符( )四.位操作符( | ^ ~)五.逗号表达式六.下标访问( [ ] )和函数调用( ( ) )七.结构成员访问操作符1.什么是结构体以及结构体的声明2.如何结构体变量的定义以及初始化3.结构体成员直接访问和间接访问3.1直接访问3.2间接访问八.操作符的优先级和结合性1.优先级2.结合性2.1左结合2.2右结合九.表达式求值1.整形提升1.1何为整型提升1.2整型提升的意义1.3如何整型提升2.算术转换一.进制转换1.有哪些进制二进制是由0和1两个数组成满2进1例如4的二进制为0100八进制是由0和1两个数组成满8进1例如10的八进制为012十进制是由0~9组成的也是我们平常算数所用的十六进制是由0至9,a至f组成满16进1例如30的十六进制为1b那如何将这些进制进行转换呢2.如何转换我们可以利用二进制当中间枢纽例如八进制转十六进制可以先将八进制转换为二进制再将二进制转换为十六进制。如八进制0153转换成十六进制首先转换为二进制因为我们知道八进制的每一位都相当于二进制的三位例如0153中的3转换二进制是011同理0153中的5转换二进制是1010153中的1转换二进制是01按排列从右到左可得八进制0153的二进制为011010110 11 0 10 1 1153再将二进制01101011转换为十六进制因为十六进制的每一位相当于二进制的四位例如01101011中的1011转换十六进制是b01101011中的0110转换十六进制是6得到二级制01101011转换成十六进制为6b0 1 1 01 0 1 16b最后我们得到八进制0153转换成十六进制为6b。总结某进制—二进制—想要的进制二.原码、反码、补码(二进制)整数的二进制表示法有三种即原码、反码和补码分为两类一类是有符号的另一类是无符号的。原码直接将数值按照正负数的形式翻译成二进制得到的就是原码。反码将原码的符号位不变(先看有没有符号位)其他位依次按位取反就可以得到反码。补码反码1就得到补码。1.有符号有符号整数的三种表示方法均有符号位和数值位两部分2进制序列中最高位的1位是被当做符号位剩余的都是数值位。符号位都是用0表示“正”用1表示“负”。例如(取32位)原码00000000000000000000000000000101反码111111111111111111111111111111111010补码1111111111111111111111111111111110112.无符号无符号整数的三种 2 进制表示相同没有符号位每⼀位都是数值位。例如(取32位)原码00000000000000000000000000000101反码00000000000000000000000000000101补码00000000000000000000000000000101对于整形来说数据存放内存中其实存放的是补码。缘由因为在计算机系统中数值一律用补码来表示和存储。原因在于使用补码可以将符号位和数值域统一处理同时加法和减法也可以统一处理CPU只有加法器此外补码与原码相互转换其运算 过程是相同的不需要额外的硬件电路。三.位移操作符( )移位操作符的操作数只能是整数。1.左移操作符( )移位规则左边抛弃、右边补0。如图所示补码00000000000000000000000000000101过程--00000000000000000000000000000101--0结果00000000000000000000000000001010代码参考#includestdio.hintmain(){intnum10;intnnum1;printf(n %d\n,n);printf(num %d\n,num);return0;}2.右移操作符( )移位规则首先右移运算分两种1.逻辑右移左边用0填充右边丢弃。2.算术右移左边用原该值的符号位填充右边丢弃。逻辑右移补码11111111111111111111111111111111过程0--11111111111111111111111111111111--结果01111111111111111111111111111111算术右移补码11111111111111111111111111111111过程1--11111111111111111111111111111111--结果11111111111111111111111111111111代码参考#includestdio.hintmain(){intnum10;intnnum1;//int n num -1;对于移位运算符不要移动负数位这个是标准未定义的,所以这种写法是错误的printf(n %d\n,n);printf(num %d\n,num);return0;}四.位操作符( | ^ ~)位操作符的操作数必须是整数。写一个小代码如下所示#includestdio.hintmain(){intnum1-3;intnum25;printf(%d\n,num1num2);printf(%d\n,num1|num2);printf(%d\n,num1^num2);printf(%d\n,~0);return0;}运行结果我们可以看到num1 num2 num2num1 | num2 num1num1 ^ num2 -1~0 -1这是如何计算的呢这就要结合前面所说的补码以及位操作符各自的计算规则了最后返回原码。按位与 规则两位都为 1结果才为 1补码-3110150101结果0101得num1 num2 num2按位或 |规则有一个为 1结果就是 1补码-3110150101结果1101得num1 | num2 num1按位或异 ^规则相同为 0不同为 1补码-3110150101结果1000其中1是符号位最后得num1 ^ num2 -8按位或异 ~规则0 变 11 变 0所以~0 1; ~1 0;五.逗号表达式逗号表达式就是用逗号隔开的多个表达式从左向右依次执行。表达式1,表达式2,表达式3,...,表达式n整个表达式的结果是最后⼀个表达式的结果。例如inta(34,5*2,10-3);先算 34 -- 7结果丢弃再算 5*2 -- 10结果丢弃最后算 10-3 -- 7所以a 7六.下标访问( [ ] )和函数调用( ( ) )这在之前数组和函数分别有讲就不再罗嗦了直接看代码参考一下下标访问 [ ]intarr[10];//创建数组arr[9]10;//实⽤下标引⽤操作符,[ ]的两个操作数是arr和9函数调用 ()#includestdio.hret_typefun_name()//ret_type--函数返回类型{//函数体}ret_typefun_name1(形式参数)//ret_type--函数返回类型{//函数体}intmain(){fun_name();//这⾥的()就是作为函数调用操作符fun_name1(形式参数);//这⾥的()就是函数调用操作符。//函数名return0;}七.结构成员访问操作符1.什么是结构体以及结构体的声明结构体就是 C 语言里你自己定义的一种 “复合型数据类型”可以把多个不同类型的变量打包在一起。结构体的声明structtag{member-list;}variable-list;注意其中variable-list可以省略但是;不可以举个例子一个学生有他自己的学号(id)、姓名(name)、年龄(age)、成绩(score)等等将这些单独定义变量(id、name、age、score)用结构体就能打包成一个整体如下所示//定义一个结构体类型structStudent{intid;//学号charname[20];//姓名intage;//年龄floatscore;//成绩};2.如何结构体变量的定义以及初始化定义结构体变量的定义structStudentstu1;直接初始化structStudentstu1{007,张三,18,95.5};3.结构体成员直接访问和间接访问3.1直接访问根据上述来写一简单代码展示结构体,如下所示#includestdio.hstructStudent{intid;//学号charname[20];//姓名intage;//年龄floatscore;//成绩};intmain(){// 定义结构体变量并赋值structStudentstu{007,张三,18,92.5};printf(学号%d\n,stu.id);printf(姓名%s\n,stu.name);printf(年龄%d\n,stu.age);printf(成绩%.1f\n,stu.score);return0;}其中结构体成员的直接访问是通过点操作符(.)访问的例如stu.id直接访问struct Student stu中的007。3.2间接访问如果是指针则需要间接访问其对应的地址用-#includestdio.hstructStudent{intid;charname[20];intage;floatscore;};intmain(){structStudentstu{1001,小李,19,88.5};structStudent*pstu;printf(学号%d\n,p-id);printf(姓名%s\n,p-name);printf(年龄%d\n,p-age);printf(成绩%.1f\n,p-score);return0;}由此我们可以看出结构体变量用.stu.id结构体指针用-p - id(p - id本质等价于(*p).id)八.操作符的优先级和结合性1.优先级操作符优先级就是代码里的 “先算谁、后算谁”和数学里 “先乘除后加减” 是一个道理。例如程序代码中的int sum (ab) * c,()的等级比*高所以(ab)先运算最后在乘c#includestdio.hintmain(){inta2,b3,c4;intsum(ab)*c;printf(%d\n,sum);return0;}算出的结果是20而不会是别的。优先级表相关链接2.结合性优先级相同的时候从左往右算(左结合)还是从右往左算(右结合)。2.1左结合算术-*/%关系!逻辑||移位逗号,例如a-b-c;//加减乘除等价于(a - b) - c2.2右结合一元运算符!-- -正负号赋值运算符-*/三元运算符? :例如abc10;//赋值等价于a (b (c 10))a?b:c?d:e;//三元运算符等价于a ? b : (c ? d : e)九.表达式求值1.整形提升1.1何为整型提升整型提升是 C 语言中小于int的整型在表达式中被自动隐式转换为int/unsigned int的规则是表达式求值的前置步骤目的是提升运算效率、避免精度丢失。核心规则若原类型所有值能被int表示 → 提升为int否则–提升为unsigned int有符号类型符号扩展高位补符号位无符号类型零扩展高位补 01.2整型提升的意义表达式的整型运算要在CPU的相应运算器件内执行CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度同时也是CPU的通用寄存器的长度。因此即使两个char类型的相加在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPUgeneral-purposeCPU是难以直接实现两个8比特字节直接相加运算虽然机器指令中可能有这种字节相加指令。所以表达式中各种长度可能小于int长度的整型值都必须先转换为int或unsigned int然后才能送入CPU去执行运算。例如chara,b,c;...charabc;b和c的值被提升为普通整型(int)然后再执行加法运算。加法运算完成之后结果将被截断然后再存储于a中。1.3如何整型提升举个例子先看下面一段代码算算它的结果是多少#includestdio.hintmain(){chara20;charb120;charcab;printf(%d\n,c);return0;}第一直觉会告诉你等于140那就把问题想得太简单了最终运行结果答案是-116这是为什么呢整形提升规则有符号整数提升是按照变量的数据类型的符号位来提升的无符号整数提升高位补0。#includestdio.hintmain(){chara20;//截断后存储到a//00000000000000000000000000010100--补码//00010100charb120;//00000000000000000000000001111000--补码//011111000charcab;//00000000000000000000000000010100//00000000000000000000000001111000//00000000000000000000000010001100//00000000000000000000000110001100//10001100---cprintf(%d\n,c);//11111111111111111111111110001100--补码//10000000000000000000000001110011//10000000000000000000000001110100--原码//-116return0;}2.算术转换在上面讲了操作符的优先性和结合性以及整型提升那我们的算术转换就会更好理解了。算术转换就是如果某个操作符的各个操作数属于不同的类型那么除非其中一个操作数的转换为另一个操作数的类型否则操作就无法进行。寻常算术转换(从大到小依次排列)1.longdouble2.double3.float4.unsignedlongint5.longint6.unsignedint7.int如果某个操作数的类型在上面这个列表中排名靠后那么首先要转换为另外⼀个操作数的类型后执行运算。