第七章:结构体与复合数据

目录

一、引言

二、结构体基础

2.1 结构体定义

2.2 结构体变量的声明与初始化

三、结构体对齐

3.1 结构体对齐的概念

3.2 影响结构体对齐的因素

四、链表实现

4.1 链表的基本概念

4.2 单向链表的实现

五、学生管理系统升级版实现

5.1 系统需求升级


一、引言

在 C 语言编程中,处理复杂数据时,简单的数据类型往往捉襟见肘。结构体作为一种复合数据类型,允许将不同类型的数据组合在一起,形成一个有机的整体。而链表作为一种基于结构体的动态数据结构,能有效解决数组在数据存储和管理上的局限性。本章将深入探讨结构体的定义、使用,特别是结构体对齐这一关键概念,同时详细介绍链表的实现,最后通过打造一个升级版的学生管理系统,综合运用这些知识,提升程序的灵活性与实用性。

二、结构体基础

2.1 结构体定义

结构体定义了一种新的数据类型,它将多个不同类型的变量组织在一起。其定义格式如下:

struct structure_name {
    data_type member1;
    data_type member2;
    // 更多成员
};

例如,定义一个表示学生信息的结构体:

#include 

struct Student {
    char name[50];
    int age;
    float grade;
};

 在上述代码中,struct Student 定义了一个新的数据类型,包含 name(字符数组)- 名字、age(整型)- 年龄 和 grade(浮点型)- 评分三个成员。

2.2 结构体变量的声明与初始化

定义结构体后,就可以声明结构体变量并进行初始化。

int main() {
    // 声明结构体变量
    struct Student student1;
    // 初始化结构体变量
    strcpy(student1.name, "Alice");
    student1.age = 20;
    student1.grade = 3.5;

    printf("Student Name: %s\n", student1.name);
    printf("Student Age: %d\n", student1.age);
    printf("Student Grade: %.2f\n", student1.grade);
    return 0;
}

 当然也可以在声明变量时直接初始化:

struct Student student2 = {"Bob", 21, 3.8};

三、结构体对齐

3.1 结构体对齐的概念

结构体对齐是指编译器为结构体成员分配内存时,会在成员之间插入一些填充字节,以确保每个成员都位于特定的内存地址边界上。这主要是为了提高内存访问效率,因为现代计算机硬件在访问特定对齐地址的数据时性能更高。

3.2 影响结构体对齐的因素

  1. 编译器:不同的编译器对结构体对齐的处理可能略有不同。例如,GCC 编译器默认按照 4 字节对齐(对于 32 位系统)或 8 字节对齐(对于 64 位系统),但可以通过编译选项进行调整。
  2. 成员类型:结构体中不同类型的成员,其对齐要求不同。例如,在 32 位系统中,char类型通常按 1 字节对齐,int类型按 4 字节对齐,double类型按 8 字节对齐。
  3. 结构体整体大小:结构体的总大小通常是其最大对齐成员大小的整数倍。例如,若一个结构体中最大对齐成员是double(8 字节对齐),则结构体的总大小会是 8 的整数倍。

 3.3 示例代码分析

#include 

struct Example1 {
    char c;
    int i;
    double d;
};

struct Example2 {
    int i;
    char c;
    double d;
};

int main() {
    printf("Size of Example1: %zu\n", sizeof(struct Example1));
    printf("Size of Example2: %zu\n", sizeof(struct Example2));
    return 0;
}

运行结果:


 在上述代码中,struct Example1 中 char 占 1 字节,int 占 4 字节,double 占 8 字节。由于 int 按 4 字节对齐,char 后会填充 3 字节,int 与 double 之间填充 4 字节,所以 struct Example1 的总大小为 16 字节。而 struct Example2 中 int 先占 4 字节,char 占 1 字节后填充 7 字节,所以总大小为 24 字节。 

四、链表实现

4.1 链表的基本概念

链表是一种动态数据结构,由一系列节点组成。每个节点包含两部分:数据部分和指针部分。指针部分指向下一个节点,通过这种方式将所有节点连接成一个链式结构。链表分为单向链表、双向链表和循环链表,这里主要介绍单向链表。

4.2 单向链表的实现

        1.定义链表节点

struct ListNode {
    int data;
    struct ListNode *next;
};

在这个结构体定义中,我们定义了一个链表节点。每个节点包含两部分:

  • int data:存储节点的数据。
  • struct ListNode *next:指向下一个节点的指针。

        2.创建新节点

struct ListNode* createNode(int value) {
    struct ListNode *newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
    newNode->data = value;
    newNode->next = NULL;
    return newNode;
}

这个函数用于创建一个新的链表节点:

  • 使用 malloc 分配内存空间,大小为 struct ListNode 结构体的大小。
  • 将新节点的数据部分设置为传入的 value
  • 将新节点的指针部分设置为 NULL,表示它目前没有指向任何其他节点。
  • 返回新创建的节点指针。

        3.插入节点到链表头部

struct ListNode* insertAtHead(struct ListNode *head, int value) {
    struct ListNode *newNode = createNode(value);
    newNode->next = head;
    return newNode;
}

 这个函数用于在链表的头部插入一个新节点:

  • 调用 createNode 函数创建一个新节点。
  • 将新节点的 next 指针指向当前的头节点 head
  • 返回新节点的指针,这个新节点现在成为了新的头节点。

        4.遍历链表

void traverseList(struct ListNode *head) {
    struct ListNode *current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

 这个函数用于遍历链表并打印每个节点的数据:

  • 使用一个临时指针 current 来遍历链表,初始时指向头节点 head
  • 在 while 循环中,打印当前节点的数据,然后将 current 移动到下一个节点。
  • 当 current 为 NULL 时,表示已经到达链表的末尾,此时打印 "NULL" 并结束循环

        5.完整示例

#include 
#include 

struct ListNode {
    int data;
    struct ListNode *next;
};

struct ListNode* createNode(int value) {
    struct ListNode *newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
    newNode->data = value;
    newNode->next = NULL;
    return newNode;
}

struct ListNode* insertAtHead(struct ListNode *head, int value) {
    struct ListNode *newNode = createNode(value);
    newNode->next = head;
    return newNode;
}

void traverseList(struct ListNode *head) {
    struct ListNode *current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

int main() {
    struct ListNode *head = NULL;
    head = insertAtHead(head, 3);
    head = insertAtHead(head, 2);
    head = insertAtHead(head, 1);

    traverseList(head);
    return 0;
}

运行结果:


在 main 函数中:

  • 初始化头节点指针 head 为 NULL,表示链表为空。
  • 调用 insertAtHead 函数三次,在链表头部依次插入数据 32 和 1。插入后,链表顺序为 1 -> 2 -> 3
  • 调用 traverseList 函数遍历并打印链表。

 

五、学生管理系统升级版实现

5.1 系统需求升级

在之前学生成绩管理系统的基础上,增加以下功能:

  1. 支持动态添加和删除学生信息。
  2. 能够按照学生成绩进行排序(使用链表实现)。
  3. 优化内存使用,避免内存泄漏。
#include 
#include 
#include 

#define MAX_NAME_LEN 20

struct Student {
    char name[MAX_NAME_LEN];
    int id;
    float score;
};

// 添加学生到数组
void addStudent(struct Student *stuList, int *count) {
    if (*count >= 100) {
        printf("人数已满!\n");
        return;
    }
    printf("输入姓名 学号 分数:");
    scanf("%s %d %f", stuList[*count].name, &stuList[*count].id, &stuList[*count].score);
    (*count)++;
}

// 按学号查找学生
void findStudent(struct Student *stuList, int count, int targetId) {
    for (int i=0; i

优化预告:下一章将结合文件操作实现数据持久化存储! 


  1. 挑战任务
    实现链表删除指定值的节点(考虑头节点、中间节点、尾节点情况),并处理空链表和值不存在的场景。


下一篇预告:第八章《文件操作》——完成学生管理系统数据存储,实现跨程序数据持久化!


 投票题目:

以下结构体的总大小是多少?(32位系统,默认对齐)  
struct Data {  
    char c;  
    int i;  
    short s;  
};

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(C/C++,c语言,改行学it,开发语言,算法)