【c语言指针详解】复杂数据结构的指针用法

简介: 【c语言指针详解】复杂数据结构的指针用法


一、动态内存分配

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 内存泄漏和野指针的概念和解决方法

内存泄漏指的是分配的内存空间在不再使用时没有被释放,导致该内存无法被重新分配使用,并且随着程序运行时间的增长,已分配但未释放的内存会不断增加,最终导致程序崩溃。

可以使用 mallocfree 函数进行内存管理,为避免内存泄漏,释放内存的方法是在不再需要内存时调用 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 类型的指针。我们将 num1num2num3 的地址依次赋给指针数组的元素,然后通过指针数组访问并打印对应的值。

2.2.2 指向指针的指针

指向指针的指针是一个指针,它存储了指针的地址。通过指向指针的指针可以间接地访问并修改指针所指向的变量。

示例代码:

#include <stdio.h>
int main() {
    int num = 10;
    int* ptr = &num;
    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 来访问和操作每个结构体对象的成员。通过输入每个学生的信息并输出学生信息,展示了动态分配内存和结构体指针的结合使用的一个简单例子。

目录
相关文章
|
14天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
90 9
|
28天前
|
存储 算法 C语言
通义灵码在考研C语言和数据结构中的应用实践 1-5
通义灵码在考研C语言和数据结构中的应用实践,体验通义灵码的强大思路。《趣学C语言和数据结构100例》精选了五个经典问题及其解决方案,包括求最大公约数和最小公倍数、统计字符类型、求特殊数列和、计算阶乘和双阶乘、以及求斐波那契数列的前20项和。通过这些实例,帮助读者掌握C语言的基本语法和常用算法,提升编程能力。
|
24天前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
45 0
|
13天前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
56 16
|
13天前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
62 8
|
15天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
43 4
|
17天前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
46 3
|
17天前
|
存储 算法 C语言
C语言数据结构(2)
【10月更文挑战第21天】
|
22天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
15 2
|
23天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。