C 语言结构体与联合体深入解析
C 语言结构体
结构体的基本概念
在 C 语言中,结构体(struct)是一种用户自定义的数据类型,它允许将不同类型的数据组合成一个单一的实体。结构体提供了一种方式来将相关的数据项组织在一起,使得代码更具结构性和可读性。
例如,假设我们要描述一个学生的信息,可能包括姓名(字符串)、年龄(整数)和成绩(浮点数)。使用结构体就可以将这些不同类型的数据组合成一个整体,方便对学生信息进行管理和操作。
// 定义一个结构体类型 student
struct student {
char name[20];
int age;
float score;
};
在上述代码中,struct student
定义了一个新的数据类型,它包含三个成员:name
是一个字符数组,用于存储学生姓名;age
是一个整数,用于表示学生年龄;score
是一个浮点数,用于记录学生成绩。
结构体变量的定义与初始化
- 定义结构体变量
在定义了结构体类型之后,就可以使用该类型来定义结构体变量。有以下几种方式:
- 先定义结构体类型,再定义变量
struct student {
char name[20];
int age;
float score;
};
struct student stu1; // 定义一个 struct student 类型的变量 stu1
- **在定义结构体类型的同时定义变量**
struct student {
char name[20];
int age;
float score;
} stu2; // 在定义结构体类型的同时定义变量 stu2
- **省略结构体标签定义变量(匿名结构体)**
struct {
char name[20];
int age;
float score;
} stu3; // 省略结构体标签,直接定义变量 stu3,这种方式定义的结构体类型后续无法再用于定义其他变量
- 初始化结构体变量
初始化结构体变量可以在定义变量时进行,也可以在定义之后单独赋值。
- 定义时初始化
struct student {
char name[20];
int age;
float score;
};
struct student stu1 = {"Tom", 20, 85.5}; // 定义结构体变量 stu1 并初始化
- **定义后赋值**
struct student {
char name[20];
int age;
float score;
};
struct student stu2;
strcpy(stu2.name, "Jerry");
stu2.age = 21;
stu2.score = 90.0;
结构体成员的访问
通过结构体变量名和成员运算符(.
)可以访问结构体的成员。例如:
#include <stdio.h>
#include <string.h>
struct student {
char name[20];
int age;
float score;
};
int main() {
struct student stu = {"Alice", 22, 88.0};
printf("Name: %s\n", stu.name);
printf("Age: %d\n", stu.age);
printf("Score: %.2f\n", stu.score);
return 0;
}
在上述代码中,通过 stu.name
、stu.age
和 stu.score
分别访问结构体 stu
的成员,并进行输出。
如果结构体变量是指针类型,则需要使用 ->
运算符来访问成员。例如:
#include <stdio.h>
#include <string.h>
struct student {
char name[20];
int age;
float score;
};
int main() {
struct student stu = {"Bob", 23, 92.0};
struct student *ptr = &stu;
printf("Name: %s\n", ptr->name);
printf("Age: %d\n", ptr->age);
printf("Score: %.2f\n", ptr->score);
return 0;
}
这里通过指针 ptr
指向结构体变量 stu
,然后使用 ptr->name
、ptr->age
和 ptr->score
来访问结构体成员。
结构体数组
结构体数组是指数组的每个元素都是一个结构体类型。例如,要存储多个学生的信息,可以使用结构体数组:
#include <stdio.h>
#include <string.h>
struct student {
char name[20];
int age;
float score;
};
int main() {
struct student students[3] = {
{"Charlie", 24, 78.0},
{"David", 25, 82.0},
{"Eve", 26, 89.0}
};
for (int i = 0; i < 3; i++) {
printf("Student %d:\n", i + 1);
printf("Name: %s\n", students[i].name);
printf("Age: %d\n", students[i].age);
printf("Score: %.2f\n", students[i].score);
}
return 0;
}
在上述代码中,定义了一个结构体数组 students
,它包含三个 struct student
类型的元素,并对其进行初始化。然后通过循环遍历数组,访问并输出每个学生的信息。
结构体嵌套
结构体可以嵌套,即一个结构体的成员可以是另一个结构体类型。例如,假设我们要描述一个地址信息,包括省份、城市和街道,同时描述一个人,包括姓名、年龄和地址,就可以使用结构体嵌套:
#include <stdio.h>
#include <string.h>
struct address {
char province[20];
char city[20];
char street[30];
};
struct person {
char name[20];
int age;
struct address addr;
};
int main() {
struct person p = {"John", 30, {"Jiangsu", "Nanjing", "Zhongshan Road"}};
printf("Name: %s\n", p.name);
printf("Age: %d\n", p.age);
printf("Address: %s, %s, %s\n", p.addr.province, p.addr.city, p.addr.street);
return 0;
}
在上述代码中,struct person
结构体包含一个 struct address
类型的成员 addr
。通过 p.addr.province
、p.addr.city
和 p.addr.street
可以访问嵌套结构体中的成员。
结构体与函数
- 结构体作为函数参数 结构体可以作为函数的参数进行传递,这样函数就可以对结构体的成员进行操作。例如:
#include <stdio.h>
#include <string.h>
struct student {
char name[20];
int age;
float score;
};
void printStudent(struct student stu) {
printf("Name: %s\n", stu.name);
printf("Age: %d\n", stu.age);
printf("Score: %.2f\n", stu.score);
}
int main() {
struct student stu = {"Mike", 27, 86.5};
printStudent(stu);
return 0;
}
在上述代码中,printStudent
函数接受一个 struct student
类型的参数 stu
,并输出该学生的信息。
- 函数返回结构体 函数也可以返回一个结构体。例如:
#include <stdio.h>
#include <string.h>
struct student {
char name[20];
int age;
float score;
};
struct student createStudent(char *name, int age, float score) {
struct student stu;
strcpy(stu.name, name);
stu.age = age;
stu.score = score;
return stu;
}
int main() {
struct student newStu = createStudent("Nancy", 28, 91.0);
printf("Name: %s\n", newStu.name);
printf("Age: %d\n", newStu.age);
printf("Score: %.2f\n", newStu.score);
return 0;
}
在上述代码中,createStudent
函数接受姓名、年龄和成绩作为参数,创建一个 struct student
类型的结构体并返回。
结构体的内存布局
结构体的内存布局是指结构体成员在内存中的存储方式。结构体的大小并不是简单地将所有成员的大小相加,因为存在内存对齐的问题。
内存对齐是为了提高 CPU 访问内存的效率。CPU 在访问内存时,通常以特定的字节数(如 4 字节、8 字节等)为单位进行读取。如果数据存储的地址能够被 CPU 读取单位整除,那么访问效率会更高。
例如:
#include <stdio.h>
struct example1 {
char a;
int b;
char c;
};
struct example2 {
char a;
char c;
int b;
};
int main() {
printf("Size of example1: %zu\n", sizeof(struct example1));
printf("Size of example2: %zu\n", sizeof(struct example2));
return 0;
}
在上述代码中,struct example1
和 struct example2
包含相同的成员,但顺序不同。由于内存对齐的原因,sizeof(struct example1)
通常会大于 sizeof(struct example2)
。具体的内存对齐规则与编译器和目标平台有关。
C 语言联合体
联合体的基本概念
联合体(union)也是一种用户自定义的数据类型,它允许不同类型的数据共享同一块内存空间。联合体的所有成员都从同一个内存地址开始存储,因此在某一时刻,联合体只能存储其中一个成员的值。
定义联合体的语法与结构体类似,例如:
// 定义一个联合体类型 data
union data {
int i;
float f;
char c;
};
在上述代码中,union data
定义了一个联合体类型,它包含三个成员:i
是一个整数,f
是一个浮点数,c
是一个字符。这些成员共享同一块内存空间。
联合体变量的定义与初始化
- 定义联合体变量
定义联合体变量的方式与结构体类似,有以下几种:
- 先定义联合体类型,再定义变量
union data {
int i;
float f;
char c;
};
union data u1; // 定义一个 union data 类型的变量 u1
- **在定义联合体类型的同时定义变量**
union data {
int i;
float f;
char c;
} u2; // 在定义联合体类型的同时定义变量 u2
- **省略联合体标签定义变量(匿名联合体)**
union {
int i;
float f;
char c;
} u3; // 省略联合体标签,直接定义变量 u3,这种方式定义的联合体类型后续无法再用于定义其他变量
- 初始化联合体变量 联合体只能初始化其第一个成员。例如:
union data {
int i;
float f;
char c;
};
union data u1 = {10}; // 初始化联合体变量 u1 的第一个成员 i 为 10
联合体成员的访问
通过联合体变量名和成员运算符(.
)可以访问联合体的成员。例如:
#include <stdio.h>
union data {
int i;
float f;
char c;
};
int main() {
union data u;
u.i = 20;
printf("Value of i: %d\n", u.i);
u.f = 30.5;
printf("Value of f: %.2f\n", u.f);
u.c = 'A';
printf("Value of c: %c\n", u.c);
return 0;
}
在上述代码中,通过 u.i
、u.f
和 u.c
分别访问联合体 u
的成员,并进行赋值和输出。需要注意的是,由于联合体成员共享内存,对一个成员的赋值会覆盖之前存储在该内存区域的值。
联合体的内存布局
联合体的大小是其最大成员的大小,因为所有成员共享同一块内存空间。例如:
#include <stdio.h>
union example {
char c;
int i;
double d;
};
int main() {
printf("Size of union example: %zu\n", sizeof(union example));
return 0;
}
在上述代码中,union example
包含一个字符、一个整数和一个双精度浮点数。由于双精度浮点数通常占用 8 个字节,是最大的成员,所以 sizeof(union example)
的值为 8。
联合体的应用场景
-
节省内存空间 当程序需要在不同时刻使用不同类型的数据,且这些数据不会同时使用时,可以使用联合体来节省内存。例如,在一个数据通信协议中,可能会根据不同的指令类型,在同一内存区域存储不同类型的数据。
-
检查系统的字节序 字节序是指多字节数据在内存中的存储顺序,分为大端序(Big - Endian)和小端序(Little - Endian)。可以使用联合体来检查系统的字节序。例如:
#include <stdio.h>
union endianCheck {
int i;
char c;
};
int main() {
union endianCheck u;
u.i = 1;
if (u.c == 1) {
printf("Little - Endian\n");
} else {
printf("Big - Endian\n");
}
return 0;
}
在上述代码中,通过将整数 1 存储在联合体中,然后检查字符成员的值来判断系统的字节序。如果字符成员的值为 1,则说明是小端序;否则是大端序。
- 实现共用数据结构 在一些情况下,需要根据不同的条件使用不同类型的数据结构,但希望它们占用相同的内存空间。联合体可以满足这种需求。例如,在一个图形绘制程序中,可能需要根据图形类型,在同一内存区域存储不同类型的图形参数(如圆形的半径、矩形的长和宽等)。
结构体与联合体的区别
-
内存占用
- 结构体:结构体的大小是其所有成员大小之和(考虑内存对齐)。每个成员都有自己独立的内存空间。
- 联合体:联合体的大小是其最大成员的大小。所有成员共享同一块内存空间。
-
数据存储
- 结构体:可以同时存储所有成员的值,各成员之间相互独立。
- 联合体:在某一时刻只能存储其中一个成员的值,对一个成员的赋值会覆盖之前存储在该内存区域的值。
-
应用场景
- 结构体:用于将相关的不同类型的数据组织在一起,方便管理和操作。例如,描述一个复杂的对象,如学生、员工等。
- 联合体:主要用于节省内存空间,以及在不同时刻使用不同类型的数据,且这些数据不会同时使用的场景。例如,检查字节序、实现共用数据结构等。
通过深入理解 C 语言结构体与联合体的概念、特性、内存布局以及应用场景,可以更好地利用这两种用户自定义数据类型来编写高效、灵活的程序。在实际编程中,应根据具体需求选择合适的数据类型,以提高程序的性能和可读性。