文章目录
指针的高级应用
指针的基础知识:
1.将指针作为参数传递,利用函数修改指针指向的内容。
2.利用指针高效处理数组以及字符串。 …
进阶:
1.动态内存分配:用指针和结构体动态管理内存,通过动态内存分配,更灵活地控制程序的资源,从而处理复杂的数据结构和算法问题。
2.二级指针:理解更复杂指针使用场景的基础。
3.函数指针:如何将函数作为参数传递,这对于高级C编程技巧如函数回调等至关重要。
1.将指针作为参数传递,利用函数修改指针指向的内容。
#include <stdio.h>
// 定义一个交换函数,使用指针
void swap(int *ptr1, int *ptr2) {
int temp = *ptr1; // 用temp保存ptr1指向的值
*ptr1 = *ptr2; // 将ptr2指向的值赋给ptr1指向的变量
*ptr2 = temp; // 将temp中保存的值赋给ptr2指向的变量
}
int main() {
int a = 5, b = 10;
printf("交换前:a = %d, b = %d\n", a, b);
// 调用swap函数,传递a和b的地址
swap(&a, &b);
printf("交换后:a = %d, b = %d\n", a, b);
return 0;
}
在这个示例中,我们定义了一个 swap 函数,它接受两个指针作为参数,分别指向要交换值的两个变量。在 swap 函数中,我们通过解引用这些指针来访问它们所指向的变量,并且进行交换操作。
2.1使用 *p++ 来遍历数组
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *p = arr; // 指针指向数组的第一个元素
int sum = 0;
// 使用指针遍历数组并计算元素的和
while (*p) {
sum += *p++; // 累加当前元素的值并将指针指向下一个元素
}
printf("数组元素的和为: %d\n", sum);
return 0;
}
在循环体中,我们先通过 *p 获取当前指针所指向的元素的值,并将其累加到 sum 变量中。然后,我们使用 p++ 将指针向后移动到下一个数组元素。循环会持续直到指针指向了数组的结尾(即遇到了数组的末尾标志)。
2.2函数的参数是字符串字面值时,形参声明中应该加上“ const char *”
当函数的参数是字符串字面值时,形参声明中应该加上 const char
*,以表明该参数是一个指向常量字符的指针。这是因为字符串字面值在C和C++中被视为常量,试图修改它们是未定义行为。
例如,如果你有一个函数需要接受字符串字面值作为参数,你可以这样声明:
void myFunction(const char *str);
这里的 const char * 表示一个指向常量字符的指针,指针本身可以被修改,但不能通过该指针修改字符串字面值的内容。这种声明方式可以帮助编译器在编译时检测出试图修改字符串字面值的错误。
3.若函数的返回值类型是一个指针类型,请注意
1.内存管理:函数返回指针时,应确保返回的指针指向的内存空间是有效的,并且在函数调用结束后该内存空间仍然有效。这可能意味着在堆上分配内存,并在适当的时候释放这些内存(例如,在调用方使用完指针后)。
2.返回局部变量的指针:避免从函数中返回指向局部变量的指针。当函数结束时,局部变量的内存空间将被释放,指向它们的指针将变为悬空,这可能导致未定义的行为。
3.指针类型和目标类型的匹配:确保函数的返回类型与返回的指针类型相匹配。例如,如果函数返回 int*
类型的指针,则返回的指针应指向整数类型的数据。4.NULL指针检查:在接收函数返回的指针之前,应该进行NULL指针检查,以确保指针指向有效的内存地址。
5.文档说明:在函数的文档中明确指明返回的指针的生命周期和所有权,以便调用方了解何时应该释放内存。
6.指针的有效性:在使用函数返回的指针之前,确保它指向有效的内存空间。避免悬挂指针和内存泄漏问题。
7.错误处理:在函数无法成功完成时,返回NULL或其他适当的错误指示符,并确保调用方知道如何处理错误情况。
综上所述,返回指针的函数需要谨慎处理内存管理,并确保返回的指针指向有效的数据。对于调用方而言,需要了解函数返回的指针的所有权和生命周期,以避免潜在的内存错误
下面是一个示例,演示了一个函数返回指向动态分配内存的指针,用于创建并返回一个动态分配的整型数组:
#include <stdio.h>
#include <stdlib.h>
// 函数声明,返回一个指向动态分配整型数组的指针
int *createIntArray(int size) {
int *arr = (int *)malloc(size * sizeof(int)); // 分配内存
if (arr == NULL) {
printf("内存分配失败\n");
exit(1); // 程序退出
}
// 初始化数组
for (int i = 0; i < size; i++) {
arr[i] = i + 1;
}
return arr; // 返回指向动态分配整型数组的指针
}
int main() {
int size = 5;
int *array = createIntArray(size);
// 打印数组元素
for (int i = 0; i < size; i++) {
printf("%d ", array[i]);
}
printf("\n");
// 释放动态分配的内存
free(array);
return 0;
}
在这个示例中,createIntArray 函数动态分配了一个包含 size 个整型元素的数组,并将数组初始化为 1 到 size 的连续整数。然后,它返回指向该动态分配数组的指针。在 main 函数中,我们调用 createIntArray 函数来创建数组,并打印数组的内容,最后释放了动态分配的内存。
当函数的返回值类型是指针类型时,需要确保返回的指针指向有效的内存空间,并且在函数调用结束后该内存空间仍然有效。
3.1动态内存分配
动态内存分配通常需要包含 <stdlib.h> 头文件。这个头文件包含了 C 标准库中与内存分配和释放相关的函数,例如 malloc()、calloc()、realloc() 和 free()。
包含 <stdlib.h> 头文件可以确保你的程序可以使用这些函数,并且能够正确地进行动态内存分配和释放操作。如果你的程序中使用了这些函数,但没有包含 <stdlib.h> 头文件,编译器可能会报告错误或警告。
void* malloc(size_t size);
void* calloc(size_t num, size_t size);
void* realloc(void* ptr, size_t new_size);
void free(void *ptr);
3.2Vector结构体定义
typedef int ElementType;
typedef struct {
ElementType* data;
int size;
int capacity;
}Vector;
3.3头文件保护
// 头文件vector.h中
#ifndef VECTOR_H
#define VECTOR_H
// 头文件中定义的函数的声明、结构体的定义、类型别名的定义等
#endif // !VECTOR_H
4.二级指针的调用
二级指针(指向指针的指针)通常用于修改指针的指向,或者通过指针间接地访问或修改某些数据。在函数中使用二级指针时,你可以通过传递指向指针的指针来修改指针的值,从而影响到调用者函数中原始指针的值。
下面是一个示例,演示了如何在函数中使用二级指针:
#include <stdio.h>
// 函数声明,接受一个二级指针作为参数,将其指向的指针修改为指向另一个值
void changePointer(int **ptr) {
int newVal = 100;
*ptr = &newVal; // 修改二级指针所指向的指针的值
}
int main() {
int val = 10;
int *ptr = &val;
printf("原始指针的值:%d\n", *ptr);
// 调用函数,传递指向指针的指针
changePointer(&ptr);
printf("修改后的指针的值:%d\n", *ptr);
return 0;
}
在这个示例中,changePointer 函数接受一个二级指针作为参数,它将该二级指针所指向的指针修改为指向一个新的局部变量 newVal。在 main 函数中,我们声明了一个指针 ptr 并将其指向 val。然后,我们调用 changePointer 函数并传递 ptr 的地址,这样 changePointer 函数可以通过二级指针 ptr 修改 ptr 的值。最后,我们打印了 ptr 所指向的值,可以看到它已经被修改为 newVal。
需要注意的是,在实际开发中,使用二级指针时要小心内存管理,确保指针指向的内存空间在修改后仍然有效。
5.使用函数指针来将函数作为参数传递给其他函数
以下是一个简单的示例,演示了如何将函数作为参数传递:
#include <stdio.h>
// 定义一个函数类型,它接受两个int类型参数并返回int类型结果
typedef int (*Operation)(int, int);
// 加法函数
int add(int a, int b) {
return a + b;
}
// 减法函数
int subtract(int a, int b) {
return a - b;
}
// 执行操作函数,接受两个操作数和一个函数指针,将操作应用于这两个操作数
int executeOperation(int a, int b, Operation op) {
return op(a, b);
}
int main() {
int a = 10, b = 5;
// 将add函数作为参数传递给executeOperation函数
int result1 = executeOperation(a, b, add);
printf("加法结果:%d\n", result1);
// 将subtract函数作为参数传递给executeOperation函数
int result2 = executeOperation(a, b, subtract);
printf("减法结果:%d\n", result2);
return 0;
}
在这个示例中,我们首先定义了一个函数类型 Operation,它接受两个 int 类型的参数并返回一个 int 类型的结果。然后,我们定义了两个函数 add 和 subtract,它们符合这个函数类型的定义。
接着,我们定义了一个 executeOperation 函数,它接受两个操作数和一个函数指针作为参数,将操作应用于这两个操作数。在 main 函数中,我们调用 executeOperation 函数两次,分别传递 add 和 subtract 函数作为参数。
通过函数指针,我们可以在不同的上下文中动态地改变函数的行为,使得代码更加灵活和可复用。
小结
理解困难,请回顾总结虚拟内存空间的概念,三种主要的存储期限,栈区(堆区)的内存管理及其特点