Skip to content

结构体

结构体是C语言中一种重要的用户自定义数据类型,它允许我们将不同类型的数据组合在一起,形成一个新的复合数据类型。结构体在C语言中被广泛用于表示具有多个属性的对象,如学生、员工、汽车等。在本章节中,我们将学习C语言中结构体的定义、使用和应用场景。

1. 什么是结构体?

结构体是一种用户自定义的数据类型,它可以包含多个不同类型的成员变量(也称为字段)。结构体的成员可以是基本数据类型(如int、float、char等),也可以是其他复合数据类型(如数组、指针、甚至是其他结构体)。

例如,我们可以定义一个表示学生的结构体,包含姓名、年龄和成绩等成员:

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

2. 结构体的定义

定义结构体就是创建一个新的结构体类型,指定其名称和包含的成员。结构体定义的基本语法如下:

c
struct 结构体名称 {
    数据类型 成员1;
    数据类型 成员2;
    // 更多成员...
};

2.1 说明

  • struct:结构体定义的关键字
  • 结构体名称:结构体的类型名,遵循标识符命名规则
  • 成员:结构体包含的变量,可以是不同的数据类型
  • 分号:结构体定义结束时的分号是必需的

2.2 示例

c
// 定义一个表示点的结构体
struct Point {
    int x;
    int y;
};

// 定义一个表示矩形的结构体
struct Rectangle {
    struct Point top_left;
    struct Point bottom_right;
    int width;
    int height;
};

// 定义一个表示日期的结构体
struct Date {
    int year;
    int month;
    int day;
};

// 定义一个表示员工的结构体
struct Employee {
    char name[50];
    int id;
    float salary;
    struct Date hire_date;
};

3. 结构体变量的声明和初始化

3.1 声明结构体变量

声明结构体变量就是创建一个结构体类型的变量。可以在定义结构体的同时声明变量,也可以在定义结构体后单独声明变量。

3.2 示例

c
#include <stdio.h>

// 定义一个表示学生的结构体
struct Student {
    char name[50];
    int age;
    float score;
};

int main() {
    // 方法1:在定义结构体的同时声明变量
    struct Student student1;
    
    // 方法2:在定义结构体后单独声明变量
    struct Student student2, student3;
    
    return 0;
}

3.3 初始化结构体变量

初始化结构体变量就是给结构体的成员赋初始值。可以在声明时初始化,也可以在声明后逐个成员赋值。

3.4 示例

c
#include <stdio.h>
#include <string.h>

// 定义一个表示学生的结构体
struct Student {
    char name[50];
    int age;
    float score;
};

int main() {
    // 方法1:在声明时初始化
    struct Student student1 = {"张三", 20, 85.5};
    
    // 方法2:在声明后逐个成员赋值
    struct Student student2;
    strcpy(student2.name, "李四");
    student2.age = 21;
    student2.score = 90.0;
    
    // 方法3:使用指定初始化器(C99及以上)
    struct Student student3 = {
        .name = "王五",
        .age = 19,
        .score = 88.5
    };
    
    // 打印学生信息
    printf("学生1: 姓名=%s, 年龄=%d, 成绩=%.1f\n", 
           student1.name, student1.age, student1.score);
    printf("学生2: 姓名=%s, 年龄=%d, 成绩=%.1f\n", 
           student2.name, student2.age, student2.score);
    printf("学生3: 姓名=%s, 年龄=%d, 成绩=%.1f\n", 
           student3.name, student3.age, student3.score);
    
    return 0;
}

4. 访问结构体成员

访问结构体成员就是获取或修改结构体变量的某个成员的值。在C语言中,使用点运算符(.)来访问结构体成员。

4.1 语法

c
结构体变量名.成员名

4.2 示例

c
#include <stdio.h>
#include <string.h>

// 定义一个表示学生的结构体
struct Student {
    char name[50];
    int age;
    float score;
};

int main() {
    // 声明并初始化结构体变量
    struct Student student = {"张三", 20, 85.5};
    
    // 访问并打印结构体成员
    printf("姓名: %s\n", student.name);  // 张三
    printf("年龄: %d\n", student.age);  // 20
    printf("成绩: %.1f\n", student.score);  // 85.5
    
    // 修改结构体成员
    strcpy(student.name, "张三(修改后)");
    student.age = 21;
    student.score = 90.0;
    
    // 再次打印
    printf("\n修改后:\n");
    printf("姓名: %s\n", student.name);  // 张三(修改后)
    printf("年龄: %d\n", student.age);  // 21
    printf("成绩: %.1f\n", student.score);  // 90.0
    
    return 0;
}

5. 结构体指针

结构体指针是指向结构体变量的指针,它存储结构体变量的内存地址。使用结构体指针可以更高效地访问和修改结构体成员,特别是在函数参数传递时。

5.1 声明结构体指针

c
struct 结构体名称 *指针变量名;

5.2 访问结构体指针的成员

使用箭头运算符(->)来访问结构体指针指向的结构体成员。

c
结构体指针->成员名

5.3 示例

c
#include <stdio.h>
#include <string.h>

// 定义一个表示学生的结构体
struct Student {
    char name[50];
    int age;
    float score;
};

int main() {
    // 声明结构体变量
    struct Student student = {"张三", 20, 85.5};
    
    // 声明结构体指针并指向结构体变量
    struct Student *p = &student;
    
    // 使用箭头运算符访问成员
    printf("姓名: %s\n", p->name);  // 张三
    printf("年龄: %d\n", p->age);  // 20
    printf("成绩: %.1f\n", p->score);  // 85.5
    
    // 使用指针修改成员
    strcpy(p->name, "张三(修改后)");
    p->age = 21;
    p->score = 90.0;
    
    // 再次打印
    printf("\n修改后:\n");
    printf("姓名: %s\n", p->name);  // 张三(修改后)
    printf("年龄: %d\n", p->age);  // 21
    printf("成绩: %.1f\n", p->score);  // 90.0
    
    // 也可以使用解引用运算符和点运算符
    printf("\n使用解引用运算符:\n");
    printf("姓名: %s\n", (*p).name);  // 张三(修改后)
    printf("年龄: %d\n", (*p).age);  // 21
    printf("成绩: %.1f\n", (*p).score);  // 90.0
    
    return 0;
}

6. 结构体数组

结构体数组是存储多个结构体变量的数组,每个元素都是一个结构体变量。

6.1 声明结构体数组

c
struct 结构体名称 数组名[数组大小];

6.2 示例

c
#include <stdio.h>

// 定义一个表示学生的结构体
struct Student {
    char name[50];
    int age;
    float score;
};

int main() {
    // 声明并初始化结构体数组
    struct Student students[] = {
        {"张三", 20, 85.5},
        {"李四", 21, 90.0},
        {"王五", 19, 88.5}
    };
    
    // 计算数组大小
    int size = sizeof(students) / sizeof(students[0]);
    
    // 遍历结构体数组
    for (int i = 0; i < size; i++) {
        printf("学生%d: 姓名=%s, 年龄=%d, 成绩=%.1f\n", 
               i + 1, students[i].name, students[i].age, students[i].score);
    }
    
    // 修改结构体数组元素
    strcpy(students[0].name, "张三(修改后)");
    students[0].score = 92.0;
    
    // 再次打印
    printf("\n修改后:\n");
    for (int i = 0; i < size; i++) {
        printf("学生%d: 姓名=%s, 年龄=%d, 成绩=%.1f\n", 
               i + 1, students[i].name, students[i].age, students[i].score);
    }
    
    return 0;
}

7. 结构体与函数

7.1 结构体作为函数参数

结构体可以作为函数的参数传递,有两种传递方式:值传递地址传递

7.1.1 值传递

c
#include <stdio.h>

// 定义一个表示点的结构体
struct Point {
    int x;
    int y;
};

// 结构体作为函数参数(值传递)
void print_point(struct Point p) {
    printf("点的坐标: (%d, %d)\n", p.x, p.y);
}

// 修改结构体(值传递,不会影响原结构体)
void move_point(struct Point p, int dx, int dy) {
    p.x += dx;
    p.y += dy;
    printf("移动后的点: (%d, %d)\n", p.x, p.y);
}

int main() {
    struct Point p = {10, 20};
    
    printf("原始点: (%d, %d)\n", p.x, p.y);  // (10, 20)
    
    print_point(p);  // 点的坐标: (10, 20)
    
    move_point(p, 5, 5);  // 移动后的点: (15, 25)
    
    printf("调用函数后原始点: (%d, %d)\n", p.x, p.y);  // (10, 20),未被修改
    
    return 0;
}

7.1.2 地址传递

c
#include <stdio.h>

// 定义一个表示点的结构体
struct Point {
    int x;
    int y;
};

// 结构体指针作为函数参数(地址传递)
void print_point(struct Point *p) {
    printf("点的坐标: (%d, %d)\n", p->x, p->y);
}

// 修改结构体(地址传递,会影响原结构体)
void move_point(struct Point *p, int dx, int dy) {
    p->x += dx;
    p->y += dy;
    printf("移动后的点: (%d, %d)\n", p->x, p->y);
}

int main() {
    struct Point p = {10, 20};
    
    printf("原始点: (%d, %d)\n", p.x, p.y);  // (10, 20)
    
    print_point(&p);  // 点的坐标: (10, 20)
    
    move_point(&p, 5, 5);  // 移动后的点: (15, 25)
    
    printf("调用函数后原始点: (%d, %d)\n", p.x, p.y);  // (15, 25),已被修改
    
    return 0;
}

7.2 结构体作为函数返回值

函数可以返回结构体类型的值,也可以返回结构体指针。

c
#include <stdio.h>

// 定义一个表示点的结构体
struct Point {
    int x;
    int y;
};

// 返回结构体
struct Point create_point(int x, int y) {
    struct Point p;
    p.x = x;
    p.y = y;
    return p;
}

// 返回结构体指针
struct Point *create_point_ptr(int x, int y) {
    static struct Point p;  // 使用static存储类,避免返回局部变量的地址
    p.x = x;
    p.y = y;
    return &p;
}

int main() {
    // 调用返回结构体的函数
    struct Point p1 = create_point(10, 20);
    printf("p1: (%d, %d)\n", p1.x, p1.y);  // (10, 20)
    
    // 调用返回结构体指针的函数
    struct Point *p2 = create_point_ptr(30, 40);
    printf("p2: (%d, %d)\n", p2->x, p2->y);  // (30, 40)
    
    return 0;
}

8. typedef与结构体

使用typedef可以为结构体类型创建别名,使代码更加简洁易读。

8.1 语法

c
typedef struct {
    数据类型 成员1;
    数据类型 成员2;
    // 更多成员...
} 类型别名;

或者

c
typedef struct 结构体名称 {
    数据类型 成员1;
    数据类型 成员2;
    // 更多成员...
} 类型别名;

8.2 示例

c
#include <stdio.h>

// 使用typedef为结构体创建别名
typedef struct {
    char name[50];
    int age;
    float score;
} Student;

// 使用typedef为结构体创建别名,同时保留结构体名称
typedef struct Point {
    int x;
    int y;
} Point;

int main() {
    // 使用别名声明结构体变量
    Student student = {"张三", 20, 85.5};
    Point p = {10, 20};
    
    // 打印
    printf("学生: 姓名=%s, 年龄=%d, 成绩=%.1f\n", 
           student.name, student.age, student.score);
    printf("点: (%d, %d)\n", p.x, p.y);
    
    return 0;
}

9. 嵌套结构体

嵌套结构体是指在一个结构体中包含另一个结构体作为成员。嵌套结构体可以用来表示更复杂的数据结构。

9.1 示例

c
#include <stdio.h>

// 定义一个表示日期的结构体
typedef struct {
    int year;
    int month;
    int day;
} Date;

// 定义一个表示时间的结构体
typedef struct {
    int hour;
    int minute;
    int second;
} Time;

// 定义一个表示日期时间的结构体
typedef struct {
    Date date;
    Time time;
} DateTime;

// 定义一个表示人员的结构体
typedef struct {
    char name[50];
    int id;
    Date birth_date;
    DateTime registration_time;
} Person;

int main() {
    // 初始化嵌套结构体
    Person person = {
        "张三",
        1001,
        {1990, 1, 1},
        {
            {2023, 10, 1},
            {10, 30, 0}
        }
    };
    
    // 访问嵌套结构体成员
    printf("姓名: %s\n", person.name);
    printf("ID: %d\n", person.id);
    printf("出生日期: %d-%d-%d\n", 
           person.birth_date.year, 
           person.birth_date.month, 
           person.birth_date.day);
    printf("注册时间: %d-%d-%d %d:%d:%d\n", 
           person.registration_time.date.year, 
           person.registration_time.date.month, 
           person.registration_time.date.day, 
           person.registration_time.time.hour, 
           person.registration_time.time.minute, 
           person.registration_time.time.second);
    
    return 0;
}

10. 结构体的内存布局

10.1 结构体的大小

结构体的大小不等于其所有成员大小的简单相加,这是因为编译器为了对齐内存地址,会在成员之间添加填充字节。内存对齐可以提高内存访问效率。

10.2 示例

c
#include <stdio.h>

// 定义结构体
struct Test1 {
    char c;  // 1字节
    int i;   // 4字节
    char d;  // 1字节
};

struct Test2 {
    int i;   // 4字节
    char c;  // 1字节
    char d;  // 1字节
};

int main() {
    printf("sizeof(char): %zu\n", sizeof(char));  // 1
    printf("sizeof(int): %zu\n", sizeof(int));    // 4
    printf("sizeof(struct Test1): %zu\n", sizeof(struct Test1));  // 12
    printf("sizeof(struct Test2): %zu\n", sizeof(struct Test2));  // 8
    
    return 0;
}

说明

  • struct Test1的大小为12字节:char(1) + 填充(3) + int(4) + char(1) + 填充(3) = 12
  • struct Test2的大小为8字节:int(4) + char(1) + char(1) + 填充(2) = 8

10.3 内存对齐的影响

内存对齐可以提高内存访问速度,但会增加内存使用量。在嵌入式系统等内存受限的环境中,可以使用#pragma pack指令来调整内存对齐方式。

c
#include <stdio.h>

// 设置对齐方式为1字节
#pragma pack(1)

struct Test {
    char c;  // 1字节
    int i;   // 4字节
    char d;  // 1字节
};

// 恢复默认对齐方式
#pragma pack()

int main() {
    printf("sizeof(struct Test): %zu\n", sizeof(struct Test));  // 6
    
    return 0;
}

11. 动态内存分配与结构体

11.1 使用malloc分配内存

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义一个表示学生的结构体
typedef struct {
    char name[50];
    int age;
    float score;
} Student;

int main() {
    // 分配单个结构体的内存
    Student *student = (Student *)malloc(sizeof(Student));
    if (student == NULL) {
        printf("内存分配失败\n");
        return 1;
    }
    
    // 初始化结构体
    strcpy(student->name, "张三");
    student->age = 20;
    student->score = 85.5;
    
    // 打印
    printf("学生: 姓名=%s, 年龄=%d, 成绩=%.1f\n", 
           student->name, student->age, student->score);
    
    // 分配结构体数组的内存
    int size = 3;
    Student *students = (Student *)malloc(size * sizeof(Student));
    if (students == NULL) {
        printf("内存分配失败\n");
        free(student);
        return 1;
    }
    
    // 初始化结构体数组
    for (int i = 0; i < size; i++) {
        sprintf(students[i].name, "学生%d", i + 1);
        students[i].age = 20 + i;
        students[i].score = 85.0 + i * 2.5;
    }
    
    // 打印结构体数组
    printf("\n学生列表:\n");
    for (int i = 0; i < size; i++) {
        printf("学生%d: 姓名=%s, 年龄=%d, 成绩=%.1f\n", 
               i + 1, students[i].name, students[i].age, students[i].score);
    }
    
    // 释放内存
    free(student);
    free(students);
    
    return 0;
}

12. 结构体的应用场景

结构体在C语言中有广泛的应用场景:

12.1 表示复杂数据

c
// 表示图形
struct Circle {
    struct Point center;
    float radius;
};

// 表示文件信息
struct FileInfo {
    char name[256];
    long size;
    time_t modification_time;
    int permissions;
};

// 表示网络地址
struct SocketAddress {
    char ip[16];
    int port;
};

12.2 数据传递

c
// 传递多个参数
void process_user_data(struct User user) {
    // 处理用户数据
}

// 返回多个值
struct Result calculate(int a, int b) {
    struct Result result;
    result.sum = a + b;
    result.difference = a - b;
    result.product = a * b;
    result.quotient = (float)a / b;
    return result;
}

12.3 实现抽象数据类型

c
// 栈的实现
typedef struct {
    int *data;
    int top;
    int capacity;
} Stack;

// 队列的实现
typedef struct {
    int *data;
    int front;
    int rear;
    int capacity;
} Queue;

13. 示例:综合运用

让我们看一个综合运用结构体的例子:

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义一个表示图书的结构体
typedef struct {
    char title[100];
    char author[50];
    int year;
    float price;
} Book;

// 打印图书信息
void print_book(Book *book) {
    printf("书名: %s\n", book->title);
    printf("作者: %s\n", book->author);
    printf("出版年份: %d\n", book->year);
    printf("价格: %.2f\n\n", book->price);
}

// 按价格排序图书
void sort_books_by_price(Book *books, int count) {
    for (int i = 0; i < count - 1; i++) {
        for (int j = 0; j < count - i - 1; j++) {
            if (books[j].price > books[j + 1].price) {
                // 交换图书
                Book temp = books[j];
                books[j] = books[j + 1];
                books[j + 1] = temp;
            }
        }
    }
}

// 查找指定作者的图书
void find_books_by_author(Book *books, int count, char *author) {
    printf("查找作者 '%s' 的图书:\n\n", author);
    int found = 0;
    
    for (int i = 0; i < count; i++) {
        if (strcmp(books[i].author, author) == 0) {
            print_book(&books[i]);
            found = 1;
        }
    }
    
    if (!found) {
        printf("未找到作者 '%s' 的图书\n\n", author);
    }
}

int main() {
    // 初始化图书数组
    Book books[] = {
        {"C语言程序设计", "谭浩强", 2017, 39.9},
        {"深入理解计算机系统", "Randal E. Bryant", 2016, 128.0},
        {"算法导论", "Thomas H. Cormen", 2013, 128.0},
        {"C Primer Plus", "Stephen Prata", 2016, 89.0},
        {"数据结构与算法分析", "Mark Allen Weiss", 2014, 79.0}
    };
    
    int count = sizeof(books) / sizeof(books[0]);
    
    // 打印所有图书
    printf("所有图书:\n\n");
    for (int i = 0; i < count; i++) {
        print_book(&books[i]);
    }
    
    // 按价格排序
    sort_books_by_price(books, count);
    
    // 打印排序后的图书
    printf("按价格排序后的图书:\n\n");
    for (int i = 0; i < count; i++) {
        print_book(&books[i]);
    }
    
    // 查找图书
    find_books_by_author(books, count, "谭浩强");
    find_books_by_author(books, count, "Unknown");
    
    return 0;
}

14. 小结

结构体是C语言中一种强大的用户自定义数据类型,它允许我们将多个不同类型的变量组合在一起,形成一个新的复合数据类型。在本章节中,我们学习了:

  1. 结构体的定义:使用struct关键字定义结构体类型
  2. 结构体变量的声明和初始化:创建结构体类型的变量并赋值
  3. 访问结构体成员:使用点运算符(.)访问结构体成员
  4. 结构体指针:使用箭头运算符(->)访问结构体指针指向的成员
  5. 结构体数组:存储多个结构体变量的数组
  6. 结构体与函数:结构体作为函数参数和返回值
  7. typedef与结构体:为结构体类型创建别名
  8. 嵌套结构体:在结构体中包含其他结构体作为成员
  9. 内存布局:结构体的内存对齐和大小计算
  10. 动态内存分配:使用mallocfree管理结构体的内存
  11. 应用场景:表示复杂数据、数据传递、实现抽象数据类型等

结构体是C语言中实现数据封装和抽象的重要工具,通过合理使用结构体,可以编写出更加模块化、可维护的C程序。在实际编程中,结构体常与指针、数组、函数等结合使用,形成更加复杂的数据结构和算法。