Files
2025-06-18 20:02:47 +08:00

21 KiB
Raw Permalink Blame History

🔵 第一层:C/C++ 语言基础与进阶(必修)

✅ 变量 / 数据类型 / 关键字 / 常量

📌 变量(Variable)

  • 用于在程序中存储数据的具名内存区域。
  • 声明格式:类型 变量名 [= 初始值];
int count = 10;
float temperature;
char c = 'A';
  • 局部变量:函数内声明,仅在函数内部可用。
  • 全局变量:函数外声明,整个文件或项目中可见(根据作用域)。

📌 数据类型(Data Types)

  • 整型:int, short, long, long long, unsigned
  • 浮点型:float, double
  • 字符型:char
  • 派生类型:指针、数组、结构体等
unsigned int u = 100;
long long big_number = 12345678900LL;

📌 关键字(Keywords)

常用 C 关键字解释如下:

关键字 说明
const 定义只读变量
volatile 防止编译器优化,常用于寄存器
static 变量作用域或函数仅在本文件可见
extern 声明外部变量/函数
typedef 为数据类型取别名
static int counter = 0;     // 内部链接
extern int g_value;         // 声明外部变量
volatile uint32_t *reg = (uint32_t *)0x40021000; // 用于寄存器访问

📌 常量(Constant)

  • 字面常量:如 10, 3.14, 'a', "abc"
  • 符号常量:用 #define 或 const 定义
 #define PI 3.14159
const int MAX_SIZE = 100;

✅ 栈 和 堆(内存管理)

栈 (stack):自动分配内存,函数退出即释放。

  1. 核心特性
  • 自动分配与释放:由编译器自动管理,函数调用时分配栈帧,函数返回时自动释放。
  • 后进先出(LIFO):类似一摞盘子,最后放入的最先取出。
  • 高速访问:栈内存访问效率高(通常通过寄存器直接操作)。
  • 空间有限:栈空间通常较小(如 Linux 默认 8MB),过大的局部变量可能导致栈溢出。
  1. 存储内容
  • 局部变量:函数内部定义的变量。
  • 返回地址:函数执行完毕后返回的位置。
  • 函数参数:调用函数时传递的参数。
  • 寄存器值:保存调用前的寄存器状态,以便恢复。
  1. 工作原理
  • 栈指针(ESP):指向当前栈顶的内存地址。
  • 栈帧(Stack Frame):每个函数调用在栈上分配的独立空间,包含局部变量和参数。
  • 示例代码:
void func(int a, int b) {
    int sum = a + b;  // sum存储在栈上
    // ...
}  // 函数返回时,sum和参数a、b自动释放
  1. 优缺点
  • 优点:无需手动管理内存,速度快,不会内存泄漏。
  • 缺点:生命周期固定(函数结束即释放),空间有限。

堆 (heap):使用 malloc / free 手动分配和释放

  1. 核心特性
  • 手动分配与释放:使用malloc/calloc/realloc分配,free释放。
  • 动态生命周期:内存块的生命周期由程序员控制,可跨函数使用。
  • 碎片化问题:频繁分配和释放可能导致内存碎片,降低空间利用率。
  • 慢速访问:需通过指针间接访问,效率低于栈。
  1. 存储内容
  • 动态分配的对象:如malloc返回的内存块。
  • 大型数据结构:如数组、链表、树等需要动态调整大小的结构。
  • 跨函数数据:需要在函数调用结束后继续存在的数据。
  1. 工作原理
  • 内存管理器:操作系统提供的堆管理器负责分配和回收内存。
  • 空闲链表:堆管理器维护空闲内存块列表,分配时查找合适大小的块。
  • 示例代码:
void createArray() {
    int* arr = (int*)malloc(10 * sizeof(int));  // 从堆分配内存
    if (arr != NULL) {
        arr[0] = 100;  // 使用堆内存
        // ...
    }
    free(arr);  // 手动释放内存
}
  1. 常见函数
  • malloc(size_t size):分配指定字节的内存,不初始化。
  • calloc(size_t num, size_t size):分配内存并初始化为 0。
  • realloc(void* ptr, size_t new_size):调整已分配内存的大小。
  • free(void* ptr):释放内存,必须与malloc配对使用。

栈 vs 堆的对比

特性 栈(Stack) 堆(Heap)
分配方式 自动(由编译器管理) 手动(如 malloc/free 或 new/delete)
生命周期 函数调用期间自动创建和销毁 程序员控制,需手动释放
内存空间 连续、有限(通常几 MB) 不连续、较大(受限于物理内存)
访问速度 快(通过寄存器快速访问) 慢(通过指针间接访问)
内存碎片 不存在(先进后出结构) 可能产生(频繁分配和释放)
使用场景 局部变量、函数调用栈帧 动态数据结构、跨函数共享数据

指针

指针的基本概念

指针的作用: 可以通过指针间接访问内存

  • 内存编号是从0开始记录的,一般用十六进制数字表示
  • 可以利用指针变量保存地址

指针变量的定义和使用

指针变量定义语法: 数据类型 * 变量名;

示例:

int main() {

	//1、指针的定义
	int a = 10; //定义整型变量a
	
	//指针定义语法: 数据类型 * 变量名 ;
	int * p;

	//指针变量赋值
	p = &a; //指针指向变量a的地址
	cout << &a << endl; //打印数据a的地址
	cout << p << endl;  //打印指针变量p

	//2、指针的使用
	//通过*操作指针变量指向的内存
	cout << "*p = " << *p << endl;

	system("pause");

	return 0;
}

指针变量和普通变量的区别

  • 普通变量存放的是数据,指针变量存放的是地址
  • 指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用

总结1: 我们可以通过 & 符号 获取变量的地址

总结2:利用指针可以记录地址

总结3:对指针变量解引用,可以操作指针指向的内存

指针所占内存空间

提问:指针也是种数据类型,那么这种数据类型占用多少内存空间?

示例:

int main() {

	int a = 10;

	int * p;
	p = &a; //指针指向数据a的地址

	cout << *p << endl; //* 解引用
	cout << sizeof(p) << endl;
	cout << sizeof(char *) << endl;
	cout << sizeof(float *) << endl;
	cout << sizeof(double *) << endl;

	system("pause");

	return 0;
}

总结:所有指针类型在32位操作系统下是4个字节,64位操作系统为8个字节

空指针和野指针

空指针:指针变量指向内存中编号为0的空间

用途: 初始化指针变量

注意: 空指针指向的内存是不可以访问的

示例1:空指针

int main() {

	//指针变量p指向内存地址编号为0的空间
	int * p = NULL;

	//访问空指针报错 
	//内存编号0 ~255为系统占用内存,不允许用户访问
	cout << *p << endl;

	system("pause");

	return 0;
}

野指针:指针变量指向非法的内存空间

示例2:野指针

int main() {

	//指针变量p指向内存地址编号为0x1100的空间
	int * p = (int *)0x1100;

	//访问野指针报错 
	cout << *p << endl;

	system("pause");

	return 0;
}

总结:空指针和野指针都不是我们申请的空间,因此不要访问。

const修饰指针

const修饰指针有三种情况

  1. const修饰指针 --- 常量指针

  2. const修饰常量 --- 指针常量

  3. const既修饰指针,又修饰常量

示例:

int main() {

	int a = 10;
	int b = 10;

	//const修饰的是指针,指针指向可以改,指针指向的值不可以更改
	const int * p1 = &a; 
	p1 = &b; //正确
	//*p1 = 100;  报错
	

	//const修饰的是常量,指针指向不可以改,指针指向的值可以更改
	int * const p2 = &a;
	//p2 = &b; //错误
	*p2 = 100; //正确

    //const既修饰指针又修饰常量
	const int * const p3 = &a;
	//p3 = &b; //错误
	//*p3 = 100; //错误

	system("pause");

	return 0;
}

技巧:看const右侧紧跟着的是指针还是常量, 是指针就是常量指针,是常量就是指针常量

指针和数组

核心概念

  • 数组本质:连续存储多个指针变量。
  • 用途:常用于处理多个字符串或动态分配的内存块。

作用: 利用指针访问数组中元素

示例:

int main() {

	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };

	int * p = arr;  //指向数组的指针

	cout << "第一个元素: " << arr[0] << endl;
	cout << "指针访问第一个元素: " << *p << endl;

	for (int i = 0; i < 10; i++)
	{
		//利用指针遍历数组
		cout << *p << endl;
		p++;
	}

	system("pause");

	return 0;
}

指针和函数

作用: 利用指针作函数参数,可以修改实参的值(和前边形参相反)

示例:

//值传递
void swap1(int a ,int b)
{
	int temp = a;
	a = b; 
	b = temp;
}
//地址传递
void swap2(int * p1, int *p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}

int main() {

	int a = 10;
	int b = 20;
	swap1(a, b); // 值传递不会改变实参

	swap2(&a, &b); //地址传递会改变实参

	cout << "a = " << a << endl;

	cout << "b = " << b << endl;

	system("pause");

	return 0;
}

总结:如果不想修改实参,就用值传递,如果想修改实参,就用地址传递

指针、数组、函数

  • 函数指针声明:int (*fp)(int) 表示指向返回 int 的函数的指针
  • 函数指针数组:用于策略模式或注册多个处理函数

案例描述: 封装一个函数,利用冒泡排序,实现对整型数组的升序排序

例如数组:int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };

示例:

//冒泡排序函数
void bubbleSort(int * arr, int len)  //int * arr 也可以写为int arr[]
{
	for (int i = 0; i < len - 1; i++)
	{
		for (int j = 0; j < len - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

//打印数组函数
void printArray(int arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i] << endl;
	}
}

int main() {

	int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
	int len = sizeof(arr) / sizeof(int);

	bubbleSort(arr, len);

	printArray(arr, len);

	system("pause");

	return 0;
}

✅ 表达式、语句、运算符

  • 表达式:a + b, x++, p[i]
  • 运算符:+, -, *, /, %, &&, ||, !, ==, !=
  • 语句:控制流程结构 if, for, while, switch

✅ 数组 / 字符串

数组(Array)

  • 数组是一组相同类型数据的有序集合,在内存中连续存储。
  • 一维数组定义:类型 数组名[大小];
int arr[5] = {1, 2, 3, 4, 5};
char str[10] = "Hello";
  • 数组索引从 0 开始,访问方式如:arr[2] 表示第三个元素。
  • 多维数组:int matrix[3][4]; 表示 3 行 4 列矩阵。

字符串(String)

  • 字符串是以空字符 '\0' 结尾的字符数组。
  • 定义方式:
char str1[] = "Hello";       // 自动添加 '\0'
char str2[6] = {'H','e','l','l','o','\0'}; // 手动指定
  • 常用字符串函数
// 需包含头文件 <string.h>
strlen(str); // 计算长度(不含 '\0')
strcpy(dest, str); // 拷贝
strcmp(a, b); // 比较字符串
strcat(a, b); // 将 b 拼接到 a 后面
 #include <string.h>

char msg[20];
strcpy(msg, "Hi");     // msg: "Hi"
strcat(msg, " there"); // msg: "Hi there"

✅ 结构体 / 共用体 / 枚举 / 位域

结构体

定义:

结构体是将多个不同类型的数据组合在一起的复合数据类型,用于表示实体的多个属性。
使用场景:

  • 表示传感器、外设状态、网络包头等复杂数据

  • 多变量统一传参,提升代码组织性

语法:


struct StructName {
    int id;
    float value;
    char name[20];
};

示例:


struct SensorData {
    int id;
    float temperature;
    char location[20];
};

struct SensorData s1 = {1, 36.5, "room_1"};
printf("Sensor: %d, Temp: %.1f\n", s1.id, s1.temperature);

共用体

定义:

共用体中的所有成员共享同一块内存,任何时刻只能使用其中一个成员。
使用场景:

  • 节省内存:如嵌入式协议帧解析

  • 多种数据格式的重解释

语法:


union UnionName {
    int i;
    float f;
    char c;
};

示例:


union Data {
    int i;
    float f;
};

union Data d;
d.i = 10;
printf("i = %d\n", d.i);
d.f = 3.14;
printf("f = %.2f\n", d.f);  // 修改 f 会破坏 i

注意事项:

  • 占用内存大小为最大成员的大小

  • 修改一个成员后,其他成员的值不可预测

枚举(Enumeration)

定义:

枚举是一种用户自定义的数据类型,用于定义一组命名的整数常量。

使用场景:

  • 定义状态机状态

  • 表示设备运行模式、错误码

语法:


enum Color { RED, GREEN, BLUE };
enum Color color = GREEN;

示例:


enum State {
    STATE_IDLE,
    STATE_ACTIVE,
    STATE_ERROR = 100,  // 可手动赋值
    STATE_SLEEP
};

enum State current = STATE_ACTIVE;
printf("State: %d\n", current);  // 输出 1

特性:

  • 默认从 0 开始递增

  • 可强制设定起始值

位域(Bit Field)

定义:

位域用于结构体中,定义每个字段占用的比特位数,实现更细粒度的内存控制。

使用场景:

  • 配置寄存器映射

  • 网络协议比特位字段解析

  • 内存空间紧张场景

语法:


struct Flags {
    unsigned int ready : 1;
    unsigned int error : 1;
    unsigned int mode  : 2;
};

示例:


struct Flags f;
f.ready = 1;
f.error = 0;
f.mode = 3;  // 占2位,最大为11(二进制)即3

printf("ready = %d, mode = %d\n", f.ready, f.mode);

注意事项:

  • 位域不能取地址(&f.ready 不合法)

  • 字段数值不能超过位数范围(2^n - 1)

  • 与具体编译器实现密切相关(跨平台需小心)

✅ 位操作

  • 嵌入式开发中用于设置寄存器位、控制硬件
#define LED_PIN (1 << 2)
PORT |= LED_PIN;   // 置位
PORT &= ~LED_PIN;  // 清零
PORT ^= LED_PIN;   // 翻转

✅ 关键语义 & 修饰符

const(只读限定符)

const int a = 10;
void print(const char* msg);  // msg 不可修改

volatile(防止优化)

volatile int *reg = (int *)0x40021000;  // 硬件寄存器访问

static(静态变量/内部链接)

static int count = 0;   // 静态变量,函数调用间保留值

extern(外部变量声明)

extern int global_var;

register(提示变量存放寄存器)

register int speed;

auto(默认局部变量)

auto int a = 10;  // 一般可省略 auto

✅ 内存存储类型与生命周期

存储类型 生命周期 作用域 关键字
栈(stack) 函数调用期间 局部变量 auto
静态区 程序全程 局部/全局 static
堆(heap) 手动管理 全局 malloc/free
寄存器 函数调用期间 局部 register

✅ 编译与调试基础

C 编译四阶段(以 GCC 为例)

gcc -E main.c -o main.i   # 预处理
gcc -S main.c -o main.s   # 编译为汇编
gcc -c main.c -o main.o   # 汇编为目标文件
gcc main.o -o main        # 链接生成可执行文件

Makefile 示例

CC = gcc
TARGET = app
OBJS = main.o utils.o

$(TARGET): $(OBJS)
	$(CC) -o $@ $^

%.o: %.c
	$(CC) -c $< -o $@

clean:
	rm -f *.o $(TARGET)

GCC 编译参数

参数 含义
-Wall 开启所有警告
-g 含调试信息
-O2 优化等级
-I 头文件路径
-L/-l 库路径和链接
-D 宏定义

GDB 基础调试

gdb ./main
(gdb) break main
(gdb) run
(gdb) next / step
(gdb) print var
(gdb) continue

内联汇编

int result;
__asm__ __volatile__ (
    "movl $5, %%eax;"
    "movl $3, %%ebx;"
    "addl %%ebx, %%eax;"
    "movl %%eax, %0;"
    : "=r"(result)
    :
    : "%eax", "%ebx"
);
printf("result = %d\n", result);  // 输出 8

排序算法

冒泡排序(Bubble Sort)

原理:

相邻元素两两比较,把最大的“冒”到最后。

时间复杂度:

  • 最坏/平均:O(n²)
  • 最好:O(n)(加优化判断)

适用场景:

数据量小、逻辑简单、嵌入式环境友好

示例代码:

void bubble_sort(int arr[], int n) {
    for (int i = 0; i < n - 1; ++i) {
        int swapped = 0;
        for (int j = 0; j < n - i - 1; ++j) {
            if (arr[j] > arr[j+1]) {
                int t = arr[j]; arr[j] = arr[j+1]; arr[j+1] = t;
                swapped = 1;
            }
        }
        if (!swapped) break; // 优化:已排序
    }
}

选择排序(Selection Sort)

原理:

每轮从未排序区间中选择最小值放到前面。

时间复杂度:

  • 所有情况:O(n²)

适用场景:

嵌入式设备中内存访问代价高,交换少

示例代码:

void selection_sort(int arr[], int n) {
    for (int i = 0; i < n - 1; ++i) {
        int min = i;
        for (int j = i + 1; j < n; ++j) {
            if (arr[j] < arr[min])
                min = j;
        }
        if (min != i) {
            int t = arr[i]; arr[i] = arr[min]; arr[min] = t;
        }
    }
}

插入排序(Insertion Sort)

原理:

每次将一个元素插入到已排序部分的合适位置。

时间复杂度:

  • 最坏/平均:O(n²)
  • 最好:O(n)

适用场景:

数据量小、数据基本有序时表现好

示例代码:

void insertion_sort(int arr[], int n) {
    for (int i = 1; i < n; ++i) {
        int key = arr[i], j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j+1] = arr[j];
            j--;
        }
        arr[j+1] = key;
    }
}

快速排序(Quick Sort)

原理:

分治法。选定基准,左边小于它,右边大于它。

时间复杂度:

  • 最坏:O(n²)
  • 平均:O(n log n)

适用场景:

高性能需求、数据量较大(慎用递归栈)

示例代码:

int partition(int arr[], int low, int high) {
    int pivot = arr[high], i = low - 1;
    for (int j = low; j < high; ++j) {
        if (arr[j] < pivot) {
            ++i;
            int t = arr[i]; arr[i] = arr[j]; arr[j] = t;
        }
    }
    int t = arr[i+1]; arr[i+1] = arr[high]; arr[high] = t;
    return i + 1;
}

void quick_sort(int arr[], int low, int high) {
    if (low < high) {
        int p = partition(arr, low, high);
        quick_sort(arr, low, p - 1);
        quick_sort(arr, p + 1, high);
    }
}

归并排序(Merge Sort)

原理:

分治法。将数组分成两半排序后合并。

时间复杂度:

  • 所有情况:O(n log n)

适用场景:

追求稳定排序、高精度处理、实时传感器数据等(需额外内存)

示例代码:

void merge(int arr[], int l, int m, int r) {
    int n1 = m-l+1, n2 = r-m;
    int L[n1], R[n2];

    for (int i = 0; i < n1; ++i) L[i] = arr[l+i];
    for (int j = 0; j < n2; ++j) R[j] = arr[m+1+j];

    int i = 0, j = 0, k = l;
    while (i < n1 && j < n2)
        arr[k++] = (L[i] <= R[j]) ? L[i++] : R[j++];

    while (i < n1) arr[k++] = L[i++];
    while (j < n2) arr[k++] = R[j++];
}

void merge_sort(int arr[], int l, int r) {
    if (l < r) {
        int m = (l + r) / 2;
        merge_sort(arr, l, m);
        merge_sort(arr, m+1, r);
        merge(arr, l, m, r);
    }
}

堆排序(Heap Sort)

原理:

利用堆结构(大根堆),反复取出最大元素构造有序序列。

时间复杂度:

  • 所有情况:O(n log n)

适用场景:

嵌入式中对最值处理(最大温度等)、优先级调度

示例代码:

void heapify(int arr[], int n, int i) {
    int largest = i, l = 2*i + 1, r = 2*i + 2;
    if (l < n && arr[l] > arr[largest]) largest = l;
    if (r < n && arr[r] > arr[largest]) largest = r;
    if (largest != i) {
        int t = arr[i]; arr[i] = arr[largest]; arr[largest] = t;
        heapify(arr, n, largest);
    }
}

void heap_sort(int arr[], int n) {
    for (int i = n/2 - 1; i >= 0; i--) heapify(arr, n, i);
    for (int i = n-1; i > 0; i--) {
        int t = arr[0]; arr[0] = arr[i]; arr[i] = t;
        heapify(arr, i, 0);
    }
}

📌 总结对比表

排序算法 时间复杂度(平均) 空间复杂度 稳定性 嵌入式适用性
冒泡排序 O(n²) O(1) ✅ ✅(小数据)
选择排序 O(n²) O(1) ❌ ✅
插入排序 O(n²) O(1) ✅ ✅(近似有序)
快速排序 O(n log n) O(log n) ❌ ⚠️(递归栈)
归并排序 O(n log n) O(n) ✅ ⚠️(额外内存)
堆排序 O(n log n) O(1) ❌ ✅