在C语言中,数据类型包括基本数据类型/内置类型(如整型、浮点型、字符型等)、数组类型、指针类型、结构体类型、联合体类型。今天要总结的是结构体类型。
一、结构体的作用
C语言允许用户自己创建由不同类型数据组成的组合型的数据结构,它称为结构体。
结构体在程序设计中扮演着组织数据(将不同类型的数据组合成一个单独的数据类型)、提高代码可读性和可维护性、实现数据封装(通过限制对数据的访问,可以提高代码的安全性和健壮性,防止数据被意外地修改或篡改)和传递参数(从而方便地将多个相关联的数据一起传递给函数,减少参数数量,简化函数调用)等重要角色。
二、结构体的设计
1. 定义形式:
a.声明类型的同时定义变量
struct 结构体名称
{
数据类型 成员1; //可以是基本数据类型、指针、数组或其他结构
数据类型 成员2;
}变量名表列; //分号后需要加“;”
示例:
//定义结构体
struct Student
{
int student_id[4];
char name[8];
int age;
float grade[5];
}student1,student2;
b.不指定类型名而直接定义结构体类型变量
struct
{
数据类型 成员表列
}变量名表列;
指定了一个无名的结构体,没有名字(不出现结构体名称)。
2.结构体变量初始化
// 定义结构体
struct Student {
int student_id[4];
char name[8];
int age;
float grade[5];
};
int main() {
// 声明结构体变量并初始化
Student student1 = {1, "Alice", 23, 85.5};
Student student2;//.cpp文件
//.c文件中定义方式为struct Student student2;
struct Student b={.name="Zhang"}; //在成员名前有成员运算符“.”
".name"隐含代表结构体变量b中的成员b.name。其他未被指定初始化的数值型成员被系统初始化为0,字符型被系统初始化为'\0',指针型成员被系统初始化为NULL。
3.结构体成员访问
(1)结构体成员变量使用 . 访问
获取和赋值结构体变量成员的格式为:结构体变量 . 成员名
示例:
#include <stdio.h>
// 定义一个结构体
struct Person {
char name[50];
int age;
float height;
};
int main() {
// 声明一个结构体变量
struct Person person1;
// 对结构体成员赋值方式一
strcpy(person1.name, "John");
person1.age = 30;
person1.height = 6.0;
//对结构体成员赋值方式二
//person1={"John",30,6.0};
//修改结构体成员的值
//person1={"Mike",20,7.0};//err,不能直接赋值进行修改
strcpy(person1.name,"Mike");
//或者用memcpy(person1.name,"Mike",5);范围更广泛,一般strcpy用于字符数组
// 访问结构体成员并打印输出
printf("Name: %s\n", person1.name);
printf("Age: %d\n", person1.age);
printf("Height: %.2f\n", person1.height);
return 0;
}
(2)只能对最低级的成员进行赋值或存取以及运算。若在结构体struct Student类型的成员中包含另一个结构体struct date类型的成员birthday,则引用方式为:
student1.num //结构体变量student1中的成员num
student1.birthday.month //结构体变量student1中的成员birthday中的成员month
student.birthdy //err
不能用student1.birthday来访问student1变量中的成员birthday,因为birthdy本身是一个结构体成员。
(3)对结构体变量的成员可以像普通变量一样进行各种运算。如:
student2.score=student1.score; (赋值运算)
sum=student1.score+student2.score;(加法运算)
student.age++; (自加运算)
(4)同类的结构体变量可以相互赋值。如:
student1=student2;
(5)可以引用结构体变量成员的地址,也可以引用结构体变量的地址。如:
scanf("%d",&student1.num); //输入student1.name的值
printf("%o",&student1); //输出结构体变量student1的起始地址
但不能用scanf语句整体读入结构体变量。如:
scanf("%d,%s,%c,%d,%f,%s\n",&student1); //err
4.结构体变量重命名
可以用 typedef 为结构体定义一个新的名称
//使用typedef关键字为struct student创建两个别名:Student和stu
typedef struct student
{
char name[50];
int age;
}Student,stu;
typedef struct Student student; //定义了一个新的类型别名 student指向了结构体 struct Student
struct Student s; //声明了一个名为 s 的结构体变量,其类型是 struct Student
Student s; //student 来声明一个结构体变量 s,其类型实际上是 struct Student,相当于第二行的代码
5.结构体嵌套
结构体嵌套是一种将一个结构体作为另一个结构体的成员的方法
示例:
#include <stdio.h>
// 定义一个地址结构体
struct Address {
char city[50];
char country[50];
};
// 定义一个用户结构体,嵌套地址结构体
struct User {
char name[50];
int age;
struct Address address; // 嵌套的地址结构体
};
int main() {
// 创建一个用户
struct User user = {
.name = "Alice",
.age = 30,
.address = {.city = "Beijing",.country = "China"}
};
// 访问嵌套的字段
printf("Name: %s\n", user.name);
printf("Age: %d\n", user.age);
printf("City: %s\n", user.address.city);
printf("Country: %s\n", user.address.country);
return 0;
}
三、结构体数组
一个结构体变量中可以存放一组有关联的数据(如一个学生的学号、姓名、成绩等)。如果有10个学生的数据需要参与运算,应用数组,这就是结构体数组。
1.定义方式
①一般定义方式:
struct 结构体名称
{
成员列表
}数组名[数组长度];
②先声明一个结构体类型,然后再用此类型定义结构体数组:
结构体类型 数组名[数组长度];
如:
struct Person student[3]; //student是结构体数组名
示例:
struct Person {
char name[50];
int age;
float height;
};
2.结构体数组初始化
对结构体数组初始化的形式是在定义数组的后面加上 ={初值列表};
示例1:
struct Person //声明结构体类型struct Person
{
char name[20]; //候选人姓名
int count; //候选人得票数
}leader[3]={"Luo",0,"Zhang",1,"Liu",2}; //定义结构体数组类型并初始化
示例2:
// 定义结构体
struct Person {
char name[50];
int age;
float height;
};
int main() {
// 声明结构体数组并初始化
struct Person people[3] = {
{"Alice", 25, 1.75},
{"Bob", 30, 1.80},
{"Charlie", 22, 1.70}
};
四、结构体指针
结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。如果把一个结构体变量的起始地址存放在一个指针变量中,那么这个指针变量就指向该结构体变量。
1.指向结构体变量的指针
指向结构体对象的指针变量既可指向结构体变量,也可以指向结构体数组中的元素。指针变量的基类型必须与结构体变量的类型相同。
如:
struct Student *p; //p可以指向struct Student类型的变量或数组元素
示例:
// 定义一个结构体
struct Person {
char name[50];
int age;
float height;
};
int main() {
// 声明一个结构体变量
struct Person person1;
// 使用结构体变量的成员运算符(.)给结构体成员赋值
strcpy(person1.name, "John");
person1.age = 30;
person1.height = 5.9;
// 声明一个结构体指针,并将其指向 person1
struct Person *ptrPerson = &person1;
return 0;
}
struct Student
{
long num;
char name[20];
char sex;
float score;
};
int main()
{
struct Student stu_1; //定义struct Student类型的变量stu_1
struct Student *p; //定义指向struct Student类型数据的指针变量p
p=&stu_1; //p指向stu_1
stu_1.num=2004; //对结构体变量进行赋值
strcpy(stu_1.name,"Luo"); //用字符串复制函数给stu_1.name赋值
stu_1.sex="M";
stu_1.score=89;
printf("No:%ld\nname:%s\nsex:%c\nscore:%5.lf\n",stu_1.num,stu_1.name,stu_1.sex,stu_1.score);
printf("\nNo:%ld\nname:%s\nsex:%c\nscore:%5.lf\n",(*p).num,(*p).name,(*p).sex,(*p).score);
return 0;
}
如果p指向一个结构体变量stu,以下三种用法等价:
①stu.成员名(如stu.name)
②(*p).成员名(如(*p).name) //*p两边的括号不可省,成员运算符”."优先于“*”运算符
③p->成员名(如p->name) //"->"代表一个箭头,p->name表示p指向的结构体变量中的name元素
2.指向结构体数组的指针
# include <stdio. h>
struct Student //声明结构体类型struct Student
{int num;
char name[20];
char sex;
int age;
};
struct Student stu[3]={{10101,"Li Lin",'M',18},{10102,"Zhang Fang",'M',19},
{10104,"Wang Min",'F',20}};//定义结构体数组并初始化
int main( )
{
struct Student *p; //定义指向struct Student结构体变量的指针变量
printf(" No. Name sex age\n");
for (p=stu;p<stu+3;p++)
printf("%5d %-20s %2c %4d\n",p->num,p->name,p->sex,p->age);
return 0;
}
(1)若p 的初值为stu,即指向stu的序号为0的元素,p加1后,p就指向下一个元素。如:
(p++)->num 先求得p->num的值(即10101),然后再使p自加1,指向stu[1]
(++p)->num 先使p自加1,然后得到p指向的元素中num成员值(即10102)
(2)p的值是stu数组中的一个元素(如stu[0]或stu[1])的起始地址,不也能用来指向stu数组中的某一成员。如:
p=stu[1].name; //err stu[1].name是stu[1]元素中的成员name的首字符的地址
若要将某成员的地址赋值给p,可以用强制类型转换,先将成员的地址转换成p的类型。如:
p=(struct Student*)stu[0].name;
此时,p 的值是stu[0]元素的name成员的起始地址。可以用“printf(”%s",p);”输出stu[0]中成员name的值,但是,p保持原来的类型。如果执行“printf("%s",p+1);”,则会输出stu[1]中name的值,执行p++时,p的值的增量时结构体struct Student的长度
五、结构体大小
1.字节对齐方式
a. cpu并非逐字节读写内存,而是以2,4,8的倍数读取,因此要考虑字节对齐
b. 预处理指令 #pragma pack(n)可以改变默认对齐数,n的取值是1,2,4,8,16
c. vs中默认值=8,gcc中默认值=4
2.结构体内存大小
(1)结构体变量的首地址,必须是MIN{“结构体最大基本数据类型成员所占字节数”,指定对齐方式}大小的整数倍
(2)结构体每个成员相对于结构体首地址的偏移量,都是MIN{基本数据类型成员,指定对齐方式}大小的整数倍
(3)结构体的总大小,为MIN{结构体“最大基本数据类型成员所占字节数”(将嵌套结构体里的基本类型也算上,得出最大基本数据类型成员所占字节数),指定对齐方式}大小的整数倍