目录
前言
在前面,我们见到了 +, -, * ,/ , < , > , =这些符号,其实它们的名字叫做操作符。按照功能的不同我们可以对它进行一个简单的分类
分类
二进制和进制转换
进制
我们经常听到的2进制、8进制、10进制、16进制是数值的不同表⽰形式。
以我们最熟悉的十进制为例:
进制转换
从前面我们可以看出,进制的原理是差不多的,接下来我们就来学习进制转换。
二进制转十进制
二进制转十进制,我们只需要把二进制每一位乘以它对应的权重就可以了。
比如二进制1011
十进制转二进制
我们只需要将十进制数不断地除二,直到结果为0,每一次得到的余数从下向上写就是转换出的二进制。比如十进制125
二进制转八进制
二进制转十六进制
与二进制转换为八进制类似,16进制的数字每⼀位是0~9, a~f 的,0~9, a~f的数字,各⾃写成2进制,最多有4个2进制位就⾜够了,所以我们可以从2进制序列中右边低位开始向左每4个2进制位会换算⼀个16进制位,剩余不够4个⼆进制位的直接换算。
比如二进制的11110101---->0xf5(十六进制),16进制表⽰的时候前⾯加0x (前导符)
我们可以在编译器上打印来验证我们的结果是否正确!
将二进制的11110101转换为十进制就是245
我们可以用%o以八进制无符号形式输出整数,可以使用%x(%X)以十六进制无符号形式输出整数。当x是小写的时候,十六进制的a~f以小写形式输出,是大写就用大写形式输出。
但是它们都不会输出前导符,我们可以自己加上,也可以在%后加上#号。
代码如下:
#include<stdio.h>
int main()
{
int a = 245;
printf("%o\n", a);
printf("%x\n", a);
printf("%X\n", a);
printf("----------\n");
printf("0%o\n", a);
printf("0x%x\n", a);
printf("0x%X\n", a);
printf("----------\n");
printf("%#o\n", a);
printf("%#x\n", a);
printf("%#X\n", a);
return 0;
}
原码、反码、补码
补码:反码+1就得到补码。
例:
-1(整型,4个字节,32个比特位)
原码:10000000 00000000 00000000 00000001
反码:11111111 11111111 11111111 11111110
补码:11111111 11111111 11111111 11111111
如果已知补码想要得到原码:
方法一:补码先减1再符号位不变,其他位按位取反
方法二:补码直接符号位不变,其他位取反后加1
补码:11111111 11111111 11111111 11111111
原码:10000000 00000000 00000000 00000001
移位操作符
<< 左移操作符
#include<stdio.h>
int main()
{
int n = 2;
//正数原码,反码,补码相同
//00000000 00000000 00000000 00000010
// //移位规则:左边抛弃、右边补0
//00000000 00000000 00000000 00001000--8
//左边舍弃,右边补两位
int ln = n << 2;
printf("%d\n", ln);
return 0;
}
>>右移操作符
#include<stdio.h>
int main()
{
int a = 2;
//00000000 00000000 00000000 00000010
//正数原码反码补码相同
int b = a >> 1;
printf("%d\n", b);
return 0;
}
#include<stdio.h>
int main()
{
int a = -1;
//10000000 00000000 00000000 00000001
//11111111 11111111 11111111 11111110
//11111111 11111111 11111111 11111111-->补码
int b = a >> 1;
printf("%d\n", b);
return 0;
}
第一个运行结果为1,第二个运行结果为-1,我们用注释简单分析一下第二个代码
#include<stdio.h>
int main()
{
int a = -1;
//10000000 00000000 00000000 00000001
//11111111 11111111 11111111 11111110
//11111111 11111111 11111111 11111111-->补码
int b = a >> 1;
//1. 逻辑右移:左边⽤0填充,右边丢弃
//01111111 11111111 11111111 11111111-->补码
// 符号位变为0,认为是正数,输出结果是一个很大的数
//不符合
//2. 算术右移:左边⽤原该值的符号位填充,右边丢弃
//11111111 11111111 11111111 11111111-->补码
//10000000 00000000 00000000 00000001-->原码,值为-1,满足
printf("%d\n", b);
return 0;
}
我们可以知道VS2022使用的是算术右移的方式。
位操作符:&、|、^、~
& 按位与
规则:两个整数补码对应的二进制位有0则为0,两个同时为1则为1
例:
#include<stdio.h>
int main()
{
int a = -3;
//10000000 00000000 00000000 00000011--> -3原码
//11111111 11111111 11111111 11111100
//11111111 11111111 11111111 11111101--> -3补码
int b = 5;
//00000000 00000000 00000000 00000101--> 5补码
//11111111 11111111 11111111 11111101--> -3补码
//00000000 00000000 00000000 00000101--》 5
printf("%d\n", a & b);
return 0;
}
| 按位或
规则:两个整数补码对应的二进制位只要有1就为1,两个同时为0则为0
例:
//| 按位或
#include<stdio.h>
int main()
{
int a = -3;
//10000000 00000000 00000000 00000011--> -3原码
//11111111 11111111 11111111 11111100
//11111111 11111111 11111111 11111101--> -3补码
int b = 5;
//00000000 00000000 00000000 00000101--> 5补码
//11111111 11111111 11111111 11111101--> -3补码
//11111111 11111111 11111111 11111101--》 a|b补码
printf("%d\n", a | b);
return 0;
}
如果我们定义一个整型变量num,那么num | 0=num
我们可以用一个简单的代码来进行验证:
#include<stdio.h>
int main()
{
int num = 0;
scanf("%d", &num);
printf("%d\n", num | 0);
return 0;
}
说明我们的结论是正确的。
^ 按位异或
规则:两个整数补码对应的二进制位相同为0,不相同为1
例:
#include<stdio.h>
int main()
{
int a = -3;
//10000000 00000000 00000000 00000011--> -3原码
//11111111 11111111 11111111 11111100
//11111111 11111111 11111111 11111101--> -3补码
int b = 5;
//00000000 00000000 00000000 00000101--> 5补码
//11111111 11111111 11111111 11111101--> -3补码
//11111111 11111111 11111111 11111000--》 a^b补码
//10000000 00000000 00000000 00000111 //补码取反加1可以得到原码
//10000000 00000000 00000000 00001000--》 a^b原码---》-8
printf("%d\n", a ^ b);
return 0;
}
~按位取反
规则:一个整数补码按二进制位全部取反,输出原码
这里我们可以看到 ~ 只有一个操作数
例:
#include<stdio.h>
int main()
{
int a = -3;
//10000000 00000000 00000000 00000011--> -3原码
//11111111 11111111 11111111 11111100
//11111111 11111111 11111111 11111101--> -3补码
//~全部补码二进制位取反
//00000000 00000000 00000000 00000010--》符号位变为0,为正数
printf("%d\n", ~a);
//2
return 0;
}
应用
不能创建临时变量(第三个变量),实现两个整数的交换。
#include<stdio.h>
int main()
{
int a = -3;
int b = a ^ a;
printf("b=%d\n", b);
int c = a ^ 0;
printf("c=%d\n", c);
return 0;
}
为了满足题目要求,我们就可以写出下面的代码
#include<stdio.h>
int main()
{
int a = -3;
int b = 5;
printf("交换前:a=%d,b=%d\n", a, b);
a = a ^ b;
b = a ^ b;//b=a^b^b=a^0=a
a = a ^ b;//a=a^a^b=b
printf("交换后:a=%d,b=%d\n", a, b);
return 0;
}
编写代码实现:统计⼀个整数存储在内存中的⼆进制中1的个数
我们知道十进制的数除2每次得到的余数就可以得到二进制原码,我们可以写出这样一个代码
#include<stdio.h>
int main()
{
int num = 0;
int count = 0;
scanf("%d", &num);
int tmp = num;
while (num)//直到num=0停止
{
if (num % 2 == 1)//余数为1
count++;
num = num / 2;//下一次除2
}
printf("%d在内存中存储1的个数:%d\n", tmp, count);
//使用num值会变化
return 0;
}
我们测试了几个值,发现正数的结果是正确的,而负数的结果是错误的,我们知道
-1
原码:10000000 00000000 00000000 00000001
反码:11111111 11111111 11111111 11111110
补码:11111111 11111111 11111111 11111111
我们知道整数在内存中存放的是补码,所以-1应该是32个1,那么为什么会出现上面的结果呢?
我们来调试一下,发现第一次进去后,num%2==0,num/2之后num变成了0,就跳出了循环。
那么这个方法是不合理的,我们可以使用操作符来解决这个问题。
如果我们定义一个整型变量num,那么num&num==num,我们可以使用num&1判断num在内存中存储的最后一位是不是1。
有一种方法是将num的二进制位每一次向左边移动一位,右边补0
解决题目我们就可以写出下面的代码
#include<stdio.h>
int main()
{
int num = 0;
int count = 0;
scanf("%d", &num);
int tmp = num;
for (int i = 0; i < 32; i++)
{
if (num & (1 << i))//结果为1为真
count++;
}
printf("%d在内存中存储1的个数:%d\n", tmp, count);
return 0;
}
不要写成 if ((num & (1 << i)) == 1) //这样需要考虑到优先级的问题,后面会讲解
代码优化
#include<stdio.h>
int main()
{
int num = 0;
int count = 0;
scanf("%d", &num);
int tmp = num;
while (num)
{
count++;
num = num & (num - 1);
}
printf("%d在内存中存储1的个数:%d\n", tmp, count);
return 0;
}
找出单身狗
题目:一组数组有只有一个数字单独出现,其他数字成对出现,找出这个数字(单身狗)
这个问题我们可以使用^这个操作符,我们知道a^a=0,a^0(在第一个应用中有讲解)
#include<stdio.h>
int main()
{
int arr[7] = { 2,5,3,8,5,2,8 };
int dog = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < sz; i++)
{
dog = dog ^ arr[i];
}
printf("单身狗是%d\n", dog);
return 0;
}
单⽬操作符
逗号表达式
#include<stdio.h>
int main()
{
int a = 2;
int b = 3;
int c = 0;
int n = (b = a, c = a + b, b);
// b=2 c=4 2
printf("%d\n", n);
return 0;
}
下标访问[]、函数调⽤()
[ ] 下标引⽤操作符
#include<stdio.h>
int main()
{
int arr[6] = { 1,2,3,4,5,6 };
printf("%d\n", arr[3]);
//arr数组名,3下标
return 0;
}
函数调⽤操作符 ()
#include<stdio.h>
void menu()
{
printf("****1.play****\n");
printf("****0.exit****\n");
}
int main()
{
menu();
//没有参数,()只有menu函数名这一个操作数
return 0;
}
#include<stdio.h>
int Add(int x,int y)
{
return x + y;
}
int main()
{
int a = 3;
int b = 5;
printf("add:%d\n", Add(a, b));
//有参数,()有Add函数名和参数a,b多个操作数
return 0;
}
结构成员访问操作符
这里首先需要知道的知识就是结构体
结构体
结构的声明
结构体类型声明的一般形式:
struct Student
{
char name[20];//姓名
char sex[10];//性别
int age;//年龄
char id[10];//学号
}student1,student2;//末尾有分号
//变量
结构体变量的定义和初始化
结构体变量我们可以直接在结构体后面进行定义和初始化,也可以在所需要使用的函数中进行定义和初始化
#include<stdio.h>
struct Student
{
char name[20];//姓名
char sex[10];//性别
int age;//年龄
char id[10];//学号
}student1 = { "lihua","male",18,"202052" }, student2 = { "zhangli","female",20,"202053" };//末尾有分号
//变量
int main()
{
printf("student1:%s,%s,%d,%s\n", student1.name, student1.sex, student1.age, student1.id);
printf("student2:%s,%s,%d,%s\n", student2.name, student2.sex, student2.age, student2.id);
return 0;
}
#include<stdio.h>
struct Student
{
char name[20];//姓名
char sex[10];//性别
int age;//年龄
char id[10];//学号
};//末尾有分号
int main()
{
struct Student student1 = { "lihua","male",18,"202052" },student2 = { "zhangli","female",20,"202053" };
//结构体变量student1,student2
printf("student1:%s,%s,%d,%s\n", student1.name, student1.sex, student1.age, student1.id);
printf("student2:%s,%s,%d,%s\n", student2.name, student2.sex, student2.age, student2.id);
return 0;
}
结构成员访问操作符
结构体成员的直接访问
结构体成员的间接访问
#include<stdio.h>
struct Student
{
char name[20];//姓名
char sex[10];//性别
int age;//年龄
char id[10];//学号
}student1 = { "lihua","male",18,"202052" }, student2 = { "zhangli","female",20,"202053" };//末尾有分号
//变量
int main()
{
struct Student* pf1 = &student1;
struct Student* pf2 = &student2;
printf("student1:%s,%s\n", pf1->name, pf1->id);
printf("student1:%s,%s\n", pf2->name, pf2->id);
return 0;
}