1. 函数间传参
① 解决的问题:
普通变量传给函数时,函数只是创建了一个与原变量地址不同的临时变量。即使函数把临时变量改变,后续程序中的原变量不会变化.
② 指针的作用:
指针变量标记了原变量的地址,使工具函数能直接修改原变量.
#include <stdio.h>
// 1. 定义工具函数:一个能"顺着地址改数据"的函数
void addOne(int *p) {
*p = *p + 1; // 找到地址 p 指向的内存,把里面的值 +1
}
// 2. 指挥部:程序从这里开始
int main() {
int num = 10; // 准备变量num,赋值为10
addOne(&num); // 把 num 的"地址"交给 addOne,请它帮忙把 num 的值 +1
printf("%d", num); // 验证结果
return 0;
}
2. 动态内存分配
① 程序创建内存空间的方式:静态分配 & 动态分配
静态分配:写代码时内存空间大小即被写定,即使未完全利用.
② 解决的问题:
Ⅰ 防止内存浪费;
Ⅱ 避免函数执行完,函数内部定义的普通变量(静态内存所存内容)被系统强制回收销毁导致后续程序报错.
③ 指针的作用:
根据用户的实际需求,向操作系统临时借一块指定大小的内存空间(这块空间叫"堆").
P.S. 想要多少借多少;想用多久用多久.
④ 动态内存分配的具体应用:
Ⅰ malloc:申请内存空间
void* malloc(size_t size);
传入需要的字节数. 因为返回的是无类型指针 void*,所以通常需要强转成对应类型的指针. 例如:
int num = 5;
// 申请能装下 5 个 int 的空间(5 * 4字节 = 20字节)
int *ptr = (int *)malloc(num * sizeof(int));
if (ptr == NULL) {
// 关键步骤:如果系统内存满了,申请会失败返回 NULL
printf("内存申请失败!\n");
return 1;
}
Ⅱ free:释放内存空间
void free(void *ptr);
把记录着之前申请的内存地址的指针传回系统.
free(ptr); // 把借来的内存还给系统
ptr = NULL; // 良好习惯:归还后把指针置空,防止误操作"野指针"
free只是把这块内存空间的状态改成了"空闲",里面的旧数据(之前存的数据)依然会残留在之前的内存空间,直到下一次有新数据写入把旧数据覆盖掉.
Ⅲ calloc(Clear Allocation):申请内存空间并清零
① 参数:分别传"元素个数"和"每个元素的大小". ② 自动清零:会自动把申请到的内存全部清零(填充为 0).
// 申请 5 个 `int` 空间,且里面每一个值都被初始化为 0
int *ptr = (int *)calloc(5, sizeof(int));
// 系统会自动相乘($5 \times 4 = 20$ 字节),在堆内存(Heap)里找一块连续的 20 字节 空间
与 malloc 的区别,malloc 执行后不会清零申请内存空间内的数据.
Ⅳ realloc:重新调整先前申请的内存空间大小
void* realloc(void *ptr, size_t new_size);
// 觉得刚才的 5 个空间不够了,想扩充到 10 个 int
int *new_ptr = (int *)realloc(ptr, 10 * sizeof(int));
if (new_ptr != NULL) {
ptr = new_ptr; // 扩容成功,更新指针
}
作用:realloc 把"找新房子、搬家、退旧房子"一系列复杂的操作流程全部封包成了自动操作. 如果运气好后面有空间,它连家都不用搬,可以直接原地把墙拆了扩大房间.
3. 传递大型数据结构,提升运行效率
① 解决的问题:
// 普通传值方式
void print_employee(struct Employee emp) {
printf("姓名:%s\n", emp.name);
}
int main() {
struct Employee e1 = { ... }; // 假设 e1 已经填满了数据
print_employee(e1); // 调用函数
}
如果这个函数被在一个 for 循环里调用了 1,000 次,CPU 就需要重复进行 1,000 次 14KB 的内存搬运(累计搬运 14MB 数据)。这不仅白白浪费了大量 CPU 算力和内存带宽,还会导致程序出现明显的卡顿.
② 指针传参的"零复制"的作用
// 指针传参方式(零复制)
void print_employee(const struct Employee *emp_ptr) {
// -> 是指针专用的结构体成员访问符,等价于 (*emp_ptr).name
printf("姓名:%s\n", emp_ptr->name);
}
int main() {
struct Employee e1 = { ... };
print_employee(&e1); // 只把 e1 的内存地址(钥匙)传过去
}
- Ⅰ 地址传输:无论 e1 里面包含多少兆(MB)的数据,指针变量
emp_ptr固定只占用 8 个字节(64 位系统下,所有指针统一为 8 字节). - Ⅱ 零内存复制:CPU 只需要把这 8 字节的地址值复制一下给函数即可,完全不需要动 e1 内部 14KB 的庞大数据.
- Ⅲ 直接访问:函数内部通过
emp_ptr这个地址,可以直接读取 e1 中的内容. - Ⅳ 安全防护:避免指针传参后不小心在函数内部写错代码把原数据改掉,我们可以使用
const关键字.
// `const` 确保了函数只有只读权限,绝对无法损坏外部的原数据
void print_employee(const struct Employee *emp_ptr) {
// emp_ptr->sales_data[0] = 0; // 如果试图修改数据,编译器会直接报错并拒绝编译
printf("姓名:%s\n", emp_ptr->name); // 安全地只读访问
}