结构枚举联合体,成长积累不放弃


结构体,联合体等类型是C语言诞生到目前为止的经典数据结构,使用这些结构,可以极大程度的提高我们的编程的生产力,结构体和链表有着非同一般的关系,学好结构体,后面对链表的掌握也就轻而易举。

一、结构体

数组是一组具有相同类型的数据集合。在实际开发编程中,我们往往需要一组类型不同的数据,如学生登记表,姓名为字符串,学号是整数或字符串,年龄为整数,所在班级为字符串。因为数据类型不同,所以C语言引申出了结构体的数据类型,结构体是一些值的集合,这些值被称作成员变量成员变量可以是不同的数据类型

1.结构体类型的声明

//结构体定义形式
strcut 结构体名
{
    
    
	成员变量;
};
//结构体例子
struct student//类型声明 
{
    
    
	char* name;//姓名
	char id[15];//学号
	int age;//年龄
};

在上面我们声明了一个学生类型,它包含3个成员,分别为name,id,age。他们是不同的数据类型,结构体成员的定义方式与变量和数组的定义方式相同,但是不可以初始化
注意:大括号后面的分号不能少,这是一个完整的语句。

2.结构体的自引用

结构体是一种可自定义的数据类型。它可以包含int,char等基础数据类型,也可以包含结构体。

struct A
{
    
    
	int a;
	int b;
};
struct B
{
    
    
	struct A a;//a为结构体A类型
	char c;
};

结构体可以包含其他结构体,结构体可以包含自身这个结构体吗?
答案是肯定的,不过要用指针来存储

struct List
{
    
    
	int val;
	struct List* next;
};

我们如果不用指针存储就会无法正常开辟空间,因为在开辟空间是会计算结构体的空间大小,不用指针储存会出现套娃现象,这个结构体无法正常开辟空间。所以我们需要用结构体指针记录下一个节点的位置,通过指针来访问下一个节点,这也是链表的雏形。

3.结构体的定义和初始化

struct student
{
    
    
	char* name;
	char id[15];
	int age;
};
struct student s = {
    
     "张三","123456789",20 };//初始化
struct A
{
    
    
	int a;
	int b;
};
struct B
{
    
    
	struct A a;
	char c;
};
struct B b = {
    
     {
    
    1,2},'b' };//结构体嵌套初始化
int main()
{
    
    
	printf("student1 name:%s	id:%s	age:%d\n", s.name, s.id, s.age);
	printf("%d %d %c\n", b.a.a, b.a.b, b.c);
	return 0;
}

在这里插入图片描述
结构体成员访问通过.(点运算符),指针形式的结构体通过->(箭头运算符)

4.结构体内存对齐

结构体对齐规则:
1.第一个成员在与结构体偏移量为0的地址处
2.其他成员变量要对齐到对齐数的整数倍的地址处
对齐数为编译器默认的对齐数与该成员大小的较小值,VS中默认为8,gcc中没有默认对齐数。
3.结构体的总大小为最大对齐数(每个成员都有一个对齐数)的整数倍
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐整数倍的地址处,结构体的整体大小就是所有最大对齐数的整数倍(含嵌套结构体的对齐数)
下面通过3个例子深刻了解一下:

struct A
{
    
    
	char a;
	int b;
	char c;
};
struct B
{
    
    
	char a;
	char c;
	int b;
};
struct C
{
    
    
	int b;
	struct A a;
	char c;
};
int main()
{
    
    
	printf("A:%d\n",sizeof(struct A));
	printf("B:%d\n",sizeof(struct B));
	printf("C:%d\n",sizeof(struct C));
	return 0;
}

在这里插入图片描述
在这里插入图片描述
如图所示:结构体A的位置在空间上对应的位置,字符a在偏移量为0的地址处。整形b的对齐数为4,与编译器默认的对齐数相比,整形字节较小,所以对齐数为4。b要到对齐数的整数倍的地址处,所以在4的位置。字符c的对齐数为1(与编译器默认的对齐数相比,字符字节较小),所以位置从8开始。结构体的总大小为最大对齐数(整形的对齐数)的整数倍,所以为12。
我们可以通过offsetof()函数来证明:

#include<stddef.h>
struct A
{
    
    
	char a; 
	int b; 
	double  c;
};
int main()
{
    
    
	//offsetof();//计算结构体偏移的函数
	printf("%d\n",offsetof(struct A,a));
	printf("%d\n",offsetof(struct A,b));
	printf("%d\n",offsetof(struct A,c));
	return 0;
}

在这里插入图片描述
如图所示:

在这里插入图片描述
结构体B的位置在空间上对应的位置,字符a在偏移量为0的地址处。字符c的对齐数为1(与编译器默认的对齐数相比,字符字节较小),所以位置从1开始。整形b的对齐数为4(与编译器默认的对齐数相比,整形字节较小),所以对齐数为4。b要到对齐数的整数倍的地址处,所以在4的位置。结构体的总大小为最大对齐数(整形的对齐数)的整数倍,所以为8。
我们变量设置的位置的改变,可能改变结构体的大小。
结构体C与之类似操作,可以把结构体嵌套的结构体看成一个整体开辟空间,结构体总大小为所有最大对齐数的整数倍(含嵌套结构体的对齐数)。
修改默认对齐数:

#include<stddef.h>
#pragma pack(1)//设置默认对齐数为8
struct A
{
    
    
	char a;
	int b;
	char c;
};
#pragma pack()//取消设置默认对齐数
int main()
{
    
    
	printf("%d\n",offsetof(struct A,a));
	printf("%d\n",offsetof(struct A,b));
	printf("%d\n",offsetof(struct A,c));
	printf("结构体大小:%d\n",sizeof(struct A));
	return 0;
}

在这里插入图片描述
#pragma pack()可以修改默认对齐数,把默认对齐数修改为括号里面的对齐数,第二次#pragma pack()为恢复默认对齐数。
为什么要内存对齐:
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。因为为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
结构体的内存对齐是拿空间换时间的做法。

5.结构体传参

结构体传参传参也有传值调用和传址调用。

struct A
{
    
    
	char a;
	int b;
	char c;
};
void change1(struct A num)
{
    
    
	num.a = 'z';
	num.b = 8;
	num.c = 'x';
	printf("调用后:A(a):%c	A(b):%d		A(c):%c \n", num.a, num.b, num.c);
}
void change2(struct A* num)
{
    
    
	num->a = 'z';
	num->b = 8;
	num->c = 'x';
	printf("调用后:A(a):%c	A(b):%d		A(c):%c \n", num->a, num->b, num->c);
}
int main()
{
    
    
	struct A num = {
    
     'a',4,'c' };
	printf("传递前:A(a):%c	A(b):%d		A(c):%c \n", num.a, num.b, num.c);
	change1(num);//传值调用
	printf("传递后:A(a):%c	A(b):%d		A(c):%c \n", num.a, num.b, num.c);
	printf("-----------------------------------------------------------------\n");
	printf("传递前:A(a):%c	A(b):%d		A(c):%c \n", num.a, num.b, num.c);
	change2(&num);//传址调用
	printf("传递后:A(a):%c	A(b):%d		A(c):%c \n", num.a, num.b, num.c);
	return 0;
}

如果我们要修改结构体里面的值我们要进行传递址,如果我们要打印等其他操作用什么传递呢?
结构体传参时,要传结构体的地址。
原因如下:在函数传参时,参数是要压栈的,会有时间和空间上的系统开销,当传递一个结构体对象的时候,结构体过大,参数压栈的系统开销就比较大,会导致性能下降。如果我们不希望改变结构体的内容,我们可以在参数前加const。

6.结构体实现位段

什么是位段:位段的声明和结构体类似,但也有不同的地方。
1.位段的成员必须是int,unsigned int和signed int,char等。
2.位段的成员函数后边有一个冒号和一个数字
例如:

struct A
{
    
    
	int a : 4;
	int b : 5;
	int c : 20;
	int d : 30;
};
int main()
{
    
    
	printf("%d\n", sizeof(struct A));
	return 0;
}

在这里插入图片描述
为什么是8个字节呢?
因为冒号后的数字代表占的比特位的个数,a,b,c三个总共29个比特位,加上d会大于32位,所有d单独在开一个整形字节,所以总共8个字节。
位段的内存分配:
1.位段的空间上是按照需要以4个字节(int)或一个字节(char)的方式来开辟的。
2.位段有很多不确定因素,位段是不可以跨平台的。

二、枚举

如果一个变量只有几种可能的值,则可以将其定义为枚举类型。所谓的枚举类型是把可能的值一一列举出来。变量的值只限于列举出来的枚举值的范围

1.枚举类型的定义

enum 枚举类型
{
    
    
	枚举成员列表(成员之间以逗号隔开,最后一个后面没有逗号)
};
enum WEEK //一周的枚举定义
{
    
    
	MON,
	TUE,
	WED,
	THU,
	FRI,
	STA,
	SUN
};

在没有显示说明的情况下,第一枚举常量的值为0,第二个为1,以此类推。

2.枚举的使用

enum WEEK //一周的枚举定义
{
    
    
	MON = 1,
	TUE,
	WED,
	THU,
	FRI,
	STA,
	SUN
};
int main()
{
    
    
	enum WEEK now;
	int sum = 0;
	printf("请输入今天星期几\n");
	scanf("%d", &sum);
	switch (sum)
	{
    
    
	case MON:
		printf("tomorrow is TUE\n");
		break;
	case TUE:
		printf("tomorrow is WED\n");
		break;
	case WED:
		printf("tomorrow is THU\n");
		break;
	case THU:
		printf("tomorrow is FRI\n");
		break;	
	case FRI:
		printf("tomorrow is STA\n");
		break;	
	case STA:
		printf("tomorrow is SUN\n");
		break;	
	case SUN:
		printf("tomorrow is MON\n");
		break;
	}
	return 0;
}

在这里插入图片描述
使用枚举需要注意以下几点:
1.同一枚举类型中的枚举常量名字必须互不相同。
2.不能对枚举常量进行赋值操作(定义枚举类型时除外)
3.枚举常量可以用于判断语句
4.一个整数不能直接赋值给一个枚举常量,必须使用该枚举常量所属的枚举类型进行类型强制类型转化。
5.在使用枚举变量时,我们不关心其值的大小,而是其表示的状态。

3.枚举的优点

1.增加代码的可读性和可维护性
2.防止命名污染(封装)
3.有类型检查,更加的严谨。
4.便于调试
5.方便使用,一次可以定义多个常量。

三、联合体

1.联合体类型的定义

联合也是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合体也叫共用体)
例如:

union UN
{
    
    
	char a;
	int b;
};
int main()
{
    
    
	union UN un;
	printf("%d\n", sizeof(union UN));
	printf("%p\n", &un);//联合体的地址
	printf("%p\n", &(un.a));//联合体中a的地址
	printf("%p\n", &(un.b));//联合体中b的地址
	return 0;
}

在这里插入图片描述
从上述代码可以看出联合体的特点:共用同一块空间

2.联合体大小的计算

1.联合体的大小至少是最大成员的大小
2.当成员大小不是最大对齐数的整数倍时,要对齐到最大对齐数的整数倍

总结

自定义类型是我们学习C语言中重要的内容,它们可以为我们后期实现链表等数据结构起到关键性的作用。
大家要多多实例练习。加深C语言的学习。

猜你喜欢

转载自blog.csdn.net/2301_76986069/article/details/130504613
今日推荐