文章目录
前言
部分的操作符已经在前面写过了,想要了解的可以点击初始C语言:常见操作符与关键字。
这一篇我们主要讲与内存相关的操作符!下面我们来了解一下我们要了解的内容吧!
正文
话题引入
内存中数据的存储——二进制
在(signed)int类型中大小为4个字节,1个字节是8个比特位,一个比特位是存数据的最小内存单元,也就是二进制的0/1,那四个字节就是32位二进制的数,并且最高位是符号位(因为是signed)。
源码,反码,补码的关系
正数
说明:源码,反码,补码相同。
举例1:
int a =1;
源码:00000000000000000000000000000001
反码:00000000000000000000000000000001
补码:00000000000000000000000000000001(在内存中实际存的数据)
注意:计算机并不是直接把正数的源码存进去了,而是把源码转换为补码存进去了。
负数
说明:遵循着运算逻辑——反码等于源码按位取反(符号位,也就是最高位不变,把源码中的0变成1,1变成0),补码等于反码加上1。
特殊:int的取值范围:-2147483648 ~2147483647
负数的最小值的存储为:10000000000000000000000000000000(这是补码,并且这是语法规定的)
因此我们可以这样记住负数与正数的存储形式:
我们可以看出,这是一个轮回,是不是很神奇?
举例2:
int a =-1;
源码:10000000000000000000000000000001
反码:1111111111111111111111111111111111110
补码:1111111111111111111111111111111111111
到这里我们已经知道内存的二进制是如何转化和进行存储了,接下来我们进入今天的课题吧!
一.移位操作符
说明:
1.移位操作符运算后不会影响操作数本身
2.移位操作符运算的范围为整数
1. 左移操作符
符号:<<(双箭头向左)
功能:将补码整体左移n位舍去,右边补0。
int a =1;
int b = a<<1;//这是将a的补码向左移动一位,
a的补码:00000000000000000000000000000001
b的补码:00000000000000000000000000000010
2. 右移操作符
符号:>>(双箭头向右)
1.算数右移
功能:将补码整体向右移n位,最左边补符号位
例子:
int a =-1;
int b = a>>1:
a的补码:11111111111111111111111111111111
b的补码:11111111111111111111111111111111
1.逻辑右移
功能:将补码整体向右移n位,最左边补0
一般右移都是算数右移(补符号位)
二.位操作符
说明:
1.移位操作符运算后不会影响操作数本身
2.移位操作符运算的范围为整数
举例:
int a =1;
int b =2;
int c =a&b;
1.按位与
符号:&
功能:将两个数的补码进行比对,如果相同位数的数都为1这个位数的结果为1,否则为0。
口诀:同1为1,其余为0。
a的补码:00000000000000000000000000000001
b的补码:00000000000000000000000000000010
c的补码:00000000000000000000000000000000
c的结果为0.
2.按位或
符号:|
功能:将两个数的补码进行比对,如果相同位数至少有一个1这个位数的结果为1,否则为0。
口诀:同0为0,其余为1。
a的补码:00000000000000000000000000000001
b的补码:00000000000000000000000000000010
c的补码:00000000000000000000000000000011
3.按位异或
说明:
1.支持交换律
2.一个数异或0为本身
3.相同数异或为0.
符号:^
功能:将两个数的补码进行比对,如果相同位数的数不同为1,否则为0。
口诀:同为0,异为1.
a的补码:00000000000000000000000000000001
b的补码:00000000000000000000000000000010
c的补码:00000000000000000000000000000011
例题:交换两个数,不用第三个变量。
方法1:这是我们平常用的,不符合题目要求。
int main()
{
int tmp = 0;
int a = 2;
int b = 1;
tmp = a;
a = b;
b = tmp;
printf("%d %d", a, b);
return 0;
}
方法2:
int main()
{
int a = 2;
int b = 1;
a = a + b;
b = a - b;
a = a - b;
printf("%d %d", a, b);
return 0;
}
说明:在两个数较大的情况下,交换可能会出现数据溢出的现象。
方法3:
要看一下异或的规律哦!
int main()
{
int a = 2;
int b = 1;
a =a^b;
b =a^b;//相当于a^b^b-->a
a =a^b; //相当于a^b^a-->b
return 0;
}
说明:代码的可读性差,并且计算较为复杂,在实际工程中建议采用第一种写法。
例2:将二进制中1的个数打印出来。
方法一:
#include <stdio.h>
int main()
{
int num = 10;
int count= 0;//计数
while(num)
{
if(num%2 == 1)
{
count++;
}
num /= 2;
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}
说明:不适用负数,只适用于正整数。
方法2:
int main()
{
int count = 0;
int b = 10;
int i = 0;
for (i = 0; i < 32; i++)
{
if (((b >> i) & 1) == 1)//判断是否为1用&右移是将b的二进制位数的值依次与1比较相同为1
{
count++;
}
}
printf("%d", count);
return 0;
}
说明:计算的比较多余,每次计算都得算32遍。
方法3:
int main()
{
int a = -1;
int count = 0;
while (a)
{
count++;
a = a & (a - 1);
}
printf("%d", count);
return 0;
}
原理:每次减去1,实际上把较低位的1变成0,此时再按位与就消掉较低位的1,最后如果没有1的话,按位与就变成了0。
说明:这种方法比较好,但是比较难想到。
三.结构体成员操作符
1.实参访问
符号:.
struct BOOK
{
char author[20];//编号0
char name[20];//编号1
int price;//编号2
};
int main()
{
struct BOOK a = { "shunhua","c江湖",0 };//定义了一本书的作者,书名,价格
printf("%s %s %d", a.author, a.name, a.price);//通过.进行访问
return 0;
}
2.地址访问
符号:->(一个减号,一个大于号)
struct BOOK
{
char author[20];
char name[20];
int price;
};
int main()
{
struct BOOK a = { "shunhua","c江湖",0 };
struct BOOK *p = &a;
printf("%s %s %d\n", (*p).author, (*p).name, (*p).price);//指针解引用,就是实参了。
printf("%s %s %d", p->author, p->name, p->price);
//指针存的地址就像箭头,可以通过地址访问。
return 0;
}
四.函数调用操作符
符号:()
说明:至少有一个操作数——函数名
void test()
{
printf("abcdef");
}
int main()
{
test();//专指的是调用函数的小括号。其它的可不是哦!
return 0;
}
五.下标引用操作符
符号:[]
说明:本质实际上地址进行运算再解引用,两个操作数有一个为地址。
公式表示:*(操作数1+操作数2)
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//这里的[10]为有10个成员
int *p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(arr + i));
}
printf("\n");
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);//本质上为上面的数组。
}
for (i = 0; i < 10; i++)
{
printf("%d ", i[arr]);//本质上为上面的数组。
}
return 0;
}
六.表达式求值
1.整形提升
说明:
1.C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
2.意义:
1.表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
2.通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。
3.截断
说明:将内存较大的数据类型转化为较为低的数据类型时,需要将二进制数据保留为低数据的内存的二进制数据大小。比如:int(32)位要转化为char(8)位。
例一:
int main()
{
char a = 128;
//发生截断存在char里面的是:(补码)10000000(-128)
//运算时再整形提升:(signed)高位补符号位(补码)11111111111111111111111110000000
char b = 2;
char c = a + b;//-128+2
printf("%d", c);
return 0;
}
例二:
int main()
{
char c = 1;
printf("%d\n", sizeof(c=c+1));//将c赋为c+1结果的类型为char
printf("%d\n", sizeof(c+1));//将c进行整形提升int
printf("%d\n", sizeof(+c));//将c进行整形提升int
printf("%d\n", sizeof(-c));//同理
return 0;
}
2.算数转换
说明:
1.将精度小的(如:int)和精度大的(如:double)互相转换,但是可能会存在数据/精度丢失。
2.分为隐式转换和显式转换
3.类型大于等于int才算
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。
例:
int main()
{
int a = 128;
char b = 2;
float c = a + b;//隐式转化
c =(int)a+(int)b;//显式转换
printf("%f", c);
return 0;
}
3.操作数的计算顺序与优先级
这是我找的比较全的!
4.问题表达式
表达式的影响要素:
前提:相邻操作符
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序。
因此:在多表达式的计算中,相邻的表达式的优先级并不一定会知道,所以要写能看出结合性与优先性明了的式子。
例一:
#include<stdio.h>
int main()
{
int b = 0;
int i = 1;
b = (++i) + (++i) + (++i);
printf("%d", b);
return 0;
}
在vs编译器上是12,但是在Linux上可不一定哦!
运算的逻辑:先计算(++i)的部分,最后计算加起来的部分。
表达式的通病:优先级结合性求值顺序相互影响,又会影响最后的值。
补充:++a+ b * ++b + c,也算问题表达式,因为++b是先算的,但是前面的b不知道在什么时候算,是++b之后,还是之前都是有可能的。
尾序
如果能认真看到这里,我坚信你能收获很多很多!也希望这篇文章能帮助到你,如果觉得不错,请点击一下不要钱的赞,如果有误请温柔的指出,在这里感谢大家了