C Language NO.6 结构体
- 1. 什么是结构体
- 2. 如何声明结构体类型
- 3. 结构体变量
- 4. 结构体数组
- 5. 结构体指针
1. 什么是结构体
在C语言中,结构体是一种自定义的数据类型,它允许将不同类型的数据项组合在一起,形成一个逻辑上的数据单元。这个数据单元可以包含多个数据项,每个数据项可以是不同的数据类型,如整数、浮点数、字符等。因此,使用结构体可以将多个相关联的数据组织在一起,形成一个完整的数据结构。例如,一个学生的学号、姓名、性别、年龄、成绩等,都属于一个学生的。结构体就是由不同类型数据组成的组合型数据结构。
struct Student
{
int num;//学号为整型
char name[20];//姓名为字符串
char sex;//性别为字符型
int age;//年龄为整型
float score;//成绩为实型
};
2. 如何声明结构体类型
在C语言中,定义结构体需要使用关键字 struct,并且需要在定义结构体的同时指定结构体的成员。
2.1 struct 关键词
定义一个结构体需要struct
关键词,后面跟一个结构体名字,为了与变量区分开,结构体名字首字母大写,然后用大括号对结构体成员进行封装,最后用分号表示结束。
struct Student {
};
2.2 结构体成员
一个结构体有很多不同类型的成员,学生姓名可以用字符串数组来表示,char name[100];
,有点浪费空间,规范就使用字符指针来存储学生名字。
struct Student {
int id; // 学号
char* name; // 姓名
int age; // 年龄
float score; // 成绩
// ... 其他成员
};
2.3 空结构体
空结构体是指没有成员变量的结构体,可以用作占位符或标记某些特定信息。空结构体在定义链表、树等数据结构时经常被使用,用于存储附加信息。
struct Empty{
//...
}
2.4 匿名结构体
匿名结构体是指在声明变量的同时定义结构体,而不给结构体命名。这样做可以简化代码。注意,匿名结构体只能在当前作用域中使用,无法在其他地方再次引用,通常用于一次性的结构体定义和使用。
// 声明并定义匿名结构体
struct {
int id;
char name[20];
float score;
} stu1 = {20240212, "John", 95.5};
// 访问匿名结构体成员
printf("ID: %d\n", stu1.id);
printf("Name: %s\n", stu1.name);
printf("Score: %.2f\n", stu1.score);
在上面的示例中,定义了一个匿名结构体,它包含了学生的学号、姓名和分数,并且在定义结构体的同时创建了一个名为 student1 的结构体变量。我们可以直接访问结构体成员,并输出学生的信息。
这里说明stu1.id
,stu1.name
,stu1.score
,.
是成员运算符在所有运算符中优先级最高,用于访问结构体或联合体的成员。在访问结构体成员时,成员运算符使用.
(点号)来连接结构体变量名和成员名。
2.5 使用 typedef
typedef
是 C 语言中的一个关键字,用于定义新的类型名。通过 typedef
,它可以为已有的数据类型或自定义的复合数据类型定义一个新的名称,以方便在程序中使用。
在结构体中使用typedef
可以为结构体类型定义一个新的名称,使得在声明结构体变量时更加简洁和方便。
#include <stdio.h>
typedef struct Student{
int num;
char *name;
char sex;
int age;
int score;
} Student;
int main() {
Student stu1 = { 20240206, "name", 'M', 24, 626 };
printf("学号:%d\n姓名:%s\n性别:%c\n年龄:%d\n期末总成绩:%d\n", stu1.num, stu1.name, stu1.sex, stu1.age, stu1.score);
return 0;
}
结构体Student
被定义了两次:第一次是用struct Student { ... }
定义结构体,第二次是使用typedef
将其定义为一个新的类型别名Student
。使用typedef
将struct Student
重命名为Student
,从而可以直接使用Student
作为结构体类型的名称。这样,在声明变量时就可以直接使用Student
而不需要再写struct
关键字。
这里是不使用typedef
定义结构体Student,用作对比参考。
#include <stdio.h>
struct Student {
int num;
char *name;
char sex;
int age;
int score;
};
int main() {
//结构体变量声明和赋值
struct Student stu1 = { 20240206, "name", 'M', 24, 626 };
//结构体成员访问
printf("学号:%d\n姓名:%s\n性别:%c\n年龄:%d\n期末总成绩:%d\n", stu1.num, stu1.name, stu1.sex, stu1.age, stu1.score);
return 0;
}
2.6 结构体嵌套定义
在一个结构体中使用另一个结构体作为其成员。通过结构体嵌套,我们可以将多个相关的数据组织在一起,形成更复杂的数据结构。
// 嵌套结构体
typedef struct Birthday {
int year;
int month;
int day;
} Birthday;
typedef struct Student{
int num;//学号为整型
char name[20];//姓名为字符串
char sex;//性别为字符型
int age;//年龄为整型
float score;//成绩为实型
Birthday birthday; // 在Student结构体中嵌套Birthday结构体
} Student;
Student stu1 = { "Alice", { 1990, 10, 15 } };
printf("姓名:%s\n", stu1.name);
printf("出生日期:%d年%d月%d日\n", stu1.birthdate.year, stu1.birthdate.month, stu1.birthdate.day);
3. 结构体变量
3.1 声明结构体变量
typedef struct Student{
int num;
char *name;
char sex;
int age;
int score;
} Student,stu1,stu2;//1、在声明结构体类型的同时定义变量
int main() {
Student stu1,stu2;//2、在定义好结构体类型后,再声明结构体变量
return 0;
}
注:
1、结构体类型是不能赋值、存取或运算,只有结构体变量可以;
2、结构体类型中的成员名可以与程序中的变量名相同,但二者不代表同一对象;
3、对结构体变量的成员可以单独使用,它的作用与地位相当于普通变量。
3.2 结构体变量的赋值
上面定义好了变量,那么接下来就要变量赋值 - - - >初始化
这里我们在main
函数里面对变量stu1
初始化它的成员num、name、sex、age、score
通过printf
函数将stu1
初始化内容打印出来。
int main()
{
//结构体变量赋值,直接赋值
Student stu1 = { 20240206,"name",'M',24,626.43 };
//访问成员变量
printf("学号:%d\n姓名:%s\n性别:%c\n年龄:%d\n期末总成绩:%.2f\n", stu1.num, stu1.name, stu1.sex, stu1.age, stu1.score);
return 0;
}
// 修改成员变量
stu1.num = 12345; // 修改学号
strcpy(stu1.name, "张三"); // 修改姓名
//修改一个字符串时,你不能简单地使用赋值运算符=来进行赋值,
//字符串是以字符数组的形式存储的,
//strcpy函数会将字符串"张三"复制到stu1.name所指向的字符数组中,从而正确地修改了name成员变量的值。
stu1.sex = 'M'; // 修改性别
stu1.age = 18; // 修改年龄
stu1.score = 95.5; // 修改成绩
3.3 结构体作为函数参数
void printStudentInfo(Student Stu)
{
printf("学号:%d\t姓名:%s\t年龄:%d\t成绩:%.2f\t生日:%d-%d-%d\n",Stu.id,Stu.name,Stu.age,Stu.score,Stu.birthday.year, Stu.birthday.mouth, Stu.birthday.day);
}
printStudentInfo(stu1);//函数调用
printStudentInfo(stu2);
说明:
1、定义好结构体变量时可以对它的成员初始化,把常量依次赋给结构体变量中的各成员并用花括号括起来,可以对某一成员初始化,其他成员被系统自动初始化为0(数值成员)或’\0’(字符成员)、NULL(指针成员);
2、对结构体成员赋值可以使用成员运算符".",如stu1.num=20240206;
3、如果成员本身又属于一个结构体类型,则要使用多个成员运算符且只能对最低级的成员进行赋初值或存取运算等。
4、同类的结构体变量可以互相赋值。
4. 结构体数组
举例来说,假设你需要存储学生的信息,包括姓名、年龄、学号等,你可以定义一个包含这些信息的结构体,并创建一个存储多个学生信息的结构体数组。这样就可以方便地管理和操作学生信息。
#include <stdio.h>
struct Student {
char name[50];
int age;
int student_id;
};
int main() {
struct Student students[5]; // 定义一个包含5个学生信息的结构体数组
// 初始化学生信息
for (int i = 0; i < 5; i++) {
printf("Enter NO.%d student name: ", i + 1);
scanf("%s", students[i].name);
printf("Enter NO.%d student age: ", i + 1);
scanf("%d", &students[i].age);
printf("Enter NO.%d student ID: ", i + 1);
scanf("%d", &students[i].student_id);
}
// 输出学生信息
for (int i = 0; i < 5; i++) {
printf("Student %d: %s, Age: %d, ID: %d\n", i + 1, students[i].name, students[i].age, students[i].student_id);
}
return 0;
}
i + 1是一个整数类型的变量,用于表示当前正在输入第几个学生的姓名。"%d student name: "
是一个格式化字符串,其中%d会被替换为相应的整数值,而student name:
则是固定的字符串部分。
有3个候选人,每个选民只能投票选一人,要求编一个统计选票的程序,先后输入被选人的名字,最后输出各人得票结果。
#include <stdio.h>
#include <string.h>
struct Person{ // 声明结构体类型struct Person,用于存储候选人姓名和票数
char name[20]; // 候选人姓名
int count; // 候选人的票数
} leader[3]= { "li", 0, "zhang", 0, "sun", 0 }; // 定义结构体数组并初始化三位候选人信息
int main()
{
char leader_name[20]; // 定义字符数组,用来存储输入的候选人姓名
for (int i = 1; i <= 10; i++) // 循环输入10次候选人姓名
{
scanf("%s", leader_name); // 从标准输入读取候选人姓名
for (int j = 1; j <= 3; j++) // 遍历三位候选人信息
if (strcmp(leader_name, leader[j].name) == 0) // 比较输入的姓名与候选人姓名是否匹配
leader[j].count++; // 如果匹配,则对应候选人票数加一
}
printf("\nResult:\n");
for (int i = 0; i < 3; i++) // 遍历输出三位候选人的姓名和得票数
printf("%5s:%d\n", leader[i].name, leader[i].count);
return 0;
}
5. 结构体指针
所谓结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。
如果把一个结构体变量的起始地址存放在一个指针变量中,那么,这个指针变量就指向该结构体变量。
示例
#include <stdio.h>
typedef struct Birthday {
int year;
int mouth;
int day;
} Birthday;
typedef struct Student {
int id; // 学号
char* name; // 姓名
int age; // 年龄
float score; // 成绩
Birthday birthday; // 生日
// ...
} Student;
void printStudentInfo(Student* pStu)
{
printf("学号:%d\t姓名:%s\t年龄:%d\t成绩:%.2f\t生日:%d-%d-%d\n",
pStu->id, pStu->name, pStu->age, pStu->score,
pStu->birthday.year, pStu->birthday.mouth, pStu->birthday.day);//结构体指针访问结构体成员
}
int main()
{
// 定义并初始化两个学生对象
Student stu1 = { 20240212, "星期日", 22, 92.5, {2002, 6, 28} };
Student stu2 = { 20240213, "知更鸟", 21, 98, {2003, 3, 7} };
// 定义一个指向学生对象的指针,并将其指向stu1
Student* pStu = &stu1;
// 输出stu1的信息
printStudentInfo(pStu);
// 将指针指向stu2
pStu = &stu2;
// 输出stu2的信息
printStudentInfo(pStu);
return 0;
}
5.1 声明结构体指针
Student* pStu = &stu1;
通过上面语句,声明了一个名为pStu
的指针变量,该指针变量指向Student
类型的对象。然后,使用取地址运算符&
将pStu
指向stu1
这个Student
对象的地址。这样,pStu
就指向了stu1
。
pStu = &stu2;
使指针pStu
指向stu2
所代表的 Student
对象
5.2 结构体指针访问结构体成员
通过结构体指针访问结构体的成员,可以使用箭头运算符->
。箭头运算符用于在结构体指针上访问结构体成员。
void printStudentInfo(Student* pStu)
{
printf("学号:%d\t姓名:%s\t年龄:%d\t成绩:%.2f\t生日:%d-%d-%d\n",
pStu->id, pStu->name, pStu->age, pStu->score,
pStu->birthday.year, pStu->birthday.mouth, pStu->birthday.day);//结构体指针访问结构体成员
}
在上面的代码中,使用结构体指针pStu
来访问 Student
结构体对象的成员变量。例如,在 printStudentInfo
函数中,通过pStu->id
访问学生的学号,通过 pStu->name
访问学生的姓名等等。
通过结构体指针修改结构体的成员,可以使用箭头运算符和赋值运算符。使用箭头运算符访问结构体成员,然后使用赋值运算符将新值赋给结构体成员变量。
例如,想要修改stu1
的成绩:
// 使用指针方式修改成员变量
pStu = &stu1; // 指针指向 stu1
pStu->score = 95.0; // 修改 stu1 的成绩为 95.0
// 使用直接访问方式修改成员变量
stu1.score = 95.0;
两种方式都可以用来修改结构体成员变量,但在使用指针的方式时,需要先将指针指向正确的结构体对象,然后通过箭头运算符来操作成员变量。而对于直接声明的结构体对象,可以直接通过点运算符来访问和修改成员变量,无需额外的指针操作。
5.3 结构体指针作为函数参数
void printStudentInfo(Student* pStu)
{
printf("学号:%d\t姓名:%s\t年龄:%d\t成绩:%.2f\t生日:%d-%d-%d\n",
pStu->id, pStu->name, pStu->age, pStu->score,
pStu->birthday.year, pStu->birthday.mouth, pStu->birthday.day);//结构体指针访问结构体成员
}
Student* pStu = &stu1; // 定义一个指向学生对象的指针,并将其指向stu1
printStudentInfo(pStu);// 输出stu1的信息
在这段代码中,结构体指针 pStu
被作为参数传递给函数 printStudentInfo
,该函数接受一个指向Student
结构体的指针作为参数,然后通过该指针来访问结构体对象的成员变量,以打印学生的信息。这种方式允许函数直接访问并操作结构体对象的内容,而无需进行对象的复制。
在函数定义中,使用指针作为参数,函数内部可以通过该指针来访问结构体对象的成员变量,如pStu->id
、pStu->name
等。这样做的好处是可以有效地避免对整个结构体对象进行复制,提高程序的运行效率,同时可以直接修改原始对象的内容。
结构体内存计算
typedef struct Student {
int A;
char B;
float C;
}Student;
printf("Student 结构体所占字节数: %d\n", sizeof(Student));
char类型占内存为1个字节,理论上来说结构体应该占9个字节,实际上确是12个字节。在默认情况下,编译器会对结构体的成员进行内存对齐,即char类型占4个字节,其中有三个为空字节,以提高访问效率。
关于内存对齐,它能提高数据存取效率。
实际内存读取的时候,是内存单元每n个一组,一次读一组。比如4个字节为一组,像上面int类型占内存4个字节刚好为一组,而char类型就不行了,为了提高读取效率,就把char类型多余字节空置,即看似只占4个字节,实际上还是一个字节,其余空字节。
如果没有内存对齐,那么在一组一组读取的时候,有时就需要字节拼接,即上一组部分字节与下一组部分字节需要组合到一起,像char类型与int类型,按一组4个字节来说,那么第一个是char类型,后面3个是int类型,下一组第一个是int类型,其余是其他类型,这样在读取完之后,还需要进行字节拼接就比较浪费时间。因此内存对齐就很有必要。