一、结构体
🐁 什么是结构体
⭕️结构体本质上还是一种数据类型,但它可以包括若干个“成员”,每个成员的类型可以相同也可以不同,也可以是基本数据类型或者又是一个构造类型。
⭕️结构体的优点:结构体不仅可以记录不同类型的数据,而且使得数据结构是“高内聚,低耦合”的,更利于程序的阅读理解和移植,而且结构体的存储方式可以提高CPU对内存的访问速度。
结构体类型的声明:
------------------------结构体声明样式------------------------
struct tag
{
member1;
member2;
} variable;
------------------------解释------------------------
▶ struct是结构体关键字
▶ tag是结构体的标签名,是自定义的
▶ struct tag就是结构体类型
▶ {}里面放的是成员列表
▶ variable是变量▶ member1 , member2 是结构体成员
▶结构体成员的定义方式与变量和数组的定义方式相同
▶结构体成员,只是不能初始化。
🐂结构体的定义与基础结构
知识点1:
结构体的定义:
🐵结构体也是一种数据类型,它由程序员自己定义,可以包含多个其他类型的数据。
🙈像 int、float、char 等是由C语言本身提供的数据类型,不能再进行分拆,我们称之为基本数据类型。🙉而结构体可以包含多个基本类型的数据,也可以包含其他的结构体,我们将它称为复杂数据类型或构造数据类型。
知识点2:
结构体的基础结构:
🔜先定义结构体类型,再定义结构体变量struct Student{ //声明结构体 结构体名 char name[20]; //姓名 int num; //学号 float score; //成绩 char addr[30]; }; struct Student stu1; //定义结构体变量
🖖定义结构体类型的同时定义结构体变量
struct Student{ char name[20]; int num; float score; }stu1; //在定义之后跟变量名
👿直接定义结构体变量
struct { //没有结构名 char name[20]; int num; float score; }stu1; //匿名定义结构体变量
注意:
▶只有结构体变量才分配地址,而结构体的定义是不分配空间的
▶结构体中各成员的定义和之前的变量定义一样,但在定义时也不分配空间
▶结构体变量的声明需要在主函数之上或者主函数中声明,如果在主函数之下则会报错
🐅结构体的使用
1️⃣结构体的初始化
知识点1:
①和其它类型变量一样,对结构体变量可以在定义时指定初始值。
struct books // 结构体类型 { char title[50]; char author[50]; //结构体成员 char subject[100]; int book_id; }book={"C 语言","xingaosheng","编程语言",12345}; //结构体变量的初始化 //定义结构体的同时定义的变量是全局变量
②也可在定义变量后初始化
struct test { char c; short tes; int num; }ad; struct Stu { struct test B;//结构体嵌套 char name[20]; int age; char id[20]; }s1,s2; //全局 int main() { //str是局部变量 struct Stu str={{'W',13,18},"李四",20,"201312"}; //创建结构体变量 return 0; }
注意: 结构体初始化赋值时是顺序赋值,没有赋值成员的默认为0
2️⃣结构体的成员访问
知识点1:
🔑为了访问结构的成员,我们使用成员访问运算符( . )。
🔑引用形式:<结构体类型变量名> . <成员名>
注意:结构体变量不能整体引用,只能引用变量成员
#include <stdio.h> struct funtr // 无标签名,匿名结构体 { char name[20]; //姓名 int num; //学号 int age; //年龄 char group; // 所在小组 float score; // 成绩 }stu1; // 结构体变量 int main() { // 给结构体成员赋值 struct funtr stu1 = {"Tom",12,18,'A',123.3f}; printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score); return 0; }
运行结果:
注意:
☸️结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据,需要内存空间来存储。
☸️注意初始化值的类型和顺序要与结构体声明时成员的类型和顺序一致
3️⃣ 结构体数组
知识点1:
🆘 结构体数组:是指数组中的每一个元素都式结构体。
🆘 结构体数组常被用来表示一个拥有相同数据结构的群体。struct Employee { char name[50]; //名字 int age; //年龄 int number; //身高 }; struct Department { char name[50]; int numEmployees; struct Employee employees[100]; //结构体嵌套结构体数组 该部门有多少人 }; int main() { struct Department departments[3]; // 假设有3个部门 return 0; }
🔄结构体数组在定义的同时也可以初始化
struct stu { char name[20]; //姓名 int num; //学号 int age; //年龄 char group; //所在小组 float score; //成绩 }class[5] = { {"Li ping", 5, 18, 'C', 145.0}, {"Zhang ping", 4, 19, 'A', 130.5}, {"He fang", 1, 18, 'A', 148.5}, {"Cheng ling", 2, 17, 'F', 139.0}, {"Wang ming", 3, 17, 'B', 144.5} };
🔄当对数组中全部元素赋值时,也可以不给出数组长度
struct Student stu[] = {{"Mike", 27, 91},{"Tom", 15, 88.0}};
注意:也可不进行赋值,在需要添加值时进行给值,但需要访问结构体
知识点2:
结构体数组的使用:有若干个部门,包含部门信息,人员数量,人员信息
#include <stdio.h> #include <string.h> struct Employee { char name[50]; //名字 int age; //年龄 int number; //身高 }; struct Department { char name[50]; int numEmployees; struct Employee employees[100]; //嵌套结构体数组 表示人员数量 }; int main() { struct Department departments[3]; // 假设有3个部门 // 部门1的信息 strcpy(departments[0].name, "部门A"); departments[0].numEmployees = 3; strcpy(departments[0].employees[0].name, "员工1"); departments[0].employees[0].age = 25; departments[0].employees[0].number = 180; strcpy(departments[0].employees[1].name, "员工2"); departments[0].employees[1].age = 30; departments[0].employees[1].number = 190; strcpy(departments[0].employees[2].name, "员工3"); departments[0].employees[2].age = 28; departments[0].employees[2].number = 170; // 部门2的信息 strcpy(departments[1].name, "部门B"); departments[1].numEmployees = 2; strcpy(departments[1].employees[0].name, "员工4"); departments[1].employees[0].age = 22; departments[1].employees[0].number = 160; strcpy(departments[1].employees[1].name, "员工5"); departments[1].employees[1].age = 27; departments[1].employees[1].number = 180; for (int i = 0; i < 2; i++) { printf("部门名称:%s\n", departments[i].name); printf("部门人数:%d\n", departments[i].numEmployees); printf("部门员工信息:\n"); for (int j = 0; j < departments[i].numEmployees; j++) { printf("姓名:%s,年龄:%d ,身高: %d\n", departments[i].employees[j].name, departments[i].employees[j].age, departments[i].employees[j].number); } printf("\n"); } return 0; }
运行结果:
5️⃣结构体指针--------------指向结构体变量的指针
知识点1:
🔑可以定义指向结构的指针,方式与定义指向奇特类型变量的指针类似。🔑定义类型:struct 结构体名 * 结构体指针名
struct Stu *p=&s;
其中 Stu 为结构体名 P 为结构体指针名
定义之后可以在上述定义的指针变量中存储结构变量的地址
✝️为了使用指向该结构的指针访问结构的成员,必须使用->运算符
printf("%d ",p->B.num);
定义结构体指针举例:
struct stu // 结构体类型 或 结构体名 { char *name; //姓名 int num; //学号 int age; //年龄 char group; //所在小组 float score; //成绩 } stu1 = { "Tom", 12, 18, 'A', 136.5 }; //结构体指针 struct stu *pstu = &stu1;
也可以在定义结构体的同时定义结构体指针:
struct stu{ char *name; //姓名 int num; //学号 int age; //年龄 char group; //所在小组 float score; //成绩 } stu1 = { "Tom", 12, 18, 'A', 136.5 }, *pstu = &stu1;
注意:
▶ 结构体变量名和数组名不同,数组名在表达式中会被转换为数组指针,而结构体变量名不会,无论在任何表达式中它表示的都是整个集合本身,要想取得结构体变量的地址,必须在前面加&符号,所以给p赋值只能写成。▶ 结构体和结构体变量是两个不同的概念:结构体是一种数据类型,是一种创建变量的模板,编译器不会为它分配内存空间,就像 int、float、char 这些关键字本身不占用内存一样;结构体变量才包含实实在在的数据,才需要内存来存储。下面的写法是错误的,不可能去取一个结构体名的地址,也不能将它赋值给其他变量。
知识点3:
🔑通过结构体指针可以获取结构体成员,一般形式为:
(*pointer).memberMane //pointer为结构体指针名 pointer->memberName // 或者
🔑第一种写法中, . 的优先级高于 * ,(*pointer)两边的括号不能少
如果去掉括号写成*pointer.memberName,
那么就等效于*(pointer.memberName),这样意义就不对了。🔑第二种写法中,-> 是一个新的运算符,习惯称它为“箭头”
有了它,可以通过结构体指针直接取得结构体成员,
这也是 -> 在C语言中的唯一用途。
知识点4:
结构体成员的使用🛅 结构体的成员可以作为函数的参数,属于值传递(成员是数组的除外)。如:
struct Student{ //声明结构体 Student char name[20]; int num; float score; }; void printNum(int num){ //定义一个函数,输出学号 printf("num = %d \n", num); } struct Student student0 = {"Mike", 27, 91}; printNum(student0.num); //调用printNum 函数,以结构成员作函数的参数 //运行结果:num = 27
注意:函数printNum并不知道也不关心实际参数是不是结构成员,它只要求实参是int类型的就可以了。
🛄结构变量名也可以作为函数的参数传递
void PrintStu(struct Student student){ //定义 PrintStu 函数,以结构变量作函数的形参 student.num = 100; //修改学号 printf("PrintStu 修改后:姓名: %s, 学号: %d, 内存地址: %p \n", student.name, student.num, &student); } struct Student student0 = {"Mike", 27, 91}; PrintStu(student0); //调用 PrintStu 函数,以结构变量名作函数的参数 printf(" 原来:姓名: %s, 学号: %d, 内存地址: %p \n", student0.name, student0.num, &student0);
形参和实参的地址不一样,是在函数中创建了一个局部结构体,然后实参对形参进行全部成员的逐个传送,在函数中对局部结构体变量进行修改并不影响原结构体变量。这样传送的时间空间开销都比较大,特别是当成员有数组的时候,程序效率较低。
所以可以考虑使用指针:
🆕结构体指针可以作为函数的形参进行地址的传递
struct test { char c; short tes; int num; }ad; struct Stu { struct test B;//结构体嵌套 char name[20]; int age; char id[20]; }s1,s2; void prin1(struct Stu t)//结构体定义参数 { printf("%d\n", t.age); } void prin2(struct Stu *ps)//结构体指针--地址传递 { printf("%c\n", ps->B.c); } int main() { //s是局部 struct Stu s ={{'W',13,18},"李四",20,"201312"}; //创建结构体变量 struct Stu *p=&s; //结构体指针指向结构体变量 printf("%d ",p->B.num); prin1(s);//做参数 //传值调用 prin2(&s);//传地址调用 ---重点
6️⃣结构体指针--------------指向结构体数组的指针
知识点1:
在我们想要用指针访问结构体数组的第n个数据时可以用:
struct Student { char cName[20]; int number; char csex; }student1; //结构体变量 struct Student stu1[5]; //结构体数组 struct Student*p; // 结构体指针 p=stu[n]; (++p).number//是指向了结构体数组下一个元素的地址
注意:定义结构体指针必须以结构体的前缀定义,不允许是整型和其他数据类型
7️⃣结构体指针--------------结构体成员是指针类型变量
知识点1:
举例代码:struct Student { char* Name;//这样防止名字长短不一造成空间的浪费 int number; char csex; }student1;
注意:在使用时可以很好地防止内存被浪费,但是注意在引用时一定要给指针变量分配地址,如果你不分配地址,结果可能是对的,但是Name会被分配到任意的一的地址,结构体不为字符串分配任何内存存储空间具有不确定性,这样就存在潜在的危险。
进行代码改进:
struct Student { char* Name; int number; char csex; }stu,*stu; stu.name=(char*)malloc(sizeof(char));//内存初始化
如果我们定义了结构体指针变量,他没有指向一个结构体,那么这个结构体指针也是要分配内存初始化的,他所对应的指针类型结构体成员也要相应初始化分配内存
struct Student { char* Name; int number; char csex; }stu,*stu; stu = (struct student*)malloc(sizeof(struct student));./*结构体指针初始化*/ stu->name = (char*)malloc(sizeof(char));/*结构体指针的成员指针同样需要初始化*/
🐍结构体占用的内存大小
计算结构体占用的字节大小时,已经不再是简单的各个数据类型占用的字节大小的和,而使用字节对齐方式。
求结构体sizeof的三个原则:
🍑结构体变量的首地址是其最宽基本类型成员的整数倍
🍍结构体每个成员相对于结构体首地址的偏移量, 都是该成员大小的整数倍
🥭结构体的总大小是其最宽基本类型成员的整数倍
直接上例题:
struct RTC { char a; //1 float b; //4 char c; //1 short d; //2 char e; //1 }no_1;
看下上面的代码,最宽的基本类型占用4个,所以占用的空间必须是4的整数倍,a取4个字节,但只放在了第一个字节,但b占用了4个,所只能再分配一个4字节空间来存放b,再往下c和d占用3个,4个字节可以存放,但注意,d存放的位置是4个字节的后两个位置,因为在存放的同时也要是2的倍数的空间,而e则还需再额外分配4个空间,即总的空间为 4+4+4+4=16
例题2:
struct RTC { double a; //8 char b; //1 char c; //1 short d; //2 int e; //4 short f; //2 float g; //4 总占用8+8+8=24 }no_1;
例题3:
struct RTC { double a; //4 int b[10]; //40 char c; //1 //1 short *d; //2 char e[3]; //3 //1 short f; //2 //2 float g; //4 4+40+1+1+2+3+1+2+2+4=60 }no_1;
注意:在考虑最宽数据类型时,同时也要考虑放进去的子数据类型是否满足整数倍
结尾:时光匆匆,希望看了文章的小伙伴能早日找到属于自己的一份工作。