C语言指针学习 一、指针基础概念1. 内存、地址、指针、指针变量区分内存程序运行时可访问的存储空间计算机以 1 字节为单位给内存编号。地址内存字节的唯一编号用来定位内存空间。指针 地址指针就是内存编号代表一块内存的位置。指针变量专门存储地址的变量本身也要占用内存空间。2. 指针变量定义语法格式基类型 *变量名;基类型指针指向空间存储的数据类型int/char/float/void 等决定解引用时读取多少字节。*类型标识说明该变量是指针不是普通变量。示例int *pint; // 指向 int 类型的指针 char *pch; // 指向 char 类型的指针 int *pi, *pj; // 同时定义两个 int 指针3. 指针变量占用空间64 位系统所有指针统一占 8 字节32 位系统所有指针统一占 4 字节sizeof(任意指针)只和系统架构有关和指向类型无关。4. 指针初始化与赋值int num 100; int *p NULL; // 初始化为空指针推荐避免野指针 int *p2 num; // 取普通变量地址赋值 p num; // 先定义后赋值NULL等价 0代表指针不指向任何有效内存。野指针未初始化、指向已释放内存、越界地址的指针操作会段错误。规避方案定义时赋值 NULL 或合法变量地址。5. 两大核心运算符 取地址、* 解引用1 取地址运算符作用获取变量首字节地址规则只能对变量取地址不能对常量、表达式、寄存器变量取址。int a; a; // 合法取 a 的地址 100; // 非法2* 解引用间接访问运算符作用通过指针保存的地址访问对应内存里的数据规则*指针等价于基类型变量读写该内存的值。读取字节数由基类型决定char *p*p只操作 1 字节int *p*p操作 4 字节double *p*p操作 8 字节int num 10; int *p num; *p 20; // 等价 num 20; 修改原变量值6. 指针算术运算指针仅支持/--/整数/-整数/指针-指针*/%无意义。指针 ± 整数偏移整数 × sizeof(基类型)字节char *pc; pc1; // 向后偏移 1 字节 int *pi; pi1; // 向后偏移 4 字节指针 1 - 指针 2结果是两个地址之间间隔的元素个数公式元素个数 (地址差值) / sizeof(基类型)前提两个指针指向同一块连续内存数组才有意义。二、指针作为函数参数地址传递核心1. 值传递 vs 地址传递普通值传递实参拷贝一份给形参形参是独立空间无法修改主函数变量指针地址传递实参传变量地址形参指针接收地址*形参直接操作主函数原变量可修改外部变量2. 指针传参三大用途在被调函数修改主调函数的普通变量函数需要返回多个计算结果传递数组避免完整拷贝数组节省内存。3. 二级指针传参场景如果要修改主函数里的指针变量本身不是指针指向的数据需要传指针的地址形参用二级指针接收。void fun(char **pstr) { *pstr new string; // 修改外部一级指针 pstr 的指向 } int main() { char *p hello; fun(p); // 传指针变量的地址二级指针接收 }三、指针与一维数组1. 数组名本质数组名 a 是数组首元素地址常量等价a[0]不能被赋值右值。下标语法和指针语法完全等价a[0] *a; a[1] *(a1); a[n] *(an); a[n] a n;2. 一维数组作函数参数两种写法完全等价// 写法 1数组形式 void print_arr(int a[], int len); // 写法 2指针形式推荐底层理解 void print_arr(int *pa, int len);注意函数内部sizeof(pa)得到的是指针大小8 字节不是数组总长度所以必须手动传长度参数。3. 字符数组 / 字符串指针char str[] hello;str str[0] char *字符串以\0结尾函数传参不需要额外传长度遍历至*str \0停止。自定义字符串库函数mystrlen/mystrcpy 等形参都可用char str[]或char *str。四、数组指针 vs 指针数组极易混淆重点1. 数组指针int (*p)[5]本质指针指向一整个数组括号不能省略()优先级高于[](*p)代表 p 是指针[5]代表指向含 5 个 int 的数组运算特性p1一次性偏移整个数组大小5×420 字节适用场景接收二维数组数组名int a[5] {1, 2, 3, 4, 5}; int (*p)[5] a; // a 是整个数组的地址匹配数组指针类型2. 指针数组int *p[5]本质数组数组里每个元素是int *指针[]优先级高于*先定义数组数组存储指针内存占用64 位系统 5×840 字节典型用途存储多个字符串节省空间// 指针数组存放 5 个字符串常量地址仅存指针不存字符串本体 char *str_arr[5] {aa, bb, cc};3. 指针数组 vs 二维字符数组对比char str1[3][100] {a, b, c}; // 二维数组存储字符串本体总 300 字节 char *str2[3] {a, b, c}; // 指针数组仅存地址总 24 字节二维字符数组开辟连续内存存完整字符串可修改每个字符指针数组仅存字符串常量区地址字符串常量只读不可修改字符。五、指针与二维数组二维数组名 a 是数组指针int (*)[列数]指向第一行一维数组等价a[0]int a[2][3] {1, 2, 3, 4, 5, 6}; a 1; // 跳过一整行3 个 int12 字节二维数组元素访问等价写法a[m][n] *( *(am) n )二维数组传参两种等价写法列数必须指定行数可省略void print2D(int a[][3], int row); void print2D(int (*pa)[3], int row);六、二级指针类型 **p定义保存一级指针变量的地址的指针int num 10; int *p num; int **pp p; // pp 是二级指针存储一级指针 p 的地址两大核心使用场景函数内修改外部一级指针变量前文示例接收指针数组数组名指针数组名 str_arr 等价char **二级指针。char *str[3] {a, b}; void func(char **p); func(str); // 数组名是二级指针直接传参七、指针函数 vs 函数指针区分难点1. 指针函数返回值为指针的函数格式基类型 *函数名(形参)char *get_str() { static char buf[] test; return buf; // 允许返回 static/全局/字符串常量/堆内存地址 // 禁止返回局部栈数组地址函数结束内存销毁变成野指针 }返回地址合法性规则✅ static 静态变量地址 / 全局变量地址 / 字符串常量地址 / 堆 malloc 地址 / 参数传入地址❌ 函数内普通局部变量、局部数组地址2. 函数指针指向函数入口地址的指针格式返回值 (*指针名)(形参列表)// 加法函数 int add(int a, int b) { return a b; } // 定义匹配 add 的函数指针 int (*pfun)(int, int) NULL; pfun add; // 函数名 函数入口地址赋值给函数指针 // 通过指针调用函数 int res pfun(1, 2);核心用途回调函数将函数作为参数传给其他函数解耦代码。八、void* 万能指针定义void *p可以接收任意类型数据的地址通用地址容器限制不能直接解引用*p运算前必须强制转换为对应类型指针典型场景malloc/free、通用工具函数参数。int a 10; void *vp a; int *p (int *)vp; // 强制转换后才能解引用 printf(%d, *p);九、指针核心易错点汇总数组名是地址常量不能a、a pa是数组指针和 a 类型不同sizeof(数组名)只有两种情况代表数组总大小单独sizeof(a)、a作为函数参数时退化为指针野指针、悬空指针不要解引用程序崩溃指针定义默认赋 NULL区分int (*p)[5]数组指针 /int *p[5]指针数组 /int **p二级指针函数返回指针禁止返回栈局部变量地址一级指针传参只能修改指向的数据二级指针才能修改外部指针本身指针算术偏移由基类型字节大小决定不是单纯 ±1 字节字符串常量存在只读段char *p abc;不可写p[0]x字符数组char str[]abc可修改。