每天进步一点点!
什么叫结构体?结构体是由相同或者不同的数据构成的数据集合
在此之前了解一些小知识
1.结构体的声明与定义
1.结构体是一种自定义数据类型
struct 结构体名{
成员变量或者数组
};
特别要注意的是末尾的分号一定不能少,表示结构体设计定义结束
结构体是一种集合,他里面包含了多个变量或者数组,变量的类型可以相同也可以不同,每一个这样的变量或者数组都被称为结构体成员,我们来看一个例子:
include
struct student { int age; char name[10]; int score; char sex[10]; };
stedent为结构体名,他一共有四个成员,分别是age,name ,score,sex,结构体的定义方式和我们在前面学过的数组十分相似,只是数组可以初始化,而在这里是不能初始化
2.先定义结构体类型在定义结构体变量
#include
struct student { int age; char name[10]; int score; char sex[10]; }; struct student s1;//s1为全局变量 int main() { struct student s2;//s2为局部变量 }
3.定义结构体类型的时候同时定义结构体变量
#include
struct student { int age; char name[10]; int score; char sex[10]; }s1, s2; int main() { return 0; }
4.定义匿名结构体
#include
struct { int age; char name[10]; int score; char sex[10]; }s1; int main() { return 0; }
但是这种方式不太好,这个结构体有一种一次性的感觉,不建议这样定义结构体
总结:
1.使用struct 关键字,表示他接下来是一个结构体
2.接下来是一个结构体类型名,可以自己选择
3.花括号,括起来了结构体成员列表,使用的都是声明的方式来描述,用;来结束描述
4.在结束花括号后的分号表示结构体设计定义的结束
5.定义的方式
注意:
结构体里面的成员可以是基本数据类型也可以是自定义数据类型
5.结构体声明的位置,及其作用
2.结构体变量的初始化
1.结构体变量和其他变量一样可以在定义的时候指定初始值 ,结构体变量的初始化用大括号初始化
例:
#include
struct student { int age; char name[10]; int score; char sex[10]; }; int main() { struct student s1={20,"zhangsan",60,"男" };//初始化s1; printf("%d %s %d %s", s1.age, s1.name, s1.score, s1.sex);//打印s1中的成员 return 0; }
运行结果:
结构体变量的初始化用大括号初始化
那下面这种行不行了?
#includestruct student { int age; char name[10]; int score; char sex[10]; }; int main() { struct student s1; s1={20, "zhangsan", 60, "男" }; printf("%d %s %d %s", s1.age, s1.name, s1.score, s1.sex);//打印s1中的成员 return 0; }
这种是不行的,s1在之前已经定义过了,下面在给他值,不是初始化了而是赋值
我们可以看到
此时编译器报错了。在结构体变量定义后,如果仍要对成员变量赋值,此时我们只能一一赋值
#include
#include
struct student { int age; char name[10]; int score; char sex[10]; }; int main() { struct student s1; strcpy(s1.name, "zhangsan"); s1.age = 40; s1.score = 60; strcpy(s1.sex, "男"); printf("%d %s %d %s", s1.age, s1.name, s1.score, s1.sex);//打印s1中的成员 return 0; }
运行结果:
2.结构体中嵌套结构体的初始化
结构体中中嵌套结构体的初始化我们依然使用大括号进行初始化
#include
struct techer { int age; char name[10]; }; struct student { int age; char name[10]; int score; char sex[10]; struct techer s2; }; int main() { struct student s1 = { 20,"zhangsan",70,"男",{20,"老师"} };//初始化s1; printf("%d %s %d %s %s", s1.age, s1.name, s1.score, s1.sex,s1.s2.name);//打印s1中的成员 return 0; }
3.访问结构体成员
访问结构体成员主要用两种
1.使用成员访问运算符 。结构体变量.成员变量
2.使用->运算符。结构体指针->成员变量名
举个例子:
#include
struct techer { int age; char name[10]; }; struct student { int age; char name[10]; int score; char sex[10]; struct techer s2; }; int main() { struct student s1 = { 20,"zhangsan",70,"男",{20,"老师"} };//初始化s1; printf("%d %s %d %s %s", s1.age, s1.name, s1.score, s1.sex,s1.s2.name);//打印s1中的成员 return 0; }
2.结构体指针访问成员变量
#include
struct t { int age; char name[12]; char c; }; int main() { struct t s1 = { 20,"zhansan",'c' };//定义一个结构体变量s1并将其初始化为对应的值 struct t* p1 = &s1; printf("%s %d", p1->name, p1->age); return 0; }
运行结果:
#include
#include
struct t { int age; char name[12]; char c; }; int main() { struct t s1 = { 20,"zhansan",'c' };//定义一个结构体变量s1并将其初始化为对应的值 struct t* p1 = &s1; printf("%s %d", p1->name, p1->age); return 0; }
总结:
1.结构体整体赋值只限定于定义变量的时候并将其初始化,在使用的过程中只能对其一 一赋值,这一点和数组是类似的。
2.结构体是一种自定义类型,是创建变量的模板,不占用空间,结构体变量才包含了实实在在的数据,需要空间来存储
4.结构体传参
1.可以把结构体作为函数参数,传参的类型和其他变量或指针类似
第一种传参方式,传结构体用结构体来接收
#include
#include
struct t { int age; char name[12]; char c; }; void print(struct t s2) { printf("%d %s", s2.age, s2.name); } int main() { struct t s1 = { 20,"zhansan",'c' };//定义一个结构体变量s1并将其初始化为对应的值 print(s1); return 0; }
运行结果:
方法二:
传结构体变量的地址:
#include
#include
struct t { int age; char name[12]; char c; }; void print(const struct t* s2) { printf("%d %s", s2->age,s2->name); } int main() { struct t s1 = { 20,"zhansan",'c' };//定义一个结构体变量s1并将其初始化为对应的值 print(&s1); return 0; }
运行结果:
两种传参方式都能达到目的,那哪一种传参方式更好 了答案是第二种。原因是第一种是值传递,会创建一个临时变量将实参的值拷贝给形参。如果结构体太大,传递的效率就会很低,空间的浪费很大,但是第二种传递的方式是传地址,只需要拷贝4个或者8个字节。如果要传参尽量选择第二种
5.结构体数组
所谓的结构体数组,指的是结构体里面的每一个元素都是结构体,在实际中常用来表示拥有相同数据结构的群体,比如一个班的学生
定义结构体数组和定义结构体变量的方式类似定义时可以初始化
例:
#include
#include
struct student { int age; char name[12]; char c; }; int main() { struct student arr[3] = { {20,"zhangsan",'c'},{30,"lisi",'d'},{40,"wangwu",'p'} }; printf("%d %d %d", arr[0].age, arr[1].age, arr[2].age); }
运行结果:
6.结构体指针
顾名思义就是指向结构体的指针,方式和定义整型指针那些类似,
定义格式: struct 结构体名*+结构体指针名字
例:struct student *struct _pointer;
struct_pointer=&s1;
访问成员时使用->运算符
struct_pointer->+成员名
#include
#include
struct student { int age; char name[12]; char c; }; int main() { struct student s1 = { 22,"zhangli",'c' }; struct student* struct_pointer = &s1; printf("%d", struct_pointer->age); }
运行结果:
注意:
结构体和结构体变量是两个不同的概念:结构体是一种数据类型,是一种创建变量的模板,编译器不会为它分配内存空间,就像 int、float、char 这些关键字本身不占用内存一样;结构体变量才包含实实在在的数据,才需要内存来存储。下面的写法是错误的,不可能去取一个结构体名的地址,也不能将它赋值给其他变量:
下列写法是错误的
#include
#include
struct student { int age; char name[12]; char c; }; int main() { struct student* p = &student; struct student* s = student; }
7.获取结构体成员
#include
#include
struct student { int age; char name[12]; char c; }; int main() { struct student s1 = { 2,"zhansan",'e' }; struct student* pointer = &s1; printf("%d\n", pointer->age); printf("%d", (*pointer).age); }
运行结果:
7.结构体变量的引用(输入和输出)
结构体变量的输入scanf和输出printf和其他变量的操作是一样的
1.值得注意的是.的优先级是比较高的
2.(如果结构体的成员本身是一个结构体,则需要继续用.运算符,直到最低一级的成员。
#include
#include
struct student { int age; char name[12]; char c; struct Date { int year; int month; int day; }brirthday; }stu1; int main() { printf("%d", stu1.brirthday);//这样写是错误的,因为brirthday是一个结构体 scanf("%d", &stu1.brirthday.month);//正确; }
8.结构体内存对齐(重点)
结构体中的内存对齐是用空间换时间的一种内存操作。
一.结构体对齐的规则
1、 数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3、结合1、2可推断:当#pragma pack的n值等于或超过所有 数据成员长度的时候,这个n值的大小将不产生任何效果。
1) 结构体变量的首地址是其最长基本类型成员的整数倍;
备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找 内存地址能是该基本数据类型的整倍的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。
2)结构体每个成员相对于结构体首地址的 偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要, 编译器会在最末一个成员之后加上填充字节(trailing padding)。
备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。
4) 结构体内类型相同的连续元素将在连续的空间内,和 数组一样。
5) 如果结构体内存在长度大于处理器位数的元素,那么就以处理器的倍数为对齐单位;否则,如果结构体内的元素的长度都小于处理器的倍数的时候,便以结构体里面最长的 数据元素为对齐单位。
2.为什么要设置结构体内存对齐?
3.结构体大小的计算
计算结构体的大小需要按照上面的规则:
在次说明一下:
下面我们来实践一下,光说不练,假把式
#include
#include
struct student { int age; char a; }stu1; int main() { printf("%d", sizeof(stu1)); return 0; }
首先按照上面的规则第一个元数放到偏移量为0的地方,第一个元素的类型是整型,大小为4个字节,而第二个元素的a的类型是char,大小是1个字节。而编译器默认的最大对对齐数是8,两者取最小值为1,那么a就要放到1的倍数下。此时a和age一共占用了5个字节,但所有成员里面最大对齐数为4也就是age,5并不是4的倍数,所以编译器会浪费3个字节的空间对齐,所以这个结构体的大小为8
运行结果:
下面我们再来看一道题
#include
#include
struct student { char c1; char c2; int i; }stu1; int main() { printf("%d", sizeof(stu1)); return 0; }
运行结果:
内存分布图:
那么这一个结果又是多少了?
#include
#include
struct student { char c1; int i; char c2; }stu1; int main() { printf("%d", sizeof(stu1)); return 0; }
运行结果:
内存图:
下面我们在来看一个例子结构体嵌套结构体的大小又应该如何计算
例:
#include
struct s3 { double d; char c; int i; }; struct s4 { char c1; struct s3 s; double d; }; int main() { struct s4 t; printf("%d ", sizeof(t)); }
运行结果:
#pragma pack()修改默认对齐数
例子:
#include
#pragma pack(1) struct s4 { char c1; double d; }; int main() { struct s4 t; printf("%d ", sizeof(t)); }
运行结果:
答案为9,我们通过#pragma修改了默认对齐数,修改为1,所有成员的大小和1取最小值,那么所有成员的对齐数为1,也就是挨着放的,9所以大小为9
offfsetof及其实现
offsetof是一个库函数在
中,其作用是计算成员变量在结构体中的偏移量·
函数原型:
如何使用了:
#include
#include
struct s4 { char c1; double d; }; int main() { struct s4 t; printf("%d ", offsetof(struct s4, d)); }
运行结果:
原理已经在上面说过了在这里就不过多赘述
实现offsetof,他实际是一个宏
代码实现:
#include
#include
#define OFFSETOFF(struct_type ,member)(int)&((struct_type*)0)->member struct s4 { char c1; int a; double d; }; int main() { printf("%d ", OFFSETOFF(struct s4, a)); }
联合体
1.定义一个联合体的格式和结构体类似
union 联合体名{
成员列表
};
成员列表中有若干成员,成员形式一般为:类型说明符 成员名。
与结构体,枚举一样,联合体也是一种构造类型
2.联合体定义的方法
方法一:先创建模板后定义变量
#include
union student{ int a; char c; }; int main() { union student s1; return 0; }
方法二:创建模板和变量
#include
union student { int a; char c; }s1; int main() { return 0; }
方法三:省略联合体名,也就是匿名联合体
#include
union { int a; char c; }s1; int main() { return 0; }
联合体的初始化和结构体基本一样,在这里就不过多赘述
2.联合体的大小计算
当没有定义#pragma pack()这种指定value字节对齐时,他的计算规则时联合体中最大成员所占的内存的大小,并且必须为最大类型所占字节的倍数
下面我们来看一个例子:
#include
union { char s[10]; int a; }s1; int main() { printf("%d", sizeof(s1)); return 0; }
运行结果:
在这个联合体中最大的成员的大小为10也就是那个数组,但是最大类型所占字节数的大小为4.并不是4的整数倍所以编译器会浪费2个字节,所以大小为12个字节
3.使用联合体判断大小端:
代码如下:
#include
union stu { int a; char c; }; int main() { union stu s; s.a = 1; if (s.c == 1) { printf("小端"); } else { printf("大端"); } return 0; }
博主实力有限如有错误请在评论区留言,如果觉得写的不错可以点个赞,谢谢!
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/229626.html原文链接:https://javaforall.net

