文章目录
前言
本文详细讲解 C 语言中指针的概念、声明方式以及正确使用方法
一、指针是什么?
指针是 C 语言中一种特殊的变量,它存储的是其他变量的内存地址。指针让程序可以直接操作内存,从而实现高效的数据传递和灵活的数据结构管理。
二、指针用法及规范
1.基本指针语法
标准写法(推荐写法):星号(*)紧贴变量名,明确表示该变量是指针:
int *p; // p 是指向 int 的指针
类型后写法:星号(*)可紧贴类型名,但需注意编译器视角:星号(*)实际绑定的是变量名而非类型。
int* p; // 合法,但注意多变量声明时的陷阱
多变量声明:如果在同一行声明多个变量,必须对每个指针变量都加上 *
int *p, *q; // 正确:p 和 q 都是 int* 类型
int* p, q; // 错误:仅 p 为指针,q 为普通 int
指针初始化:未初始化的指针(野指针)指向未知内存,直接解引用可能导致程序崩溃
// 正确示范
int x = 5;
int *p = &x; // 指向变量 x
int *q = NULL; // 初始化为空指针
// 错误示范
int *p; // 未初始化的野指针
*p = 10; // 崩溃风险极高
多级指针:多级指针即指针的指针,常用于需要多重间接访问的场景
int x = 10;
int *p = &x; // 一级指针
int **pp = &p; // 二级指针
printf("%d", **pp); // 输出 10
2.指针数组与数组指针
指针数组:指针数组是一个数组,数组中的每个元素都是指针。由于数组下标 []的优先级高于 *
,下面的声明表示数组内元素为指针
int *arr[5]; // 定义一个包含 5 个 int* 的数组
// 可定义一个包含若干整数地址的数组
int a = 1, b = 2, c = 3;
int *ptr_arr[3] = {&a, &b, &c};
数组指针:数组指针则是一个指针,指向一个数组(整个数组的首地址)。为避免优先级混淆,需要使用括号明确表示指针绑定的是变量,而非类型
int (*ptr)[5]; // ptr 是指向包含 5 个 int 的数组的指针
// 处理二维数组
int matrix[3][4] = {0}; // 3 行 4 列的二维数组
int (*p)[4] = matrix; // p 指向一个含 4 个 int 的一维数组
// 通过函数参数传递二维数组时:
void print_matrix(int (*mat)[4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", mat[i][j]);
}
printf("\n");
}
}
3.函数指针
基本语法:函数指针允许将函数作为参数传递或返回,函数指针声明时(*)必须被括号包裹,后面紧跟参数列表。
int (*func_ptr)(int, int); // func_ptr 是指向接受两个 int 参数并返回 int 的函数的指针
int *func(int, int); // 错误:声明了一个返回 int* 的函数
回调函数
// 回调函数 compare 用于排序比较
int compare(int a, int b) {
return a - b;
}
void sort(int *arr, int size, int (*compare)(int, int)) {
// 简化示例:假设排序实现调用 compare 比较数组元素
}
int main() {
int data[] = {5, 2, 9};
sort(data, 3, compare);
return 0;
}
4.常量指针与指针常量
常量指针:指向常量的指针,指针所指向的数据不能通过该指针修改,但指针本身可以指向其他地址。
const int *p = &x; // 或写作 int const *p;
// *p = 10; // 错误,不能通过 p 修改 *p 的值
p = &y; // 正确,可以改变 p 的指向
指针常量:指针本身是常量,一旦初始化后就不能再改变指向的地址,但可以修改所指数据的值。
int *const p = &x;
*p = 10; // 正确,可以修改 x 的值
// p = &y; // 错误,不能改变 p 的指向
指向常量的指针常量:指针和所指向的数据都不可修改
const int *const p = &x;
// *p = 10; // 错误,不能修改数据
// p = &y; // 错误,不能改变指向
5.函数参数与返回值
作为函数参数:
(1)传递变量地址
void modifyValue(int *p) {
*p = 20;
}
int main() {
int a = 10;
modifyValue(&a);
printf("%d\n", a); // 输出 20
return 0;
}
(2)传递数组指针(数组首地址)
void modifyArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
arr[i]++; // 每个元素加 1
}
}
int main() {
int nums[] = {1, 2, 3};
modifyArray(nums, 3);
return 0;
}
(3)传递只读数据
// 传入常量指针
void printArray(const int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}
作为函数返回值:
错误示范:返回局部变量地址
int *createValue() {
int x = 100;
return &x; // 错误:x 是局部变量,函数返回后内存无效
}
正确示范:使用动态内存分配
int *createValue() {
int *p = malloc(sizeof(int));
if (p) {
*p = 100;
}
return p;
}
int main() {
int *p = createValue();
if (p) {
printf("%d\n", *p);
free(p);
p = NULL;
}
return 0;
}
6.typedef与指针
使用typedef 可以简化复杂指针的声明,使代码更易读
typedef定义指针类型
typedef int* IntPtr;
IntPtr p1, p2; // p1 和 p2 都是 int* 类型
// 注意与int* p, q; 中 q 变为 int 的区别
typedef与函数指针
typedef int (*FuncPtr)(int, int);
int add(int a, int b) { return a + b; }
FuncPtr f = add;
typedef与结构体指针
typedef struct Node {
int data;
struct Node *next;
} Node, *NodePtr;
7.动态内存管理
int main() {
int *arr = malloc(5 * sizeof(int));
if (!arr) {
perror("Memory allocation failed");
exit(1);
}
// 使用 arr...
free(arr); // 释放内存
arr = NULL; // 避免悬空指针
return 0;
}
8.代码风格建议
指针声明风格
int* p; // 推荐(指针和 `int*` 绑定)
int *p1, *p2; // 避免 `int *p1, p2;` 误解
对于不应修改的数据,使用const 限制
void printData(const int *data);
避免内存泄漏
free(ptr);
ptr = NULL; // 避免 `free()` 之后的悬空指针
对于固定常量,使用 #define
或 typedef
,增加代码可维护性
#define MAX_SIZE 100
typedef unsigned char byte;
总结
本文从指针的基本概念出发,详细介绍了指针的声明、指针数组与数组指针、函数指针、常量指针及其在函数参数和返回值中的应用。同时,还讨论了使用typedef简化复杂指针声明、动态内存管理的正确方法,以及代码风格和最佳实践。希望通过这些内容,能帮助你写出更安全、可读和高效的 C 语言代码。
通过不断实践和积累经验,你会发现指针既强大又灵活,祝你在 C 语言的学习和编程中不断进步!