5.3按位异或(^)

按位异或(^)规则:使用补码进行按位异或,相同为0,相异为1。
以下面代码为例:

#include 
int main()
{
int a = 4;
//4的补码:00000000000000000000000000000100
int b = -7;
//-7的补码:11111111111111111111111111111001
int c = a ^ b;
//c的补码:11111111111111111111111111111101
printf("%d\n",c);//-3
return 0;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

异或运算的一些特殊场景:
a ^ a = 0; a ^ 0 = a; 3 ^ 3 ^ 5 = 5 = 3 ^ 5 ^ 3

5.4按位取反(~)

按位取反(~)规则:使用补码进行按位取反,全部取反即可。
以下面代码为例:

#include 
int main()
{
int a = 0;
//a的补码:00000000000000000000000000000000
int b = ~ a;
//b的补码:11111111111111111111111111111111
printf("%d\n",b);//-1
return 0;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

5.5例题讲解

5.5.1例题1:一道很难的面试题

下面我们看一道很变态的面试题:
题目:不能创建临时变量(第三个变量),完成两个整数的交换。
代码如下:

#include 
int main()
{
int a = 3;
//3的补码:00000000000000000000000000000011
int b = 5;
//5的补码:00000000000000000000000000000101
printf("交换前: a = %d b = %d\n",a,b);
a = a ^ b;
//此时a的补码:00000000000000000000000000000110
//即此时a = 6 
b = a ^ b;//b = a ^ b ^ b = a
//此时b的补码:00000000000000000000000000000011
//即此时b = 3
a = a ^ b;//a = a ^ b ^ a = b
//此时a的补码:00000000000000000000000000000101
//即此时a = 5
printf("交换后: a = %d b = %d\n",a,b);
return 0;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

运行结果如下图:
在这里插入图片描述
这里运用了b = a ^ b ^ b = a和a = a ^ b ^ a = b的按位异或运算,解决了两个数不创建新的变量交换的问题。

5.5.2例题2

题目:编写代码实现:求一个整数存储在内存中的二进制中1的个数。
方法一:用%、/来实现代码,循环的取余、取商用count变量对二进制1的个数进行统计。

#include 
int main()
{
int num = 0;
scanf("%d",&num);
int count = 0;
while(num != 0)
{
	if(num % 2 == 1)
{
	count++;
}
	num = num / 2;
}
printf("%d",count); 
return 0;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

观察上述代码我们可以看出:这种方法对于负数不起作用,%运算时,要求前后的数据类型要相同。
方法二:用右移位操作符(>>)来实现代码,让原数据不断向右移位的同时,再和1按位与就能够实现对负整数和正整数的二进制1的个数的统计。

#include 
int main()
{
int num = 0;
scanf("%d",&num);
int count = 0;
int i = 0;
for(i = 0;i < 32;i++)
{
	if((num >> i) & 1 == 1)
{
	count++;
}
}
printf("%d",count);
return 0;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

观察上述代码,可以看出:虽然解决了负整数的二进制1的个数统计问题,但是仍旧需要循环32次,运行效率低下,那如何使他更加优化呢?
方法三:用操作符按位与(&)来实现代码,因为数字在计算机中是以二进制的数字存放的,通过num和它的后一位的按位与实现减一的操作,再通过count来计数。

#include 
 int main()
 {
 int num = -1;
 int i = 0;
 int count = 0;//计数
 while(num)
 {
 	count++;
 	num = num & (num-1);
 }
 printf("二进制中1的个数 = %d\n",count);
 return 0;
 }
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

观察上述代码,可以看出:利用按位与操作符(&),通过减少循环的次数来提高了代码的运行效率,当然这个方法是很难想到。

5.5.3例题3:二进制位置0或者置1

题目:编写代码将13二进制序列的第5位修改为1,然后再改回0。
代码实现如下:

#include 
int main()
{
int n = 13;
//13的补码:00000000000000000000000000001101
//将n的第 5位改成 1
n = n | (1 << 4);
//左移后:00000000000000000000000000010000
//按位或后:00000000000000000000000000011101
printf("%d\n",n);//29
n = n & (~(1 << 4));
//左移后:00000000000000000000000000010000
//按位取反后:11111111111111111111111111101111
//按位与后:00000000000000000000000000001101
//将n的第 5位改为 0
printf("%d\n",n);//13
return 0;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

运行结果如下图:
在这里插入图片描述

六、单目操作符

!、++、- -、&、*、+、-、~、sizeof、(类型)
单目操作符的特点是:只有一个操作数。
& 和 *,指针的时候会深入讲解。
& —— 取地址操作符
*解引用操作符(间接访问操作符)
单目操作符基本上以前都深入了解过,如有不清楚的地方可以回顾以前的博文,博文链接如下:http://iyenn.com/rec/1644906.html?fromshare=blogdetail&sharetype=blogdetail&sharerId=141575238&sharerefer=PC&sharesource=2301_80179750&sharefrom=from_link

七、逗号表达式

exp1,exp2,exp3,exp4,……,expN
逗号表达式:逗号隔开的一串表达式。
特点:从左到右依次执行,整个表达式的结果是最后一个表达式的结果。

int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式,c = 13
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

八、下标访问[ ]、函数调用( )

[ ] —— 下标引用操作符
() —— 函数调用操作符

8.1[ ]下标引用操作符

操作数:一个数组名 + 一个索引值(下标)

int arr[10];//创建数组
arr[7] = 8;//实用下标引用操作符。
这里的两个操作数为:arr和7。

8.2函数调用操作符

函数在调用的时候,函数名后边的括号就是函数调用操作符
() —— 操作数是:函数名,参数
函数调用操作符,最少有1个操作数 —— 函数名

九、结构(结构体)成员访问操作符

9.1结构体

内置数据类型 —— C语言本身的类型
自定义类型 —— 根据实际的需要自己可以创建的类型
结构体是自定义类型,需要我们使用结构体语法,自己创建复杂类型。

9.1.1结构体的声明

struct tag//自定义的名称
{
member-list;//成员
}variable-list;//变量列表
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

注:结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如:标量、数组、指针,甚至是其他结构体。
创建一个与学生有关的结构体:
名字 —— 字符串 —— 字符数组中
性别 —— 字符串 —— 字符数组中
年龄 —— 整数
学号 —— 字符串 —— 字符数组中

 struct Student
 {
 	char name[20];//名字
	int age;//年龄
	char sex[10];//性别
	char id[18];//学号
 }s5,s6,s7; //这里的分号一定不能丢
 //s5,s6,s7都是结构体变量(全局)
 int main()
 {
 struct Student s1;
 return 0;
 }
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

9.1.2结构体变量的定义和初始化

数组中存放多个元素,使用{}初始化;结构体中存放多个成员,使用{}初始化。

struct Point
{
int x;
int y;
}p3 = {0,0},p4 = {4,1};
struct Point p2 = {6,8};
int main()
{
struct Point p1 = {4,5};
return 0;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">
#include 
struct Point
{
int x;
int y;
}p3 = {0,0},p4 = {4,1};
struct Point p2 = {6,8};
struct Student
{
char name[20];
char sex[12];
int age;
char id[16];
};
struct Node
{
int data;
struct Point p;
struct Node*next;
};
int main()
{
struct Student s1 = {"张三","男",18,"2024090203"};
struct Student s2 ={.age = 20,.id = "2025011722",.name = "Lisi",.sex = "nv"};
struct Node n1 = {200,{20,30},NULL};
scanf("%d",&(s1.age));//直接访问成员
printf("%s %s %d %s\n",s1.name,s1.sex,s1.age,s1.id);//结构体变量.结构体成员
return 0;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}"> class="hide-preCode-box">

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

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

10.1优先级

优先级指的是:如果一个表达式包含多个运算符,哪个运算符应该优先执行,各种运算符的优先级是不一样的。
3 + 4 * 5 就是先运算 * 再运算+。
注:相邻操作符优先级高的先进行,优先级低的后进行。

10.2结合性

如果两个运算符优先级相同,优先级没办法确定,则根据运算符是左结合,还是右结合。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值操作符(=)。

运算符的优先级顺序很多,下面是部分运算符的优先级顺序(按照优先级从高到低排列):
圆括号(())
自增运算符(++),自减运算符(- -)
单目运算符(+和-)
乘法(*),除法(/)
加法(+),减法(-)
关系运算符(<,>等)
赋值运算符(=)
注:由于圆括号的优先级最高,可以使用它改变其他运算符的优先级
C语言运算符优先级:https://zh.cppreference.com/w/c/language/operator_precedence

十一、表达式求值

11.1整型提升

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

整型提升的意义:表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purposeCPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned
int,然后才能送入CPU去执行运算。

整型提升适用于

①当使用算术运算符(如加法、减法、乘法、除法)时,参与运算的操作数将自动进行整型提升。比如,如果一个操作数是int类型,另一个操作数是char类型,那么char类型的操作数将被提升为int类型。
②当使用位运算符(如按位与、按位或、异或)时,参与运算的操作数也会自动进行整型提升。
③当使用关系运算符(如大于、小于、等于)时,比较的操作数也会进行整型提升。

char —— 是字符类型
char —— 也是属于整型家族 —— 存储的是ASCII码值,ASCII码值是整数。
如何进行整型提升呢?
1、有符号整数提升是按照变量的数据类型的符号位来提升的,直接补符号位。
2、无符号位整数提升,高位补0。
那么问题来了,char倒是signed char ?还是unsigned char标准未规定,取决于你使用的编译器,VS上char是signed char。
负数的整形提升:

char c1 = -1; 变量c1的进制位(补码)中只有8个比特位:1111111
因为 char 为有符号的 char,所以整型提升的时候高位补充符号位,即为1 ,提升之后的结果是:11111111111111111111111111111111。

正数的整型提升:

char c2 = 1; 变量c2的⼆进制位(补码)中只有8个比特位:00000001
因为 char为有符号的char,所以整型提升的时候高位补充符号位,即为0,提升之后的结果是:00000000000000000000000000000001。

11.2算术转换

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

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

注:如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外⼀个操作数的类型后执行运算。(长度 < int整型提升;长度 > int算术转换)

11.3问题表达式的解析

11.3.1表达式1

//表达式的求值部分由操作符的优先级决定。
//表达式1 
a*b + c*d + e*f
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

表达式1在计算的时候,由于* 比+的优先级高,但只能保证,* 的计算是比+早,但是优先级并不能决定第三个*比第⼀个+早执行,即无法确定唯一的计算路径。
解决办法:1.使用括号;2.拆表达式

11.3.2表达式2

//表达式2 
c + --c;
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

和表达式1差不多,操作符的优先级只能决定自减- -的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,即结果是不可预的,是有歧义的。

11.3.3表达式3

#include 
int main()
{
int i = 10;
i = i-- - --i * (i = -3) * i++ + ++i;
printf("i = %d\n",i); 
return 0;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

表达式3在不同编译器中测试结果:非法表达式程序的结果。
在不同编译器上的运行结果不同,不建议写这样的表达式。

11.3.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;
}
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

这个代码有没有实际的问题?有问题!
虽然在大多数的编译器上求得结果都是相同的。
但是上述代码answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。函数的调用先后顺序无法通过操作符的优先级确定。

11.3.5表达式5

#include 
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n",ret);
printf("%d\n",i);
return 0;
}
//尝试在linux 环境gcc编译器,VS2022环境下都执行,看结果。
 class="hljs-button signin active" data-title="登录复制" data-report-click="{"spm":"1001.2101.3001.4334"}">

在VS2022环境下执行结果如下图:
在这里插入图片描述
在小熊猫C++(gcc)编译器下运行结果如下图:
在这里插入图片描述
看看同样的代码产生了不同的结果,这是为什么?
简单看一下汇编代码,这段代码中的第一个+在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个+和第三个前置++的先后顺序。
注:如果用操作符的优先级和结合性无法得出计算的唯一路径,那么就不推荐写这种代码,可以换种思路来写代码使其满足唯一计算路径。

data-report-view="{"mod":"1585297308_001","spm":"1001.2101.3001.6548","dest":"https://blog.csdn.net/2301_80179750/article/details/145189164","extend1":"pc","ab":"new"}">>
注:本文转载自blog.csdn.net的四念处茫茫的文章"https://blog.csdn.net/2301_80179750/article/details/145189164"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接

评论记录:

未查询到任何数据!