【C语言】操作符 目录进制字节原码、反码与补码移位操作符位操作符按位取反操作符sizeof条件操作符逗号表达式隐式类型转换整形提升算术转换操作符的优先级、结合性进制进制也就是进位计数制是人为定义的带进位的计数方法。一、常见进制二进制基数为2使用0和1两个数字来表示数。在计算机中二进制被广泛应用因为计算机的硬件电路可以很方便地表示0和1两种状态。十进制基数为10使用0 1 2 3 4 5 6 7 8 9 这十个数字表示数。这是我们日常生活中最常用的进制。十六进制使用0到 9以及A 到F这十六个符号来表示数其中A 到 F 分别表示10 11 12 13 14 15。十六进制常用于表示计算机内存地址等信息因为它可以用较短的符号表示较大的数。二、进制转换十进制数 → R进制数● 分整数和小数两部分处理:● 整数部分:连续除以R,取余反序排列,直到商为0。● 小数部分:连续乘以R,取整正序排列,直到乘积的小数部分为0,或者满足误差要求。例如十进制数 13 转二进制13 ÷ 2 6 ⋯ ⋯ 113÷26 ⋯⋯ 16 ÷ 2 3 ⋯ ⋯ 06÷23⋯⋯ 03 ÷ 2 1 ⋯ ⋯ 13÷21⋯⋯ 11 ÷ 2 0 ⋯ ⋯ 11÷20⋯⋯1 从下往上将余数排列得到1101就是13的二进制表示。十六进制转二进制每一位十六进制数可以转换为4位二进制数。例如十六进制数2A2转换为二进制是0010A即10转换为二进制是1010所以2A转换为二进制就是00101010。二进制转十六进制将二进制数从右到左每4位一组分别转换为十六进制数。例如二进制数11011010分组为1101和10101101转换为十六进制是D1010转换为十六进制是A所以11011010转换为十六进制是DA。八进制与二进制互化方法与上述相似原码、反码与补码计算机中的整数有三种2进制表示方法 , 即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用 0 表示 “正” , 用 1 表示 “负”正数的原码、反码、补码是相同的负数的原码、反码、补码要经过计算的原码按照一个数的正负, 直接写出它的二进制表示形式得到的就是原码反码反码是原码的符号位不变, 其他位按位取反补码补码是反码1例整型占4个字节 (32bit)如 10 的00000000000000000000000000001010 - 原码00000000000000000000000000001010 - 反码00000000000000000000000000001010 - 补码-10 的10000000000000000000000000001010 - 原码111111111111111111111111111111110101 - 反码111111111111111111111111111111110110 - 补码原码、反码、补码的互相转化取反符号位不变, 其他位按位取反减去一不好得出结果时也可以取反1在计算机中为什么要用用补码存储一个数呢在计算机系统中 , 数值一律用补码来表示和存储。原因在于,使用补码 , 可以将符号位和数值域统一处理 ; 同时 ,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。一个简单的例子如果计算机用原码存储一个数00000000 00000000 00000000 00000001 - 1 的原码10000000 00000000 00000000 00000001 - 负1的原码我们计算 1 - 1 就是 1 -110000000 00000000 00000000 00000010 - 1 -1的结果是 -2这就出现问题了结果显然不对。如果我们使用补码00000000 00000000 00000000 00000001 - 1 的补码11111111 11111111 11111111 11111111 - 负1的补码现在用 1 的补码和 -1 的补码相加1 00000000 00000000 00000000 00000000由于整形占 4 个字节所以最高位的那个 1 会溢出截断最终的结果是00000000 00000000 00000000 00000000这样结果就正确了所以计算机要用补码存储一个数。移位操作符左移操作符b am作用a的补码左边丢弃m位右边补m个零然后作为b的补码赋值给ba 的值没有变化。右移操作符b am作用1、算术右移大多数a的补码右边丢弃m位左边补上原来的符号然后作为b的补码赋值给ba 的值没有变化。这个操作等价于将 a 除以 2 的 m 次幂比如 a a 1等价于 a / 22、逻辑右移略左移操作符例子int a 10; 10 的补码00000000000000000000000000001010int b a 1; a 1 : 00000000000000000000000000010100 —值为 20 赋给 b但 a 的值没有变化。printf(%d,b)//打印出来的是原码若a为负数如int a -10-10 的10000000000000000000000000001010 - 原码111111111111111111111111111111110101 - 反码111111111111111111111111111111110110 - 补码int b a 1;a的补码左移一位作为b的补码111111111111111111111111111111101100 - b的补码由b的补码得到b的原码111111111111111111111111111111101100 - b的补码111111111111111111111111111111101011 - b的反码10000000000000000000000000010100 - b的原码位操作符按位与c ab作用a 和 b 的补码中对应位的二进制数有0则 c 的对应位为0都为 1 则 c 的对应位才为1得到的结果作为 c 的补码。一个神奇的表达式a a a - 1 这个表达式的作用每执行一次a 的二进制表示去掉最右边的一个 1可以用来计算一个数的二进制表示中有多少个 1。对于一个二进制数减去 1 一定能将最右边的 1 所在的数位变成 0 如 00000001减去 1 变成 0000000000000010 减去 1 变成 00000001 再 “与上”减 1 之前的数减 1 之前的数最右边的 1 对应的一定是 0十进制数中 2 的 k 次方的二进制表示只有若干 0 和一个 1。如十进制2 二进制00000010十进制4 二进制00000100十进制8 二进制00001000利用这个性质可以设计出一个检查一个数是不是 2 的 k 次方的代码只需一条 If 语句搞定if a a - 1 0按位或c a|b作用a 和 b 的补码中对应位的二进制数有1则 c 的对应位为1都为0则 c 的对应位才为0得到的结果作为 c 的补码。按位异或c a^b作用a 和 b 的补码中对应位的二进制数相同则 c 对应位为 0 不同则 c 对应位为 1 得到的结果作为 c 的补码。性质1、a ^ a 02、a ^ 0 a3、a ^ b ^ c a ^ c ^ b ( 交换律 4、(a ^ b) ^ c a ^ (c ^ b) 结合律应用举例不使用中间变量交换两个变量的值int a 5; int b 3; a a ^ b;// ----1 b a ^ b;// ----2 把 1 式当作公式代入b a ^ b ^ a而 a ^ b ^ a 等于 a a a ^ b;// 把 2 式当作公式代入a a ^ a ^ b而a ^ a ^ b 等于 b其实这种方法效率不如使用中间变量且可读性差只能交换整数。也可以使用这种方法int a 5; int b 3; b a b//可能会溢出 a b - a b b - a但若 a、b 都是很大的数字则 b a b这一步可能会溢出。按位取反操作符b ~a作用将 a 的原码按位取反包括符号位然后作为 b 的补码赋给 b 。如int a 0b 0b ~aa 的原码00000000000000000000000000000000取反1111111111111111111111111111111111111 —— 把它作为 b 的补码 b 的值为 -1 。移位操作符、位操作符、按位取反操作符都是针对二进制数的运算符。综合运用1、修改一个变量的 bit 位想要修改一个变量的 bit 位用指针是不行的因为指针访问的是字节。可以使用移位操作符、位操作符、按位取反操作符将一个数的二进制表示的第 x 位从0开始修改为 1a | 1 x);将一个数的二进制表示的第 x 位从0开始修改为 0a ( ~ (1 x) );int main() { int a 9; //00000000000000000000000000001001 //00000000000000000000000000010000 1 4 //把a的二进制中第5位改成1 a | (1 4); //把a的二进制中的第5位改回来,变成0 //00000000000000000000000000011001 //11111111111111111111111111101111 //00000000000000000000000000001001 a (~(1 4)); return 0; }2、得到二进制任一位的数( m i ) 1sizeofsizeof 是既是操作符也是关键字。作用计算并返回操作数的类型长度以字节为单位返回值的类型是 size_t 这是专门为 sizeof 运算符创造的类型它其实是 unsigned int 类型操作数可以是类型名、普通变量、数组名、数组元素等。函数调用的时候要加 但 sizeof 可以不加 说明 sizeof 不是函数但是如果 sizeof 的操作数是类型名的时候 不能省略sizeof int -----错误请分析以下代码为什么 sizeof(a b 1) 这个表达式的值为 2 sizeof(a b 1) 中明明已经将 b 1 赋给了 a 为什么 a 的值仍然是 1 这是因为sizeof(a b 1) 中将整形数据赋值给短整形数据结果为短整形sizeof 在编译时已确定结果sizeof(a b 1) 这条语句在编译时已确定为 2在运行时相当于printf(%d\n, 2);sizeof ( ) 括号内的表达式不计算所以 a 的值依然是 1 。字符串长度的求法1、strlen函数如 char a[ ] abc 则 strlena 3 ,不包括\02、sizeof操作符如 char a[ ] abc 则 sizeofa 4 包括\0,字符数组的最后一个元素的下标 sizeofa/sizeofa[0]) - 2减去2是因为要减去 \0 和数组下标从0开始sizeof 与数组上图中a 是数组名a 数组有 10 个类型为 int 的元素 sizeof (a计算的是数组的所有元素的类型所占字节的总和也就是一个数组占用的字节数。请分析以下代码为什么 test 函数中 sizeof(arr) 的值是 8 呢不应该是 40 吗这是因为数组传参传的是指针即使形参写成数组的形式本质上还是指针。所以 arr 其实是指针变量sizeof(arr) 计算的是指针变量 arr 所占的字节数而不是数组所占的字节数。数组的类型int arr[5]; int 是数组元素的类型int [5] 才是数组的类型。printf%zd\n,sizeof(arr)); //输出5*sizeofint20printf%zd\n,sizeof(int [5])); //输出5*sizeofint20数组名表示整个数组只有两种情况1. sizeof(数组名) , 数组名表示整个数组。计算的是整个数组的大小只有数组名单独放在括号里才表示整个数组若 a 是一维数组名则sizeofai此时 a i 表示 a[ i ] 这个数组元素的地址只要是地址它的大小就是 4 字节或 8 字节。所以 sizeofai这个表达式的结果就是 4 或 8。sizeofaa 是整个数组的地址只要是地址它的大小就是 4 字节或 8 字节所以 sizeofai这个表达式的结果就是 4 或 8。sizeof*a等价于 sizeofa这个表达式的结果是整个数组所占的字节数2. 数组名 , 数组名表示整个数组。取出的是整个数组的地址除此之外 , 所有的数组名都是数组首元素的地址//一维数组int a[ ] { 1,2,3,4 };// 所占字节数 4*416printf(%d\n, sizeof(a));//16printf(%d\n, sizeof(a 0));//a0 其实是数组第一个元素的地址,是地址就是 4/8 字节printf(%d\n, sizeof(*a));//*a是数组首元素,计算的是数组首元素的类型int大小,4printf(%d\n, sizeof(a 1));//a1是第二个元素的地址 , 是地址大小就是 4/8printf(%d\n, sizeof(a[1]));//a[1] 是第二个元素,计算的是第二个元素的类型int大小4printf(%d\n, sizeof(a));//a是整个数组的地址,整个数组的地址也是地址 , 地址的大小就是4/8字节//a --- 类型: int(*)[4]printf(d\n, sizeof(*a));//a是数组的地址,*a就是拿到了数组 , *a -- a , a就是数组名 , sizeof(*a) -- sizeof(a) /计算的是整个数组的大小,单位是字节 - 16printf(d\n, sizeof(a 1));//a是整个数组的地址 , a1,跳过整个数组 , 指向数组后边的空间 , 是一个地址 , 大小是4/8字节printf(%d\n, sizeof(a[0]));//a[0]是首元素的地址 , 计算的是首元素地址的大小 , 4/8 字节printf(%d\n, sizeof(a[0] 1));//a[0] 1是第二个元素的地址 , 地址的大小就是 4/8 字节//字符数组char arr[] { a , b , c , d , e , f } ; \\ f 后面没有 \0printf(%d\n, strlen(arr));//从‘a开始找 \0 , 随机值printf(%d\n, strlen(arr 0));//也是从‘a开始找 \0随机值//printf(%d\n, strlen(*arr));//strlen(a)-strlen(97) , 非法访问0x00 00 00 61 这个地址未分配不能访问)-err//printf(%d\n, strlen(arr[1]));// b - 98 , 和上面的代码类似 , 是非法访问 - errprintf(%d\n, strlen(arr));//arr虽然是数组的地址 , 但是也是从数组起始位置开始找 \0 的 , 还是随机值printf(%d\n, strlen(arr 1)); //arr是数组的地址 , arr1是跳过整个数组的地址,求字符串长度也是随机值printf(%d\n, strlen(arr[0] 1));//arr[0] 1是第二个元素的地址,是 b 的地址 , 求字符串长度也是随机值条件操作符条件操作符也称为三目操作符通常的语法形式是condition? expression1 : expression2。作用如果condition为真或满足某些条件则计算并返回expression1的值否则计算并返回expression2的值。逗号表达式基本语法使用逗号将两个或多个表达式分隔开例如expression1, expression2。求值顺序从左到右依次对每个表达式进行求值整个逗号表达式的值是最后一个表达式的值。应用场景for循环中的初始化和更新例如for(int i 0,j 10; i j; i,j--) { // 循环体 }。函数调用中的多个表达式例如int a 1 , b 2;printf( %d\n , ( a 1 , b 2 , a*b) ); // 输出 6。赋值语句中的多个操作逗号表达式可以用于在单个赋值语句中执行多个操作。例如int x;x (1 , 2 , 3); // x的值为3。隐式类型转换整形提升C的整型算术运算总是至少以缺省默认整型类型的精度来进行的。为了获得这个精度,表达式中的字符1个字节和短整型两个字节操作数在使用之前被转换为普通整型int, 这种转换称为整型提升。整型提升的意义:表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。例如char a 1;char b 2;char c a b;a 和 b 的值被提升为整形然后再执行加法运算。加法运算完成之后结果将被截断然后存储于 c 中。如何进行整形提升整形提升是按照变量的数据类型的符号位来提升的有符号的数据类型整形提升时补码的高位补充符号位的数无符号的数据类型整形提升时补码的高位补充0.以 char 为例char c1 - 1;变量c1的二进制位(补码)中只有8个比特位:11111111因为 char 为有符号的 char 所以整形提升的时候 , 高位补充符号位 , 即为1提升之后的结果是:11111111111111111111111111111111char c2 1;变量c2的二进制位(补码)中只有8个比特位:00000001因为 char 为有符号的 char 所以整形提升的时候 , 高位补充符号位 , 即为0提升之后的结果是:00000000000000000000000000000001//无符号整形提升,高位补0分析以下代码计算过程char a 3 3 的原码00000000000000000000000000000011由于 char 类型只占 1 个字节所以将 3 存储到 a 时要发生截断因此a 的补码00000011内存中以补码形式存储char b 127;b 的补码01111111char c a b要先对 a、b 进行整形提升a 整形提升后00000000000000000000000000000011b 整形提升后00000000000000000000000001111111a b 00000000000000000000000010000010截断后存储到 c c 的补码10000010最高位是 1 c 已经是负数了printf(%d, c);%d 是以有符号十进制整数形式打印二进制原码所以要将 c 进行整形提升再找到 c 的原码。c 进行整形提升11111111111111111111111110000010取反1得到原码100000000000000000000001111110 这是 -126 的原码所以打印的是 -126 。有符号的 char 类型signed char的取值范围是 -128 到 127 如何得来的signed char 类型占 1 个字节即 8 bit 1 个 bit 有 0 或者 1 两种可能8 个 bit 可以有 2 的 8 次方即 256 种组合可以表示十进制的0 到 257但最高位为符号位即表示数值实际有 7 个 bit 位128 种组合当最高位为 0 表示正数时可以表示十进制的 0 到 127 当最高位为 1 表示负数时可以表示十进制的 -12810000000 规定为 -128 到 -1 11111111所以有符号的 char 类型signed char的取值范围是 -128 到 127 。其他类型如 int 类型可以依此类推。对于 unsigned char 无符号字符型最高位不再表示符号此时 8 个 bit 位都表示数值所以 unsigned char 的取值范围是 0 到 255 其他类型如 unsigned int 类型也可以依此类推。char a 127a01111111此时对 a 加一a 110000000这个二进制数规定为 -128a 的值为 -128再对 a 加一a 210000001-127a 的值为 -127一直对 a 加一a 的值会从 -127 到 -126 ... 一直到 -111111111再加一1000000001溢出a 的值变为00000000即0a 的值变为 0。所以说对一个 char 类型的数无休止的加一这个数不会一直增加它的值呈现为在一定范围内周期性的变化。请分析以下代码最后只打印出了 c 请思考为什么tip0xb6 表示十六进制数字 b6 二进制形式为10110110十六进制数字每一位占半个个字节 运算有时也要整形提升 下面这个例子也说明了整形提升的存在 “”也是操作符对 c 要整形提升算术转换C语言中的算术转换是指在执行算术运算时操作数的类型会被自动转换为一种公共类型以确保运算的正确性。这种转换通常发生在二元运算符如 、-、*、/ 等的操作数之间。如果某个操作数的类型在上面这个列表中排名较低 , 那么首先要转换为另外一个操作数的类型后执行运算。如int a 1foalt b 3.14f ;a b ;在执行 a b 时会把 a 转换为 float 类型再进行相加。操作符的优先级、结合性总结下标圆括号成员选择 单目运算符 算术运算符 位移运算符 关系运算符 按位运算符 逻辑运算符 三目运算符 赋值运算符 逗号运算符当多种操作符在一个表达式时首先确定优先级,相邻操作符按照优先级高低计算 优先级相同的情况下,结合性才起作用。有些表达式的计算路径不是唯一确定的这种表达式容易出 BUG 称为问题表达式比如例1a*b c*d e*f ;1 3 2 5 4 ----计算顺序1 1 4 2 5 3 ----计算顺序2如果 a*b 的结果影响 c*d 的结果那么就容易出现BUG例2​ int fun() { static int count 1; return count; } int main() { int a fun() - fun() * fun() //这里可能会出现BUG //因为你不知道编译器会先调用 - 号前面的 fun还是 * 前面的 fun return 0; } ​例3可以在不同的编译器运行以上代码ret 的结果可能不同。