在之前的学习中我们了解了非常多的数据类型如  int double float char 数组啊等等

但是在我们的需要上这些数据类型能够准确描述一个学生的全部信息嘛

例如

1.学校的名称

2.学生的学号

3.学生的姓名

显然用上述这些数据类型是不能描述清楚一个学生的类型的

因此,我们就不得不提出我们接下来的内容自定义类型

自定义类型有三种

1.结构体

2.联合体

3.枚举

一.结构体

1.1结构体的定义及初始化

结构体的定义下面我们简单定义一个学生的基本信息表

struct Stu
{
    char name[20];
    int age;
    float grade;
    char id[20];
};
//这里定义了一个结构体的类型名字是struct Stu
int main()
{
    struct Stu S1={"wangwu",20,98.3f,"20231110"};
    //这里定义了一个s1的结构体变量 他的数据类型就是struct Stu。并完成了初始化
}

上述我们定义了一个结构体变量,并完成了初始化。通过这个,了解了结构体的初始化需要{}。

对于访问结构体成员的时候我们就需要运用到几个操作法

. 这个操作符是对于操作符变量的

s1.age = 18;

我们就可以就把s1的年龄改成了18.

->这个操作符是对于结构体类型的指针来完成的

struct s1* ptr1 = &s1;
strcpy(ptr->name,"lisi");

我们就把s1的名字改成了"lisi"

1.2匿名结构体类型

匿名结构体的定义

struct
{
	char i ;
	int j;
	double d;
} s = {'x',100,3,14};
//这样的代码没有给结构体名字,只能用s不能定义其他结构体变量

匿名结构体只能使用一次

1.3结构体内存的大小

struct S
{
	char i ;
	int j;
	double d;
};
int main()
{
    struct S s = {'x',100,3,14};
    printf("%d ",sizeof(s));
}

      为什么是16呢?

我们讲一下结构体在内存中的内存对齐问题

对齐规则

1.结构体的第一成员对其到结构体变量起始位置偏移量为0的地址处

2.其他成员变量要求对齐到该对其数的整数倍处。

对齐数=编译器默认的一个数于该成员变量的大小中较小值(数组不影响对齐数)

-vs中默认的值是8

-linux中gcc没有默认对齐数,对齐数就相当于其本身的大小

3.结构体的总大小为最大对齐数(结构体每个成员都有一个对齐数,所有对齐数中最大的数)的整数倍

4.如果嵌套了结构体 找嵌套结构体中对齐数最大的值作为该嵌套结构体的最大对齐数  结构体的大小是所有最大对其数(含嵌套结构体成员的对齐数)的最大值的整数倍

我们根据此分析一下

假设内存情况如下

我们可以通过预处理指令来改变默认对齐数

// 设置对齐字节数为 n(n 通常为 1、2、4、8 等)
#pragma pack(n)
//恢复默认值
#pragma pack()
为什么要这样内存对齐呢

1.平台原因

不是所有硬件都能访问任意地址的任意位置,某些硬件只能在某些地址处取某些特定类型的数据

2.性能原因

数据结构(尤其是栈)应该尽可能地在自然界边界对齐,原因在于。为了访问未对齐地内存,处理器需要做两次内存访问,对内存对齐的访问仅需一次访问     

总结来说就是:结构体内存对齐就是拿空间来换时间的做法

让占有空间小的成员尽量聚集在一起。

1.4结构体传参

struct Stu
{
    char name[20];
    int age;
    float grade;
    char id[10];
};
char* my_strcpy(char* des,const char* src)
{
    assert(des&&src);
    char* ret = des;
    while(*des++=*src++)
    {
        ;
    }
    return des;
}
void Init_name(struct Stu*ptr)
{
    my_strcpy(ptr->name,"lisi");
    
    printf("%s\n",ptr->name);
}
int main()
{    
    struct Stu S1={"wangwu",20,98.3f,"20231110"};
    Init_name(&S1);
//结构体变量作为作为参数,最好使用传址调用。若是传值调用申请的内存太大了
}

因此在结构体变量作为参数时我们选择传址调用。

1.5结构体位段

 只能在int,unsigned int,signed int,char这几种类型才有位段

位段的定义
struct s
{
    int _a:3;
    int b:5;
}

在定义时:后面的就时该变量所占几个bit位。

位段的内存分配

如果时int 就先给4个字节 然后把变量存进这4个字节中(要注意存储时从右存储还是从左存储)

分配完之后再看这个字节还剩几位够不够下面变量使用的 在vs这个编译器上会选择浪费剩余的空间

若是char 就给一个字节

位段的跨平台问题

1.int位段被当成有符号还是无符号是不确定的

2.位段的最大数目是不能确定的

3.位段成员内存从左向右分配,还是从右向左分配

4.当一个结构体包含两个位段时,第二个成员比较大,无法容纳第一个位段剩余的位时,是舍弃还是利用,这是不确定的

位段的应用

位段在网络协议上的运用是十分有效的,可以节省不占用过多的网络栈

二.联合体

联合体可以有多个成员,其特点是多个变量占用同一块内存叫做联合体也叫共用体

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

联合体举例

//判断当前机器是大端还是小段

union un
{
	int i;
	char c;
};
int check()
{
	union un u1;
	u1.i = 1;
	return u1.c;
}
int main()
{	
	if (check())
	{
		printf("小段\n");
	}
	else printf("大端\n");


	return 0;
}

这里设计一个联合体来判断机器是大小端的问题我们画图解释一下

返回u1.c只会访问第一个字节中的内容

我们运用结构体的内存分配是因为我们拿空间来换取时间相反,联合体这样的存储方式就是极大利用了内存的存储

三.枚举

3.1枚举的定义

enum ENUM_A
{
    x1,  //0
    y1,  //1
    z1=255, //255
    A1, //256
    B1,//257
};

枚举成员的本质其实就是不断++

3.2枚举的优点

1.增加代码的可读性

2.和#define定义的标识符相比枚举有类型检查,更加谨慎

3.便于调试,预处理阶段会删除#define定义的符号

4.枚举常量遵循作用域规则,枚举声明在函数内,只能在函数内使用

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐