C语言之结构体
结构体
结构体的概念
是用户自定义的一种数据结构,它可以把不同类型的数据结构组合成一个有机的整体。有利于数据的管理
例子:一个学生有学号、姓名、性别、年龄
int num;
char name[20];
char sex;
int age;
// 显然单独定义这些变量,不利于数据的管理,所有C语言就发明了结构体类型
结构体属于构造类型
构造类型: 不是基本类型,也不是指针类型。它是由若干个相同类型或不同类型的数据结构的组合。如数组、结构体。
数组:描述一组相同数据类型的有序集合,用于处理大量相同类型的数据
结构体的定义
1.一般定义形式
struct 结构体名{
数据类型 成员变量1;
数据类型 成员变量2;
……
数据类型 成员变量n;
};
struct 结构体名 变量名;
struct stu{
int num;
char name[20];
char sex;
int age;
};
struct stu student1;
2.在定义的时候就声明变量
struct 结构体名{
数据类型 成员变量1;
数据类型 成员变量2;
……
数据类型 成员变量n;
} 变量1,变量2;
struct stu{
int num;
char name[20];
char sex;
int age;
} student1,student2;
3.定义匿名结构体
struct {
数据类型 成员变量1;
数据类型 成员变量2;
……
数据类型 成员变量n;
} 变量1,变量2;
// 这种形式只能在定义结构体的时候声明变量。
struct {
int num;
char name[20];
char sex;
int age;
} student1,student2;
4.最常用的定义方式
通常用typedef关键字将结构体类型取一个别名,用这个别名代表原本的数据类型。
typedef struct 结构体名{
数据类型 成员变量1;
数据类型 成员变量2;
……
数据类型 成员变量n;
}STU;
SIU student1;
// SIU student1;等价与struct 结构体名 student1;
typedef struct stu{
int num;
char name[20];
char sex;
int age;
}STU;
struct stu student1;
结构体变量
结构体变量是一个变量,是包含多种数据类型的数据的集合。
结构体中包含多个成员变量。
1.在定义结构体变量的时候初始化
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct stu
{
int num; // 学号
char zhanye[20]; // 专业
char name[20]; // 姓名
char sex[3]; // 性别
} STU;
int main(int argc, char *argv[])
{
STU p = {// 在定义变量的时候顺序初始化
10001,
"computer",
"zhangsan",
"男"};
STU q = {.num = 10002, // 在定义变量的时候指定成员初始化
.zhanye = "match",
.name = "lisi",
.sex = "女"};
printf("学号: %d\n", p.num); // 变量采用变量.成员变量的形式访问对应的数据。
printf("专业: %s\n", p.zhanye);
printf("姓名: %s\n", p.name);
printf("性别: %s\n", p.sex);
printf("学号: %d\n", q.num);
printf("专业: %s\n", q.zhanye);
printf("姓名: %s\n", q.name);
printf("性别: %s\n", q.sex);
}
2.结构体变量的使用
-
结构体变量成员变量的引用
通过 结构体变量.成员变量 的形式引用
#include <stdio.h> #include <string.h> #include <stdlib.h> typedef struct stu{ int num; //学号 char zhanye[20]; //专业 char name[20]; //姓名 char sex[3]; //性别 char *addr; }STU; int main(int argc, char *argv[]) { STU lihua; lihua.num = 1001; // lihua.name = "lihua";//错误的,name是数组名,是常量,不能直接用“=”赋值 strcpy(lihua.name,"lihua"); strcpy(lihua.addr,"翻斗花园");//错误的,addr目前是野指针 lihua.addr = "翻斗花园"; }
3.相同类型的结构体变量赋值
相同类型的结构体:当两个结构体的成员变量列表(包括成员变量的类型、顺序)完全一致时,它们就是相同类型的结构体,结构体名可以不同。
只要当结构体类型和结构体名都一致的时候,其声明的变量才能相互之间赋值。仅仅是结构体类型一样(即成员变量列表完全一致)但结构体名不一样的两个结构体变量之间是不能相互赋值的。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct stu1{
int num; //学号
char zhanye[20]; //专业
char name[20]; //姓名
char sex[3]; //性别
char *addr;
}STU1;
typedef struct stu2{
int num; //学号
char zhanye[20]; //专业
char name[20]; //姓名
char sex[3]; //性别
char *addr;
}STU2;
int main(int argc, char *argv[])
{
struct stu1 p1,p3;
struct stu2 p2;
p1.num = 1001;
// p2 = p1; // 错误的,因为结构体名不一样,不能够直接整体赋值
p3 = p1; // 正确的,因为结构体类型和结构体名都一样,可以整体赋值
printf("%d\n",p3.num);
return 0;
}
结构体指针
结构体指针的定义
结构体指针是指向结构体的指针,当定义一个结构体指针时,这个指针可以存储结构体变量在内存中的地址。
struct Student {
char name[20];
int age;
float score;
} *q; // 可以在这里直接定义结构体指针
typedef struct Student{
char name[20];
int age;
float score;
} STU, *PSTU; // // 定义结构体别名STU和结构体指针别名PSTU
struct Student *p; // 也可以像这样定义结构体指针
STU *a; // 这样也行
PSTU c,d; // 定义了结构体指针变量c、d
// C语言中任何指针变量在32位系统中占4,在64系统位中占8个字节。
结构体指针的初始化
1.通过取地址运算符&获取已定义的结构体变量的地址来初始化结构体指针。
#include <stdio.h>
typedef struct Student
{
char name[20];
int age;
float score;
} STU;
int main()
{
STU stu1 = {"lihua", 18, 99.9};
STU *p = &stu1;
STU stu2;
STU *q=&stu2;
*q = (STU){"bob", 20, 60.1};
printf("q->name = %s\n", q->name);
printf("q->age = %d\n", q->age);
printf("q->score = %f\n", q->score);
}
2.动态分配内存并初始化
#include <stdio.h>
#include <stdlib.h>
typedef struct Student
{
char name[20];
int age;
float score;
} STU;
int main()
{
STU *p = (STU *)malloc(sizeof(STU));// 动态分配内存
*p = (STU){"zhangsan", 18, 99.9};
printf("%s %d %.1f", p->name, p->age, p->score);
}
结构体指针指向内存的成员变量的访问和赋值
引用形式
指针变量名->成员变量名 或者 (*指针变量名).成员变量名。
1.p->age 或者 2.(*p).age
第二种方式相当于是先用解引用将指针降级成变量,然后用变量的方式访问。(较为繁琐)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct Student
{
char name[20];
int age;
float score;
} STU;
int main()
{
STU *p = (STU *)malloc(sizeof(STU));
*p = (STU){"zhangsan", 18, 99.9};
p->age = 33;
(*p).score = 10.1;
strcpy(p->name, "lisi"); // 成员变量的name是个数组名,不能直接用"="赋值
printf("%s %d %.1f\n", (*p).name, (*p).age, (*p).score);
printf("%s %d %.1f\n", p->name, p->age, p->score);
}
注意
-
结构体变量的地址与第一个成员变量的地址一样,当其数据类型不一样
#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct stu{ int num; char name[20]; char sex[10]; } STU, *PSTU; // 定义结构体别名STU和结构体指针别名PSTU int main(){ STU stu; printf("&stu=%p\n", &stu); // 类型是STU * printf("&stu.num=%p", &stu.num); // 类型是int * } PS E:\code\c_code> cd "e:\code\c_code\" ; if ($?) { gcc test.c -o test } ; if ($?) { .\test } &stu=0xffffcbf0 &stu.num=0xffffcbf0
结构体数组
结构体数组是一个数组,由若干个相同类型的结构体构成的一个有序集合。
结构体数组的定义方法
struct 结构体类型名 数组名[元素个数]
例子:
struct stu{
int num;
char name[20];
char sex[10];
};
struct stu edu[3]; // 定义了struct stu类型的结构体数组edu,这个数组有三个元素,分别是edu[0]、edu[1]、edu[2]
结构体数组的引用
数组名[下标]
*(数组名 + 下标)
…
和普通的数组一样
对结构体数组的单个元素的成员变量的引用
edu[0].num = 100; // 用100给结构体数组edu的第0个元素的成员变量num赋值。
strcpy(edu[1].name,"bob");
案例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct stu{
int num;
char name[20];
char sex[10];
} STU, *PSTU; // 定义结构体别名STU和结构体指针别名PSTU
int main(){
STU suts[3] = {{1,"zhangsan","男"},{2,"lisi","女"},{3,"wangwu","男"}};// 初始化结构体数组
int suts_size = sizeof(suts) / sizeof(suts[0]); // 获取数组长度
for (int i = 0; i < suts_size; i++){
printf("num:%d, name:%s, sex:%s\n", suts[i].num, suts[i].name, suts[i].sex);
}
printf("\n");
suts[0].num = 4; // 修改结构体数组0号元素的成员变量num
strcpy(suts[0].name, "zhaoliu"); // 修改结构体数组0号元素的成员变量name
suts[2] = (STU){5, "夏波", "女"}; // 修改结构体数组2号元素的所有成员变量
PSTU p = suts; // 定义结构体指针指向结构体数组
for(int i = 0; i < 3; i++){
printf("num:%d, name:%s, sex:%s\n", p->num, p->name, p->sex);
p++;
}
}
PS E:\code\c_code> cd "e:\code\c_code\" ; if ($?) { gcc test.c -o test } ; if ($?) { .\test }
num:1, name:zhangsan, sex:男
num:2, name:lisi, sex:女
num:3, name:wangwu, sex:男
num:4, name:zhaoliu, sex:男
num:2, name:lisi, sex:女
num:5, name:夏波, sex:女
结构体对齐规则
概念
- 结构体是一种用户自定义的数据类型,它允许将不同类型的数据组合在一起。在计算机存储结构体变量时,为了提高存储和访问效率,存在结构体对齐规则。
偏移量
- 在结构体(以及其他一些数据存储结构)的语境中,偏移量是指一个成员变量相对于结构体起始地址(也就是结构体第一个字节的地址)的字节数差值。它用于确定每个成员在结构体内部的具体存储位置。
基本对齐规则
-
规则一:数据成员对齐
- 结构体的第一个数据成员的地址和结构体变量的地址相同,即偏移量为 0。例如,有一个结构体
struct S {int a; char b;}
,成员a
的存储地址就是结构体变量存储地址的起始位置。
- 结构体的第一个数据成员的地址和结构体变量的地址相同,即偏移量为 0。例如,有一个结构体
-
规则二:按自身大小对齐(基本数据类型)
- 对于基本数据类型(如
char
、short
、int
、long
、float
、double
等),每个成员变量的存储起始位置必须是其自身大小(字节数)的整数倍。例如,32位系统中int
类型一般占 4 个字节,那么int
类型的成员存储的起始位置应该是 4 的倍数。 - 以
struct S {char a; int b;}
为例,char
类型占 1 个字节,它可以从偏移量为 0 的位置开始存储。而int
类型占 4 个字节,它的存储起始位置应该是 4 的倍数。由于char
已经占用了 1 个字节,所以int
成员b
的存储起始位置是 4(偏移量为 4),在a
之后会有 3 个字节的填充。
- 对于基本数据类型(如
-
规则三:结构体整体对齐
- 结构体的总大小必须是其内部最大(基本类型)成员大小(字节数)的整数倍。继续上面的
struct S {char a; int b;}
例子,内部最大(基本类型)成员是int
,占 4 个字节,而结构体S
目前大小为 1(char
)+ 3(填充)+ 4(int
)=8 字节,8 是 4 的倍数,所以结构体S
的大小为 8 字节。
- 结构体的总大小必须是其内部最大(基本类型)成员大小(字节数)的整数倍。继续上面的
// 32位机
struct S{
char a; //a占编号0,由于c是int类型,从编号4开始,故此处偏移量为3个字节
int c; // 到这里共8个字节
short b; // 到这里10个字节
} // 整体对齐,最大基本类型成员int的整数倍,故补2个字节
// 总字节: 1 + 3 + 4 + 2 + 2 = 12
嵌套结构体对齐规则
-
当结构体中有嵌套的结构体时,嵌套结构体的对齐要求和普通成员一样,它的起始存储位置是它内部最大成员大小的整数倍。并且,结构体的总大小也需要考虑嵌套结构体的对齐情况。
-
//32位系统 struct Inner { char c; int d; }; // 8个字节 struct Outer { char a; // 以int大小开辟空间,占4个字节 struct Inner in; }
对于
Inner
结构体,大小为 8 字节(前面分析过类似结构)。对于Outer
结构体,char a
占 1 个字节,然后Inner
结构体的起始位置要按照Inner
内部最大成员(int
,4 字节)对齐,所以在a
之后会有 3 个字节填充,Inner
结构体占 8 字节,所以Outer
结构体总大小为 1 + 3+ 8 = 12 字节。
指定对齐原则
使用#pragma pack(value) 改变默认对齐
格式:#pragma pack(value)指定对齐值是value
注意:
#pragma pack 指令
-
语法和作用
- 语法:
#pragma pack(value)
,其中value是一个正整数,表示字节对齐数。这个指令告诉编译器按照value字节的边界来对齐结构体的成员。 - 作用:它可以改变编译器默认的对齐规则,使结构体成员的存储更加紧凑或者按照特定的要求对齐。当设置
n = 1
时,结构体成员将紧密排列,没有额外的填充字节,这种方式可以节省内存空间,但可能会牺牲一些访问效率。
- 语法:
-
注意: value的取值只能为0、1、2、4、8、16。取0时是系统默认对齐原则。
// 32位系统中 #include <stdio.h> #pragma pack(2) // 以2字节指定对齐 struct test { char a; // a占编号0,偏移量为1, // 1+1 = 2字节 int b; // 占编号2、3、4、5,偏移量为0 // 2 + 4 = 6字节 double c; // 占编号6、7、8、9、10、11、12 // 6 + 8 = 14字节 }; int main() { printf("size of char: %d\n", sizeof(char)); printf("size of int: %d\n", sizeof(int)); printf("size of double: %d\n", sizeof(double)); printf("size of struct test: %d\n", sizeof(struct test)); } PS E:\code\c_code> cd "e:\code\c_code\" ; if ($?) { gcc test.c -o test } ; if ($?) { .\test } size of char: 1 size of int: 4 size of double: 8 size of struct test: 14