"Paragram" 应该是 "Pointer" 的拼写错误,指针是 C 语言中最核心、最强大,也最容易让初学者感到困惑的特性,理解了指针,你才算真正入门了 C 语言。

指针是什么?
指针就是一个变量,它存储的不是普通的数据(如 int, char),而是另一个变量的内存地址。
你可以把计算机的内存想象成一排排的储物柜,每个储物柜都有一个唯一的编号(地址),你可以在储物柜里放东西(数据),也可以通过储物柜的编号(地址)来找到它。
- 普通变量:相当于直接把东西放进某个储物柜,并记住柜子里的内容。
int a = 10;,你创建了一个名为a的柜子,里面放了数字10。 - 指针变量:相当于拿了一个小本子,用来记录某个储物柜的编号。
int *p = &a;,你创建了一个名为p的小本子,在上面记下了a这个储物柜的地址。
为什么需要指针?
- 间接访问:通过指针可以间接地访问和修改其他变量的值。
- 高效传递大数据:函数传参时,传递指针(地址)比传递整个大数据结构(如大数组、结构体)要快得多,节省内存。
- 动态内存管理:在程序运行时动态地分配和释放内存(如
malloc,free),这是创建灵活、高效程序的基础。 - 复杂数据结构:是实现链表、树、图等复杂数据结构的关键。
指针的核心概念与语法
1 & (取地址运算符)
这个符号用于获取一个变量在内存中的地址。

int a = 10; int *p; // 声明一个指针变量 p p = &a; // 将变量 a 的地址赋值给指针 p
a的值是10。&a是变量a的内存地址。p存储的就是&a这个地址。
2 (解引用/间接寻址运算符)
这个符号用在指针变量前,表示“访问该指针指向地址处的值”。
int a = 10;
int *p = &a;
// 下面两种方式都能得到 a 的值
printf("a 的值是: %d\n", a); // 直接访问,输出 10
printf("p 指向的值是: %d\n", *p); // 通过指针 p 间接访问,输出 10
// 通过指针修改 a 的值
*p = 20;
printf("修改后 a 的值是: %d\n", a); // 输出 20
*p和a在这里指向的是同一个内存空间,所以修改*p就等于修改a。
3 指针的声明
声明指针的语法是:数据类型 *指针名;
int *p;:声明一个指向int类型数据的指针,称为int指针。char *pc;:声明一个指向char类型数据的指针。double *pd;:声明一个指向double类型数据的指针。
注意: 在声明中和在使用中的含义不同。
- 在声明时:
int *p;中的 表示p是一个指针。 - 在使用时:
*p;中的 是一个运算符,表示解引用。
4 NULL 指针
一个指针在声明后,如果没有被初始化,它会指向一个随机的、未知的内存地址,直接使用这样的指针(如 *p = 5;)会导致程序崩溃或不可预测的行为。

一个好的习惯是:声明指针后,立即将其初始化为 NULL。
NULL 是一个预定义的常量,表示“空指针”,它不指向任何有效的内存地址。
int *p = NULL;
// 在使用 p 之前,最好先检查它是否为 NULL
if (p != NULL) {
*p = 100;
}
指针与数组的关系
在 C 语言中,数组名和指针的关系非常紧密,数组名 arr 会“退化”为其第一个元素的地址。
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // 等价于 int *p = &arr[0];
// 通过指针遍历数组
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, *(p + i));
// *(p + i) 等价于 p[i],也等价于 arr[i]
}
arr[i]和*(arr + i)是完全等价的。p[i]和*(p + i)也是完全等价的。
数组指针 vs. 指针数组
int (*p)[5];:这是一个数组指针,它指向一个包含 5 个int元素的数组。int *p[5];:这是一个指针数组,它是一个包含 5 个int指针的数组。
前者用得较少,后者比较常见。
指针与函数
1 指针作为函数参数
当函数需要修改外部变量的值,或者需要传递一个很大的数据结构时,使用指针作为参数非常高效。
示例1:交换两个变量的值
#include <stdio.h>
// 如果没有使用指针,函数内部的交换不会影响主函数的变量
// void swap(int a, int b) { ... } // 错误示例
// 使用指针,函数可以访问并修改主函数中的变量
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
int main() {
int a = 10, b = 20;
printf("交换前: a = %d, b = %d\n", a, b);
swap(&a, &b); // 传递 a 和 b 的地址
printf("交换后: a = %d, b = %d\n", a, b);
return 0;
}
示例2:传递数组给函数 当把数组名传给函数时,实际上传递的是数组首元素的地址,这使得函数可以修改原数组的内容。
#include <stdio.h>
void print_array(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
void modify_array(int *arr, int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // 直接修改原数组
}
}
int main() {
int my_arr[] = {1, 2, 3, 4, 5};
int size = sizeof(my_arr) / sizeof(my_arr[0]);
printf("原始数组: ");
print_array(my_arr, size);
modify_array(my_arr, size);
printf("修改后数组: ");
print_array(my_arr, size);
return 0;
}
2 函数指针
函数指针是一个指向函数的指针,它存储的是函数的入口地址,这使得我们可以将函数作为参数传递给其他函数,或者实现回调机制。
#include <stdio.h>
// 定义两个函数
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
// 一个函数,它接受两个整数和一个函数指针作为参数
int calculate(int x, int y, int (*operation)(int, int)) {
return operation(x, y);
}
int main() {
int result;
// 将 add 函数的地址传递给 calculate
result = calculate(10, 5, add);
printf("10 + 5 = %d\n", result); // 输出 15
// 将 subtract 函数的地址传递给 calculate
result = calculate(10, 5, subtract);
printf("10 - 5 = %d\n", result); // 输出 5
return 0;
}
动态内存分配
使用指针可以在程序运行时向操作系统申请内存,并在使用完毕后释放,这主要通过以下四个函数实现:
malloc(size_t size):分配size字节的内存,返回一个指向这块内存的指针,内存是未初始化的。calloc(size_t num, size_t size):分配num * size字节的内存,并将所有位初始化为 0。realloc(void *ptr, size_t size):重新调整之前分配的内存块的大小。free(void *ptr):释放由malloc,calloc,realloc分配的内存。
必须记得 free!否则会导致内存泄漏**。
#include <stdio.h>
#include <stdlib.h> // 包含 malloc 和 free 的头文件
int main() {
int n;
printf("请输入要存储的数字个数: ");
scanf("%d", &n);
// 1. 分配内存
int *ptr = (int *)malloc(n * sizeof(int));
// 使用 (int *) 是为了将 malloc 返回的 void* 指针转换为 int* 指针
// 2. 检查内存是否分配成功
if (ptr == NULL) {
printf("内存分配失败!\n");
return 1; // 异常退出
}
// 3. 使用内存
printf("请输入 %d 个数字:\n", n);
for (int i = 0; i < n; i++) {
scanf("%d", &ptr[i]);
}
printf("你输入的数字是: ");
for (int i = 0; i < n; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
// 4. 释放内存
free(ptr);
ptr = NULL; // 好习惯:将指针置为 NULL,防止成为野指针
return 0;
}
总结与最佳实践
| 概念 | 符号/语法 | 作用 | 示例 |
|---|---|---|---|
| 声明指针 | int *p; |
声明一个指向 int 类型的指针变量。 |
int *p = NULL; |
| 取地址 | & |
获取一个变量的内存地址。 | p = &a; |
| 解引用 | 访问指针指向地址处的值。 | printf("%d", *p); |
|
| 指针与数组 | arr[i] == *(arr+i) |
数组名可以当作指针使用。 | int *p = arr; |
| 指针参数 | void func(int *x) |
允许函数修改外部变量或高效传递数据。 | swap(&a, &b); |
| 函数指针 | int (*p)(int, int) |
指向一个函数,可以用于回调。 | p = add; |
| 动态内存 | malloc, free |
在运行时分配和释放内存。 | int *p = malloc(...); free(p); |
最佳实践:
- 初始化指针:始终初始化指针,如果暂时没有指向,就设为
NULL。 - 检查
malloc返回值:malloc可能会失败,返回NULL,使用前必须检查。 - 记得
free:使用malloc分配的内存,一定要用free释放,避免内存泄漏。 - 将指针置为
NULL:free一个指针后,最好立即将其设为NULL,防止成为“野指针”(dangling pointer)。 - 避免悬垂指针:不要使用指向已释放内存的指针。
指针是 C 语言的灵魂,虽然一开始很难,但只要多加练习,理解了其“地址”和“间接访问”的本质,你就会发现它的强大和优雅。
