1. 指针的理解与定义
- 变量的访问方式:
- 直接访问:直接使用变量名进行访问。
- 间接访问:通过指针访问,指针存储的是变量的内存地址。
- 内存地址与指针:
- 每个内存单元都有一个编号,称为内存地址。
- 变量的地址形象化地称为“指针”。
- 指针变量:用于存放变量地址的变量。
- 指针变量的定义:
- 格式:
数据类型 *指针变量名 [=初始地址值];
- 举例:
int *p;
表示一个指向int类型数据的指针变量。
- 格式:
2. 指针的运算
- 取址运算符(&):
- 作用:取出指定变量在内存中的地址。
- 举例:
int num = 10; int *p = #
- 取值运算符(*):
- 作用:根据给定的内存地址取出该地址对应变量的值。
- 举例:
int a = 2024; int *p = &a; printf("%d\n", *p);
- 指针的常用运算:
- 指针与整数值的加减运算:表示指针所指向的内存地址的移动。
- 指针的自增、自减运算:指针向前或向后移动。
- 同类指针相减运算:返回它们之间的距离(以数据单位计)。
- 指针间的比较运算:比较内存地址的大小。
3. 野指针
- 野指针:指针指向的位置是不可知、不正确、没有明确限制的。
- 野指针的成因:
- 指针使用前未初始化。
- 指针越界访问。
- 指针指向已释放的空间。
- 野指针的避免:
- 指针初始化。
- 小心指针越界。
- 避免返回局部变量的地址。
- 指针指向空间释放后,及时置为NULL。
- 指针使用之前检查有效性。
4. 二级指针(多重指针)
- 二级指针:指向指针的指针。
- 格式:
数据类型 **指针名;
- 举例:
int a = 10; int *pa = &a; int **ppa = &pa;
5. 指针与数组
- 一维数组与指针:
- 指针可以指向数组元素,并遍历数组。
- 指针带下标的使用:
p[i]
等价于*(p+i)
。 &数组名
与数组名
的区别:&数组名
是数组的地址,类型为数组类型指针
;数组名
是数组首元素的地址。
- 二维数组与指针:
- 使用数组名访问二维数组。
- 使用指针变量访问二维数组,例如通过指针遍历二维数组元素。
- 指针数组:
- 数组指针 vs 指针数组:
- 数组指针:指针变量里存放一个数组的首地址。
- 指针数组:数组用来存放指针,每个元素都是一个指针变量。
- 使用:例如,通过指针数组表示字符串数组。
- 数组指针 vs 指针数组:
- 字符数组 vs 字符指针变量:
- 字符数组:由若干字符元素组成。
- 字符指针变量:存放字符串/字符数组的首地址。
- 字符串数组的表示:
- 二维字符数组:每个元素是一个字符数组。
- 字符指针数组:每个元素是一个字符指针,指向一个字符串。
- 拓展:指向固定长度数组的指针变量:
- 定义格式:
(*标识符)[一维数组元素个数];
- 举例:
int (*p)[4];
表示指向包含4个元素的一维数组的指针变量。当然,我们可以继续深入探讨C语言中指针和其他相关概念的应用和细节。
指针的高级应用
-
1. 动态内存分配
-
在C语言中,
malloc
、calloc
和realloc
等函数用于动态地在堆上分配内存。这些函数返回指向分配的内存的指针。使用这些函数时,程序员需要负责在不再需要这块内存时,使用free
函数来释放它,以避免内存泄漏。 -
malloc:分配指定字节大小的内存块,并返回指向该内存块的指针。如果分配失败,则返回NULL。
int *arr = (int*)malloc(n * sizeof(int)); if (arr == NULL) { // 处理错误 }
-
calloc:分配指定数量的元素,每个元素大小为指定的大小,并将所有位初始化为零。它同样返回指向分配的内存的指针。
int *arr = (int*)calloc(n, sizeof(int)); if (arr == NULL) { // 处理错误 }
-
realloc:调整之前通过
malloc
、calloc
等分配的内存块的大小。如果原内存块足够大,则可能不需要移动;否则,会分配一个新的内存块,复制旧数据,并释放旧内存块。int *new_arr = (int*)realloc(arr, new_size * sizeof(int)); if (new_arr == NULL) { // 处理错误 } arr = new_arr; // 更新指针
-
2. 指针与结构体
结构体是一种允许将不同类型的数据项组合成一个单一类型的方式。在C语言中,结构体通常与指针一起使用,以便于动态创建和管理结构体实例。
typedef struct { int id; char name[50]; float score; } Student; // 动态分配一个Student结构体 Student *student = (Student*)malloc(sizeof(Student)); if (student != NULL) { student->id = 1; strcpy(student->name, "Alice"); student->score = 92.5f; // 使用完毕后,释放内存 free(student); }
3. 指针与函数
-
函数指针:指向函数的指针,允许程序在运行时调用不同的函数。
int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } int main() { int (*op)(int, int); op = add; printf("%d\n", op(5, 3)); // 输出8 op = subtract; printf("%d\n", op(5, 3)); // 输出2 return 0; }
-
回调函数:作为参数传递给另一个函数的函数。在C标准库中,很多函数都接受函数指针作为参数,以实现回调函数的功能。
-
4. 指针与数组的高级操作
-
指针算术:在遍历数组时,指针算术允许我们直接通过指针的递增来访问数组的下一个元素,而无需使用索引
int arr[] = {1, 2, 3, 4, 5}; int *p = arr; for (; p < arr + sizeof(arr)/sizeof(arr[0]); p++) { printf("%d ", *p); }
-
指针与多维数组:多维数组在内存中实际上是以一维数组的形式存储的,但可以通过指针运算来访问。
-
5. 指针与文件操作
在C语言中,文件操作也大量使用了指针。例如,
FILE* fopen(const char *path, const char *mode);
函数返回一个指向FILE
对象的指针,该对象包含了所有用于文件操作的信息。FILE *fp = fopen("example.txt", "w"); if (fp != NULL) { fprintf(fp, "Hello, world!\n"); fclose(fp); }
- 定义格式: