自定义类型:结构体

时间:2024-04-24 20:53:24

正文

        结构体是什么?结构体是一种是自定义的类型,当我们想要定义一个学生的成绩时我们可以使用short或者double的数据要存储成绩,学生的年龄可以用int类型的数据来存储。如果想要定义一个学生的类该如何定义呢?这时我们就可以使用自定义的类型:结构体。

1.结构体类型的声明

        结构体可以定义任何一个对象,例如:定义一个学生类,学生所拥有的属性是什么?姓名、年龄、性别、年纪......等等。那么如何来定义这个学生类呢?

//定义一个学生类
struct student
{
    char name[100];//学生姓名
    char sex[10];//性别
    int age;//学生年龄
};

结构体的创建和初始化

#include<stdio.h>
int main()
{
    //声明一个学生类
    struct student
    {    
        char name[100];//学生姓名
        char sex[10];//性别
        int age;//学生年龄
    };
    //有了学生类的结构体我们就可以定义一个学生
   struct student s1={"zhangsan","女",20};//这样我们就创建一个学生
   //我们可以使用printf函数来打印学生的信息
   printf("%s %s %d",s1.name,s1.sex,s1.age);  
}

特殊结构体的声明

在声明结构体的时候不够完全,这样的结构体被称为匿名结构体,这样的结构体只能使用一次。这样的结构体的是不合法的。

//定义一个学生类
struct 
{
    char name[100];//学生姓名
    char sex[10];//性别
    int age;//学生年龄
}x;

2.结构体的自引用

结构体如何自引用?在结构体中再创建一个同样类型的结构体?


struct student
{
    char name[100];//学生姓名
    struct student s1;
};

上面的写法是否正确?如果上面的写法是正确的话那么sizeof(struct student)的值是多少呢?这样的写法是错误的,这样去计算结构体的大小是结构体s1中有s1s1s1中还有s1,这样下去就是无穷多个s1的大小无法计算结构体的大小,所以这样的写法是错误的。正确的写法应该是创建一个结构体类型的指针变量去指向一个结构体。注:结构体的自引用运用于链表。

正确的写法:

int main()
{
	struct student
	{
		int age;
		struct student* next;//结构体的指针
	};
	return 0;
}

3.结构体内存对齐

我们知道任何变量都是有大小的那么结构体也是变量,结构体也是有大小的。用sizeof函数可以计算出结构体的大小吗?那么下面这个结构体的大小是多少呢?

int main()
{
	struct student
	{
		char naem;
		int age;
		char sex;
	}student;
	printf("%zd\n", sizeof(student));
	return 0;
}

分析一下:char类型的数据占1个字节,int类型占4个字节所以结构体student的大小是6个字节运行一下代码求证一下,当我们运行代码的时候我们会发现并不是我们所分析的那样。是什么原因导致这样的呢?这里我们就需要引入一个很重要的知识点:结构体的内存对齐。

 3.1对齐的规则

对齐的规则:1.结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处。

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

3.对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值。(vs中默认的对数为8,LINux和gcc中是没有默认对齐数的,它们的对齐数就是成员自身的大小)。

4.结构体总大小为最⼤对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的 整数倍。

5.如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构 体的整体大小就是所有最⼤对齐数(含嵌套结构体中成员的对齐数)的整数倍。

3.2练习:问下面结构体的大小是多少?

代码
 //
练习
1 
struct S1
 {
 char c1;
 int i;
 char c2;
 };
 printf("%d\n", sizeof(struct S1));
 //
练习
2 
struct S2
 {
 char c1;
 char c2;
 int i;
 };
//
练习
3
结构体嵌套问题
struct S4
 {
 char c1;
 struct S2 s2;
 double d;
 };

根据上面我们所学的规则以文字来说明不好理解理解这类的理论知识就需要用画图去理解 。

以上只是对程序分析下面运行代码求证一下 和分析的结果一样。

运行结果

4.为什么要存在内存对齐?

举个列子:假设我们的处理器一次可以读取8个字节的内存,我们知道数据在内存中都是连续存放的,如果一个double的数据前面还有一个char类型的数据如果我们要完全的读取这个double类型的数据就需要读取两次,但如果我们采取内存对齐的方式,就只需要读取一次。总的来说内存对齐的方式就是需要用空间来换时间。如果我们想要节省内存,那么只需要将占用空间小的数据放在一起就能相对的减少空间的占用。

5.修改默认对齐数

#pragma这个预处理指令可以修改编译器的默认对齐数。(#pragma只针对有默认对齐数的编译器)

int main()
{
	#pragma pack(1)//修改默认对齐数
	struct s1 {
		char c1;
		int i;
		char c2;
	};
	#pragma pack()//取消修改默认对齐数
	printf("%zd\n", sizeof(struct s1));结果为12
}

6.结构体传参

#include<stdio.h>
struct S {
	int age;
	char name[10];
}s1, * s2;
void print1(struct S s1)
{
	printf("%d\n", s1.age);
	printf("%s\n", s1.name);
}
void print2(struct S* s2)
{
	printf("%d\n", s2->age);
	printf("%s\n", s2->name);
}
int main()
{
	struct S s1 = { 9,"张三" };
	print1(s1);
	print2(&s1);
}

上面有两个函数用来打印结构体中的数据,print1和print2相比,print2更优一些,因为print1函数在运行的时候需要压栈要额外的创建空间,如果一个结构体过大的时候就会占过多的内存,导致性能的下降,当使用print2时传的结构体的指针也就是结构体的的地址,这时压栈的时候就只需要创建一个结构体类型的指针这样更节省内存。

7.结构体的位段

结构体的位段,这里的位是二进制位的意思,尾段的意义和作用最好用画图的方式来理解。尾段的写法就是在结构体数据的后面加上 :和数字。

char类型的数据开始尾段的时候只创建1个字节的内存,
存储了一个数据之后如果不够下一个数据那么就再创建一个字节的内存

int类型的数据开始使用尾段的时候只创建4个字节的内存,
存储了一个数据之后如果不够下一个数据那么就再创建4个字节的内存
struct A
{
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};
这个结构体的大小是8

int的类型占4个字节也就是32个比特位,假设_a只存储0,1,2,3这几个数字的话那么换算成二进制的话就只需要两个比特位就够了,尾段的作用就是让原本_a有32个比特位的大小现在只用2个比特位其他的丢掉。

int main()
{
	struct S
	{
		char a : 3;
		char b : 4;
		char c : 5;
		char d : 4;
这个结构体的大小是3
	};
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

画图可以更加直观的体会到尾段的作用。如图

综上,尾段可以有效的节省空间。

但是有些也需要注意:

位段的⼏个成员共有同⼀个字节,
这样有些成员的起始位置并不是某个字节的起始位置,
那么这些位置处是没有地址的。

内存中每个字节分配⼀个地址,
⼀个字节内部的bit位是没有地址的。

所以不能对位段的成员使⽤&操作符,
这样就不能使⽤scanf直接给位段的成员输⼊值,
只能是先输⼊放在⼀个变量中,然后赋值给位段的成员。

 以上就是对结构体的叙述,请大佬们多多指点!也希望大家三连!万分感谢!