指针的定义与本质
指针是什么?
指针是一种 存储变量内存地址 的特殊变量。所有数据存储在内存中,每个内存单元都有唯一地址(编号),指针通过记录地址实现对数据的间接访问。
指针的核心作用
- 直接操作内存:动态内存分配、硬件编程等。
- 提高效率:传递大对象时避免复制(如结构体)。
- 灵活数据结构:实现链表、树、图等动态结构。
指针基本语法
声明指针
数据类型 *指针变量名;// *表示这是个指针变量
示例:
int *p;// p指向int类型变量
char *str;// str指向char类型变量
指针初始化
-
取地址操作符
&:获取变量的内存地址。int num = 10; int *p = #// p指向num的地址 -
直接赋值(需确保地址合法):
int *p = (int *)0x00007FFF1234;// 避免直接操作未知地址
指针的解引用
-
解引用操作符 * :通过指针访问或修改目标地址的数据。
int num = 10; int *p = # printf("num的值 = %d\n", *p);// 输出10(访问值) *p = 20;// 修改num为20
指针的算术运算
指针的加减操作以 数据类型大小 为单位进行偏移。
指针与整数运算
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;// p指向arr[0]
p++;// p指向arr[1](地址+4字节,假设int为4字节)
printf("%d\n", *p);// 输出2
p += 3;// p指向arr[4]
printf("%d\n", *p);// 输出5
指针之间的减法
计算两个指针之间的元素距离(同类型指针):
int *p1 = &arr[0];
int *p2 = &arr[4];
printf("距离 = %ld\n", p2 - p1);// 输出4(间隔4个元素)
多级指针
- 二级指针:指向指针的指针。
int num = 10;
int *p = #//数据类型 *指针变量名;
int **pp = &p;// pp指向p的地址
printf("%d\n", **pp);// 通过二级指针访问num的值 → 输出10
遍历二维数组(数组指针)
#include <stdio.h>
int main() {
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*p)[3] = matrix;// 指向第一行的地址
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", p[i][j]);// 等价于 *(*(p + i) + j)
}
printf("\n");
}
return 0;
}
指针与数组与字符串
数组名的本质
数组名是数组首元素的地址常量(不可修改)。
int arr[3] = {10, 20, 30};
int *p = arr;// p指向arr[0]
printf("%d\n", *(p + 1));// 右移一个int单位 → 输出20
指针遍历数组
for (int *ptr = arr; ptr < arr + 3; ptr++) {
printf("%d ", *ptr);// 输出10 20 30
}
遍历字符串
char *p = "Hello";
while (*p != '\0') {
printf("%c ", *p);// 输出 H e l l o
p++;
}
字符串数组(指针数组)
char *fruits[] = {"Apple", "Banana", "Cherry"};// 指针数组
for (int i = 0; i < 3; i++) {
printf("%s\n", fruits[i]);// 输出各水果名称
}
printf("%s\n",*(fruits + 1));//输出Banana,数组的第二个元素
printf("%c\n",*(*(fruits + 1) + 1));//输出a,Banana的第二个字符
指针与函数
传递指针到函数
// 修改外部变量值
void increment(int *x) {
(*x)++;
}
int main() {
int a = 5;
increment(&a);// a变为6
return 0;
}
返回指针的函数(指针函数)
/* 返回数组最大元素的地址,只能返回静态变量的地址、动态开辟(malloc)的地址,全局变量
的地址等,不能返回局部变量的地址(否则程序会崩溃)
*/
int* find_max(int *arr, int size) {
int *max = arr; // max指向数组首元素
for (int *p = arr; p < arr + size; p++) {
if (*p > *max) max = p; // 找到更大元素时,让max指向该元素
}
return max; // 返回指向数组中最大元素的指针
}
int* find_max(int *arr, int size) {
static int max = *arr; // 静态变量,仅初始化一次
for (int *p = arr; p < arr + size; p++) {
if (*p > max) max = *p; // 更新max为更大的元素值
}
return &max; // 返回静态变量max的地址
}
函数指针
指向函数的指针,用于回调机制:
int add(int a, int b) { return a + b; }
int (*func_p)(int, int) = add;// 声明函数指针
printf("%d\n", func_p(3, 4));// 输出7
void * 指针
- 通用指针,可指向任意类型数据。
- 使用前需强制类型转换。
void *ptr;
int x = 10;
ptr = (void *)&x;// 合法
printf("%d\n", *(int *)ptr);// 需转换为int*
const型指针
核心规则:const 的位置决定保护的对象
const 在 * 的左边,保护的是指针所指向的数据(*p 不可变)。
const 在 * 的右边,保护的是指针本身(p 不可变)。
指向常量的指针(Pointer to Constant)
格式: const 类型 *ptr 或 类型 const *ptr
含义: 指针 ptr 可以指向不同的地址,但不能通过 ptr 来修改它所指向的数据。
示例:
int a = 10, b = 20;
// 指向常量的指针
const int *ptr1 = &a;// ptr1 指向一个const int
int const *ptr2 = &a;// 等价写法
// *ptr1 = 15; // 错误!不能通过ptr1修改数据
a = 15;// 合法,a本身不是const
ptr1 = &b;// 合法,ptr1可以指向其他地址
// *ptr1 = 25; // 错误!仍然不能通过ptr1修改数据
指针常量(Constant Pointer)
格式: 类型 * const ptr
含义: 指针 ptr 自身是常量,一旦初始化就不能再指向其他地址,但可以通过 ptr 修改它所指向的数据。
示例:
int x = 10, y = 20;
// 指针常量
int * const ptr = &x;// ptr是常量指针
*ptr = 15;// 合法,可以修改指向的数据
// ptr = &y; // 错误!ptr本身是常量,不能改变指向
指向常量的指针常量(Constant Pointer to Constant)
格式: const 类型 * const ptr
含义: 既不能修改指针 ptr 的指向,也不能通过 ptr 修改所指向的数据。
示例:
const int z = 30;
int w = 40;
// 指向常量的指针常量
const int * const ptr = &z;
// *ptr = 35; // 错误!不能修改数据
// ptr = &w; // 错误!不能修改指针指向
int read = *ptr;// 合法,只能读取
总结表格(带变量名)
| 声明格式 | 指针(ptr)是否可修改 | *数据(ptr)是否可修改 | 说明 |
|---|---|---|---|
int *ptr | 是 | 是 | 普通指针 |
const int *ptr | 是 | 否 | 指向常量的指针 |
int * const ptr | 否 | 是 | 指针常量 |
const int * const ptr | 否 | 否 | 指向常量的指针常量 |
野指针
野指针是指指向无效内存地址的指针。这些指针通常指向已经被释放或未分配的内存区域。对野指针进行操作会导致未定义行为。
主要成因
成因一:指针未初始化
int *ptr;// 未初始化,值是随机的垃圾值
*ptr = 10;// 危险!向随机内存地址写入数据
printf("%d", *ptr);// 可能导致程序崩溃
成因二:malloc后未检查或释放后继续使用
int *ptr = (int*)malloc(sizeof(int));
if (ptr != NULL) {
*ptr = 100;
}
free(ptr);// 释放内存
// ptr现在成为野指针
//*ptr = 200;// 危险!访问已释放的内存
printf("%d", *ptr);// 未定义行为
成因三:指向局部变量(函数返回后)
int* createWildPointer() {
int local = 42;
return &local;// 返回局部变量的地址
}
int main() {
int *ptr = createWildPointer();// ptr是野指针
printf("%d", *ptr);// 危险!local已销毁
return 0;
}
成因四:指针越界访问
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
for (int i = 0; i < 10; i++) {
// 越界访问
printf("%d ", *(ptr + i));
// 后5次是野指针访问
}
成因五:指针运算错误
int *ptr = (int*)malloc(5 * sizeof(int));
// ... 使用ptr
free(ptr);
int *ptr2 = ptr + 2;// ptr2也是野指针
野指针的危害
程序崩溃:段错误(Segmentation Fault)
数据损坏:意外修改其他有效数据
安全漏洞:可能被利用进行攻击
难以调试:错误表现随机,难以追踪
避免野指针的方法
方法一:总是初始化指针
int *ptr = NULL;// 初始化为NULL
int *ptr2 = (int*)malloc(sizeof(int));// 或初始化为有效地址
if (ptr2 != NULL) {
*ptr2 = 10;
}
方法二:释放后立即置NULL
int *ptr = (int*)malloc(sizeof(int));
if (ptr != NULL) {
*ptr = 100;
printf("%d\n", *ptr);
}
free(ptr);// 释放内存
ptr = NULL;// 立即置NULL
// 安全检查
if (ptr != NULL) {
printf("%d\n", *ptr);// 不会执行
}
else {
printf("指针已释放\n");
}
方法三:避免返回局部变量地址
// 错误做法
int* badFunction() {
int local = 5;
return &local;
}
// 正确做法1:使用静态变量
int* goodFunction1() {
static int staticVar = 5;// 静态变量生命周期延长
return &staticVar;
}
// 正确做法2:使用动态内存
int* goodFunction2() {
int *dynamicVar = (int*)malloc(sizeof(int));
if (dynamicVar != NULL) {
*dynamicVar = 5;
}
return dynamicVar;
}
// 正确做法3:通过参数返回
void goodFunction3(int **result) {
*result = (int*)malloc(sizeof(int));
if (*result != NULL) {
**result = 5;
}
}
方法四:使用函数返回值检查
int *createArray(int size) {
if (size <= 0) {
return NULL;// 返回NULL而不是野指针
}
return (int*)malloc(size * sizeof(int));
}
int main() {
int *arr = createArray(5);
if (arr == NULL) {
printf("内存分配失败\n");
return 1;
}
// 使用arr...
free(arr);
arr = NULL;
return 0;
}
方法五:封装内存管理函数
// 安全的malloc封装
void* safeMalloc(size_t size) {
void *ptr = malloc(size);
if (ptr == NULL) {
fprintf(stderr, "内存分配失败\n");
exit(EXIT_FAILURE);
}
return ptr;
}
// 安全的free封装
void safeFree(void **ptr) {
if (ptr != NULL && *ptr != NULL) {
free(*ptr);
*ptr = NULL;// 自动置NULL
}
}
int main() {
int *arr = (int*)safeMalloc(5 * sizeof(int));
// 使用arr...
safeFree((void**)&arr);// 自动置NULL
// 现在arr为NULL,不会成为野指针
return 0;
}
检测野指针的工具
使用Valgrind(Linux)
gcc -g program.c -o program
valgrind --leak-check=full ./program
动态内存分配
使用指针管理堆内存(需引入 <stdlib.h>):
int *arr = (int *)malloc(5 * sizeof(int));// 分配5个int的空间
if (arr != NULL) {
arr[0] = 100;
// 使用完毕后释放内存
free(arr);
arr = NULL;// 避免野指针
}
指针的注意事项与应用场合
| 问题 | 说明 | 规避方法 |
|---|---|---|
| 野指针 | 指向未知内存的指针 | 初始化时置空,释放后置空 |
| 空指针解引用 | 对 NULL 指针进行操作导致崩溃 | 使用前检查指针是否为 NULL |
| 内存泄漏 | 未释放动态分配的内存 | 确保每个 malloc 对应一个 free |
| 指针类型不匹配 | 操作不同类型指针导致数据错误 | 避免强制类型转换的误用 |
- 当函数需要传递一个数组时,指针代替数组
- 当函数需要返回一个地址(例如函数需要返回数组)的时候,需要使用指针
实现。 - 当函数需要改变实参的时候,需要使用指针实现。-----形参改变实参
代码示例
指针操作基本流程
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
printf("a的地址 = %p\n", (void *)p);// 输出地址(如0x7ffd...)
printf("a的值 = %d\n", *p);// 输出10
*p = 20;
printf("修改后a的值 = %d\n", a);// 输出20
return 0;
}
动态数组操作
#include <stdio.h>
#include <stdlib.h>
int main() {
int size = 5;
int *arr = (int *)malloc(size * sizeof(int));
if (arr == NULL) {
printf("内存分配失败!\n");
return 1;
}
for (int i = 0; i < size; i++) {
arr[i] = i * 10;
}
for (int *p = arr; p < arr + size; p++) {
printf("%d ", *p);// 输出0 10 20 30 40
}
free(arr);
arr = NULL;
return 0;
}
994

被折叠的 条评论
为什么被折叠?



