基础巩固
一. 程序
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* xx是一个指向struct Books结构体的指针变量
-
Book* y; == struct Books** yy是一个指向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);