详细照片请私信
一、指针基础概念
1. 本质与内存地址
指针本质是存储内存地址的变量。所有变量在内存中占据空间,地址唯一标识变量位置。
示例:
int x = 10; // 变量x存储在内存地址0x1000
int *p = &x; // p存储地址0x1000,*p访问x的值
内存示意图:变量x的地址为0x1000,指针p存储该地址。
2. 声明与初始化
-
声明方式:
类型 *指针名;
-
初始化:指针必须指向有效内存地址后才能使用。
int *p1; // 未初始化,野指针(危险)
int y = 20;
int *p2 = &y; // 正确初始化
int *p3 = NULL; // 初始化为空指针(安全)
3. 操作符详解
-
&
:获取变量地址,不可用于常量或表达式。
int a = 5;
printf("%p", &a); // 输出a的地址(如0x7ffd42a1c)
-
*
:解引用,访问指针指向的值。
int *p = &a;
printf("%d", *p); // 输出5(等同于a的值)
二、指针的常见操作
int *p_start = arr;
int *p_end = arr + 3;
while (p_start < p_end) { // 判断是否到达数组末尾
printf("%d ", *p_start);
p_start++;
}
// 输出:10 20 30
1. 指针算术运算
指针加减整数时,步长为类型大小(单位:字节)。
示例:
int arr[3] = {10, 20, 30};
int *p = arr; // p指向arr[0]
p++; // p移动到arr[1](地址增加sizeof(int)字节)
printf("%d", *p); // 输出20
指针p++后,地址增加4字节(假设int为4字节)。
2. 指针比较
比较两个指针是否指向同一内存区域(通常用于数组遍历)。
int *p_start = arr; int *p_end = arr + 3; while (p_start < p_end) { // 判断是否到达数组末尾 printf("%d ", *p_start); p_start++; } // 输出:10 20 30
3. 空指针与野指针
-
空指针(NULL):指向地址0的指针,表示“无指向”。
int *p = NULL;
if (p == NULL) { // 安全检查
printf("指针未初始化!");
}
-
野指针:指向无效内存的指针,可能引发崩溃。
int *p; // 未初始化(野指针) *p = 100; // 未定义行为(可能崩溃)
三、指针与数组
1. 数组名的本质
数组名是首元素地址的常量指针(不可修改)。
int arr[3] = {1, 2, 3};
printf("%p == %p", arr, &arr[0]); // 输出相同地址
// arr++ 非法(数组名是常量)
2. 指针遍历数组
通过指针移动高效访问数组元素。
int *p = arr;
for (int i = 0; i < 3; i++) {
printf("%d ", *(p + i)); // 等价于arr[i]
}
3. 多维数组与指针
多维数组的行指针需要明确列数。
int matrix[2][3] = {{1,2,3}, {4,5,6}};
int (*ptr)[3] = matrix; // 指向含3个元素的数组的指针
printf("%d", ptr[1][2]); // 输出6(第二行第三列)
四、函数与指针
1. 指针作为函数参数
通过指针实现函数内修改外部变量(传址调用)。
void increment(int *num) {
(*num)++; // 修改外部变量的值
}
int main() {
int a = 5;
increment(&a);
printf("%d", a); // 输出6
return 0;
}
2. 函数返回指针
需确保返回的指针指向有效内存(避免返回局部变量地址)。
// 正确示例:返回堆内存指针
int* createArray(int size) {
int *arr = (int*)malloc(size * sizeof(int));
for (int i = 0; i < size; i++) {
arr[i] = i + 1;
}
return arr; // 调用者需负责free
}
3. 函数指针
指向函数的指针,用于动态调用或回调。
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int main() {
int (*funcPtr)(int, int); // 声明函数指针
funcPtr = &add; // 指向add函数
printf("%d\n", funcPtr(2, 3)); // 输出5
funcPtr = &subtract; // 切换指向subtract
printf("%d\n", funcPtr(5, 2)); // 输出3
return 0;
}
五、高级指针应用
1. 指针数组 vs 数组指针
-
指针数组:存储多个指针的数组。
int x = 10, y = 20;
int *arr[2] = {&x, &y}; // arr是包含两个int指针的数组
printf("%d %d", *arr[0], *arr[1]); // 输出10 20
-
数组指针:指向数组的指针。
int (*ptr)[3]; // 指向含3个int元素的数组的指针
int matrix[2][3] = {0};
ptr = matrix; // ptr指向第一行
ptr++; // 移动到第二行
2. 动态内存分配
使用malloc
和free
管理堆内存。
int *dynamicArr = (int*)malloc(5 * sizeof(int));
if (dynamicArr == NULL) {
// 处理内存分配失败
}
for (int i = 0; i < 5; i++) {
dynamicArr[i] = i * 2;
}
free(dynamicArr); // 必须释放
3. 多级指针
指向指针的指针,用于修改指针本身。
void allocateMemory(int **ptr) {
*ptr = (int*)malloc(sizeof(int));
**ptr = 100;
}
int main() {
int *p;
allocateMemory(&p); // 通过二级指针修改p的值
printf("%d", *p); // 输出100
free(p);
return 0;
}
4. const与指针
-
const int *p
:指针指向的值不可变。 -
int *const p
:指针本身不可变。
int a = 10, b = 20;
const int *p1 = &a; // 不能通过p1修改a的值
// *p1 = 30; // 编译错误
p1 = &b; // 允许修改p1的指向
int *const p2 = &a; // p2的地址不可变
*p2 = 30; // 允许修改a的值
// p2 = &b; // 编译错误
5. 结构体指针
通过->
操作符访问成员。
typedef struct {
int id;
char name[20];
} Student;
Student s = {1, "Alice"};
Student *ptr = &s;
printf("%s", ptr->name); // 输出Alice
六、指针安全与常见问题
1. 内存泄漏
未释放动态分配的内存。
int *leak = malloc(100); // 分配内存
// 忘记调用free(leak)
2. 越界访问
指针超出数组范围导致未定义行为。
int arr[3] = {1, 2, 3};
int *p = arr + 5; // 越界访问
printf("%d", *p); // 不可预测的结果
3. 类型转换风险
强制转换可能导致数据解释错误。
float f = 3.14;
int *p = (int*)&f; // 强制转换
printf("%d", *p); // 输出浮点数的二进制整数表示(非3)
七、典型应用场景
-
链表实现
typedef struct Node {
int data;
struct Node *next;
} Node;
Node *head = NULL;
head = (Node*)malloc(sizeof(Node));//扩容
head->data = 1;
head->next = NULL;
-
字符串操作
char str[] = "Hello";
char *p = str;
while (*p != '\0') {
printf("%c", *p);
p++;
}
八、常见误区
-
数组名不是指针
虽然数组名可转换为指针,但其类型为int[5]
,不可修改。 -
指针类型必须匹配
int*
与char*
的步长不同,强制转换需谨慎。 -
未检查malloc返回值
内存分配可能失败,需检查指针是否为NULL。