
1. 数据的二进制
| 进制 | 规则 | 使用数字 |
|---|---|---|
| 二进制 | 逢 2 进 1 | 0-1 |
| 八进制 | 逢 8 进 1 | 0-7 |
| 十进制 | 逢 10 进 1 | 0-9 |
| 十六进制 | 逢 16 进 1 | 0-9、A-F |
2. 进制转换
(1)其他进制转二进制
- 八进制转二进制:三位一体,一位八进制对应三位二进制。
- 十进制转二进制:短除法(除 2 取余)。
- 十六进制转二进制:四位一体,一位十六进制对应四位二进制。
(2)其他进制转八进制
- 二进制转八进制:三位一体。
- 十进制转八进制:短除法(除 8 取余)。
- 十六进制转八进制:先转二进制,再转八进制。
(3)其他进制转十进制
各个位上的数字乘以对应进制的位数次方(从右为 0 开始)后相加。
(4)其他进制转十六进制
- 二进制转十六进制:四位一体。
- 十进制转十六进制:短除法(除 16 取余)。
- 八进制转十六进制:先转二进制,再转十六进制。
3. 程序中的数据分类
| 分类 | 说明 | 包含类型 |
|---|---|---|
| 基本数据类型 | C 语言设计之初所规定的类型 | 整型、单精度浮点型、双精度浮点型、字符型 |
| 构造数据类型 | C 语言编程过程中为了方便编程而规定的类型 | 数组、指针、结构体、共用体、枚举 |
4. 存储单位换算
| 单位 | 换算 |
|---|---|
| 1 Byte | 8 bit(比特位,即二进制位数) |
| 1 KB | 1024 Byte(字节) |
| 1 MB | 1024 KB |
| 1 GB | 1024 MB |
| 1 TB | 1024 GB |
5. while循环
注意:char类型循环输入时,需要清空输入缓冲区 –getchar接收换行
什么时候清空缓冲区:1、结束
6. 字符数组
1. 字符数据是什么?
字符数组是一个字符型的一维数组。
1 | char ch[5] = {'h', 'e', 'l', 'l', 'o'}; |
如果作为字符串使用,需要以 '\0' 结尾:
1 | char ch[6] = {'h', 'e', 'l', 'l', 'o', '\0'}; |
2. 字符串的本质
字符串本质上是一个以 '\0' 结尾的字符型一维数组。
3. 字符串的操作
- 使用数组形式操作字符串:循环打印。
- 使用字符串格式操作字符串:
%s:自带循环,从指定地址开始输出字符,直到'\0'结束。'\0':字符串结束符。系统在定义字符串时自动添加;定义字符数组时,长度要提前预留'\0'的位置。
4. 字符串常用函数
(1)输入
gets:输入一个字符串,可以包含空格;即将过时,可能有警告。scanf:输入一个字符串,不能接收空格;空格在scanf中表示结束当前格式输入,不作为有效字符。
(2)字符串长度strlen:获取字符串实际长度,遇到'\0'结束,不计算'\0'。需要#include <string.h>。sizeof:获取数组空间大小,默认包含'\0'的位置。
(3)字符串复制
1 | strcpy(接收方, 提供方); |
会把提供方结尾的 '\0' 也复制过来。需要 #include <string.h>。
(4)字符串拼接
1 | strcat(接收方, 提供方); |
会把接收方结尾的 '\0' 覆盖掉。需要 #include <string.h>。
(5)字符串比较
1 | strcmp(s1, s2); |
返回值:
0:相等- 非
0:不相等,通常正数表示s1 > s2,负数表示s1 < s2
需要#include <string.h>。
7. 指针
7.1 指针的定义
格式:
1 | 指针指向的空间类型 *变量名; |
指针指向的空间类型:指针变量中存放谁的地址,就称为指针指向谁。
指针的大小:
- 64 位操作系统:指针大小固定为 8 字节。
- 32 位操作系统:指针大小固定为 4 字节。
7.2 解指针
格式:
1 | *变量名; |
含义:通过地址,操作指针指向空间的内容。
7.3 指针指向的空间类型
- 全局变量:变量的值不赋值默认为 0。
- 局部变量:变量的值不赋值默认为随机值。
7.4 野指针
野指针:没有明确指向的指针,应避免出现。
1 | int *p; // 假设 p 是局部变量 |
操作 *p,会操作一块未知空间,非常危险。
- 在 Linux 中有保护机制,会默认指向 0 地址,该地址会被保护起来,不能被操作。
- 若在 MCU 中没保护,操作的地址未知,非常危险。
避免野指针出现:
一般定义指针时,如果暂时不知道存放谁的地址,直接让指针指向 NULL(0 号地址)。
1 | int *p = NULL; // NULL 是 0 号地址,计算机预留,所有人都不能操作 |
假设不小心操作 *p,比如:
1 | *p = 200; |
会报段错误。
7.5 指针的偏移
指针的加减:以指针指向的空间大小为单位进行加减。
7.6 指针和数组
- 数组指针:指向数组的指针。
- 指针数组:数组中的元素都是指针。
(1)数组指针
指针指向一维数组:
1 | int num[6] = {1, 2, 3, 4, 5, 6}; |
p + 1:以 int 为单位。
指针指向二维数组:
1 | int a[2][3] = {{1, 2, 3}, {4, 5, 6}}; |
- 数组名:
a - 数组中的元素:
a[0]、a[1],每个元素类型都是int [3] p里面存放数组的首元素地址,p指向首元素p指针指向的空间类型:int [3]p的类型:int (*)[3]
取元素:
1 | a[i][j] |
(2)指针数组
定义数组:
1 | 元素类型 数组名[元素个数]; |
示例:
1 | int *ptr[10] = {NULL}; |
例子:
1 | char *s[3] = {"hello", "xyz", "abc"}; |
sizeof(s):64 位下为 24 字节。s[0]:char *,常量区"hello"的地址值。s[1]:char *,常量区"xyz"的地址值。s[2]:char *,常量区"abc"的地址值。
7.7 C 语言的 5 大区
| 区域 | 说明 |
|---|---|
| 栈区 | 存放局部变量 |
| 堆区 | 需要程序员用 malloc 等函数手动申请,用完之后用 free 函数释放 |
| 全局区/静态区 | 存放全局变量或者静态变量 |
| 代码区 | 存放二进制代码 |
| 常量区 | 存放常量 |
示例:
1 | char *p = "hello"; // p 是指针,p 里面存放地址,"hello" 会被自动存放到常量区 |
7.8 const
const:修饰变量,使其只读。
位置:定义变量的时候。
| 方式 | 写法 | 效果 |
|---|---|---|
| 方式 1 | const int num = 12; |
num 只读,num = 13; 报错 |
| 方式 2 | int num = 100; const int *p = # |
const 修饰 *p,不能通过 *p 改变 num,*p = 200; 报错 |
| 方式 3 | int num = 100; int const *p = # |
同方式 2 |
| 方式 4 | int a = 100, b = 200; int * const p = &a; |
const 修饰 p,p 的指向不能改,p = &b; 报错 |
| 方式 5 | int a = 100, b = 200; const int * const p = &a; |
第一个 const 修饰 *p,*p = 300; 报错;第二个 const 修饰 p,p = &b; 报错 |
const 在库函数中的应用:
1 | size_t strlen(const char *s); |
7.9 拓展:数组名与数组首地址
1 | int num[6] = {11, 22, 33, 44, 55, 66}; |
num:表示首元素地址,等于&num[0](特殊规定)。&num:表示整个数组的首地址。
用 %p 打印:
1 | num |
三个的数值一样,但是做指针偏移的时候,偏移量不一样:
num + 1:以数组的元素为单位,等于&num[0] + 1。&num + 1:以整个数组为单位。
定义指针的格式:
1 | int num[6] = {11, 22, 33, 44, 55, 66}; |
方式 1:
1 | int *p = num; // num == &num[0] |
p + 1:以 int 为单位。
方式 2:
1 | int (*p)[6] = # // &num 整个数组的首地址 |
p + 1:以 int [6] 为单位。
8. 函数
8.1 函数的意义
封装代码,减少重复。
8.2 自定义子函数从定义到执行的过程
| 阶段 | 说明 |
|---|---|
| 函数定义 | 函数真正实现功能的过程 |
| 函数声明 | 告知编译器该函数存在 |
| 函数调用 | 函数真正被执行 |
8.3 函数的定义
格式:
1 | 返回值类型 函数名(形参类型 形参名, ...) |
注意事项:
- 返回值类型和返回值必须一致。
- 如果没有返回值,返回值类型写
void。 return后面什么也不写,甚至return也可以不写。- 返回值最多只能返回 1 个数。
8.4 形参和实参的关系
- 形参的起始值来自于实参,即参数传递的过程。
- 实参的个数和位置必须和形参一一对应,传参是按照对应位置传递。
- 形参名和实参名可以一样,也可以不一样。
- 形参和实参各自占用不同的内存空间。形参在子函数中发生改变时,实参不变。
8.5 参数传递方式
| 方式 | 说明 |
|---|---|
| 值传递 | 形参的改变不会引起实参的改变,形参和实参占用不同的存储空间 |
| 地址传递 | 通过地址改变指针指向空间的值 |
| 数组传参 | 数组名是首元素地址;通过地址修改数组中的值,属于地址传递 |
8.6 static 的使用
| 类型 | 生命周期 | 作用范围 |
|---|---|---|
| 局部变量 | 从定义处开始,到当前函数结束 | 从定义处开始,到当前函数结束 |
| 全局变量 | 从定义处开始,到当前 .c 结束 |
从定义处开始,到当前 .c 结束 |
static 修饰局部变量 |
延长生命周期到整个 .c 结束 |
作用范围不变 |
补充说明:
static修饰局部变量,延长生命周期,但作用范围不变。- 一般返回值为数组时,数组前面要加
static。 - 如果子函数被多次调用,被
static修饰的变量只定义一次。
8.7 函数的递归操作
函数自身调用自身的一种操作。
注意:一定会存在递归的结束条件。
8.8 函数、指针和数组
(1)指针函数
指针函数:返回值为指针的函数。
格式:
1 | 返回类型 *函数名(形参列表); |
(2)函数指针
函数指针:定义指针,指针指向函数。
格式:
1 | 指针指向的空间类型 (*变量名)(形参列表); |
示例:
1 | float Avg_Fun(float *p, int n); |
(3)函数指针数组
当多个函数指针的类型一样时,可以将它们封装成数组。
示例:
1 | int Max_Fun(int a, int b); |
由于 p1 和 p2 的类型一样,因此可以将它们封装成数组:
1 | int (*ptr[2])(int a, int b) = {Max_Fun, Min_Fun}; |
说明:
ptr[0]:类型是int (*)(int, int),赋值为Max_Funptr[1]:类型是int (*)(int, int),赋值为Min_Fun- 操作
ptr[0]就是操作Max_Fun - 操作
ptr[1]就是操作Min_Fun
调用等价关系:
1 | Max_Fun(24, 9); // 等价于 ptr[0](24, 9); |
9. 结构体
9.1 结构体是什么?
把具有不同类型的相同属性特征的事物归为一类。
9.2 结构体的意义
| 需求 | 使用类型 |
|---|---|
| 保存多个不同数据类型的数据 | 结构体 |
| 保存单个数据类型的数据 | 基本数据类型 |
| 保存多个具有相同数据类型的数据 | 数组 |
9.3 结构体的表现形式
(1)定义自定义类型:struct
1 | struct 类型名 { |
(2)声明时是否占用内存空间?
声明时不占用内存空间。
9.4 结构体的空间大小
(1)自动计算
使用 sizeof,项目中直接使用。
(2)手动计算
- 先找到成员变量中基本数据类型占用最大的成员。
- 每一个成员变量的起始位置从该成员类型大小的整数倍开始计算,不够补齐;第一个成员变量默认从 0 开始计算。
- 最终大小要是最大成员变量的整数倍,不够需要补齐。
9.5 结构体应用
(1)结构体变量
- 定义方式:
- 在声明结构体类型的同时,进行变量的定义。
- 先声明结构体类型,再定义结构体变量。
格式:
1
结构体类型 标识符 = 初始值;
- 类型重定义:
- 宏定义:
#define,本质是替换。 - 类型重定义:
typedef。
- 宏定义:
- 初始化:
- 在定义变量的同时进行初始化:使用
{}。 - 先定义变量,再进行初始化:使用
.运算指定成员变量进行初始化。 - 必须在函数内部进行初始化。
- 在定义变量的同时进行初始化:使用
- 应用:
- 输入输出:结构体变量、成员变量。
- 结构体数组:获取结构体的成员变量,例如
stus[i].id。 - 结构体指针:获取结构体的成员变量,例如
结构体指针->成员变量、p->id,表示成员变量的空间。
10. 预处理命令及模块化
10.1 预处理命令
1. 预处理命令是什么
处理在编译之前需要完成的内容。
2. 查看预处理后的文件
1 | gcc -E a.c -o a.i |
3. 常见预处理命令
(1)包含:#include
| 写法 | 查找优先级 |
|---|---|
#include <file.h> |
优先从系统库目录中查找,其次在项目结构目录中查找 |
#include "file" |
优先从项目结构目录中查找,其次在系统库目录中查找 |
(2)宏定义:#define
- 本质:完全替换。
- 不带参:
1
- 带参:
1
2注意:
div的参数是否分别加括号,结果可能不同。
(3)条件编译:#if / #endif
#if / #elif / #else / #endif:代码执行。如果1
2
3
4
// 代码……N为 0,比较后#if与#endif中间的内容会消失。#ifdef / #endif:如果定义……#ifndef / #define / #endif:如果未定义,则定义。- 作用:防止重复定义,常用于
.h文件。
10.2 模块化
1. 模块化是什么
以项目级文件对程序进行分类管理。
2. 项目级文件系统
| 目录 | 说明 | 文件/产物 |
|---|---|---|
include |
声明文件 | .h 文件 |
lib |
第三方库文件 | |
output |
编译后的可执行文件 | a.out |
src |
源码文件 | .c 文件 |
3. 模块化开发的作用
- 方便代码移植
- 保护源代码
4. 如何进行模块化开发
按功能模块进行文件划分:
- 源码文件:
.c文件- 功能模块的具体实现
- 一般是受保护的文件,不对外
- 声明文件:
.h文件- 功能实现所需要的资源
- 对外暴露的内容:功能函数的声明
5. 关键字 static / extern
| 关键字 | 作用 |
|---|---|
extern |
修饰函数或全局变量的声明,表示该函数或全局变量是借用外部文件的内容 |
static |
修饰局部变量:提高局部变量的生命周期,但是不提高其作用域。 修饰全局变量或函数的定义:表示该全局变量或函数私有化,不允许外部文件借用 |
6. 项目级程序编译
终端位置在项目目录下:
1 | gcc src/*.c -o output/main -I include |
7. 在 .h 文件中使用 #include 出现报错
CSDN 上有解决办法。
11. 链表
11.1 链表是什么?
存储方式对比
| 存储方式 | 地址特点 | 典型结构 | 内存区域 | 管理方式 |
|---|---|---|---|---|
| 线性存储 | 地址连续 | 数组 | 栈区 | 自动开辟,自动释放 |
| 链式存储 | 地址不连续 | 链表 | 堆区 | 手动开辟,手动释放 |
11.2 链表的组成
- 链表由一个个节点组成。
- 节点是一个具有保存数据和下一个节点地址的空间。
- 节点定义:示例:
1
2
3
4struct node {
数据域;
指针域;
};1
2
3
4typedef struct node {
int data; // 数据域
struct node *next; // 指针域
} Node;
11.3 常用函数
| 函数名 | 功能 | 头文件 | 函数原型 | 参数 | 返回值 |
|---|---|---|---|---|---|
malloc |
开辟空间 | #include <stdlib.h> |
void *malloc(size_t size); |
size_t size:需要开辟的空间大小 |
void *:开辟好的空间地址,需要强转为需要的空间类型 |
memset |
空间初始化 | #include <string.h> |
void *memset(void *s, int c, size_t n); |
void *s:要初始化的空间首地址int c:初始化内容,一般写 0size_t n:要初始化的空间大小 |
void *:初始化后的空间首地址(等于要初始化的空间首地址) |
bzero |
空间初始化 | #include <string.h> |
void bzero(void *s, size_t n); |
void *s:要初始化的空间首地址size_t n:要初始化的空间大小 |
无 |
free |
释放空间 | #include <stdlib.h> |
void free(void *ptr); |
void *ptr:要释放的空间地址 |
无 |
使用示例:
1 |
|
11.4 单链表操作
1. 创建结点
- 声明节点类型(
.h文件) - 根据节点类型,开辟节点空间(
malloc) - 初始化节点空间
- 返回开辟好的节点地址
1 |
|
2. 新增节点
(1)尾插法
1 | void append_node(Node *head, int data) { |
(2)头插法
1 | void insert_head(Node **head, int data) { |
3. 遍历链表
1 |
|
4. 删除节点
按值删除节点:
1 | void delete_node(Node **head, int data) { |
5. 节点排序
以冒泡排序为例,交换数据域:
1 | void sort_list(Node *head) { |
11.5 循环链表
- 循环链表是一种首尾相连的链表。
- 尾节点的
next指针不再为NULL,而是指向头节点。
结构定义与单链表相同,只是操作时需要注意尾节点指向头节点。
1 | // 创建循环链表节点 |
11.6 双向链表
- 双向链表每个节点有两个指针域:一个指向前驱,一个指向后继。
- 可以双向遍历,操作更灵活。
结构定义:
1 | typedef struct dnode { |
12. 文件
12.1 文件存储形式
- 二进制形式存储
- 字符(ASCII)形式存储
12.2 文件操作的目的
将程序中的数据永久保存在磁盘中,以文件形式存储。
12.3 文件操作的方式
| 方式 | 特点 | 说明 |
|---|---|---|
| 基于缓冲区的文件操作 | 数据安全,速度慢 | 标准输入缓冲区 stdin、标准输出缓冲区 stdout、标准错误缓冲区 stderr |
| 基于非缓冲区的文件操作 | 数据不安全,速度快 | 直接操作磁盘 |
12.4 文件操作的步骤
- 打开文件:建立程序与文件之间的通道
- 读写数据:操作数据到程序或文件中,读写以程序为参照
- 关闭文件:关闭文件通道
12.5 常用函数
函数概览
| 函数 | 功能 | 头文件 |
|---|---|---|
fopen |
打开文件 | <stdio.h> |
fclose |
关闭文件 | <stdio.h> |
perror |
打印错误信息 | <stdio.h> |
fputc |
写一个字符 | <stdio.h> |
fgetc |
读一个字符 | <stdio.h> |
fputs |
写一个字符串 | <stdio.h> |
fgets |
读一个字符串 | <stdio.h> |
fwrite |
写一块数据 | <stdio.h> |
fread |
读一块数据 | <stdio.h> |
fprintf |
格式化写 | <stdio.h> |
fscanf |
格式化读 | <stdio.h> |
rewind |
光标移到首位 | <stdio.h> |
fseek |
光标移到指定位置 | <stdio.h> |
ftell |
获取光标位置 | <stdio.h> |
feof |
判断是否到文件末尾 | <stdio.h> |
详细说明与示例
1. fopen
- 功能:打开文件
- 原型:
FILE *fopen(const char *pathname, const char *mode); - 参数:
pathname:路径/文件名,默认当前目录mode:操作模式"r":只读,文件不存在则打开失败"w":只写,创建新文件,若原文件存在则覆盖(清空写入)"a":追加写入,在光标位置追加,默认打开时光标在首位"r+"、"w+"、"a+":读写能力,保留原特性
- 返回值:
FILE *文件流指针,失败返回NULL
示例:
1 | FILE *fp = fopen("test.txt", "w"); |
2. perror
- 功能:打印函数执行结果(失败或成功信息)
- 原型:
void perror(const char *s); - 参数:
s:函数名称或提示信息 - 返回值:无
示例:
1 | perror("fopen"); |
3. fclose
- 功能:关闭文件
- 原型:
int fclose(FILE *stream); - 参数:
stream:打开的文件流指针 - 返回值:0 成功,-1 失败
示例:
1 | fclose(fp); |
4. fputc
- 功能:写出一个字符到文件
- 原型:
int fputc(int c, FILE *stream); - 参数:
c:写出的字符;stream:文件流指针或stdout - 返回值:写出字符的 int 类型值,-1 失败
示例:
1 | fputc('A', fp); |
5. fgetc
- 功能:从文件读取一个字符到程序
- 原型:
int fgetc(FILE *stream); - 参数:
stream:文件流指针或stdin - 返回值:读取字符的 int 类型值,-1 失败
示例:
1 | int ch = fgetc(fp); |
6. rewind
- 功能:移动光标到首位
- 原型:
void rewind(FILE *stream); - 参数:
stream:文件流指针 - 返回值:无
示例:
1 | rewind(fp); |
7. fseek
- 功能:移动光标到指定位置
- 原型:
int fseek(FILE *stream, long offset, int whence); - 参数:
stream:文件流指针offset:光标偏移量(正数向右,负数向左)whence:光标参考位置SEEK_SET:首位(0)SEEK_CUR:当前(1)SEEK_END:末尾(2)
- 返回值:0 成功,-1 失败
示例:
1 | fseek(fp, 0, SEEK_END); // 移动到文件末尾 |
8. fputs
- 功能:写出一个字符串到文件
- 原型:
int fputs(const char *s, FILE *stream); - 参数:
s:写出的字符串;stream:文件流指针 - 返回值:写出字符串的字节数,-1 失败
示例:
1 | fputs("Hello World\n", fp); |
9. fgets
- 功能:从文件读取一个字符串到程序
- 原型:
char *fgets(char *s, int size, FILE *stream); - 参数:
s:保存读取字符串的首地址(数组)size:保存的最大长度stream:文件流指针
- 返回值:保存读取字符串的首地址,失败返回
NULL
示例:
1 | char buf[100]; |
10. fwrite
- 功能:写出一个块数据到文件(以二进制形式写出)
- 原型:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); - 参数:
ptr:任意类型的块数据首地址size:块大小nmemb:块数量stream:文件流指针
- 返回值:写出的数据字节数,-1 失败
示例:
1 | int arr[3] = {1, 2, 3}; |
11. fread
- 功能:读取一个块数据到程序
- 原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); - 参数:
ptr:任意类型的块数据首地址size:块大小nmemb:块数量stream:文件流指针
- 返回值:读取的数据字节数,-1 失败
示例:
1 | int arr[3]; |
12. fprintf
- 功能:格式化写出
- 原型:
int fprintf(FILE *stream, const char *format, ...); - 参数:
stream:文件流指针format:格式字符串...:可变参数,输出列表
- 返回值:写出数据的字节数
示例:
1 | fprintf(fp, "%d %s\n", 100, "hello"); |
13. fscanf
- 功能:格式化读取
- 原型:
int fscanf(FILE *stream, const char *format, ...); - 参数:
stream:文件流指针format:格式字符串...:可变参数,地址列表
- 返回值:读取数据的字节数
示例:
1 | int num; |
14. feof
- 功能:判断光标位置是否在末尾
- 原型:
int feof(FILE *stream); - 参数:
stream:文件流指针 - 返回值:文件指针指向文件结束位置返回 1,否则返回 0
示例:
1 | while (!feof(fp)) { |
15. ftell
- 功能:获取光标位置(距离首位的字节数)
- 原型:
long ftell(FILE *stream); - 参数:
stream:文件流指针 - 返回值:光标距离首位的字节数(文件大小)
示例:
1 | fseek(fp, 0, SEEK_END); |
12.6 综合示例
1 |
|