Appearance
结构体
结构体是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) = 12struct 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语言中一种强大的用户自定义数据类型,它允许我们将多个不同类型的变量组合在一起,形成一个新的复合数据类型。在本章节中,我们学习了:
- 结构体的定义:使用
struct关键字定义结构体类型 - 结构体变量的声明和初始化:创建结构体类型的变量并赋值
- 访问结构体成员:使用点运算符(
.)访问结构体成员 - 结构体指针:使用箭头运算符(
->)访问结构体指针指向的成员 - 结构体数组:存储多个结构体变量的数组
- 结构体与函数:结构体作为函数参数和返回值
- typedef与结构体:为结构体类型创建别名
- 嵌套结构体:在结构体中包含其他结构体作为成员
- 内存布局:结构体的内存对齐和大小计算
- 动态内存分配:使用
malloc和free管理结构体的内存 - 应用场景:表示复杂数据、数据传递、实现抽象数据类型等
结构体是C语言中实现数据封装和抽象的重要工具,通过合理使用结构体,可以编写出更加模块化、可维护的C程序。在实际编程中,结构体常与指针、数组、函数等结合使用,形成更加复杂的数据结构和算法。