C语言指针与参数传递详解 —— 从底层存储到高效编程
一、为什么要使用指针传参?
在函数调用时,如果直接传递变量(即值传递),系统会将实参复制一份到形参中。
这种方式简单、安全,但当参数数据量较大(例如数组、结构体等)时,会导致:
效率低:复制数据耗时;
内存浪费:产生多份数据;
不灵活:函数内部无法修改主函数的数据。
为此,我们可以使用 指针传参,即传递变量的地址,实现主函数与子函数共享同一份数据。
二、指针的定义与存储机制
指针的本质
指针本质上是一个变量,用于存放另一个变量的首地址。
int a = 10; int *p = &a; // p中保存a的地址
此时:
p
是指针变量;*p
表示“取出p指向的内容”;&a
表示变量a的内存地址。
指针大小与系统位数
系统位数 | 指针大小 |
---|---|
16位系统 | 2字节 |
32位系统 | 4字节 |
64位系统 | 8字节 |
无论指向什么类型(int
、float
、char
),指针的大小固定,但步长不同。
三、计算机存储机制与大小端模式
存储方式
内存按“字节”为单位,每个地址存放1字节。
int a = 0x12345678;
int
占4字节,其内存布局取决于系统的字节序:
模式 | 内存中排列(低地址→高地址) |
---|---|
小端模式(Little Endian) | 78 56 34 12 |
大端模式(Big Endian) | 12 34 56 78 |
现代PC(x86、ARM)多采用小端存储。
十六进制与二进制的关系
十六进制是二进制的“简写”,每个十六进制数对应4个二进制位。
二进制 | 十六进制 |
---|---|
0000 | 0 |
1111 | F |
例如:
1010 0011 0110 1111 0010 1001 1100 1101 → A36F29CD
优势:方便阅读与打印内存数据。
四、指针的基本操作
int a = 10; int *p; p = &a;
操作 | 示例 | 含义 |
---|---|---|
取地址 | p = &a; | 获取变量a的首地址 |
取内容 | *p | 获取p所指向的值 |
自增 | p++ | 指向下一个元素 |
自减 | p-- | 指向上一个元素 |
五、数组与指针的关系
数组名本质上是一个常量指针,指向数组首元素。
int a[5] = {0,1,2,3,4}; int *p = a; // 等价于 int *p = &a[0];
访问方式等价:
写法 | 等价表达 |
---|---|
a[i] | *(a + i) |
p[i] | *(p + i) |
例:
for (int i = 0; i < 5; i++) {printf("%d ", *(p + i));}输出:0 1 2 3 4
六、指针传参:节省内存与实现多返回值
普通传参(值传递)
void fun(char param){printf("%x\n", param); } int main(){ char a = 0x77; fun(a); }会复制一份 a 的值给 param。
使用指针传参(地址传递)
void fun(char *param){printf("%x\n", *param); } int main(){char a = 0x77; fun(&a); }此时主函数与子函数共用同一份数据。
防止子函数修改主函数数据
int FindMax(const int *array, int length){int max = array[0]; for(int i = 1; i < length; i++){ if(array[i] > max) max = array[i]; } return max; }const 表示“只读”,函数中若修改 array[i] 将会报错。
多返回值设计(输出参数)
void FindMaxAndCount(int *max, int *count, const int *array, int length){ *max = array[0]; *count = 1; for(int i = 1; i < length; i++){if(array[i] > *max){ *max = array[i]; *count = 1; }else if(array[i] == *max) {(*count)++; } } } int main(){int a[6] = {3,2,3,4,5,6};int max, count; FindMaxAndCount(&max, &count, a, 6); printf("max=%d\n", max); printf("count=%d\n", count); }结果:max=6 count=1通过指针参数实现了“多返回值”设计。
七、模块封装与返回指针
模块化设计中,函数可以返回指针,让主函数持有模块内部的数据句柄:
/******** 模块内部 ********/ int Time[] = {23,59,55}; int* GetTime(){ return Time; // 返回数组首地址 } /*************************/ int main(){ int *pt = GetTime(); printf("Hour=%d\n", pt[0]); return 0; }
这样主函数就能访问模块内部的变量,实现“间接控制”。
八、嵌入式场景:直接访问物理地址
#define DEVICE_ID_ADDR 0x40001000 unsigned int *deviceID = (unsigned int *)DEVICE_ID_ADDR; printf("Device ID: 0x%X\n", *deviceID);
典型用途:读取寄存器、访问硬件设备信息。
九、知识总结表
内容 | 核心要点 |
---|---|
指针定义 | 存储变量首地址 |
存储机制 | 小端模式为主流 |
数组与指针 | 数组名即首地址 |
指针传参 | 节省内存、共享数据 |
const修饰 | 保护数据不被修改 |
输出参数 | 实现多返回值 |
返回指针 | 主函数可访问模块数据 |
嵌入式应用 | 访问硬件物理地址 |