C 语言部分操作符详解 -- 进制转换,原码、反码、补码,位操作符,逗号表达式,操作符的优先级和结合性,整型提升,算术转换

目录

1. 操作符分类

2. 二进制和进制转换

2.1 2 进制和 10 进制的相互转换

2.2 2 进制和 8 进制、16 进制的转换

3. 原码、反码、补码

4. 位操作符

5. 逗号表达式

6. 下表访问[]、函数调用()

8. 操作符的属性:优先级、结合性

9. 表达式求值

9.1 整型提升

9.2 算术转换


1. 操作符分类

        C 语言中很很多操作符,根据功能可以将操作符分为很多类,比如算数操作符、位操作符、赋值操作符、关系操作符、逻辑操作符等等。关系操作符、逻辑操作符以及条件操作符https://blog.csdn.net/CSQCSQCC/article/details/137715007 中已经有所介绍,算数操作符以及部分单目操作符https://blog.csdn.net/CSQCSQCC/article/details/148639715中已经有所介绍。

2. 二进制和进制转换

        2 进制、8 进制、10 进制、16 进制是数值的不同表示形式

15的2进制:1111
15的8进制:17
15的10进制:15
15的16进制:F

        10 进制就是满 10 进 1,每一位都是 0-9 的数组组成。相对的二进制就是满 2 进 1,每一位都是 0-1 的数字组成。 

2.1 2 进制和 10 进制的相互转换

        10 进制的每一位是权重,10 进制的数字从右往左是个位、十位、百位...,分别每一位的权重是 10 的 0 次方,10 的 1 次方,10 的 2 次方...

        2 进制和 10 进制类似,只不过 2 进制的每一位权重不一样,从右往左分别是 2 的 0 次方,2 的 1 次方,2 的 2 次方... 

        现在将 10 进制的 125 转为 2进制,则将 125 依次除以 2,每次除以 2 的余数就是 2 进制中从低数值位到高数值位的值。

        如上图,最后得到的二进制就是从下到上的余数。 

        2 进制转成 10 进制,就是将 2 进制每一位的值乘以每一位的权重然后相加即可

2.2 2 进制和 8 进制、16 进制的转换

        8 进制的数字每一位是 0-7 的数字组成,写成 2 进制就是 3 位 2 进制数字,比如 8 进制的 7 就是 2 进制的 111,所以在 2 进制转 8 进制的时候,从低位开始,每 3 位转换成一个 8 进制数字即可,最后一组不够 3 位则将高位看做 0 即可

        如上图,01 转成 8 进制的 1,101 转为 8 进制的 5,011 转为 8 进制的 3,所以 2 进制的 01101011 就会转成 8 进制的 153。 

        16 进制的数字每一位是 0-9 以及 a-f 的数字组成,和 8 进制相似,16 进制写成 2 进制就是 4 位 2 进制数字,所以在 2 进制转 16 进制的时候,从低位开始,每 4 位转换成一个 16 进制数字即可,最后一组不够 4 位则将高位看做 0 即可

        如上图,0110 转成 16 进制的 6,1011 转为 16 进制的 b,所以 2 进制的 01101011 就会转成 16 进制的 6b。 

3. 原码、反码、补码

        整数的 2 进制表示方法有三种,原码、反码、补码。

        有符号整数的三种表示方法均有符号位数值位,2 进制序列中,最高位的 1 位是被当做符号位,0 表示“正”,1 表示“负”,剩余的都是数值位

        (1)正整数的原码、反码、补码都相同。

        (2)负整数的原码、反码、补码各不相同。

                原码:将数值按照正负数的形式翻译成 2 进制得到的就是原码。

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

                补码:反码 + 1 就得到的是补码。反码取反然后 + 1 得到的就是原码。

        在计算机系统中,数值一律用补码来表示和存储。因为使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理,此外,补码和原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

4. 位操作符

操作符名称描述
&按位与对两个操作数逐位进行与运算
|按位或对两个操作数逐位进行或运算
^按位异或对两个操作数逐位进行异或运算
~按位取反对两个操作数逐位进行取反运算
<<左移将操作数的 bit 位左移指定位数
>>右移将操作数的 bit 位右移指定位数

        注:位操作符的操作数只能是整数

#include <stdio.h>
int main()
{
	int num1 = -3;
	// -3 存在 int a 中,是 4 个字节,32 个 bit 位
	// 源码:1000 0000 0000 0000 0000 0000 0000 0011
	// 反码:1111 1111 1111 1111 1111 1111 1111 1100
	// 补码:1111 1111 1111 1111 1111 1111 1111 1101
	int num2 = 5;
	// 5 存在 int a 中,是 4 个字节,32 个 bit 位,整数原码,反码,补码都一样
	// 源码:0000 0000 0000 0000 0000 0000 0000 0101
	// 反码:0000 0000 0000 0000 0000 0000 0000 0101
	// 补码:0000 0000 0000 0000 0000 0000 0000 0101

	// -3 补码:1111 1111 1111 1111 1111 1111 1111 1101
	// 5  补码:0000 0000 0000 0000 0000 0000 0000 0101
	printf("%d\n", num1 & num2);
	//	  补码:0000 0000 0000 0000 0000 0000 0000 0101
	//	  反码:0000 0000 0000 0000 0000 0000 0000 0101
	//	  源码:0000 0000 0000 0000 0000 0000 0000 0101 -- 5

	printf("%d\n", num1 | num2);
	//	  补码:1111 1111 1111 1111 1111 1111 1111 1101
	//	  反码:1000 0000 0000 0000 0000 0000 0000 0010
	//	  原码:1000 0000 0000 0000 0000 0000 0000 0011 -- -3

	printf("%d\n", num1 ^ num2);
	//	  补码:1111 1111 1111 1111 1111 1111 1111 1000
	//	  反码:1000 0000 0000 0000 0000 0000 0000 0111
	//	  原码:1000 0000 0000 0000 0000 0000 0000 1000 -- -8

	printf("%d\n", ~0);
	// 0 补码:	  0000 0000 0000 0000 0000 0000 0000 0000
	// 补码取反:  1111 1111 1111 1111 1111 1111 1111 1111 -- 补码
	//            1000 0000 0000 0000 0000 0000 0000 0000 -- 反码
	//            1000 0000 0000 0000 0000 0000 0000 0001 -- 原码 -- -1
	return 0;
}

        例:不创建临时变量,实现两个整数的交换。 

// 方法1:
#include <stdio.h>
int main()
{
	int a = 3;
	int b = 5;

	printf("交换前: a = %d, b = %d", a, b);
	a = a + b;	// a 为 a 和 b 的和
	b = a - b;	// b = a 和 b 的和减去 a,则 b = a
	a = a - b;	// 上一步中,b 已经等于 a 了,再用 a 和 b 的和减去 a 得到 b
	printf("交换后: a = %d, b = %d", a, b);

	return 0;
}

// 该方法在 a + b 超过整型的最大值时,就会出现溢出的情况,计算出来的值不对
// 方法2:异或
#include <stdio.h>
int main()
{
	int a = 3;
	int b = 5;

	printf("交换前: a = %d, b = %d", a, b);
	a = a ^ b;
	b = a ^ b;	// b = a ^ b ^ b = a;
	a = a ^ b;	// a = a ^ b ^ a = b;
	printf("交换后: a = %d, b = %d", a, b);
	
	return 0;
}

// a ^ a = 0; a ^ 0 = a;

        左移操作符 " << ":左边抛弃,右边补0。 

#include <stdio.h>
int main()
{
	int num = 10;
	int n = num << 1;
	printf("n= %d\n", n);
	printf("num= %d\n", num);
	return 0;
}

        右移操作符 " >> ":(1)逻辑右移:左边用 0 填充,右边丢弃。(2)算数右移:左边用原该值的符号位填充,右边丢弃。 

        右移到底是算数右移还是逻辑右移是取决于编译器的实现,常见的编译器都是算数右移。

        算数右移:

        逻辑右移:

        注:对于移位运算符,不要移动负数位,这个是标准未定义的。 

5. 逗号表达式

        逗号表达式就是用逗号隔开多个表达式逗号表达式从左往右依次执行,整个表达式的结果是最后一个表达式的结果

#include <stdio.h>

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

        在上述逗号表达式中,从左向右计算,a 的值被更新为 12,b 的值被更新为 13,则 c 等于最后一个表达式的结果,所以 c = b = 13。 

        逗号表达式在 while 循环中使用可以减少代码冗余。 

a = get_val();
count_val(a);
while (a > 0)
{
    //业务处理
    //...
    a = get_val();
    count_val(a);
}


// 如果使⽤逗号表达式,改写为如下代码,可以减少代码冗余
while (a = get_val(), count_val(a), a>0)
{
    //业务处理
}

6. 下表访问[]、函数调用()

        (1)下标引用操作符 []:该操作符的操作数为一个数组名和一个索引值

int arr[10];    // 创建数组。
arr[9] = 10;    // 使用下标引⽤操作符。

        (2)函数调用操作符 ():接收一个或者多个操作数,第一个操作数是函数名,剩余的操作数就是传递给函数的参数

#include <stdio.h>
void test1()
{
	printf("hehe\n");
}
void test2(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	test1();	// ()作为函数调⽤操作符。
	test2("hello XiaoC.");	// ()就是函数调⽤操作符。
	return 0;
}

8. 操作符的属性:优先级、结合性

        C 语言的操作符有两个重要的属性:优先级和结合性,这两个属性决定了表达式求值的计算顺序。

        (1)优先级:如果一个表达式包含多个运算符,运算符的优先级决定哪个运算符应该优先执行。

3 + 4 * 5;

        上述表达式中既有加法运算也有乘法运算,由于乘法运算优先级高于加法,所以先计算乘法。 

        (2)结合性:如果两个运算符优先级相同,优先级没办法决定哪个先计算,根据运算符左结合还是右结合,决定执行顺序。⼤部分运算符是左结合(从左到右执⾏),少数运算符是右结合(从右到左执⾏),⽐如赋值运算符( = )。

5 * 6 / 2;

        上述乘法和除法的优先级相同,都是左结合运算符,所以从左到右执行。 

9. 表达式求值

9.1 整型提升

        C 语言中整型算数运算总是至少以缺省整型类型的精度来进行的,为了获取这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

        整型提升规则:(1)有符号整数提升是按照变量的数据类型的符号位来提升的。(2)无符号整型提升,高位补0

#include <stdio.h>

int main()
{
	char a = 3;
	char b = 127;
	char c = a + b;
	printf("c = %d", c);
	return 0;
}

        这里解释为什么上述结果为 126。

#include <stdio.h>

int main()
{
	char a = 3;
	// 00000011 -- a在内存中存储的补码
	char b = 127;
	// 01111111 -- b在内存中存储的补码
	char c = a + b;
	// 当a和b进行运算时要进行整型提升
	// 00000000 00000000 00000000 00000011 -- a整型提升后在内存中存储的补码
	// 00000000 00000000 00000000 01111111 -- b整型提升后在内存中存储的补码
	// 00000000 00000000 00000000 10000010 -- a+b运算的结果在内存中存储的补码
	// 10000010 -- c在内存中存储的补码
	printf("c = %d", c);
	// 使用%d进行打印的时候也是需要进行整型提升的,c为有符号字符,所以整型提升时按照符号为进行提升
	// 11111111 11111111 11111111 10000010 -- c整型提升后的补码
	// 10000000 00000000 00000000 01111101 -- c整型提升后的反码
	// 10000000 00000000 00000000 01111110 -- c整型提升后的原码 -- -126
	return 0;
}

        整型提升的意义:表达式的整型运算要在 CPU 的相应运算器内执行,CPU 内整型运算器(ALU)的操作数的字节长度一般就是 int 的字节长度,同时也是 CPU 的通用寄存器的长度。因此,即使两个 char 类型的相加,在 CPU 执行时实际上也要先转换为 CPU 内整型操作数的标准长度。 

9.2 算术转换

        如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另一个操作数的类型,否则操作无法进行。下面的层次体系称为寻常算术转换

long double
double
float
unsigned long int
long int
unsigned int
int

        在上述表中,如果一个操作数的类型排在另外一个操作数类型的后面,则该操作数要先转换为另外一个操作数的类型再进行计算。比如 int 类型和 double 类型进行计算,则 int 类型要先转换成 double 类型在进行计算。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值