
指针是 C/C++ 的灵魂,更是嵌入式开发的核心考点。本文系统梳理数组指针、指针数组、函数指针、指针函数等易混淆概念,结合代码示例和面试真题,拆解指针的底层逻辑,帮你彻底掌握指针的使用技巧和避坑方法。
1 数组指针与指针数组
- 数组指针 :本质是指针,指向一个数组(重点:指针);
- 指针数组 :本质是数组,数组元素都是指针(重点:数组)。
1.1 数组指针(指向数组的指针)
cs
// 指向包含N个T类型元素的数组的指针
T (*p)[N];
(*p)表明p是指针;[N]表明该指针指向一个长度为N的数组;T表明数组元素的类型。
cs
#include <stdio.h>
int main() {
// 一维数组,可视为3个长度为4的子数组:{1,2,3,4}、{5,6,7,8}、{9,10,11,12}
int b[12] = {1,2,3,4,5,6,7,8,9,10,11,12};
int (*p)[4]; // 数组指针:指向包含4个int的数组
p = b; // p指向第一个子数组{1,2,3,4}
// ++p:指针向后移动1个数组长度(4个int=16字节),指向{5,6,7,8}
// *++p:取该数组的首地址;**++p:取数组第一个元素5
printf("%d\n", **(++p));
return 0;
}
输出结果 :5
核心特点:
- 指针步长 = 指向数组的总字节数(如上例中
p++移动4×4=16字节); - 常用于遍历二维数组(视为 "数组的数组")。
1.2 指针数组(元素为指针的数组)
cs
// 数组包含N个元素,每个元素是指向T类型的指针
T *p[N];
p[N]表明p是长度为N的数组;T*表明数组元素是指向T类型的指针。
cs
#include <stdio.h>
int main() {
int *p[4]; // 指针数组:4个元素,每个元素是int*
int a[4] = {1,2,3,4};
// 数组元素指向a的对应位置
p[0] = &a[0];
p[1] = &a[1];
p[2] = &a[2];
p[3] = &a[3];
// 遍历指针数组,解引用取值
for(int i=0; i<4; i++) {
printf("%d", *p[i]);
}
printf("\n");
return 0;
}
输出结果 :1234
核心特点:
- 数组每个元素独立指向不同内存地址;
- 常用于存储多个字符串(如
char *strs[] = {"a", "b", "c"})。
1.3 快速记忆技巧
- 看括号/优先级:
(*p)[N]先算*p(指针),*p[N]先算p[N](数组); - 口诀:数组指针是指针,指针数组是数组。
2 函数指针与指针函数
- 函数指针 :本质是指针,指向一个函数(重点:指针);
- 指针函数 :本质是函数,返回值是指针(重点:函数)。
2.1 函数指针(指向函数的指针)
cs
// 指向"返回值为T、参数为T1/T2/...的函数"的指针
T (*p)(T1, T2, ...);
(*p)表明p是指针;(T1, T2, ...)表明指向的函数参数类型;T表明指向的函数返回值类型。
cs
#include <stdio.h>
// 普通函数:返回两个int的最大值
int Max(int x, int y) {
return x > y ? x : y;
}
int main() {
int(*p)(int, int); // 函数指针:指向返回int、参数为两个int的函数
p = Max; // 函数名就是函数地址,赋值给指针
int a = 10, b = 20, c;
c = (*p)(a, b); // 通过函数指针调用Max
// 简化写法:c = p(a, b); (函数指针可省略*)
printf("max = %d\n", c);
return 0;
}
输出结果 :max = 20
核心特点
- 函数指针无
++/--运算(函数地址固定); - 常用于回调函数(如中断处理、排序函数
qsort)。
1.2 指针函数(返回指针的函数)
cs
// 返回值为指向T类型指针的函数,参数为T1/T2/...
T *p(T1, T2, ...);
p(...)表明p是函数;T*表明函数返回值是指向T类型的指针。
cs
#include <stdio.h>
// 指针函数:返回指向float数组的指针
float *find(float(*pointer)[4], int n) {
float *pt;
pt = *(pointer + n); // 指向第n行数组的首地址
return pt;
}
int main() {
// 3个学生的4门成绩(二维数组)
static float score[][4] = {{60,70,80,90},{56,89,34,45},{34,23,56,45}};
float *p;
int m;
printf("输入要查询的学生序号(1-3):");
scanf("%d", &m);
p = find(score, m-1); // 调用指针函数,返回第m个学生的成绩首地址
printf("第%d个学生的成绩:\n", m);
for(int i=0; i<4; i++) {
printf("%5.2f\t", *(p+i));
}
return 0;
}
输出示例:
输入要查询的学生序号(1-3):2
第2个学生的成绩:
56.00 89.00 34.00 45.00
3 数组名与指针的区别与联系
| 维度 | 数组名 | 指针 |
| 本质 | 数组首元素地址(常量) | 存储地址的变量 |
| 内存占用 | 无额外占用(仅表示地址) | 占用 4/8 字节(32/64 位系统) |
| 可修改性 | 不可修改(a++非法) | 可修改(p++合法) |
| 访问方式 | 直接访问(a[i]) | 间接访问(*p) |
| sizeof | 数组总字节数(sizeof(a)) | 指针本身字节数(sizeof(p)) |
&操作 |
&a指向整个数组(步长 = 数组长度) |
&p指向指针变量本身 |
|---|
关键示例
cs
#include <stdio.h>
int main() {
int a[5] = {1,2,3,4,5};
int *p = a;
printf("sizeof(a) = %zu\n", sizeof(a)); // 输出20(5×4)
printf("sizeof(p) = %zu\n", sizeof(p)); // 输出8(64位系统)
// a++;// 非法:数组名是常量
p++; // 合法:指针变量可修改
printf("*p = %d\n", *p); // 输出2
return 0;
}
4 指针类型详解(常量指针/指向常量的指针)
4.1 常量指针(指针常量)
cs
int *const p;
-
解读:
const修饰p(指针本身),指针指向不可改 ,指向的值可改; -
示例:
csint a=10, b=20; int *const p = &a; *p = 30; // 合法:修改指向的值 // p = &b; // 非法:修改指针指向
4.2 指向常量的指针(常量的指针)
cs
const int *p;
-
解读:
const修饰*p(指针指向的值),指针指向可改 ,指向的值不可改; -
示例:
csint a=10, b=20; const int *p = &a; // *p = 30; // 非法:修改指向的常量值 p = &b; // 合法:修改指针指向
4.3 指向常量的常量指针
cs
const int *const p;
-
解读:指针指向不可改 ,指向的值也不可改;
-
示例:
csint a=10, b=20; const int *const p = &a; // *p = 30; // 非法 // p = &b; // 非法
4.4 快速记忆技巧
- 看
const位置:const在*前:值不可改(指向常量);const在*后:指针不可改(指针常量);- 两边都有:值和指针都不可改。
5 指针与引用的异同
5.1 相同点
- 本质都是地址概念:指针存储地址,引用是变量的别名;
- 都可间接访问变量,避免值拷贝(提升效率)。
5.2 核心区别
| 特性 | 指针 | 引用 |
| 本质 | 独立变量(存储地址) | 变量别名(无独立空间) |
| 初始化 | 可空、可后初始化 | 必须初始化、不可空 |
| 可修改性 | 可指向不同变量(p=&b) | 一旦绑定不可修改 |
| 解引用 | 需要*(*p) | 无需解引用(直接用) |
| ++运算 | 地址偏移(p++) | 值自增(q++) |
sizeof |
指针本身大小(4/8 字节) | 指向变量的大小 |
|---|
cs
#include <stdio.h>
int main() {
int x = 5;
int *p = &x; // 指针
int &q = x; // 引用
printf("*p = %d, sizeof(p) = %zu\n", *p, sizeof(p)); // 5 8(64位)
printf("q = %d, sizeof(q) = %zu\n", q, sizeof(q)); // 5 4(int大小)
p++; // 指针地址偏移
q++; // 引用值自增(x变为6)
printf("x = %d\n", x); // 输出6
return 0;
}
5.3 相互转换
- 指针转引用:解引用指针(
*p),作为引用参数; - 引用转指针:取引用地址(
&q),得到指针。
cs
void fun(int &x) { x++; }
int main() {
int a = 5;
int *p = &a;
fun(*p); // 指针转引用
int &q = a;
int *p2 = &q; // 引用转指针
printf("a = %d\n", a); // 输出6
return 0;
}
6 野指针:成因与避免
6.1 野指针定义
指向不可用内存的指针(地址无效 / 已释放 / 越界),访问野指针会导致程序崩溃或内存错误。
6.2 野指针成因
- 指针未初始化(默认随机地址);
- 指针指向的内存被
free/delete后未置NULL; - 指针操作超出变量作用域(如返回栈变量地址)。
6.3 避免方法
-
强制初始化 :
cschar *p = NULL; // 空指针 char *p2 = (char*)malloc(10); // 指向合法内存 if (p2 == NULL) { // 检查malloc是否成功 perror("malloc failed"); return 1; } -
释放后置 NULL :
csfree(p2); p2 = NULL; // 避免野指针 -
清空堆内存 (避免垃圾值):
cs// 清空p2指向的10字节内存为0 memset(p2, 0, 10); // 或bzero bzero(p2, 10);
7 C++ 智能指针(解决内存泄漏)
7.1 核心问题
普通指针易导致内存泄漏(忘记释放)、二次释放、野指针,智能指针通过RAII 机制(资源获取即初始化)自动管理内存。
7.2 常用智能指针
unique_ptr:独占所有权,禁止拷贝,避免二次释放;shared_ptr:共享所有权,引用计数,计数为 0 时释放内存;weak_ptr:弱引用,解决shared_ptr循环引用导致的内存泄漏。
7.3 循环引用问题与解决
cpp
#include <memory>
using namespace std;
class B; // 前向声明
class A {
public:
shared_ptr<B> b_ptr;
~A() { printf("A析构\n"); }
};
class B {
public:
// 改为weak_ptr,避免循环引用
weak_ptr<A> a_ptr;
~B() { printf("B析构\n"); }
};
int main() {
shared_ptr<A> a = make_shared<A>();
shared_ptr<B> b = make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
// 离开作用域时,引用计数为0,正常析构
return 0;
}
输出:
B析构
A析构