每日英语:
operation:操作
core dumped:核心已转储,表示程序崩溃了
inc = increase:自增
dec = decrease:自减
rel = relationship:关系
logic:逻辑
shift:移位
文章目录
回顾:
1. 变量的数据类型
1.1 数据类型的基本功能
指定变量分配的内存大小
1.2 12类基本数据类型
char/unsigned char/short/unsigned short/int/unsigned int/long/unsigned long/long long/unsigned long long/float/double
注意:unsigned int 和unsigned long随着操作系统的为数不一样而不一样
1.3 详解字符类型
字符常量:‘A’
字符变量:存储的不是字符,而是存储对应的ASCII码(实际就是1个字节的整数)
占位符:%c
1.4 整数类型:int
short/unsigned short/long/unsigned long进行修饰
六种形式
unsigned long a = 250; 等价于 unsigned long int 啊= 250;
1.5 整型常量
100/100L/100UL/100u等
1.6 浮点数
float和double
1.7 数据类型和对应的占位符
%c / %hhd / %hd / %hu / %d / %u / %ld / %lu / %f /%lf / %g / %lg
注意:回滚现象
2. 进制转换
2.1 计算机只认二进制
内存只能存储二进制
2.2 8进制, 10进制, 16进制
这三种进制仅仅是二级制的另外一种表现形式而已
内存中的数不会随着进制的改变而改变
2.3 2进制,8进制,10进制之间转换
用计算器
2进制和16机制的口算必须口算!
例如:
0x83ac9fe2(4字节数据):1000 0011 1010 1100 1001 1111 1110 0010
2.4 大招
务必拿下数据在内存中的二进制存储形式
第五课:运算符和表达式
1. 概念
明确计算机程序最终玩的是内存,而内存存储的是各种二进制数据
这些数据之间必然存在各种运算,浙江就需要相应的数据运算的操作方式
运算符:对内存中的数字进行各种运算的符号,例如:+,-,*,/等
表达式:运算符和数字结合起来的式子,简称表达式
2. C语言提供的运算符
2.1 算数运算符
加, 减, 乘, 除, 取余(求模),对应的符号:+, -, *, /, %
注意:
1. 如果参与除法计算的两个数字是整型数则计算结果之保留整数部分
例如:5 / 2 = 2
2. / 和 % 不能对0进行操作,否则程序崩溃
例如:5 / 0 或者 5 % 0
3. % 不能对浮点数使用,否则程序崩溃
4. % 结果与其左边的数字符号一致
例如:-7 % 2 = -1 7 % -2 = 1
5. / 如果除数位浮点数,最后得到int无穷大的结果
例如:5 % 0.0
案例演练运算符:
实验步骤:
mkdir 桌面/tarena/stdc/day04
cd 桌面/tarena/stdc/day04
vim oper.c
gcc -o oper oper.c // 建议练习分步法
./oper
1 /*算数运算符演练*/
2 #include <stdio.h>
3
4 int main(void)
5 {
6 int a = 5, b = 2, c = 0;
7
8 printf("a + b = %d\n", a + b);
9 printf("a - b = %d\n", a - b);
10 printf("a * b = %d\n", a * b);
11 printf("a / b = %d\n", a / b);
12 printf("a %% b = %d\n", a % b);
13
14 //printf("a / c = %d\n", a / c);
15
16 double d = 0;
17 //printf("a %% d = %lf\n", a % d);//程序崩溃
18 printf("a / d = %lf\n", a / d);
19
20 printf("-7 / 2 = %d, 7 / -2 = %d\n", -7 / 2, 7 / -2);
21 printf("-7 %% 2 = %d, 7 %% -2 = %d\n", -7 % 2, 7 % -2);
22
23 return 0;
24 }
结果:
a + b = 7
a - b = 3
a * b = 10
a / b = 2
a % b = 1
a / d = inf
-7 / 2 = -3, 7 / -2 = -3
-7 % 2 = -1, 7 % -2 = 1
2.2 赋值运算符
=, 就是将右边的值给左边的变量,也就是将变量对应的内存值进行修改
形式1:
int a = 10;
a = 20;
形式2:
int a, b, c;
a = b = c = 10;
printf("%d %d %d\n", a, b , c);
形式3:
符复合运算符:赋值运算符=和其他运算符结合起来使用
例如:
a += b; 等价于a = a + b;
a -= b; 等价于a = a - b;
a *= b; 等价于a = a * b;
a /= b; 等价于a = a / b;
a % = b; 等价于a = a % b;
注意事项:不能个常量和表达式赋值:
100 = 200; // gcc报错
100 = a; // gcc报错
a + b = c; // gcc报错,gcc先算a + b结果肯定是一个常量(数字)
参考代码:assign.c
1 /*赋值运算符演练*/
2 #include <stdio.h>
3
4 int main(void)
5 {
6 int a = 10;
7 a = 250;
8 printf("a = %d\n", a);
9
10 int b, c, d;
11 b = c = d = 250;
12 printf("%d %d %d\n", b, c, d);
13
14 b = 1;
15 c = 2;
16 b += c;// b = b + c
17 printf("b = %d, c = %d\n", b, c);
18
19
20 return 0;
21 }
结果:
a = 250
250 250 250
b = 3, c = 2
2.3 自增运算符(++)和自减运算符(–)
2.3.1 定义
自增运算符就是让变量对应的内存数值加1
自减运算符就是让变量对应的内存数值减1
2.3.2 四种形式
前++:先对变量的值加1,后计算表达式的值
例如:
int a = 1;
int b = 0;
b = ++a;
printf(“a = %d, b = %d\n”, a, b); // a = 2 b = 2
后++:先计算表达式的值(b = a =1),后对变量的值加1(a++ = 1 + 1)
例如:
int a = 1;
int b = 0;
b = a++;
printf(“a = %d, b = %d\n”, a, b); // a = 2 b = 1
前–:先对变量值减1,后计算表达式的值
例如:
int a = 2;
int b = 0;
b = --a;
printf(“a = %d, b = %d\n”, a, b); // a = 1 b = 1
后–:先计算表达式的值,后对变量值减1
例如:
int a = 2;
int b = 0;
b = a–;
printf(“a = %d, b = %d\n”, a , b); // a = 1 b = 2
2.2.3 不能对常量自增或者自减
100++;报错
100–;报错
参考代码:inc_dec.c
1 /*自增自减运算演示*/
2 #include <stdio.h>
3
4 int main(void)
5 {
6 int a =1;
7 int b = 0;
8
9 //100++; gcc报错
10
11 b = ++a;
12 printf("a = %d, b = %d\n", a, b); // a = 2, b = 2
13
14 b = a++;
15 printf("a = %d, b = %d\n", a, b); // a = 3, b = 2
16
17 b = --a;
18 printf("a = %d, b = %d\n", a, b); // a = 2, b = 2
19
20 b = a--;
21 printf("a = %d, b = %d\n", a, b); // a = 1, b = 2
22
23 return 0;
24 }
2.3.4 关系运算符
==:等于
!=:不等于
>:大于
<:小于
>=:大于等于
<=:小于等于
注意:
1. 关系运算符的结果是:1(俗称真)或者0(俗称假)
2. 不要进行连续的关系运算,例如:5<4<3,gcc编译器先算5<4,拿着结果0再跟3做比较
问:如果要进行连续的关系运算,怎么办呢?
答:采用逻辑运算实现
参考代码:rel.c
1 /*关系运算符演示*/
2 #include <stdio.h>
3
4 int main(void)
5 {
6 printf("1 = 1的结果是%d\n", 1 == 1);
7 printf("1 != 1的结果是%d\n", 1 != 1);
8 printf("1 > 2的结果是%d\n", 1 > 2);
9 printf("1 < 2的结果是%d\n", 1 < 2);
10
11 // 注意:gcc编译器先算5>4的结果是0,然后拿着0和3比较,结果为1
12 printf("5>4>3的结果是%d\n", 5<4<3);
13 printf("3<7<5的结果是%d\n", 3<7<5);
14
15 return 0;
16 }
结果:
1 = 1的结果是1
1 != 1的结果是0
1 > 2的结果是0
1 < 2的结果是1
5>4>3的结果是1
3<7<5的结果是1
2.3.5 逻辑(真真假假,假假真真)运算符
1. 明确:计算机中的“真”就是非0(包括1),“假”就是0
2. 逻辑运算符的类型:三类
1. 逻辑与:&&(并且,与此同时的意味)
2. 逻辑或运算:||(具有或者的意味)
3. 逻辑非:!(对着干)
-
逻辑与&&运算符的特点:
使用语法:c = 表达式A && 表达式B;
语义:只有当A和B的值都为真(非0),c的值才为真
只要A,B有一个为假,c的值就为假
例如:登录抖音时,只有用户名和密码都对了,都为真才能登录
只要有一个不对,登录就是失败
例如:1&& 1; // 结果为真
1 && 0; // 结果为假
0 && 250; //结果为假
0 && 0; // 结果为假
-
逻辑或||运算符的特点:
使用语法:c = 表达式A || 表达式B
语义:只要A和B中有一个为真,c的值就为真
只有A和B都为假,c的值就为假
例如:登录抖音时,可以采用QQ登陆或者微信登陆或者手机号登陆
只要有一个对了,就可以登陆抖音
都不对,登陆失败
例如:250 || 520; //真
250 || 0; //真
0 || 250; // 真
0 || 0; // 假
-
逻辑非!运算符特点
使用语法:!表达式A
语义:A假结果为真,A真结果为假
例如: !250; // 结果为假
!0; // 结果为真
2.3.6 切记:短路运算
笔试题必考,开发常用
形式1:A && B:如果A的值为假,B的1代码就不会被执行
例如:
int a = 1;
1 > 2 && ++a;
printf("a = %d\n", a); // a = 1
int b = 1;
2 > 1 && ++b
printf("a = %d\n", a); // b = 2
形式2:A || B:如果A的值为真,B的代码就不会被执行;如果A为假,B的代码接着被执行
int a = 1;
1 > 2 && ++a;
printf("a = %d\n", a); // a = 2
int b = 1;
2 > 1 && ++b
printf("a = %d\n", a); // b = 1
参考代码:logic.c
1 /*逻辑运算符*/
2
3 #include <stdio.h>
4
5 int main(void){
6
7 //逻辑非
8 printf("!1的结果是%d\n", !1); // 0
9 printf("!250的结果是%d\n", !250); //0
10 printf("!0的结果是%d\n", !0); // 1
11
12 // 逻辑与
13 printf("2 && 3的结果是%d\n", 2 && 3); // 1
14 printf("0 && 3的结果是%d\n", 0 && 3); // 0
15
16 //逻辑或
17 printf("2 || 3的结果是%d\n", 2 || 3);
18 printf("0 || 1的结果是%d\n", 0 || 1);
19 printf("1 || 1的结果是%d\n", 1 || 1);
20
21 // 关系运算符bug:5<4<3,采用逻辑运算符解决
22 printf("5<4<3的结果是%d\n", 5<4 && 4<3); // o
23
24 return 0;
25 }
结果:
!1的结果是0
!250的结果是0
!0的结果是1
2 && 3的结果是1
0 && 3的结果是0
2 || 3的结果是1
0 || 1的结果是1
1 || 1的结果是1
5<4<3的结果是0
a = 1, b = 2
a = 2, b = 2
2.3.7 位(bit)运算
1.功能
就是专门对内存中的二进制数进行各种运算
2.位运算四种形式
位与:&
位或:|
位异或:^
位反:~
3.位与&运算的特点
语法:C = A & B;
例如:
A:0 1 0 1 1 0 1 0 0x5a
B:1 1 1 0 0 0 1 1 0xe3
&----------------------------------
C:0 1 0 0 0 0 1 0 0x42
规律:任何数跟0做位与,结果都是0,任何数1跟位与,保持原值
4.位或|运算符特点:
语法:C = A | B
例如
A:0 1 0 1 1 0 1 0 0x5a
B:1 1 1 0 0 0 1 1 0xe3
|----------------------------------
C:1 1 1 1 1 0 1 1 0xfb
规律:任何数跟1做位或,结果都是1,任何数跟0位与,保持原值
5. 位异运算符特点:
语法:C = A ^ B;
例如:
A:0 1 0 1 1 0 1 0 0x5a
B:1 1 1 0 0 0 1 1 0xe3
^----------------------------------
C:1 0 1 1 1 0 0 1 0xb9
规律:相同为0,不同为1
6. 位反运算符对的特点
语法:C = A ^ B
例如:
A:0 1 0 1 1 0 1 0 0x5a
~A:1 0 1 0 0 1 0 1 0xa5
规律:1变0, 0变1
参考代码:bit.c
/*位运算符演示*/
#include <stdio.h>
int main(void){
// 位与运算&
printf("0x5a & 0xe3的结果是%#x\n", 0x5a & 0xe3); // 01011010 & 11100011 = 01000010 = 0x42
printf("0x44 & 0xc1的结果是%#x\n", 0x44 & 0xc1); // 01000100 & 11000001 = 01000000 = 0x40
printf("0xa3 & 0xfe的结果是%#x\n", 0xa3 & 0xfe); // 10100011 & 11111110 = 10100010 = 0xa2
// 位或运算|
printf("0x5a | 0xe3的结果是%#x\n", 0x5a | 0xe3); // 01011010 | 11100011 = 11111011 = 0xfb
printf("0x44 | 0xc1的结果是%#x\n", 0x44 | 0xc1); // 01000100 | 11000001 = 11000101 = 0xc5
printf("0xa3 | 0xfe的结果是%#x\n", 0xa3 | 0xfe); // 10100011 | 11111110 = 11111111 = 0xff
// 位异或运算^
printf("0x5a ^ 0xe3的结果是%#x\n", 0x5a ^ 0xe3); // 01011010 ^ 11100011 = 10111001 = 0xb9
printf("0x44 ^ 0xc1的结果是%#x\n", 0x44 ^ 0xc1); // 01000100 ^ 11000001 = 10000101 = 0x85
printf("0xa3 ^ 0xfe的结果是%#x\n", 0xa3 ^ 0xfe); // 10100011 ^ 11111110 = 01011101 = 0x5c
// 位反运算~
// 1. 0x5a默认的数据类型为什么类型int类型,4字节,32位
// 2. 0x5a真正的二进制是:0000 0000 0000 0000 0000 0000 0101 1010
// 3. ~0x5a结果: 1111 1111 1111 1111 1111 1111 1010 0101
// 16进制结果: f f f f f f a 5
printf("~0x5a结果是%#x\n", ~0x5a);
printf("~0x5a结果是%d\n", ~0x5a);
printf("~0x5a结果是%u\n", ~0x5a);
// 问:就想看到0x5a,不想看到前面一堆的f、
unsigned char a = ~0x5a; // 将4字节的0xffffffa5的1字节0xa5保存到a变量中,其他的f全部丢失
printf("a = %#x\n", a);
char b = ~0x5a; // 将4字节的0xffffffa5的1字节0xa5保存到a变量中,其他的f全部丢失
printf("b = %#x\n", b);
return 0;
}
结果:
0x5a & 0xe3的结果是0x42
0x44 & 0xc1的结果是0x40
0xa3 & 0xfe的结果是0xa2
0x5a | 0xe3的结果是0xfb
0x44 | 0xc1的结果是0xc5
0xa3 | 0xfe的结果是0xff
0x5a ^ 0xe3的结果是0xb9
0x44 ^ 0xc1的结果是0x85
0xa3 ^ 0xfe的结果是0x5d
~0x5a结果是0xffffffa5
~0x5a结果是-91
~0x5a结果是4294967205
a = 0xa5
b = 0xffffffa5
2.3.8. 移位运算符:<<,>>
-
功能:就是将二进制数统一向左或者向右移动n个位置(简称左移,右移)
-
左移,右移:
左移:A << B
语义:将A左移B个位置
右移:A >> B
语义:将A右移B个位置
例如:
1 << 3:将1左移3位(bit)
3 >> 1:将3右移1位(bit)
- 移位运算符的特点:
1.向左移动后右边空出来的数据用0来填充
例如:A数据前提是char类型
A = 0x5a = 01011010
A << 2 = 01011010 << 2 结果为:011010 + 00 即:01101000 = 0x68
2.无符号类型数字右移时,左边空出来的数据用0填充
例如:01011010 >> 2 结果为:00 + 010110 即:00010110 = 0x16
3.有符号类型数字右移时,左边空出来的数据用符号位来填充
例如:10100101(前提是char类型)>> 2 结果为:11 + 101001 即:11101001 = 0xe9
4.左移n位相当于乘以2的n次方
5.右移n位相当于除于2的n次方
切记:如果将来程序中涉及乘以或者除以2的n次方运算,务必用左移或者右移
严重鄙视用*或者/,因为乘或者效率极低
例如:
int a = 1; int b = a * 4; // 遭鄙视的垃圾代码 int b = a << 2; // 高薪代码
6.移位运算不会改变变量本身的值
例如:
int a = 3; int b = a << 1; printf("a = %d, b = %d\n", a, b); // a = 3 ,b = 6
2.3.9 实际开发常用的位操作公式(切记!):
位清零:通过一下需求找规律:
1. 将某个数据A第0位清0,其他位保持不变:
A &= ~(1 << 0); // 等价于:A = A & ~(1 << 0);
2. 将某个数据A的第1位清0,其他位保持不变
A & = ~(1 << 1);
3. 将某个数据A的第2位清零,其他位保持不变
A &= ~(1 << 2);
4. 将某个数据的第0位清零和第1位清零,其他位保持不变
A &= ~(3 << 0); // 3的二进制0011
5. 将某个数据的第1位清零和第2位清零,其他位保持不变
A &= ~(3 << 1);
6. 将某个数据的第2位清零和第3位清零,其他位保持不变
A &= ~(3 << 2);
7. 将某个数据的第0位清零和第1位清零和第2位清零,其他位保持不变
A &= ~(7 << 0); // 7的二进制是0111
8. 将某个数据的第1位清零和第2位清零和第3位清零,其他位保持不变
A &= ~(7 << 1); // 7的二进制是0111
9. 将某个数据的第0位清零,第1位清零,第2位清零和第3位清零,其他位保持不变
A &= ~(0xf << 0); // f的二进制是1111
9. 将某个数据的第1位清零,第2位清零,第3位清零和第4位清零,其他位保持不变
A &= ~(0xf << 1); // f的二进制是1111
……
结论:连续位清零0,其他位保持不变
公式:A &= ~(B << C);
A:操作数据
B:连续的位数:1 / 3 / 7 / 0xF / 0x1F / 0x3F / 0x7F / 0xFF / 0x1FF / 0x3FF / …
C:起始位
公式推导演练:目标:将0xA5(10100101)的第0位清0,其他位保持不变(结果:10100100 = 0xa4)
最终代码:int a = 0xA5;
a & = ~(1 << 0); // 等价于a = a & ~(1 << 0);
推导如下:
1.先算:1 << 0 0000 0000 0000 0000 0000 0000 0000 0001
2.然后算~(1 << 0) 1111 1111 1111 1111 1111 1111 1111 1110
3.a的二进制 0000 0000 0000 0000 0000 0000 1010 0101
4.然后算a & ~ (1 << 0); 0000 0000 0000 0000 0000 0000 1010 0101
0 0 0 0 0 0 A 4
5.然后把运算结果在赋值给a = 0x000000A4 = 0xA4
公式推导演练:目标:将0xAF(10101111)的第1,2,3位清0,其他位保持不变(结果:10100001 = 0xA1)
最终代码:int a = 0xAF;
a &= ~(7 << 1);
推导:
1.先算 7(0111) << 1 0000 0000 0000 0000 0000 0000 0000 1110
2.然后再算~(7 << 1); 1111 1111 1111 1111 1111 1111 1111 0001
3.a的二进制; 0000 0000 0000 0000 0000 0000 1010 1111
4.然后算a & ~(7 << 1); 0000 0000 0000 0000 0000 0000 1010 0001
5.最后将结果赋值给a = 0x000000A1 = 0xA1
位置1操作,通过一下需求找规律:
1.将某个数据A的第0位置1,其他位置保持不变
A |= (1 << 0);
2.将某个数据A的第1位置1,其他位置保持不变
A |= (1 << 1);
3.将某个数据A的第2位置1,其他位保持不变
A |= (1 << 2);
4.将某个数据的A的第0,1位置1,其他位保持不变
A |= (3 << 0);
4.将某个数据的A的第1,2位置1,其他位保持不变
A |= (3 << 1);
结论:连续位置1,其他位保持不变
公式:A |= ~(B << C);
A:操作数据
B:连续的位数:1 / 3 / 7 / 0xF / 0x1F / 0x3F / 0x7F / 0xFF / 0x1FF / 0x3FF / …
C:起始位
公式推导演练:目标:将0xA1(10100001)的第1,2,3位置1,其他位保持不变(结果:10101111 = 0xAF)
最终代码:int a = 0xA1;
a |= (7 << 1);
推导如下:
1.先算:7 << 1: 0000 0000 0000 0000 0000 0000 0000 1110
2.a的二进制: 0000 0000 0000 0000 0000 0000 1010 0001
3.a | (7 << 1) 0000 0000 0000 0000 0000 0000 1010 1111
4.最后将结果赋值给a=0x000000AF = 0XAF
参考代码:bit2.c
1 /*位运算终级公式演练*/
2 #include <stdio.h>
3
4 int main(void)
5 {
6 int a = 0x5a; //01011010
7
8 // 将a变量的第四位清零,其他位保持不变
9 a &= ~(1 << 4); // 01001010
10 printf("a = %#x\n", a); // 0x4a
11
12
13 // 将a变量的第2,3位清领,其他位保持不变
14 a &= ~(3 << 2);
15 printf("a = %#x\n", a); // 0x42
16
17 //将a变量的第4位置1(0001 0000)
18 a |= (1 << 4); // 0x42:0100 0010
19 printf("a = %#x\n", a); // 0101 0010 0x52
20
21 // 将第2,3位置1
22 a |= (3 << 2); // 0000 1100
23 printf("a = %#x\n", a); // 0101 1110 0x5e
24
25
26 int b = 0x12345678;
27 // 将b的第4,5,6,7,8连续五位清0
28 b &= ~(0x1f << 4);
29 printf("b = %#x\n", b);
30 return 0;
31 }
建议:如果建拿过来操作的为数大于22位,建议先统一清0后置1
例如:将数据0xe578a431的第4,5,6,7置0xa,其他位保持不变,代码:
int a = 0xe578a431;
a &= ~(0xf << 4); // 先将4,5,6,7位清0
a |= (0xa << 4); // 然后将4,5,6,7位的值修改为0xa
参考代码:bit3.c
1 /*位运算终极公式演练*/
2 //结论:将来位操作,如果操作的位数大于等于2位,建议先统一清0后置1
3 #include <stdio.h>
4
5 int main(void)
6 {
7 int a = 0xfa78dc23;
8
9 //将a数据中的0xc修改为0x8,因为c是4位二进制,所以用0xf
10 a &= ~(0xf << 8);
11 a |= (0x8 << 8);
12 printf("a = %#x\n", a);
13
14 //将a数据中的0xa7修改位0x7a
15 a &= ~(0xff << 20);
16 a |= (0x7a << 20);
17
18 printf("a = %#x\n", a); // 0xf7a8d823
19
20
21 //将a中的0x8d8修改为0xccc
22 a &= ~(0xfff << 8);
23 a |= (0xccc << 8);
24 printf("a = %#x\n", a);
25
26 return 0;
27 }
结论:
a = 0xfa78d823
a = 0xf7a8d823
a = 0xf7accc23
2.3.10 取地址运算符&和解引用*运算符:
1.明确内存地址特性:计算机内存地址由32位二进制数组成,也就是任何地址都是32位,4字节
2.取地址运算符&作用:获取一个变量对应的内存首地址,打印地址值的占位符%p
3.取地址运算符&语法格式:&变量名;
例如: int a = 250; // 分配4字节空间放250这个数字
pritnf(“变量a的首地址是%p\n”, &a);
4.解引用运算符*作用:根据变量的首地址获取内存中的数据
或者根据变量的首地址来修改内存中的数据
5.解引用运算符*的语法: *变量的首地址;结果就是可以操作内存了
例如:
int a = 250;
printf("变量a的首地址是%p\n", &a);
printf("a = %d\n", *&a); // 根据变量a的首地址获取内存中的数据250
*&a = 520; // 根据变量a的首地址修改对应的内存数据,将250修改为520
切记:只要获取了变量的首地址,就可以对这个变量的内存无所欲为,可读取里面的数据
还可以修改里面的数据
参考代码:address.c
*取地址运算符&和解引用*演练*/
#include <stdio.h>
int main(void)
{
int a = 250;
printf("变量a的首地址是%p\n", &a);
return 0;
}