基础巩固
一. 程序
1. 程序为什么要经过编译器编译才能运行
- 
计算机能识别的只有机器语言 - 机器语言: 二进制代码. 由 0和1组成010101等指令
 
- 机器语言: 二进制代码. 由 
- 
机器语言难记, 为方便编写程序增加易记忆的助记符, 汇编语言便应运而生 
- 
在汇编语言基础上为了更进一步方便编写程序, 高级语言登场了 - 高级语言: C、C++、Java、python 等
 
- 
高级语言编写的程序虽好, 但计算机无法识别, 为让其执行必须翻译成机器语言 
2. 高级程序转为机器语言做了什么
(1) 预处理
- 展开头文件 / 宏替换 / 去掉注释 等等 (生成 .i文件)
① 头文件
- 包含函数声明、宏定义、数据类型、全局变量声明等内容
Ⅰ. 为什么要用头文件
- 
将多次使用的代码写在头文件里, 需要的时候直接调用, 节省开发时间 
- 
编译器会将头文件内容拷贝一份, 后续调用的函数则有了来源, 才能通过编译 
Ⅱ. 全局变量是干嘛的?
- 多处代码需要用到同一个变量, 将其声明成全局变量写进头文件, 需要的时候直接调用, 降低代码量
Ⅲ. 注意事项
- 
<stdio.h>- 用 < > 包起来的 .h头文件是 C语言、C++ 标准库自带的
 
- 用 < > 包起来的 
- 
"test.h"- 用 " " 包起来的 .h头文件是自编写的头文件
 
- 用 " " 包起来的 
- 
学习 C++ 的要特别注意: - C++有万能头文件, 但其并非标准库自带的, 有的编译器能用, 有的不行, 为保证程序能正常运行, 尽量使用自带的
 
② 宏
- 
define: C、C++关键字, 用于定义宏
- 
#define Hello "Hello, World!"- 
类似这种用一个标识符 ( Hello) 来表示一个字符串 ("Hello, World!"), 就称为宏
- 
宏的标识符则称为宏名 
 
- 
Ⅰ. 为什么要用 define 定义宏
- 增强代码可读性和减少代码量
Ⅱ. 怎么定义宏
- 
#define 宏名 替换文本- 替换文本: 可以是任何常数、表达式、字符串等
 
- 
示例: - 
#define MAX 20
- 
#define MAX(x,y) ((x) > (y) ? (x) : (y))- 将一些简短函数以表达式形式写出, 方便引用 (很少用)
 
- 
#define ll long long- 给数据类型起一个别名
 
 
- 
(2) 编译
- 检查语法 (看有没有错误), 生成汇编代码 (生成 .s文件)
(3) 汇编
- 将汇编代码转化成二进制的机器码 (生成 .o文件)
(4) 链接
- 将 .o文件链接合成可执行程序
二. 结构体
1. typedef
- typedef: C、C++关键字
(1) 用途
- 
为现有的数据类型起别名 
- 
增强代码可读性和减少代码量 
(2) 用法
- 
typedef 原数据类型 新数据类型;- typedef long long ll;
 
(3) 注意
- 
用途有且只有为数据类型起别名, 功能没有宏定义强大 - 故而常用于结构体
 
- 
与宏定义不同, 前面是要修改的, 后面是修改结果 
2. 结构体定义
struct Books{char a[10];int b;
}
(1) 使用
- 
C语言: struct Books c;
- 
C++: Books c;
- 
因此在C语言中为方便写代码, 为结构体起一个别名方便编写程序 
typedef struct BOOks{char a[10];int b;
}Book;
- 后续使用: Book c;
(2) 等价
typedef struct Books{int a;char b;int c;
}* Book;
- 
Book x; == struct Books* x- x是一个指向- struct Books结构体的指针变量
 
- 
Book* y; == struct Books** y- y是一个指向- struct Books指针的指针变量 (二级指针)
 
以上一种容易理解错的写法, 以下是更好的写法
typedef struct Books{int a;char b;int c;
} Book;
- 
Book* x == struct Books* x
- 
Book** y == struct Books** y
三. 全局变量和局部变量
1. 变量名
- 
程序中局部变量和全局变量名称可以相同 (尽量不要这么干!!!) 
- 
在一个函数内, 如果名字相同, 会使用局部变量, 不使用全局变量 
2. 参数
- 
函数参数 (形参) 会被当成函数内的局部变量, 如果全局变量与其同名, 也是使用函数参数 - void func(int x),- x就是函数参数 (形参)
 
3. 初始化
- 
局部变量定义后不会被系统自动初始化, 需手动初始化 
- 
全局变量定义后会被系统自动初始化 (设为默认值 0)- 不同类型全局变量显示: 整数为 0、字符为'\0'、指针为NULL
 
- 不同类型全局变量显示: 整数为 
以上变量知识代码示例:
int x = 100;void func(int x){printf("\n函数内形参与全局变量同名, 使用形参 x . x = %d\n", x);x = 50;printf("\n函数内修改值修改的是形参 x 的值, x = %d\n", x);
}int main(){printf("\nmain函数里是全局变量 x , 因为函数内无同名的. x = %d\n", x);func(10);return 0;
}
--------------------------------------------------------------------
// 以下是部分类型全局变量初值
int test_int;
char test_char;
double test_double;
int *test_ptr;
int test_array[3];int main(){printf("test_int: %d\n", test_int);printf("test_char: %d\n", test_char);printf("test_double: %f\n", test_double);printf("test_ptr: %s\n", test_ptr == NULL ? "NULL" : "non-NULL");for (int i = 0; i < 3; i++){printf("test_array %d: %d\n", i, test_array[i]);}printf("\n");return 0;
}
四. 常量
1. 定义与使用
- 
常量是一个固定值, 在程序运行过程中不会改变 
- 
固定值又称字面量 
- 
使用 #define或const声明
2. 示例
#define MAX 20const int a = 5;// 错误写法
const int a; //常量必须定义就赋值const int a;
a = 5; // 也不行,常量必须定义就赋值!!!
五. 进制转换
1. 十进制转二进制
- 使用短除法一直除以2, 直到商1
 
- 商1后以倒着的形式写出二进制结果
- 十进制: 11 二进制: 1011
2. 十进制转任意进制
- 
假设十进制要转成 R 进制 
- 
使用短除法一直除以 R , 直到商1 
- 
商1后以倒着的形式写出 R 进制结果 
3. 十进制转十六进制
(1) 十六进制每一位的数字
- 
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
- 
数字 10 ~ 15用A ~ F替换, 以方便表示
(2) 十六进制表示方法
- 
十六进制需加上 0x前缀来表示 (常见)- 0xD: 十六进制的13
 
- 
后缀表示 (少见) : DH- 与 0xD等价, 但是是添加的后缀H
 
- 与 
(3) 转换方法
- 
使用短除法一直除以16, 直到商1或商0 
- 
商1后以倒着的形式写出十六进制结果 

- 十进制: 100 十六进制: 64 (0x64)
4. 二进制转十进制
- 
将二进制 110101 转十进制 
- 
方法: 将每一位乘以 2 的 n 次方 (n是位数 - 1) - 
1 * 2^5 + 1 * 2^4 + 0 * 2^3 + 1 * 2^2 + 0 * 2^1 + 1 * 2^0
- 
32 + 16 + 0 + 4 + 0 + 1 = 53
 
- 
5. 任意进制转十进制
- 
假设有 R 进制 转十进制 
- 
方法: 将每一位乘以 R 的 n 次方 (n是位数 - 1) - 以十六进制举例: 0xD ⭢ D * 16^0 = 13 * 1 = 13
 
- 以十六进制举例: 
6. 二进制转十六进制
- 
将二进制 110101 转十六进制 
- 
方法: 将二进制每四位隔开, 不足四位前面补0, 然后把四位二进制全转成十进制后拼起来 - 
110101 ⭢ 0011 | 0101 ⭢ 3 | 5 ⭢ 0x35
- 
验证: 3 * 16^1 + 5 * 16^0 = 48 + 5 = 53
 
- 
7. 十六进制转二进制
- 
将十六进制 0xD 转二进制 
- 
方法: 将每一位转二进制然后拼起来 - 0xD ⭢ 13 ⭢ 0011 | 0101 ⭢ 00110101 ⭢ 110101
 
8. 二进制转八进制
- 
将二进制 110101 转八进制 
- 
方法: 将二进制每三位隔开, 不足三位前面补0, 然后把三位二进制全转成十进制后拼起来 - 110101 ⭢ 110 | 101 ⭢ 6 | 5 ⭢ 65 ⭢ 065(八进制前缀为 0 )
 
常用的20个数字的各个进制表示法

六. 原码、反码、补码
二进制最高位为符号位 : 0 为正数, 1为负数
正数的原码、反码、补码都一样
1. 源码
- 
负数: -6 
- 
二进制: 1000 0110 
2. 反码
- 
二进制: 1111 1001 
- 
在源码基础上除符号位都取反 
3. 补码
计算机中最终存储的是补码
- 
二进制: 1111 1010 
- 
在反码基础上加一 
七. 运算符
1. 基础运算符
(1) 整除 /
① 特性
- 向下取整
扩展:
向下取整: 找到最大的小于等于它的整数
(1.3 取1 ; 5.6 取5)
向上取整: 找到最小的大于等于它的整数
(1.3 取2 ; 5.6 取6)
floor()向下取整,ceil()向上取整, 需加上<math.h>头文件
② 示例代码
int a = 5 / 2;
printf("%d\n", a);
(2) 自增 ++
① 特性
- 
++在前先加一再输出
- 
++在后先输出再加一
② 示例代码
int b = 1;
printf("%d\n", ++b);
printf("%d\n", b++);
printf("%d\n", b);
(3) 自减 --
① 特性
- 
--在前先减一再输出
- 
--在后先输出再减一
② 示例代码
int c = 5;
printf("%d\n", --c);
printf("%d\n", c--);
printf("%d\n", c);
(4) 三目运算符 ? :
① 示例代码
int *test_ptr;
printf("test_ptr: %s\n", test_ptr == NULL ? "NULL" : "non-NULL");
② 解释
- 
test_ptr 等于 NULL吗 ? 
- 
等于 (真) 则输出冒号左边的 NULL
- 
不等于 (假) 则输出冒号右边的 non-NULL
2. 位操作符
(1) 左移 <<
① 原理
- 
假设十进制 6左移一位
- 
首先将十进制 6转为二进制0110
- 
然后左移一位, 得到 1100
- 
再转成十进制, 结果为 12
② 特性
- 
左移直接对二进制进行操作, 更快 
- 
左移一位相当于乘二 
③ 示例代码
int d = 6;
d = d << 1;
printf("%d\n",d);
(2) 右移 >>
① 原理
- 
假设十进制 6右移一位
- 
首先将十进制 6转为二进制0110
- 
然后右移一位, 得到 0011
- 
再转成十进制, 结果为 3
② 特性
- 
右移直接对二进制进行操作, 更快 
- 
右移一位相当于除二 (向下取整) 
③ 示例代码
int e = 6;
e = e >> 1;
printf("%d\n",e);
(3) 取反 ~
① 原理
- 
假设十进制 6取反
- 
首先将十进制 6转为二进制补码0000 0110
- 
然后将每一位取反, 包括符号位, 得到 1111 1001
- 
再转成原码, 得到 1000 0111
- 
最后转成十进制, 结果为 -7
② 示例代码
int f = ~6;
printf("%d\n",f);
(4) 按位或、按位与、按位异或
① 原理
Ⅰ. 按位或 |
- 
十进制: 5 二进制: 0101 
- 
十进制: 9 二进制: 1001 
- 
按位或: 有一为一: 1101 
Ⅱ. 按位与 &
- 
十进制: 5 二进制: 0101 
- 
十进制: 9 二进制: 1001 
- 
按位与: 都一为一: 0001 
Ⅲ. 按位异或 ^
- 
十进制: 5 二进制: 0101 
- 
十进制: 9 二进制: 1001 
- 
按位异或: 不同为一: 1100 
② 示例代码
int g = 5 | 9;
printf("%d\n", g);int h = 5 & 9;
printf("%d\n", h);int l = 5 ^ 9;
printf("%d\n", l);
③ 按位异或 特殊用法
- 交换两个整数
int m = 10, o = 11;
m = m ^ o;
o = o ^ m;
m = m ^ o;printf("\n%d\n", m);
printf("\n%d\n", o);
