c语言指针学习
好的,我们来系统地、详细地讲解 C 语言中指针的用法。指针是 C 语言的精髓,也是最强大且最容易出错的部分。
一、指针是什么?
指针的本质是一个变量,但它存储的不是普通的数据,而是另一个变量的内存地址。
你可以把它想象成一张“地址纸条”。假设变量 a
住在一栋大楼(内存)的 101 房间,那么指针 p
就是一张写着 “101” 的纸条。通过这张纸条,你就能找到 a
。
&
(取地址运算符): 用于获取一个变量的内存地址。- 例如:
&a
表示获取变量a
的地址。
- 例如:
*
(解引用运算符): 用于获取指针所指向的内存地址中存储的值。- 例如:
*p
表示获取指针p
所指向地址的值。
- 例如:
二、指针的声明与初始化
// 1. 声明一个整型变量 a,并赋值为 10
int a = 10;// 2. 声明一个指向整型的指针 p
// 语法:目标数据类型 *指针变量名;
int *p;// 3. 将变量 a 的地址赋值给指针 p (初始化)
// 现在 p 存放着 a 的地址,我们说 “p 指向 a”
p = &a;// 也可以在声明的同时初始化
// int *p = &a;
理解 *
的两种角色:
int *p;
中的*
: 用于声明一个指针变量,告诉编译器p
是一个指向int
的指针。printf("%d", *p);
中的*
: 用于解引用,获取p
指向的值。
三、指针的核心操作与用法
1. 基本操作:通过指针修改变量的值
#include <stdio.h>int main() {int a = 10;int *p = &a; // p 指向 aprintf("a 的值: %d\n", a); // 输出: 10printf("a 的地址: %p\n", &a); // 输出: 类似 0x7ffd42a1a23cprintf("p 的值(即a的地址): %p\n", p); // 输出: 和上一行相同printf("*p 的值(即a的值): %d\n", *p); // 输出: 10// 通过指针修改变量 a 的值*p = 20; // 找到 p 指向的地址,向里面写入 20printf("修改后 a 的值: %d\n", a); // 输出: 20printf("修改后 *p 的值: %d\n", *p); // 输出: 20return 0;
}
2. 指针与数组
数组名本质上就是一个指向数组第一个元素的常量指针。
#include <stdio.h>int main() {int arr[5] = {1, 2, 3, 4, 5};int *p = arr; // 等价于 int *p = &arr[0];// 通过指针访问数组元素// 指针加法: p + i 表示向后移动 i 个“单位”(单位大小取决于指针类型,int 是 4 字节)printf("第一个元素: arr[0]=%d, *p=%d\n", arr[0], *p);printf("第二个元素: arr[1]=%d, *(p+1)=%d\n", arr[1], *(p + 1));printf("第三个元素: arr[2]=%d, *(p+2)=%d\n", arr[2], *(p + 2));// 通过指针遍历数组printf("遍历数组: ");for (int i = 0; i < 5; i++) {printf("%d ", *(p + i));// 另一种常见写法: printf("%d ", p[i]); // 下标和指针可以混用}printf("\n");// 指针也可以自增来遍历p = arr; // 重新指向开头printf("指针自增遍历: ");for (int i = 0; i < 5; i++) {printf("%d ", *p);p++; // p 指向下一个元素}printf("\n");return 0;
}
3. 指针与函数(重点)
a) 传址调用 (Call by Reference)
C 语言是“传值调用”,函数内部无法修改外部实参的值。通过指针,我们可以实现“传址调用”,在函数内部修改外部变量的值。
#include <stdio.h>// 错误的交换函数:只交换了形参的值,不影响实参
void swap_wrong(int x, int y) {int temp = x;x = y;y = temp;
}// 正确的交换函数:通过指针操作实参所在的内存
void swap_correct(int *x, int *y) {int temp = *x; // 取出 x 指针指向的值*x = *y; // 把 y 指针指向的值,放入 x 指针指向的地址*y = temp; // 把 temp 的值,放入 y 指针指向的地址
}int main() {int a = 10, b = 20;printf("交换前: a=%d, b=%d\n", a, b);swap_wrong(a, b);printf("错误交换后: a=%d, b=%d\n", a, b); // 输出: a=10, b=20 (未改变)swap_correct(&a, &b); // 传入变量的地址printf("正确交换后: a=%d, b=%d\n", a, b); // 输出: a=20, b=10 (成功交换)return 0;
}
b) 返回多个值
函数只能返回一个值,但如果需要返回多个值,可以通过指针参数来实现。
void getMinMax(int arr[], int len, int *min, int *max) {*min = arr[0];*max = arr[0];for (int i = 1; i < len; i++) {if (arr[i] < *min) *min = arr[i];if (arr[i] > *max) *max = arr[i];}
}int main() {int array[] = {21, 54, 11, 8, 92, 67};int min_val, max_val;getMinMax(array, 6, &min_val, &max_val);printf("最小值: %d, 最大值: %d\n", min_val, max_val);return 0;
}
4. 动态内存分配
这是指针至关重要的用途。使用 malloc
, calloc
, realloc
在堆 (Heap) 上申请内存,并使用 free
释放。
#include <stdio.h>
#include <stdlib.h> // 包含动态内存管理函数int main() {int n;int *dynamic_arr;printf("请输入数组大小: ");scanf("%d", &n);// 在堆上申请 n 个 int 大小的连续内存,并将首地址赋给 dynamic_arrdynamic_arr = (int *)malloc(n * sizeof(int));if (dynamic_arr == NULL) {printf("内存申请失败!\n");exit(1); // 退出程序}// 使用这块内存for (int i = 0; i < n; i++) {dynamic_arr[i] = i * 10;}printf("动态数组元素: ");for (int i = 0; i < n; i++) {printf("%d ", dynamic_arr[i]);}printf("\n");// 非常重要:使用完毕后必须释放内存,防止内存泄漏free(dynamic_arr);// 良好习惯:释放后将指针置为NULL,防止“悬空指针”dynamic_arr = NULL;return 0;
}
四、指针的进阶概念(了解)
- **指针的指针 (int pp): 指向指针的指针。
- 函数指针: 指向函数的指针,可用于回调函数等高级功能。
int (*funcPtr)(int, int); // 声明一个函数指针,该指针指向一个接受两个int参数并返回int的函数 funcPtr = &add; // 指向一个名为 add 的函数 result = (*funcPtr)(3, 4); // 通过指针调用函数
const
与指针:const int *p
: 指针指向的值是常量,不可修改 (*p = 20;
错误)。int *const p
: 指针本身是常量,不可再指向别处 (p = &b;
错误)。const int *const p
: 指针本身和指向的值都是常量。
五、常见错误与陷阱
-
未初始化的指针(野指针):
int *p; // 未初始化,p 指向一个随机地址 *p = 10; // 向未知地址写入数据,可能导致程序崩溃!严重错误!
解决方法: 声明时初始化为
NULL
。 -
访问已释放的内存(悬空指针):
int *p = (int*)malloc(sizeof(int)); free(p); // 释放 p 指向的内存 *p = 10; // 错误!p 现在是一个悬空指针,指向的内存已无效。
解决方法:
free(p);
后立刻p = NULL;
。 -
内存泄漏:
申请了内存 (malloc
),但忘记释放 (free
),导致程序持续占用内存,最终可能耗尽系统资源。
总结
用途 | 描述 | 关键点 |
---|---|---|
基本操作 | 通过指针间接访问和修改变量 | & , * |
与数组 | 高效地遍历和操作数组 | 数组名即指针,指针算术运算 |
与函数 | 实现传址调用,修改实参,返回多个值 | 函数参数定义为指针 |
动态内存 | 在运行时申请任意大小的内存 | malloc , calloc , realloc , free |
掌握指针需要大量的练习和实践。先从简单的例子开始,理解地址和值的区别,然后再逐步深入到数组、函数和动态内存管理中。