一、指针基础概念
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 = # // 取普通变量地址赋值 p = # // 先定义后赋值- 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 = # *p = 20; // 等价 num = 20; 修改原变量值6. 指针算术运算
指针仅支持++/--/+整数/-整数/指针-指针,*/%无意义。
- 指针 ± 整数:偏移整数 × sizeof(基类型)字节
char *pc; pc+1; // 向后偏移 1 字节 int *pi; pi+1; // 向后偏移 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] == *(a+1); a[n] == *(a+n); &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 的数组 - 运算特性:
p+1一次性偏移整个数组大小(5×4=20 字节) - 适用场景:接收二维数组数组名
int a[5] = {1, 2, 3, 4, 5}; int (*p)[5] = &a; // &a 是整个数组的地址,匹配数组指针类型2. 指针数组:int *p[5]
- 本质:数组,数组里每个元素是
int *指针 []优先级高于*,先定义数组,数组存储指针- 内存占用:64 位系统 5×8=40 字节
- 典型用途:存储多个字符串(节省空间)
// 指针数组:存放 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 个 int,12 字节)二维数组元素访问等价写法
a[m][n] == *( *(a+m) + n )二维数组传参两种等价写法(列数必须指定,行数可省略)
void print2D(int a[][3], int row); void print2D(int (*pa)[3], int row);六、二级指针类型 **p
- 定义:保存一级指针变量的地址的指针
int num = 10; int *p = # 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 = p;&a是数组指针,和 a 类型不同; sizeof(数组名)只有两种情况代表数组总大小:单独sizeof(a)、&a;作为函数参数时退化为指针;- 野指针、悬空指针不要解引用,程序崩溃;指针定义默认赋 NULL;
- 区分:
int (*p)[5]数组指针 /int *p[5]指针数组 /int **p二级指针; - 函数返回指针禁止返回栈局部变量地址;
- 一级指针传参只能修改指向的数据,二级指针才能修改外部指针本身;
- 指针算术偏移由基类型字节大小决定,不是单纯 ±1 字节;
- 字符串常量存在只读段,
char *p = "abc";不可写p[0]='x',字符数组char str[]="abc"可修改。