跳转到主内容
极星编程网:以代码为星,赴技术山海!

【C语言】/*操作符(下)*/

目录 一、操作符的分类 二、二进制和进制转换 2.1 进制 2.2 进制之间的转换 三、原码、反码、补码 四、单目操作符 五、逗号表达式 六、下标引用操作符[] 七、函数调用操作符() 八、结构体成员访问操作符 8.1 直接访问操作符(.) 8.2 间接访问操作符(->) 九、操作符的属性:优先级、结合性 9.1 优先级 9.2 结合性 十、表达式求值 10.1 整型提升 10.2 算数转换 十一、问题表达式 11.1 示例1 11.2 示例2 11.3 示例3 11.4 示例4 11.5 总结 一、操作符的分类 算术操作符: + 、- 、* 、/ 、% 移位操作符:

<<、>>

位操作符: &、|、^ 赋值操作符: = 、+= 、 -= 、 *= 、 /= 、%= 、

<<= 、>>=、&= 、|= 、^=

单⽬操作符: !

、++、--、&、*、+、- 、 ~ 、 sizeof、(类型) 关系操作符: > 、>= 、< 、<= 、 == 、 != 逻辑操作符: && 、|| 条件操作符: ?  : 逗号表达式: , 下标引⽤: [] 函数调⽤: () 结构成员访问: . 、 -> 二、二进制和进制转换 2.1 进制 1. 其实我们经常能听到2进制、8进制、10进制、16进制这样的讲法,那是什么意思呢?

其实2进制、8进制、10进制、16进制是数值的不同表⽰形式⽽已 。

2. 数值15的各种进制的表⽰形式: 15的2进制:1111 15的8进制:17 15的10进制:15 15的16进制:F 3.

4. 二进制的每一位只能是:0~1( 满2进1) 八进制的每一位只能是:0~7( 数字描述八进制会以0开头 ) 十进制的每一位只能是:0~9 十六进制的每一位只能是:0~9,A~F/a~f ( 数字描述十六进制会以0x/0X开头 ) 2.2 进制之间的转换 1. 2进制转10进制:2进制的每⼀位的权重,从右向左是: 2^0 , 2^1 , 2^3 ... 要计算一个二进制的十进制表示形式是多少,按如下图所示的方法计算即可 ( 二进制的每一位乘以对应权重再加起来即可 )。

2. 10进制转2进制: (让10进制数除2直到余数是0/1为止,再从下至上取余数即可 )

3. 2进制转8进制:( 将二进制从右向左3个3个划分为一组,不够的补0,每三个二进制数经过转换后就是一个8进制数,转换的方法如下图,每三个二进制位分别乘以421加起来就是转换的一个八进制位 )

4. 2进制转16进制:( 将二进制从右向左4个4个划分为一组,不够的补0,每四个二进制数经过转换后就是一个16进制数,转换的方法如下图,每四个二进制位分别乘以8421加起来就是转换的一个16进制位 )

三、原码、反码、补码 1. 整数的2进制表⽰⽅法有三种,即原码、反码和补码。

2. 有符号整数的三种表⽰⽅法均有符号位和数值位两部分 , 2进制序列中,最⾼位的1位是被当做符号位,剩余的都是数值位,符号位⽤0表⽰“正”,⽤1表⽰“负” 。

3. 正整数的原、反、补码都相同 。

4. 负整数的三种表⽰⽅法各不相同 。

5. 原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。

反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。

补码:反码+1就得到补码。

6. 例如:-10存放在整型变量a中,即用4字节来存放整型-10 原码:10000000 00000000 00000000 00001010 反码:111111111 111111111 111111111 111110101 补码:111111111 111111111 111111111 111110110 6. 补码得到原码也是可以使⽤:符号位不变,其他位按位取反,+1的操作。

7. 对于整形来说:整型数据存放内存中存放的是补码,在计算的时候也是用补码来计算的 ,为什么呢?

答:在计算机系统中,数值⼀律⽤补码来表⽰和存储。

原因在于,使⽤补码,可以将符号位和数值域统⼀ 处理;同时,加法和减法也可以统⼀处理(CPU只有加法器),此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

四、单目操作符 1. 单⽬操作符: !

、++、--、&、*、+、- 、 ~ 、 sizeof、(类型) 2. 单⽬操作符的特点是只有⼀个操作数。

3. 单目操作符 &,名为取地址操作符 ,利用它就 可以得到一个变量的地址 ,用printf打印地址时,用占位符%p(%p是以16进制形式打印地址,一个16进制位表示4个二进制位,例如,0x00AFF760用二进制表示为0000 0000 1010 1111 1111 0111 0110 0000); 取地址得到的地址是变量开辟的所有内存单元中的最低的地址 ,因为知道最低的地址,变量所开辟的其他内存单元的地址顺藤摸瓜就能找到。

4. 单目操作符 *,名为解引用操作符 ,它的操作数通常为一个指针变量,如果想要通过指针变量中的地址找到地址所指向的变量,可以通过对指针变量解引用就能找到,例如:int a = 10; int* pa = &a; 这两句代码中如果对变量pa进制解引用,则*pa就是变量a,对*pa赋值相当于对变量a赋值。

5. 单目操作符 ~,名为按位取反操作符,它的操作数必须是整型,对0按位取反的结果是-1 。

6. 温馨提示:橙色部分的单目操作符在 【C语言】/*操作符(上)*/-CSDN博客 中已经讲解过了。

五、逗号表达式 1. 逗号表达式的语法形式:exp1, exp2, exp3, …expN ( 就是用逗号隔开的多个表达式 ) 2. 特点: 从左向右依次执⾏,整个表达式的结果是最后⼀个表达式的结果 。

( 注意每个表达式都会计算 哦!

!

!

) 3. 示例1:下列为伪代码 a = get_val(); count_val(a); while (a > 0) { //业务处理 a = get_val(); count_val(a); } 4. 上面的伪代码,我们会发现第1、2行和6、7行这样写会有些冗余,学习完逗号表达式后,我们可以将代码改进为: while (a = get_val(), count_val(a), a>0) { //业务处理 }

//示例2:

#include int main() { int a = 1; int b = 2; int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式 // 0 12 12 13 printf("c = %d\n", c);//13 return 0; }

//示例3:

#include int main() { int a = 1; int b = 2; int c = 3; int d = 4; if (a = b + 1, c = a / 2, d > 0)//a=3,c=1,1 printf("hehe\n");//打印hehe return 0; } 六、下标引用操作符[] 1. 下标引用操作符[]有两个操作数 。

2. 语法形式:⼀个数组名[⼀个索引值] 3. 例如: int arr[10];//创建数组 arr[9] = 10;// 使用下标引⽤操作符访问数组元素 ,[ ]的两个操作数是arr和9 七、函数调用操作符() 1. 函数调用操作符()可以接受⼀个或者多个操作数,第⼀个操作数是函数名,剩余的操作数就是传递给函数的参数 。

( 函数调用操作符最少有一个操作数 ) 2. 函数调用时:Add(x, y);//这里的()就是函数调用操作符 函数定义时:void Mul(int x, int y);//这里的()是函数调用操作符 函数声明时:extern Sub(int x, int y);//这里的()也是函数调用操作符 八、结构体成员访问操作符 8.1 直接访问操作符(.) 1. 使用场景:当我们知道结构体变量名时,想要找到它的成员变量,可以对结构体变量使用直接访问操作符(.)来找到它的成员变量。

2. 语法形式:结构体变量.成员名 3. 举例:如下图代码

#include

struct Point { int x; int y; } p = { 1,2 };

int main() { printf("x: %d\ny: %d\n", p.x, p.y);//使用(.)找到p中的成员变量 return 0; } 8.2 间接访问操作符(->) 1. 使用场景:当我们只知道存放着结构体变量地址的指针变量时,想要找到该指针变量中地址所指向的结构体变量的成员变量,可以对该指针变量使用间接访问操作符(->)来找到成员变量。

2. 语法形式:结构体指针->成员名 3. 举例:如下图代码

#include

struct Point { int x; int y; }; int main() { struct Point p = { 3, 4 }; struct Point* ptr = &p; ptr->x = 10; ptr->y = 20; printf("x = %d\ny = %d\n", ptr->x, ptr->y);//相当于ptr找到它所指向的p的成员x、y return 0; } 九、操作符的属性:优先级、结合性 操作符的优先级和结合性决定了表达式求值的计算顺序 。

9.1 优先级 1. 概念:优先级讨论的是,如果⼀个表达式包含多个操作符,哪个操作符应该优先执⾏的问题。

2. 例如表达式 3 + 4 * 5 ⾥⾯既有加法操作符( + ),⼜有乘法操作符( * ),由于乘法的优先级⾼于加法,所以会先计算 4 * 5 ,⽽不是先计算 3 + 4 。

3. 优先级起作用的条件 :对象: 相邻操作符, 效果: 优先级高的先执行 。

4. 由于圆括号的优先级最高,因此在实际应用过程中可以使用它改变其他操作符的优先级 。

9.2 结合性 1. 概念:如果两个操作符优先级相同,优先级是没办法确定先计算哪个得,这时候就得看结合性,根据操作符是左结合,还是右结合,决定执⾏顺序。

⼤部分操作符是左结合(从左向右执⾏),少数运算符是右结合(从右向左执⾏),⽐如赋值运算符( = )。

2. 例如表达式5 * 6 / 2里面 * 和 / 的优先级相同,它们都是左结合运算符,所以从左到右执⾏,先计算 5 * 6 , 再计算 6 / 2 。

3.  各操作符优先级和结合性的官方参考: C 运算符优先级 - cppreference.com

十、表达式求值 10.1 整型提升 1. 概念: C语⾔中整型算术运算总是⾄少以默认整型类型int的精度来进⾏运算的 ,为了获得这个精度,表达式中的字符和短整型操作数在使⽤之前会被转换为普通整型,这种转换称为整型提升。

( 注意:整型提升针对的是整型 ) 2. 整型提升的意义: 表达式的整型运算要在CPU的相应运算器件内执⾏,CPU内整型运算器的操作数的字节⻓度⼀般就是int的字节⻓度,同时也是CPU的通⽤寄存器的⻓度。

因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准⻓度。

通⽤CPU是难以直接实现两个8⽐特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。

所以,表达式中各种⻓度可能⼩于int⻓度的整型值,都必须先转换为int或unsigned int,然后才能送⼊CPU去执⾏运算。

3. //实例1 char a,b,c; ... a = b + c; 4. 上面示例中b和c的值被提升为普通整型,然后再执⾏加法运算,加法运算完成之后,结果将被截断,然后再存储于a中。

5. 如何进⾏整体提升呢?

① 有符号整数提升按照符号位来提升。

② ⽆符号整数提升,⾼位补0。

6. //负数的整形提升 char c1 = -1; 变量c1的⼆进制位(补码)中只有8个⽐特位: 1111111 因为 char 为有符号的 char 所以整形提升的时候,⾼位补充符号位,即为1 提升之后的结果是: 11111111 11111111 11111111 11111111 7. //正数的整形提升 char c2 = 1; 变量c2的⼆进制位(补码)中只有8个⽐特位: 00000001 因为 char 为有符号的 char 所以整形提升的时候,⾼位补充符号位,即为0 提升之后的结果是: 00000000 00000000 00000000 00000001 10.2 算数转换 1. 如果表达式中各操作数的类型不统一,编译器会先将该表达式中各操作符的类型统一后才会开始进行运算,这种将表达式各操作数统一类型的行为叫做算术转换。

2. 下⾯的层次体系称为寻常算术转换。

long double double float unsigned long int long int unsigned int int 说明: 找表达式中各 操作数的类型在上⾯这个列表中排名最靠前的类型,然后将所有不是该类型的操作数全都转换成该类型再进行表达式运算 。

十一、问题表达式 11.1 示例1 1. //表达式的求值部分由操作符的优先级决定 //表达式1 a*b + c*d + e*f 2. 表达式1在计算的时候,由于 * ⽐ + 的优先级⾼, 只能保证, * 的计算是⽐ + 早,但是优先级并不能决定第三个 * ⽐第⼀个 + 早执⾏ 。

3. 所以即使我们知道了优先级和结合性,也未必能准确得出表达式的计算顺序,它可能不具有唯一性,当表达式中的a、b、c、d、e、f不是单纯的数值而是表达式时,可想而知在不具有唯一性的表达式中,计算的结果可能是有所差异的。

4. 解决方法,不要写有连续几个操作符的表达式,把复杂的计算尽量拆开去写;或者在复杂的表达式中用()来明确表示哪个先算,哪个后算 。

11.2 示例2 1. //表达式2 c + --c; 2. 上面的表达式中,操作符的优先级只能决定⾃减 -- 的运算在 + 的运算的前⾯,但是我们并没有办法得知, + 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。

(例如,c原本为5,--c后c为4,但我们无法判断,第一个c是将5带入表达式,还是将4带入表达式。

如果要修改,可以写成c + (--c),这样写会默认+ 的左操作数的获取在右操作数之后。

) 11.3 示例3 1. //表达式3 int main() { int i = 10; i = i-- - --i * ( i = -3 ) * i++ + ++i; printf("i = %d\n", i); return 0; } 2. 表达式3在不同编译器中测试结果:

3. 这个示例也说明了不同的编译器对复杂表达式的解读方式是有差异的。

11.4 示例4

#include

int fun() { static int count = 1; return ++count; }

int main() { int answer; answer = fun() - fun() * fun(); printf( "%d\n", answer);//输出多少?

return 0; }

1. 虽然在⼤多数的编译器上求得结果都是相同的,但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。

函数的调⽤先后顺序⽆法通过操作符的优先级确定 ,answer = fun() - fun() * fun()可能是 2-3*4 也可能是 3-4*2 等。

11.5 总结 1. 即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的计算路径,那这个表达式就是存在潜在⻛险的,建议不要写出特别负责的表达式。

本篇文章已完结,谢谢支持哟 ^^ !

!

!

相关文章