本文整理了 C 语言开发中最核心的概念,供面试复习和日常开发参考。内容涵盖指针、内存、数组、函数、结构体等关键知识点。
指针基础
指针声明与初始化
| 声明 | 含义 |
int *p | 指向 int 的指针 |
int **p | 指向指针的指针(二级指针) |
int *p[10] | 指针数组(10个指针元素) |
int (*p)[10] | 数组指针(指向10个int的数组) |
int *p() | 返回指针的函数 |
int (*p)() | 函数指针 |
指针运算
- p + n - 向前移动 n 个元素(p + 1 真正地址偏移 sizeof(*p) 字节)
- p - n - 向后移动 n 个元素
- p++ / ++p - 移动到下一个元素
- p1 - p2 - 两个指针相差的元素个数
NULL 与 void*
| 类型 | 说明 |
| NULL | 空指针常量,值为 0 或 (void*)0 |
| void* | 通用指针,可转换为其他指针类型 |
| NULL 检查 | 使用 if (p != NULL) 或简写 if (p) |
数组与指针
数组名与指针的区别
int arr[10];
/* 以下情况数组名等同于指针:*/
int *p = arr; // 数组名作为右值
printf("%zu", sizeof(p)); // 指针大小(64位8字节)
/* 以下情况数组名是数组,不是指针:*/
sizeof(arr); // 整个数组大小:40 字节
sizeof(arr); // &arr 获取的是整个数组的地址
数组作为函数参数
/* 三种等价的函数声明 */
void func(int arr[10]); // 声明时写长度(被忽略)
void func(int arr[]); // 不写长度
void func(int *arr); // 写成指针形式(推荐)
/* 传递数组及长度 */
void process_array(int *arr, size_t n) {
for (size_t i = 0; i < n; i++) {
/* 处理 arr[i] */
}
}
内存管理
内存分配函数对比
| 函数 | 用途 | 初始化 |
| malloc(n) | 分配 n 字节 | 不初始化 |
| calloc(n, s) | 分配 n 个元素,每个 s 字节 | 全部初始化为 0 |
| realloc(p, n) | 调整已分配内存大小 | 可能保留原内容 |
| free(p) | 释放内存 | - |
常见内存错误
| 错误类型 | 描述 | 调试工具 |
| 内存泄漏 | 分配后未释放 | Valgrind |
| 双重释放 | free 两次同一指针 | Valgrind |
| 悬挂指针 | 释放后继续使用 | Valgrind |
| 缓冲区溢出 | 写入超出分配范围 | AddressSanitizer |
| 空指针解引用 | NULL *p = ... | GDB |
结构体
结构体定义与使用
typedef struct {
int id;
char name[32];
double score;
} Student;
/* 初始化 */
Student s1 = {1, "Alice", 95.5};
Student s2 = {.name = "Bob", .id = 2}; /* 指定初始化器(C99)*/
/* 指针使用 */
Student *p = &s1;
p->id = 10; /* 等价于 (*p).id = 10 */
printf("%s", p->name);
内存对齐规则
- 结构体起始地址必须能被最宽成员大小整除
- 每个成员起始地址必须是其自身大小的整数倍
- 结构体总大小必须是最大对齐要求的整数倍
- 使用
#pragma pack(n) 可改变对齐规则
- 优化技巧: 按大小递减顺序排列成员可减少填充
字符串处理
安全函数(C11/C99 扩展)
| 不安全 | 安全替代 | 说明 |
| strcpy | strcpy_s | 目标缓冲区大小参数 |
| strcat | strcat_s | 目标缓冲区大小参数 |
| sprintf | sprintf_s | 格式化安全 |
| gets | gets_s | 已废弃,用 fgets |
常用字符串函数
strlen(s) /* 字符串长度(不含\0)*/
strcmp(s1,s2) /* 字符串比较,0表示相等 */
strncmp(s1,s2,n) /* 比较前n个字符 */
strchr(s,c) /* 查找字符首次出现位置 */
strstr(s,sub) /* 查找子串首次出现位置 */
strtok(s,delim) /* 字符串分割(非线程安全)*/
预处理器
常用宏与指令
| 指令 | 用途 |
| #define | 定义宏和常量 |
| #ifdef / #ifndef | 条件编译 |
| #include | 文件包含 |
| #pragma | 编译器特定指令 |
| __FILE__ | 当前文件名 |
| __LINE__ | 当前行号 |
| __DATE__ | 编译日期 |
| __TIME__ | 编译时间 |
防止头文件重复包含
/* 方式1:传统宏保护 */
#ifndef HEADER_H_
#define HEADER_H_
/* 头文件内容 */
#endif
/* 方式2:Pragma once(大多数编译器支持)*/
#pragma once
/* 头文件内容 */
函数
函数指针基础
/* 比较函数类型,qsort 使用 */
int (*compare)(const void *, const void *);
/* 定义并使用函数指针 */
int max(int a, int b) { return a > b ? a : b; }
int (*pmax)(int, int) = &max;
printf("%d", (*pmax)(3, 4)); /* 输出 4 */
printf("%d", pmax(3, 4)); /* 也可简化写法 */
可变参数
#include
int sum(int count, ...) {
int total = 0;
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
/* 调用 */
printf("%d\n", sum(3, 1, 2, 3)); /* 输出 6 */
位运算
常用位操作
| 操作 | 表达式 | 用途 |
| 获取第 n 位 | (x >> n) & 1 | 检查某位 |
| 设置第 n 位为 1 | x | (1 << n) | 开启某位 |
| 设置第 n 位为 0 | x & ~(1 << n) | 关闭某位 |
| 切换第 n 位 | x ^ (1 << n) | 翻转某位 |
| 清除最低位的 1 | x & (x - 1) | 快速清除最低1 |
| 获取最低位的 1 | x & (-x) | 提取最低1 |
标志位操作示例
#define FLAG_READ 0x01 /* 0001 */
#define FLAG_WRITE 0x02 /* 0010 */
#define FLAG_EXEC 0x04 /* 0100 */
#define FLAG_ALL 0x07 /* 0111 */
int flags = 0;
/* 设置标志位 */
flags |= FLAG_READ | FLAG_WRITE;
/* 检查标志位 */
if (flags & FLAG_READ) { /* 可读 */ }
/* 清除标志位 */
flags &= ~FLAG_WRITE;
/* 切换标志位 */
flags ^= FLAG_EXEC;
常见陷阱
易错点总结
- 赋值 vs 等于:if (a = b) 是赋值,不是比较
- 浮点数比较:避免用 == 比较浮点数,用 fabs(a-b) < eps
- 贪心解析:*p++ 是 *(p++),不是 (*p)++
- switch 穿透:每个 case 记得加 break,除非有意穿透
- 整数溢出:a + b 可能溢出,考虑用 long long
- 隐式类型转换:char + int 会自动提升为 int
- size_t:循环变量用 size_t 更安全(无符号)
常用调试命令
GDB 快速参考
| 命令 | 简写 | 用途 |
| run | r | 启动程序 |
| break | b | 设置断点 |
| next | n | 单步执行(不进入函数) |
| step | s | 单步执行(进入函数) |
| print | p | 打印变量值 |
| backtrace | bt | 显示调用栈 |
| continue | c | 继续执行 |
| quit | q | 退出 GDB |
总结
这份速查表涵盖了 C 语言最核心的概念。建议收藏本文,面试前或开发中遇到不确定的地方可以快速查阅。更多详细内容请参考站内其他技术文章。