第八章 结构体与共用体
第一节 结构体
概述
结构体(struct)是C语言中一种重要的数据结构,它允许程序员将不同类型的数据组合成一个整体,形成一个新的数据类型。结构体的引入极大地丰富了C语言的表达能力,使得对复杂数据的处理更加方便和高效。本节将全面讲解结构体的定义、使用及其内存布局,帮助考生系统掌握结构体的相关知识,为全国计算机等级考试二级C语言程序设计科目打下坚实基础。
学习目标:
- 理解结构体的基本概念和定义方法
- 掌握结构体变量的声明和访问方式
- 了解结构体内存分配及对齐原则
- 掌握结构体作为函数参数的传递方式
- 通过实例加深对结构体的应用理解
核心概念
结构体(struct):C语言中一种自定义的数据类型,用于将不同类型的变量组合成一个整体,形成一个复合数据类型。
成员(成员变量):结构体中包含的各个变量称为成员,每个成员可以是不同的数据类型。
结构体变量:根据结构体类型定义的变量,存储结构体中所有成员的数据。
结构体标签:结构体定义时用于标识该类型的名称,方便后续声明变量。
访问运算符(.):用于访问结构体变量中的成员。
typedef:用来给结构体类型定义新的别名,提高代码的简洁性。
内存对齐:为了提高访问效率,结构体成员在内存中通常按一定规则排列,可能会有填充字节。
原理分析
结构体的核心在于将多个不同类型的数据集合成一个整体,方便统一管理。它的定义类似于自定义类型,编译器会根据成员类型和顺序为结构体分配连续的内存空间。访问结构体成员时,编译器根据成员偏移量定位数据。
内存分配原则:
- 结构体成员按照声明顺序依次存储。
- 为了满足CPU对数据访问的对齐要求,成员之间可能插入填充字节。
- 结构体整体大小通常是最大成员对齐数的倍数。
结构体传参:
- 结构体变量可以作为函数参数传递,传递方式有值传递和指针传递。
- 值传递会复制整个结构体,开销较大。
- 指针传递传递地址,效率更高。
详细内容
1. 结构体的定义与声明
结构体的定义语法如下:
struct 结构体标签 {
成员类型 成员名1;
成员类型 成员名2;
...
};
- 结构体标签是结构体的名称,用于声明结构体变量。
- 成员可以是基本数据类型,也可以是数组、指针,甚至其他结构体类型。
示例:
struct Student {
char name[20];
int age;
float score;
};
声明结构体变量:
struct Student s1;
或者使用typedef简化:
typedef struct {
char name[20];
int age;
float score;
} Student;
Student s2;
2. 结构体变量的初始化与访问
结构体变量可以逐个成员赋值,也可以整体初始化:
struct Student s1 = {"张三", 20, 88.5};
访问成员使用.运算符:
printf("姓名:%s\n", s1.name);
printf("年龄:%d\n", s1.age);
指针访问结构体成员时使用->运算符:
struct Student *p = &s1;
printf("成绩:%.2f\n", p->score);
3. 结构体内存布局与对齐
结构体内存分配并非简单的成员顺序堆积,因为CPU对齐规则会导致填充字节出现。
内存对齐规则(通常情况):
- 基本类型在内存中的起始地址必须是其大小的整数倍。
- 结构体大小是最大成员大小的整数倍。
示例:
struct Example {
char a; // 1字节
int b; // 4字节
};
a占1字节,后面会有3字节填充,使得b从4的倍数地址开始,整体结构体大小为8字节。
理解内存对齐有助于编写高效代码和调试程序。
4. 结构体作为函数参数传递
结构体参数传递方式:
- 值传递:将整个结构体复制一份,传递给函数。适合小型结构体。
- 指针传递:传递结构体地址,函数通过指针访问成员。适合大型结构体,节省内存。
示例:
void printStudent(struct Student s) { ... } // 值传递
void printStudentPtr(struct Student *p) { ... } // 指针传递
指针传递常用于修改结构体内部数据。
5. 嵌套结构体及数组成员
结构体成员可以是其他结构体,支持复杂数据的层级组织。
示例:
struct Date {
int year;
int month;
int day;
};
struct Student {
char name[20];
struct Date birthday;
};
结构体也可以包含数组成员,用于存储多组数据。
实例分析
实例一:学生信息管理
背景:需要存储学生姓名、年龄和成绩。
struct Student {
char name[20];
int age;
float score;
};
int main() {
struct Student s = {"李华", 19, 92.5};
printf("学生姓名:%s\n", s.name);
printf("学生年龄:%d\n", s.age);
printf("学生成绩:%.1f\n", s.score);
return 0;
}
分析:结构体封装了多个数据,访问简洁明了。
结论:结构体方便管理相关数据,提高代码可读性。
实例二:函数中传递结构体指针
背景:写一个函数,修改学生成绩。
void updateScore(struct Student *p, float newScore) {
p->score = newScore;
}
int main() {
struct Student s = {"王强", 21, 85.0};
updateScore(&s, 90.0);
printf("更新后的成绩:%.1f\n", s.score);
return 0;
}
分析:通过指针传递结构体,函数内部直接修改原变量。
结论:指针传递提高效率,适合修改结构体内容。
实例三:嵌套结构体表示日期
背景:学生信息中包含出生日期。
struct Date {
int year;
int month;
int day;
};
struct Student {
char name[20];
struct Date birthday;
};
int main() {
struct Student s = {"赵敏", {2002, 5, 15}};
printf("姓名:%s,出生日期:%d-%d-%d\n", s.name, s.birthday.year, s.birthday.month, s.birthday.day);
return 0;
}
分析:嵌套结构体实现对复杂数据的层级管理。
结论:结构体支持多层次数据组合,便于表达复杂信息。
常见误区
误区:结构体定义后必须立即声明变量
- 事实:结构体定义和变量声明可以分开,结构体定义是新类型的声明。
误区:结构体赋值只能逐个成员赋值
- 事实:结构体变量之间可以整体赋值,如
s1 = s2;,实现成员逐一拷贝。
- 事实:结构体变量之间可以整体赋值,如
误区:结构体指针成员访问用
.运算符- 事实:指针访问结构体成员必须用
->运算符,.是变量访问成员。
- 事实:指针访问结构体成员必须用
误区:结构体大小等于所有成员大小之和
- 事实:由于内存对齐,结构体大小可能大于成员大小之和。
误区:结构体传递函数总是复制结构体
- 事实:传递指针不会复制结构体,只传递地址。
应用场景
- 学生信息管理系统:用结构体存储学生姓名、学号、成绩等数据。
- 图形界面坐标表示:使用结构体组合x,y坐标。
- 文件头信息存储:结构体存储文件格式的元数据。
- 嵌入式设备配置:结构体保存硬件参数配置。
- 游戏开发中角色属性管理:结构体封装角色的生命值、攻击力等属性。
知识拓展
- 共用体(union):与结构体类似,但所有成员共用同一内存空间,用于节省内存。
- 位域结构体:允许对结构体成员按位定义,控制内存使用。
- 结构体与指针数组结合使用:动态管理多个结构体数据。
- 结构体嵌套链表:实现复杂数据结构如链表、树。
- 编译器内存对齐选项:不同编译器或平台的对齐规则可能不同,影响结构体大小。
总结回顾
本节详细介绍了C语言结构体的定义、使用及其内存管理原理。结构体是将不同类型数据组合成整体的强大工具,极大提升了程序设计的灵活性和表达能力。通过结构体,程序员可以更清晰地组织和管理复杂数据。重点包括结构体定义语法、成员访问方式、内存对齐带来的影响及结构体作为函数参数的两种传递方式。结合丰富的实例,帮助考生深刻理解和掌握结构体的应用。掌握结构体是通过全国计算机等级考试二级C语言程序设计的重要保证。