C 语言的指针复习笔记
用着用着就糊涂了,重新复习一下。。。。。。。。
内容由 AI 生成
C 语言的指针是其最核心、最具特色的特性之一,理解指针是掌握 C 语言的关键。指针本质上是存储内存地址的变量,通过指针可以直接操作内存中的数据,这使得 C 语言具备高效、灵活的特性,但也增加了学习难度。以下从基础到进阶详细讲解指针的用法:
一、指针的基本概念
- 内存地址:计算机内存被划分为一个个字节,每个字节都有唯一的编号(类似门牌号),这个编号就是内存地址(通常用十六进制表示,如
0x7ffd8a5b5a4c
)。 - 指针变量:专门用于存储内存地址的变量,其值是另一个变量(或内存空间)的地址。
- 指针的意义:通过指针可以间接访问或修改其指向的内存空间中的数据,尤其在处理数组、函数参数传递、动态内存分配等场景中不可替代。
二、指针的声明与初始化
1. 指针的声明
指针声明的基本语法:数据类型 *指针名;
数据类型
:指针所指向的变量的数据类型(决定了通过指针操作内存时的 "步长",如int*
每次操作 4 字节,char*
每次操作 1 字节)。*
:表示该变量是指针类型。指针名
:遵循标识符命名规则。
示例:
int *p; // 声明一个指向int类型的指针p
char *cp; // 声明一个指向char类型的指针cp
float *fp; // 声明一个指向float类型的指针fp
2. 指针的初始化(指向有效内存)
指针必须初始化后才能使用(否则可能成为 "野指针",操作未知内存导致程序崩溃)。初始化的核心是让指针存储一个有效变量的地址。
通过&
(取地址运算符)获取变量的地址,赋值给指针:
int a = 10;
int *p = &a; // 指针p指向变量a(p存储a的地址)
注意:指针的类型必须与所指向变量的类型一致(除非进行强制类型转换),例如
char*
不能直接指向int
变量。
三、指针的核心操作
1. 解引用(访问指向的内容)
通过*
(解引用运算符)可以访问指针所指向的内存中的数据。
示例:
int a = 10;
int *p = &a;printf("a的值:%d\n", a); // 直接访问a:10
printf("p存储的地址:%p\n", p); // 输出a的地址(如0x7ffd8a5b5a4c)
printf("p指向的值:%d\n", *p); // 解引用p,获取a的值:10
通过指针修改指向的变量的值:
*p = 20; // 等价于 a = 20(通过p间接修改a的值)
printf("修改后a的值:%d\n", a); // 输出20
2. 指针的赋值与比较
指针之间可以直接赋值(让两个指针指向同一块内存):
int a = 10; int *p = &a; int *q = p; // q和p指向同一个变量a printf("*q的值:%d\n", *q); // 输出10
指针可以比较(判断是否指向同一块内存):
if (p == q) {printf("p和q指向同一块内存\n"); // 条件成立 }
四、指针与数组
数组和指针关系密切,数组名本质上是指向数组首元素的指针常量(不能被修改)。
1. 数组名作为指针
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 等价于 p = &arr[0](arr是首元素地址)// 访问数组元素的两种方式
printf("arr[0] = %d\n", arr[0]); // 数组下标法:1
printf("*p = %d\n", *p); // 指针解引用:1
2. 指针的算术运算(核心)
指针的加减运算以 "所指向类型的大小" 为步长(而非字节):
p++
:指针向后移动一个元素(对于int*
,移动 4 字节;char*
移动 1 字节)。p + i
:指针向后移动 i 个元素(指向arr[i]
)。
示例:
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;for (int i = 0; i < 5; i++) {printf("%d ", *(p + i)); // 等价于 arr[i],输出:1 2 3 4 5
}
注意:指针越界访问(如访问
p+5
及以外的地址)是未定义行为,可能导致程序崩溃。
3. 指针与二维数组
二维数组可以理解为 "数组的数组",其数组名是指向首行(一维数组)的指针。
示例:
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*p)[3] = arr; // p是指向"包含3个int的数组"的指针(与二维数组匹配)printf("%d\n", *(*(p + 0) + 1)); // 访问第0行第1列:2
printf("%d\n", *(*(p + 1) + 2)); // 访问第1行第2列:6
五、指针与函数
指针作为函数参数或返回值,可以实现对函数外部变量的修改、传递大型数据(避免拷贝开销)等功能。
1. 指针作为函数参数(传址调用)
C 语言函数参数默认是 "值传递"(形参是实参的拷贝,修改形参不影响实参)。通过指针传递变量地址,可以在函数内部修改外部变量的值。
示例:交换两个变量的值
// 错误示例:值传递无法交换
void swap1(int x, int y) {int temp = x;x = y;y = temp; // 仅修改形参x、y,实参不变
}// 正确示例:指针传递(传址)
void swap2(int *x, int *y) {int temp = *x; // 解引用获取x指向的值*x = *y; // 修改x指向的变量*y = temp; // 修改y指向的变量
}int main() {int a = 10, b = 20;swap1(a, b);printf("swap1后:a=%d, b=%d\n", a, b); // 仍为10,20(未交换)swap2(&a, &b); // 传递a和b的地址printf("swap2后:a=%d, b=%d\n", a, b); // 交换为20,10return 0;
}
2. 指针作为函数返回值(指针函数)
函数可以返回指针(指向某个内存空间),但需注意:不能返回函数内部局部变量的地址(局部变量在函数结束后被释放,其地址变为无效)。
示例:返回静态变量的地址(静态变量生命周期与程序一致)
int* get_num() {static int num = 100; // 静态变量,存储在全局区return # // 返回有效地址
}int main() {int *p = get_num();printf("%d\n", *p); // 输出100(有效)return 0;
}
3. 函数指针(指向函数的指针)
函数指针是指向函数的指针变量,其值是函数的入口地址。可用于实现回调函数(如排序算法中的比较函数)。
声明语法:返回值类型 (*指针名)(参数列表);
示例:
// 定义一个加法函数
int add(int a, int b) {return a + b;
}int main() {int (*fptr)(int, int); // 声明函数指针fptr(指向"参数为int,int,返回int的函数")fptr = add; // fptr指向add函数(函数名即地址)int result = fptr(3, 5); // 通过函数指针调用add,等价于(*fptr)(3,5)printf("3+5=%d\n", result); // 输出8return 0;
}
六、多级指针(指向指针的指针)
多级指针是指向指针的指针,常用于处理 "指针的地址"(如二维数组、动态分配的指针数组等)。
语法:数据类型 **指针名;
(**
表示二级指针,***
表示三级指针,以此类推)
示例:二级指针
int a = 10;
int *p = &a; // p是一级指针(指向a)
int **pp = &p; // pp是二级指针(指向p)// 访问a的值
printf("a = %d\n", a); // 直接访问:10
printf("*p = %d\n", *p); // 一级解引用:10
printf("**pp = %d\n", **pp); // 二级解引用:10
应用场景:通过二级指针修改一级指针的指向
void change_ptr(int **pp) {static int b = 20;*pp = &b; // 修改pp所指向的指针(p)的指向,使其指向b
}int main() {int a = 10;int *p = &a;printf("修改前*p = %d\n", *p); // 10change_ptr(&p); // 传递p的地址(二级指针参数)printf("修改后*p = %d\n", *p); // 20(p现在指向b)return 0;
}
七、指针与动态内存分配
C 语言通过malloc
、calloc
、realloc
和free
函数在堆区动态分配内存,这些函数的返回值是指向分配内存的指针。
1. 动态分配内存(malloc
)
#include <stdlib.h> // 包含动态内存函数int main() {int n = 5;// 分配n个int类型的内存(总大小:n * sizeof(int))int *arr = (int*)malloc(n * sizeof(int)); // 返回void*,需强制转换为int*if (arr == NULL) { // 内存分配失败时返回NULL,必须检查printf("内存分配失败\n");return 1;}// 使用动态数组for (int i = 0; i < n; i++) {arr[i] = i + 1;}// 释放内存(堆区内存不会自动释放,必须手动释放)free(arr);arr = NULL; // 避免野指针(释放后指针仍存储原地址,需置空)return 0;
}
2. 其他动态内存函数
calloc(n, size)
:分配 n 个大小为 size 的内存,并初始化为 0(malloc
不初始化)。realloc(ptr, new_size)
:调整已分配内存的大小(可能迁移内存地址)。
八、指针的常见错误与注意事项
野指针:未初始化或已释放的指针(指向无效内存)。
避免:指针声明后立即初始化(如int *p = NULL;
),释放后置空(p = NULL;
)。空指针解引用:对
NULL
指针使用*
操作(NULL
是无效地址0
)。
避免:解引用前检查指针是否为NULL
(if (p != NULL) { ... }
)。指针越界:访问超出数组或动态内存范围的地址。
避免:严格控制指针运算的范围(如循环变量不超过数组长度)。内存泄漏:动态分配的内存未用
free
释放,导致内存被永久占用。
避免:确保malloc
与free
配对使用,释放后指针置空。指针类型不匹配:不同类型的指针直接赋值(如
char* p = &a;
,a 是 int 类型)。
避免:如需转换,使用显式强制类型转换(char* p = (char*)&a;
),但需谨慎(可能导致访问错误)。
总结
指针是 C 语言的灵魂,其核心是 "通过地址间接操作内存"。掌握指针需要理解:
- 指针的声明与初始化(指向有效内存);
- 解引用(
*
)和取地址(&
)的操作; - 指针与数组、函数的结合使用;
- 动态内存分配中指针的管理;
- 规避野指针、内存泄漏等常见错误。