在 C/C++ 学习过程中,指针 是一大难点。很多初学者一看到 int *p[10];
、int (*p)[10];
或者 int (*f)(int);
就一脸懵。本文就从指针的基础开始,逐步过渡到各种复杂的定义,并配合示例进行解释,帮助你彻底搞懂指针。
一、认识指针
指针是 保存地址的变量。在 C 语言中,所有对象(变量、数组、函数)在内存中都有地址。指针变量就是用来存储这些地址,并间接访问目标对象。
例如:
cpp
int a = 10;
int *p = &a; // p 保存了变量 a 的地址
printf("%d\n", *p); // 输出 10
为什么需要指针?
-
通过指针可以访问和修改其他变量的值;
-
指针可以用来操作数组、字符串;
-
动态内存分配必须使用指针;
-
指针是函数参数传递大数据的高效方式;
-
函数指针可以实现回调和"面向接口"的设计。
二、各种指针的定义
我们来看一些常见的指针定义。技巧:从变量名往外读,结合括号优先级。
- 一个整形数:
cpp
int a;
- 一个指向整形数的指针:
cpp
int *a;
- 一个指向指针的指针,它指向的指针指向一个整形数:
cpp
int **a;
- 一个有 10 个整形数的数组:
cpp
int a[10];
- 一个有 10 个指针的数组,每个指针指向一个整形数:
cpp
int *a[10];
- 一个指向有 10 个整形数的数组的指针:
cpp
int (*a)[10];
- 一个指向指针的指针,被指向的指针指向一个有 10 个整形数的数组:
cpp
int (**a)[10];
- 一个指向数组的指针,该数组有 10 个整形指针:
cpp
int *(*a)[10];
- 一个指向函数的指针,该函数有一个整形参数并返回一个整形数:
cpp
int (*a)(int);
- 一个有 10 个指针的数组,每个指针指向一个函数,该函数有一个整形参数并返回一个整形数:
cpp
int (*a[10])(int);
- 一个函数指针,指向的函数有两个整形参数,并且返回一个函数指针;返回的函数指针指向一个函数,该函数有一个整形参数并返回整形数:
cpp
int (*(*a)(int, int))(int);
三、指针与数组
1.指针引用数组
cpp
int arr[5] = {1,2,3,4,5};
int *p = arr; // 等价于 &arr[0]
printf("%d\n", *(p+2)); // 输出 3
2.指针和二维数组
cpp
int arr[3][4];
int (*p)[4] = arr; // p 指向一个有 4 个元素的数组
-
数组指针 vs 指针数组
-
int (*p)[10];
→ 数组指针,p 指向一个整型数组 -
int *p[10];
→ 指针数组,p 是数组,里面每个元素是整型指针
-
四、二维数组与指针的关系(重点)
二维数组在内存中是按行顺序存储 的,本质上可以看作"一维数组的数组"。理解这一点对于写对指针运算非常关键。下面举例说明(假设 int a[3][4]
的基地址是 2000,每个元素占 4 字节):
表达式 | 含义 | 地址/值 |
---|---|---|
a |
数组名,指向第 0 行数组(a[0] ) |
2000 |
a[0] / *(a+0) / *a |
第 0 行第 0 列元素地址 | 2000 |
a+1 / &a[1] |
第 1 行首地址 | 2016 |
a[1] / *(a+1) |
第 1 行第 0 列元素地址 | 2016 |
a[1]+2 / *(a+1)+2 / &a[1][2] |
第 1 行第 2 列元素地址 | 2024 |
*(a[1]+2) / *(*(a+1)+2) / a[1][2] |
第 1 行第 2 列元素的值(假设为 13) | 13 |
🔑 规律:
a[i]
相当于第 i 行首地址
a[i]+j
相当于第 i 行第 j 列元素地址
*(*(a+i)+j)
相当于a[i][j]
的值
这个表能帮助你彻底掌握二维数组和指针的对应关系。
五、函数指针
函数在内存中也有地址,指针可以指向函数。
cpp
int add(int x, int y) { return x + y; }
int (*pf)(int,int) = add;
printf("%d\n", pf(2,3)); // 输出 5
函数指针常用于:
-
回调函数 (如
qsort
) -
策略模式(选择不同的函数执行)
六、qsort
与函数指针(重点)
qsort
是 C 标准库 <stdlib.h>
提供的一个快速排序函数,使用时需要 传入一个函数指针作为比较函数。
函数原型
cpp
void qsort(
void *base, // 待排序数组的首地址
size_t num, // 元素个数
size_t size, // 每个元素的大小(字节数)
int (*cmp)(const void*, const void*) // 比较函数指针
);
参数说明
-
base
→ 待排序数组首地址 -
num
→ 元素个数 -
size
→ 每个元素大小(用sizeof
) -
cmp
→ 比较函数,需要用户自己写,返回值规则:
- < 0:第一个元素在前
- = 0:相等
- >0:第一个元素在后
示例 1:排序整型数组
cpp
#include <stdio.h>
#include <stdlib.h>
// 比较函数:升序
int cmpInt(const void *a, const void *b) {
return *(int*)a - *(int*)b;
}
int main() {
int arr[] = {42, 15, 7, 23, 4};
int n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(int), cmpInt);
for(int i=0; i<n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
输出:
bash
4 7 15 23 42
示例 2:按字符串长度排序
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int cmpStrLen(const void *a, const void *b) {
const char *s1 = *(const char**)a;
const char *s2 = *(const char**)b;
return strlen(s1) - strlen(s2);
}
int main() {
const char *arr[] = {"apple", "banana", "pear", "grape"};
int n = sizeof(arr) / sizeof(arr[0]);
qsort(arr, n, sizeof(char*), cmpStrLen);
for(int i=0; i<n; i++) {
printf("%s ", arr[i]);
}
return 0;
}
输出:
bash
pear grape apple banana
🔑 规律:
qsort
的精髓在于"比较函数指针"通过函数指针,
qsort
可以对任意类型进行排序(整型、浮点型、字符串、结构体等)这就是函数指针在 C 语言里"回调函数"的经典应用
七、多级指针
-
二级指针 :
int **pp;
常用于:
-
动态内存分配二维数组
-
修改指针本身的值(如
char **argv
)
-
-
多级指针 :
int ***ppp;
实际项目中一般用在链表、树等复杂数据结构。
八、总结
-
指针是存放地址的变量,是 C 语言的精华。
-
理解指针声明的技巧:从变量名往外读,注意括号优先级。
-
常见指针包括:
-
指向基本类型的指针
-
指针数组、数组指针
-
函数指针、指针函数
-
多级指针
-
-
二维数组和指针的关系一定要通过"地址运算表"掌握,写程序时才不会乱。