操作符的分类:
算术操作符: + - * / %
移位操作符 : << >>
位操作符 : & | ^
赋值操作符 : = += -= *= /= %= <<= >>= &= |= ^=
单⽬操作符: ! ++ -- & * + - ~ sizeof (类型)
关系操作符 : > >= < <= == !=
逻辑操作符: && ||
条件操作符: ? :
逗号表达式:,
下标引⽤:[]
函数调⽤:()
结构成员访问:. ->
二进制和进制转换
int main()
{
十进制 0~9
二进制 0~1
八进制 0~7
十六进制 0~15
/*十进制 ——> 二进制*/ //不停地 %2 取余,余数的颠倒顺序即是二进制
十进制:15
二进制:1111
printf("%d\n", 1111);
/*二进制转换为十进制*/ //如1111 从右边开始权重为2^0 2^1 2^2 2^3...
以此类推,1111 就是(2^0) * 1 + (2^1) * 1 + (2^2) * 1 + (2^3) * 1
/*二进制转换为八进制*/
我们知道 八进制 0~7
7的二进制位数为 111 所以二进制位转换为八进制时最高只需3位
如一个二进制数为1111 --> 001 111 001二进制为1 111二进制为7
所以该二进制转换为八进制是 017 ---在17前加0表示为八进制,这是编译器规定的,不然编译器无法分辨17为
十进制还是八进制
printf("%d\n", 17); //17
printf("%d\n", 017);//15
/*二进制转换为十六进制*/
同样的 十六进制0~15
15的二进制数为 1111 所以最高只需4位
但是我们知道十六进制有十六个数字,为了防止误解
0~9 a b c d e f
10 11 12 13 14 15
转换方式与八进制相同,表示十六进制前需要加0x;
printf("%d\n", 16); //16
printf("%d\n", 0x16);//22
八进制和十六进制转换为二进制反推即可。
return 0;
}
原码 反码 补码 - 进行整数运算时才使用
原码 反码 补码 - 进行整数运算时才使用
int main()
{
整数的二进制表示方法有三种即 -> 原码 反码 补码
符号则由最高位表示符号位,其他表示数值位。
符号位0表示正数,1表示负数
整数类型是4字节->32个bit位的
00000000 00000000 00000000 00001010 -原码
int a = 10;
10000000 00000000 00000000 00001010 -原码
int b = -10;
正整数的原码 反码 补码相同
int a = 10;
00000000 00000000 00000000 00001010 -原码
00000000 00000000 00000000 00001010 -反码
00000000 00000000 00000000 00001010 -补码
负整数
/*原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码 + 1就得到补码。
反码得到原码也是可以使⽤:取反, + 1的操作。*/
int b = -10;
10000000 00000000 00000000 00001010 -原码
11111111 11111111 11111111 11110101 -反码
11111111 11111111 11111111 11110110 -补码
了解以上后,为什么要使用原码,反码,补码呢,原因是计算机系统中数据存放在内存中是
采用补码的形式,gpu中只有加法器,不可以算减法
如 1-1 这个是不能计算的 1+(-1)这个可以计算
printf("%d", 1 + (- 1)); //答案毋庸置疑是0
但是需要使用补码来计算
1的原码:00000000 00000000 00000000 00000001 -原码
-1的原码:10000000 00000000 00000000 00000001 -原码
相加则为 10000000 00000000 00000000 00000010 - 这个答案是-2 则计算错误
但是使用补码计算就是对的
1的补码:00000000 00000000 00000000 00000001 -补码
-1的补码:11111111 11111111 11111111 11111111 -补码
相加则为:10000000 00000000 00000000 000000000 前面多的一位是存不下的必然丢失
00000000 00000000 00000000 00000000 - 0
return 0;
}
移位操作符
<<左移位操作符
>>右移位操作符
只能在整数上操作,因为挪位置是操作补码
<<左移位操作符
int main()
{
//正数
int a = 10;
//10二进制数字 00000000 00000000 00000000 00001010 -补码
//左边丢弃,右边补上
// 00000000 00000000 00000000 00010100 -相当与 *2 就等于20
int b = a << 1;//20
int c = a << 2;//40
int d = a << 3;//80
int e = a << 4;//160
printf("a == %d\n", a);
printf("b == %d\n", b);
printf("c == %d\n", c);
printf("d == %d\n", d);
printf("e == %d\n", e);
//负数
int f = -10;
//10二进制数字 10000000 00000000 00000000 00001010 -原码
// 11111111 11111111 11111111 11110101 -反码
// 11111111 11111111 11111111 11110110 -补码
int g = f << 1;
//左边丢弃,右边补上
// 11111111 11111111 11111111 11101100 -补码 - g
// 10000000 00000000 00000000 00010100 -原码 - g 等于-20
printf("f == %d\n", f); // -10
printf("g == %d\n", g); // -20
return 0;
}
>>右移位操作符
右移有两种——逻辑和算术,这取决于编译器,vs编译器是采用算术右移。
逻辑右移:
算术右移:
注意:左移和右移的都是补码
正数:
逻辑右移
右移前:00000000 00000000 00000000 00001010 10
右移后:00000000 00000000 00000000 00000101 他总是在前面补0,这显然会带来一定的错误
算术右移
右移前: 00000000 00000000 00000000 00001010
右移后: 000000000 00000000 00000000 0000101 左边⽤原该值的符号位填充,右边丢弃
int main()
{
int a = 10;
int b = a >> 1;
printf("a == %d\n", a);//10
printf("b == %d\n", b);//5
//算术右移和左移差不多但是是反过来 /2
return 0;
}
负数:
逻辑右移
右移前:11111111 11111111 11111111 11110110 -10
右移后:011111111 11111111 11111111 1111011 他总是在前面补0,这显然会带来一定的错误
011111111 11111111 11111111 1111011 // 2147483643 是一个很大的正数
算术右移
右移前: 11111111 11111111 11111111 11110110
右移后: 111111111 11111111 11111111 1111011 左边⽤原该值的符号位填充,右边丢弃
取反+1
111111111 11111111 11111111 1111011
100000000 00000000 00000000 0000101 // -5
int main()
{
int a = -10;
int b = a >> 1;
printf("a == %d\n", a);//-10
printf("b == %d\n", b);//-5
//算术右移和左移差不多但是是反过来 /2
return 0;
}
注意:不要写出 a<<-1 这种逻辑代码 //error
位操作符
& - 按位与 && - 逻辑与(并且)
| - 按位或 || - 逻辑或(或者)
^ - 按位异或
~ - 按位取反
& - 按位与
int main()
{
int a = 4;
int b = -7;
int c = a & b;
//使用补码进行按位与
//对应二进制位有0就是0,两个同时为1才是1。
//00000000 00000000 00000000 00000100 - 4的补码
//11111111 11111111 11111111 11111001 - (-7)的补码
//00000000 00000000 00000000 00000000 - 0
printf("c == %d\n", c);
return 0;
}
| - 按位或
int main()
{
int a = 4;
int b = -7;
int c = a | b;
//使用补码进行按位或
//对应二进制位有1就是1,两个同时为0才是0。
//00000000 00000000 00000000 00000100 - 4的补码
//11111111 11111111 11111111 11111001 - (-7)的补码
//11111111 11111111 11111111 11111101 - 补码
//取反 +1
//10000000 00000000 00000000 00000011 - 原码 - (-3)
printf("c == %d\n", c);// -3
return 0;
}
^ - 按位异或
int main()
{
int a = 4;
int b = -7;
int c = a ^ b;
//使用补码进行按位异或
//规则:相同为0,相异为1
//00000000 00000000 00000000 00000100 - 4的补码
//11111111 11111111 11111111 11111001 - (-7)的补码
//11111111 11111111 11111111 11111101 - 补码
//10000000 00000000 00000000 00000011 - 原码
printf("c == %d\n", c);// -3
return 0;
}
~ - 按位取反
int main()
{
int a = 0;
int b = ~a;
//00000000 00000000 00000000 00000000 -补码
//11111111 11111111 11111111 11111111 -b中的补码
//取反 +1
//10000000 00000000 00000000 00000001 -- (-1)
printf("b == %d\n", b);// -1
return 0;
}
异或运算的特殊场景
相同为0,相异为1
a^a = 0
a^0 = a
假设a为1
二进制 001
a ^ a 000 - 0
a ^ 0 001 - 1
支持交换律 即 a ^ b ^ a == a ^ a ^ b;
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 ^ b) ^ a; 0 ^ b; b
printf("交换后:a = %d b = %d\n", a, b);
return 0;
}
实际题目应用
统计二进制数中1的个数
我们知道十进制数统计每个各位数时是 %10 /10
那么二进制数可以通过 %2 /2
方法一:
int count_of_bit(unsigned int m)
{
int count = 0;
while (m)
{
if (m % 2 == 1)
count++;
m /= 2;
}
return count;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = count_of_bit(n);
printf("%d\n", ret);
return 0;
}
方法二:
int count_of_bit(unsigned int m)
{
int count = 0;
int i = 0;
for (i = 0; i < 32; i++)
{
if (((m >> i) & 1) == 1)
count++;
}
return count;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = count_of_bit(n);
printf("%d\n", ret);
return 0;
}
方法三:
int count_of_bit(unsigned int m)
{
int count = 0;
while (m)
{
count++;
m = m & (m - 1);
}
return count;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = count_of_bit(n);
printf("%d\n", ret);
return 0;
}
改动单独一个位置
int main()
{
int n = 13;
//00000000 00000000 00000000 00001101 改动前
//00000000 00000000 00000000 00010000 改动或
//00000000 00000000 00000000 00011101 改动后
//将n的第五位改成1
//1<<(5-1)
n = n | (1 << 4);
printf("%d\n", n);
//00000000 00000000 00000000 00011101 改动前
//11111111 11111111 11111111 11101111 改动与
//{这个数字就是
//00000000 00000000 00000000 00010000 按位取反}
//00000000 00000000 00000000 00001101 改动后
//改回来
n = n & (~(1 << 4));
printf("%d\n", n);
return 0;
}
单目操作符和双目操作符
故名思意:只有一个操作数的就叫做单目操作符,存在两个操作数的称作双目操作符。
如:
!、++、--、&、*、+、-、~ 、sizeof、(类型)
int main()
{
int a = 1;
int b = 10;
像以下的加减乘除等等,是对两个操作数进行操作称作双目操作符
printf("%d\n", a + b); // 11
printf("%d\n", a - b); // -9
printf("%d\n", a * b); // 10
printf("%d\n", a / b); // 0
printf("%d\n", a % b); // 1
printf("%d\n", a & b);
// & 有0就是0,两个为1才是1
// 00001010 - 10
// 00000001 - 1
// 00000000 - 0
printf("%d\n", a | b);
// | 有1就是1,两个为0才是0
// 00001010 - 10
// 00000001 - 1
// 00001011 - 11
printf("%d\n", a ^ b);
// ^ 相同为0,相异为1
// 00001010 - 10
// 00000001 - 1
// 00001011 - 11
printf("%d\n", ~a);
// 00000000 00000000 00000000 00000001 - 1
// 11111111 11111111 11111111 11111110
// 取反+1
// 10000000 00000000 00000000 00000010 - -2
printf("%d\n", ~b);
// 00000000 00000000 00000000 00001010 - 10
// 11111111 11111111 11111111 11110101
// 取反+1
// 10000000 00000000 00000000 00001011 - -11
return 0;
}
像自增,自减这种是单目操作符
int main()
{
int a = 10;
int b = ++a;
int c = --a;
printf("%d\n", a); // 10
printf("%d\n", b); // 11
printf("%d\n", c); // 10
return 0;
}
三目操作符
列如:
表达式1 ? 表达式2 :表达式3;
如果表达式1的判断为真,执行表达式2,否则执行表达式3,我们可以利用这个特性来判断两个整数的大小。
int main()
{
int a = 1;
int b = 2;
int max = a > b ? a : b;
printf("max = %d\n", max); // max = 2
return 0;
}
下标访问[],函数调用()
下标访问 [ ]
操作数:一个数组名+一个索引值
int arr[10];
arr[10] = 1;
函数调用()
接受⼀个或者多个操作数:第⼀个操作数是函数名,剩余的操作数就是传递给函数的参数。
void Text01()
{
printf("hehe\n");
}
int main()
{
Text01();
return 0;
}
结构体成员访问操作符
结构体
用于存放一类型的元素集合,如学生这一对象,我们可以存放学生的名字,学生的性别,学生的学号等等。
声明格式如下:
struct Student
{
char name[20]; // 学生的名字
char sex[5]; // 学生的性别
int age; // 学生的年龄
char id[20]; // 学生的学号
};
初始化: