一、动态内存分配
1.1 使用malloc和free函数进行内存的动态分配和释放
malloc
函数用于在运行时动态分配内存。它接受一个参数,表示需要分配的内存大小(以字节为单位),并返回一个指向分配内存的指针。如果分配成功,则返回的指针指向一块连续的、未初始化的内存区域;如果分配失败,则返回一个特殊的空指针(NULL)。
free
函数用于释放之前通过 malloc
或者类似函数动态分配的内存。它接受一个参数,表示需要释放的内存区域的起始地址。调用 free
函数将释放指定内存区域,这样释放的内存可以被重新分配给其他部分。
示例代码:
#include <stdio.h> #include <stdlib.h> int main() { int* ptr; // 动态分配内存 ptr = (int*)malloc(5 * sizeof(int)); if (ptr == NULL) { printf("内存分配失败\n"); exit(1); // 终止程序 } // 使用分配的内存 for (int i = 0; i < 5; i++) { ptr[i] = i + 1; } // 打印数组的值 for (int i = 0; i < 5; i++) { printf("%d ", ptr[i]); } printf("\n"); // 释放内存 free(ptr); return 0; }
输出结果如下:
上面的代码首先使用 malloc
函数动态分配了一个数组,然后使用循环给数组赋值。最后打印数组的值,并使用 free
函数释放了之前分配的内存空间。
1.2 内存泄漏和野指针的概念和解决方法
内存泄漏指的是分配的内存空间在不再使用时没有被释放,导致该内存无法被重新分配使用,并且随着程序运行时间的增长,已分配但未释放的内存会不断增加,最终导致程序崩溃。
可以使用 malloc
和 free
函数进行内存管理,为避免内存泄漏,释放内存的方法是在不再需要内存时调用 free
函数将其释放。
野指针则是指指向已释放或未分配内存空间的指针。
野指针的出现通常是由于对已释放的内存空间进行操作,或者未初始化指针的值导致指针指向未知的内存空间。使用野指针可能导致程序崩溃或者产生不可预测的结果。为避免野指针,应该在使用指针之前对其进行初始化,并在释放内存之后将指针置为 NULL
。
示例代码:
#include <stdio.h> #include <stdlib.h> int main() { // 内存泄漏 int* ptr = (int*)malloc(sizeof(int)); if (ptr != NULL) { *ptr = 10; } // 需要释放内存 free(ptr); // 野指针 int* ptr2 = NULL; ptr2 = (int*) malloc( sizeof(int) ); if ( ptr2 != NULL ) { *ptr2 = 20; // 在释放完内存后,应该将指针赋值为 NULL,避免出现野指针问题 free(ptr2); ptr2 = NULL; } return 0; }
二、复杂数据结构的指针用法
2.1 结构体指针和成员访问操作符
在 C 语言中,结构体是一种自定义数据类型,可以将不同类型的数据组合在一起,形成一个整体的数据类型。
结构体指针可以指向结构体变量,也可以指向动态分配的结构体内存空间,使用结构体指针可以方便地对结构体成员进行操作。
成员访问操作符有两种,一种是点操作符号 .
,另一种是箭头操作符号 ->
。点操作符号用于访问结构体变量的成员,箭头操作符号则用于访问结构体指针指向的结构体变量的成员。
示例代码:
#include <stdio.h> #include <string.h> // 定义一个结构体类型 struct Student { char name[20]; int age; float score; }; int main() { // 定义一个结构体变量 struct Student stu1 = {"Tom", 18, 90.5}; // 定义一个结构体指针,指向结构体变量 struct Student *p = &stu1; // 使用成员访问操作符号点操作符访问结构体变量的成员 printf("%s %d %.2f\n", stu1.name, stu1.age, stu1.score); // 使用成员访问操作符号箭头操作符访问结构体指针指向的结构体变量的成员 printf("%s %d %.2f\n", p->name, p->age, p->score); // 修改结构体指针指向的结构体变量的成员 strcpy(p->name, "Jim"); p->age = 19; p->score = 88.5; // 使用成员访问操作符号点操作符访问结构体变量的成员 printf("%s %d %.2f\n", stu1.name, stu1.age, stu1.score); // 使用成员访问操作符号箭头操作符访问结构体指针指向的结构体变量的成员 printf("%s %d %.2f\n", p->name, p->age, p->score); return 0; }
输出结果如下:
在上面的代码中,我们首先定义了一个结构体类型 Student
,然后定义了一个 Student
类型的结构体变量 stu1
和一个指向 stu1
的结构体指针 p
。接着,我们使用点操作符号和箭头操作符号分别访问了结构体变量和结构体指针指向的结构体变量的成员,并修改了结构体指针指向的结构体变量的成员。
2.2 指针数组和指向指针的指针
2.2.1 指针数组
指针数组是一个数组,其元素都是指针类型。每个指针指向一个特定类型的对象或单元。可以通过索引来访问数组中的每个指针,并使用指针进一步操作对应的对象或单元。
示例代码:
#include <stdio.h> int main() { int num1 = 10, num2 = 20, num3 = 30; int* ptrArr[3]; // 声明一个指针数组 ptrArr[0] = &num1; // 指针数组的第一个元素指向 num1 ptrArr[1] = &num2; // 指针数组的第二个元素指向 num2 ptrArr[2] = &num3; // 指针数组的第三个元素指向 num3 for (int i = 0; i < 3; i++) { printf("Element %d: %d\n", i, *(ptrArr[i])); } return 0; }
输出结果如下:
在上述示例中,我们定义了一个指针数组 ptrArr
,它有 3 个元素,每个元素都是 int
类型的指针。我们将 num1
、num2
和 num3
的地址依次赋给指针数组的元素,然后通过指针数组访问并打印对应的值。
2.2.2 指向指针的指针
指向指针的指针是一个指针,它存储了指针的地址。通过指向指针的指针可以间接地访问并修改指针所指向的变量。
示例代码:
#include <stdio.h> int main() { int num = 10; int* ptr = # int** ptrPtr = &ptr; // 声明一个指向指针的指针 printf("Value of num: %d\n", num); printf("Value of *ptr: %d\n", *ptr); printf("Value of **ptrPtr: %d\n", **ptrPtr); return 0; }
输出结果如下:
在上述示例中,我们定义了一个指针 ptr
,它存储了 num
的地址。然后,我们定义了一个指向指针的指针 ptrPtr
,它存储了 ptr
的地址。通过 **ptrPtr
,我们可以间接地访问并输出 num
的值。
2.3 动态内存分配与结构体指针的结合使用
可以使用动态内存分配和结构体指针的结合使用,来动态创建和操作结构体对象。这种组合可以在运行时动态地分配内存空间以存储结构体对象,并使用结构体指针来访问和操作这些对象。
示例代码:
#include <stdio.h> #include <stdlib.h> typedef struct { int id; char name[20]; } Student; int main() { int numStudents; printf("Enter the number of students: "); scanf("%d", &numStudents); // 动态分配内存以存储指定数量的结构体对象 Student* students = (Student*)malloc(numStudents * sizeof(Student)); // 输入每个学生的信息 for (int i = 0; i < numStudents; i++) { printf("Enter information for student %d:\n", i + 1); printf("ID: "); scanf("%d", &(students[i].id)); printf("Name: "); scanf("%s", students[i].name); } // 输出每个学生的信息 printf("\nStudent Information:\n"); for (int i = 0; i < numStudents; i++) { printf("ID: %d, Name: %s\n", students[i].id, students[i].name); } // 释放动态分配的内存 free(students); return 0; }
在上述示例中,我们首先通过 malloc
函数动态分配了足够的内存空间来存储指定数量的 Student
结构体对象。然后,我们使用结构体指针 students
来访问和操作每个结构体对象的成员。通过输入每个学生的信息并输出学生信息,展示了动态分配内存和结构体指针的结合使用的一个简单例子。