C语言结构体用法详解(包括定义、初始化、赋值、成员访问等细节)
在现实生活中,我们经常需要用多个不同类型的数据来描述一个对象,比如要描述一个学生,需要记录他的姓名、学号、年龄、成绩等信息;要描述一个商品,需要记录它的名称、价格、库存等信息。
如果使用普通的变量来存储这些数据,不仅需要定义很多独立的变量,而且这些变量之间缺乏关联性,不利于数据的管理和使用。为了解决这个问题,C语言提供了结构体(Structure)这种复合数据类型。
结构体可以将多个不同类型的数据组织在一起,形成一个新的数据类型。这些数据可以是基本数据类型(如 int、float、char 等),也可以是其他的结构体类型。每个数据在结构体中被称为成员或者字段。通过结构体,我们可以将所有相关的数据打包成一个整体,更好地表达数据之间的逻辑关系。
结构体的定义
要使用结构体,需要先定义结构体类型,它的基本语法格式如下:
struct 结构体名称 { 成员类型1 成员名称1; 成员类型2 成员名称2; ... 成员类型n 成员名称n; };
struct 是定义结构体的关键字,结构体名称
用于标识这个新的数据类型,花括号内部包含了若干成员变量的声明。每个成员变量都有自己的类型和名称,它们之间用分号分隔。
下面通过一个具体的例子来说明结构体的定义和使用。假设我们要开发一个学生信息管理系统,需要存储学生的基本信息:
- struct Student {
- char name[50]; // 姓名
- int id; // 学号
- int age; // 年龄
- float score; // 成绩
- };
这里定义了一个名为 Student 的结构体类型,它包含 4 个成员。注意,为了和变量名、函数名进行区分,结构体名的首字母一般要大写。
结构体的使用
结构体是一种类型,而不是变量,不能赋值;定义了结构体类型后,还需要声明结构体变量才能使用。
声明结构体变量
声明结构体变量有两种方式:先定义类型再声明变量,或者在定义类型的同时声明变量。
- // 方式 1:先定义类型,再声明变量
- struct Student s1; //使用了已经定义的结构体类型 Student
- // 方式 2:定义类型的同时声明变量
- struct Student {
- char name[50];
- int id;
- int age;
- float score;
- } s1, s2;
初始化结构体成员
结构体变量的成员可以在声明时初始化,也可以在声明后单独赋值。初始化时要注意成员的顺序必须与定义时的顺序一致。
- // 声明时初始化
- struct Student s1 = {"张三", 10001, 18, 85.5};
- // 声明后单独赋值
- struct Student s2;
- strcpy(s2.name, "李四"); // 字符数组不能直接赋值,需要使用 strcpy
- s2.id = 10002;
- s2.age = 19;
- s2.score = 92.5;
访问结构体成员
如果是结构体变量,可以使用点运算符.
访问结构体成员;如果是结构体指针,则使用箭头运算符->
访问结构体成员。请看下面的例子:
- #include <stdio.h>
- #include <string.h>
- struct Student {
- char name[50];
- int id;
- int age;
- float score;
- };
- int main() {
- struct Student s1 = {"张三", 10001, 18, 85.5};
- struct Student *ptr = &s1;
- // 使用点运算符访问成员
- printf("姓名:%s\n", s1.name);
- printf("学号:%d\n", s1.id);
- // 使用箭头运算符访问成员
- printf("年龄:%d\n", ptr->age);
- printf("成绩:%.1f\n", ptr->score);
- return 0;
- }
输出结果:
姓名:张三 学号:10001 年龄:18 成绩:85.5
结构体变量之间的赋值
结构体变量之间可以直接赋值,这将复制所有成员的值,也即把一个结构体的内存直接拷贝给另一个结构体。
但是需要注意,这是一种浅拷贝,如果结构体中包含指针成员,赋值操作只会复制指针的值,而不会复制指针指向的数据,这种情况下需要手动进行深拷贝。
请看下面的例子:
- #include <stdio.h>
- #include <string.h>
- struct Student {
- char name[50];
- int age;
- float score;
- };
- int main() {
- struct Student stu1 = {"张三", 18, 92.5};
- struct Student stu2;
- // 结构体变量赋值
- stu2 = stu1;
- // 修改 stu2 的成员
- strcpy(stu2.name, "李四");
- stu2.age = 20;
- printf("stu1:%s, %d, %.1f\n", stu1.name, stu1.age, stu1.score);
- printf("stu2:%s, %d, %.1f\n", stu2.name, stu2.age, stu2.score);
- return 0;
- }
输出结果:
stu1:张三, 18, 92.5 stu2:李四, 20, 92.5
结构体的嵌套定义
结构体还可以嵌套定义,即一个结构体的成员可以是另一个结构体,这种特性使得我们可以构建更复杂的数据类型。请看下面的代码:
- struct Date {
- int year;
- int month;
- int day;
- };
- struct Student {
- char name[50];
- int age;
- struct Date birthday; // 结构体嵌套
- float score;
- };
- // 嵌套结构体的初始化
- struct Student stu = {
- "张三",
- 18,
- {2005, 8, 15}, // 嵌套的结构体成员也用花括号
- 92.5
- };
使用 typedef 重命名
在实际编程中,为了简化代码,我们经常使用 typedef 关键字为结构体类型定义一个新的类型名,这样在声明变量时就可以省略 struct 关键字。请看代码:
- typedef struct Student {
- char name[50];
- int age;
- float score;
- } Stu; // 定义别名 Stu
- // 使用别名定义变量,不需要再写 struct
- Stu stu1 = {"张三", 18, 92.5};
- Stu stu2;
结构体类型的大小
需要注意的是,结构体的大小不一定等于所有成员大小的简单相加。由于内存对齐的原因,编译器可能会在成员之间插入一些填充字节。如果需要了解结构体的实际大小,可以使用 sizeof 运算符:
- #include <stdio.h>
- struct Test {
- char c; // 1 字节
- int i; // 4 字节
- double d; // 8 字节
- };
- int main() {
- printf("结构体大小:%zu 字节\n", sizeof(struct Test));
- return 0;
- }
输出结果可能是:
结构体大小:16 字节
虽然成员大小加起来是 13 字节,但由于内存对齐,实际的结构体大小是 16 字节。这种内存对齐机制是为了提高程序的运行效率,但同时也会占用更多的内存空间。
总起来说,结构体是一种自定义的、复合的数据类型,而不是变量。变量可以直接使用,而数据类型还需要声明变量才能使用。理解这一点非常重要,这是结构体的本质。
结构体在C语言编程中有着广泛的应用,通过合理使用结构体,我们可以更好地组织和管理复杂的数据。在开发较复杂的程序时,结构体常常与其他高级特性(如动态内存分配、文件操作等)结合使用,构建更复杂的数据结构和算法。