前言👾
在剖析数据的存储时,我提到过自定义类型,其中包括结构体、联合体等,而本篇博客则介绍基本的自定义类型以及各类型特性等;
1.结构体👾
1.1结构体的声明🎈
结构体:由不同数据组成的组合型的数据结构 一般声明方式为:
struct 结构体名
{
成员表列
};
其中,结构体名又称“结构体标记”,花括号内是结构体所包含的子项,成员表列又被称为“域表”,每一个结构体成员是结构体中的每一个域。
1.2定义与初始化结构体类型变量🎈
结构体变量的定义有三种方式,我们逐一来看:
1.2.1先声明结构体类型在进行初始化🎉
#define _CRT_SECURE_NO_WARNINGS
struct Student
{
int num;
char name[20];
char sex;
int age;
char addr[30];
};
int main()
{
struct Student Stu1 = { 10,"kkk",'n',20,"11111" };
return 0;
}
1.2.2声明结构体类型同时定义变量🎉
#define _CRT_SECURE_NO_WARNINGS
struct Student
{
int num;
char name[20];
char sex;
int age;
char addr[30];
}Stu2 = { 30,"zzz",'n',40,"kkkkkkkkk" };
1.2.3不指定类型名而直接定义结构体类型变量🎉
struct
{
int num;
char name[20];
char sex;
int age;
char addr[30];
}Stu2 = { 30,"zzz",'n',40,"kkkkkkkkk" };
此时指定了一个无名的结构体类型,显然不能一此结构体类型区定义其他变量。
1.3结构体数组🎈
当需要多个相同结构体类型的数据时,此刻我们可以引入结构体数组;其中每一个元素都是结构体类型的。 其定义方式如下: 1??
struct 结构体名
{
成员表列
}数组名[数组长度];
2??
struct 结构体名
{
成员表列
};
结构体类型 数组名[数组长度];
1.4结构体指针🎈
其定义方式为(struct 类型名 *p);此时p是一个指向结构体类型的指针变量。
1.4.1指向结构体变量的指针🎉
struct Student
{
int num;
char name[20];
char sex;
int age;
char addr[30];
};
int main()
{
struct Student* pt;
struct Student Stu;
pt = &Stu;
Stu.num = 1;
Stu.age = 20;
Stu.sex = 'M';
strcpy(Stu.name, "zhangsan");
strcpy(Stu.addr, "kkkkkkkk");
printf("%-5d %-5d %-5c %-10s %-10s\n", Stu.num,
Stu.age,
Stu.sex,
Stu.name,
Stu.addr);
printf("%-5d %-5d %-5c %-10s %-10s\n", pt->num,
pt->age,
pt->sex,
pt->name,
pt->addr);
printf("%-5d %-5d %-5c %-10s %-10s\n", (*pt).num,
(*pt).age,
(*pt).sex,
(*pt).name,
(*pt).addr);
return 0;
}
由上述代码易知,pt指针指向结构体,再给结构体赋值时,我们有3中方式,分别是结构体引用,(*p)引用,以及指向运算符->引用。
1.4.2指向结构体数组的指针🎉
struct Student
{
char name[20];
int age;
};
int main()
{
struct Student stu[3] = { {"zhangsan",20},{"lisi",17},{"wangwu",22} };
struct Student* pt;
pt = stu;
for (; pt < stu + 3; pt++)
{
printf("%-10s %-5d\n", pt->name, pt->age);
}
return 0;
}
此时,指针指向结构体数组,使pt先指向数组首元素的地址,从而不断打印,使用方法与指针指向普通数组一致。
1.4.3用结构体变量和结构体变量的指针作参数🎉
- 用结构体变量成员做参数,用法与普通变量一致,属于"值传递",但形参和实参的类型需要保持一致
- 用结构体变量做实参,也是采用"值传递",在函数调用时,形参也需要占用内存单元;这种方式在空间和时间上开销较大,且改变形参后,不能返回主调函数,往往不经常使用。
- 用指向结构体变量的指针作为实参,将结构体变量的地址作为实参进行传递。
1.5结构体内存对齐🎈
1.5.1结构体内存计算🎉
在剖析数据存储中,我提到过每一种类型都是有大小的,因此接下来介绍结构体在内存中的大小。 我们接下来看实例:
1??
struct s1
{
char c1;
int i;
char c2;
};
printf("%d", sizeof(struct s1));
2??
struct s1
{
char c1;
char c2;
int i;
};
printf("%d", sizeof(struct s1));
在前两例,我们可以看到设计结构体时将占用空间小的成员集中在一起比较省空间。 3??
struct s2
{
double d;
char c;
int i;
};
printf("%d", sizeof(struct s1));
4??
struct s1
{
double d;
char c;
int i;
};
struct s2
{
char c1;
struct s1 s1;
double d;
};
int main()
{
printf("%d", sizeof(struct s2));
}
1.5.2内存对齐的原因🎉
- 平台原因:不是所有的硬件平台都可以访问任意地址数据的,某些平台只能在特定的地址处取特定的数据类型,否则抛出异常,这一点与单片机有些许类似。
- 性能原因:数据结构(栈)应尽可能在自然边界处对齐,因为对于未对齐的内存,是需要进行两次内存访问的,而对于对齐的内存,只需要进行一次内存访问。
所以:结构体的内存对齐是空间换时间的做法。
1.6修改默认对齐数🎈
#pragma pack(1)
struct s1
{
char c1;
int i;
char c2;
};
#pragma pack()
struct s2
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct s1));
printf("%d\n", sizeof(struct s2));
return 0;
}
利用#pragma指令进行对齐数的设定; #pragma pack()恢复默认对齐数。
在VS中默认对齐数是8,当然,有的编译器并不存在默认对齐数,所以对齐数就是其类型的大小!
2.位段👾
2.1位段的介绍🎈
位段它的声明与结构与结构体是类似的;
- 位段的成员必须是int、unsigned int、char即整型。
- 位段的成员名后边有一个冒号和数字(数字代表其所占位的大小且不能超过其类型的位的大小)
比如:
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
其中_a占2个比特位,_b占5个比特位,以此类推。
2.2位段的内存分配🎈
- 位段的成员可以int、unsigned int 、char类型;
- 位段的空间按照需要4个字节(int)或者1个字节(char)的方式进行开辟;
- 位段涉及很多不稳定因素,且位段是不跨平台的,注重可移植性的程序应该避免位段。
如下,我们具体看一下位段如何进行存储:
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main()
{
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
return 0;
}
将上述16进制的62 03 04转换为2进制则与我们假设的情况一致!
2.3位段的跨平台问题🎈
- int位段被当成有符号数还是无符号数是不确定的;
- 位段的最大位的数目是不确定的(16位机器上最大是16,32位机器上最大是32);
- 位段中是从左向右还是从右向左分配位是不确定的,我们上述假设是从低位向高位分配,即从右向左分配的;
- 当存在多个位段时,第二个位段比较大,在第一个位段分配完成后,剩下的空间不足以支持第二个位段的分配,所以开辟新字节后是利用之前剩余的位还是不利于剩余的位这一点是不确定的。
因此,总的来说,与结构体相比,位段可以很好的节省空间但是其跨平台的问题仍然不可忽视!
3.枚举👾
3.1枚举的定义🎈
enum Day
{
Mon,
Tues,
Wed,
Whur,
Fri,
Sat,
Sun
};
默认是从0开始的,每次递增1,当然也可以对其重定义初值。 {}中的内容是枚举类型的可能取值,也叫枚举常量。
enum Day
{
Mon,
Tues,
Wed,
Whur,
Fri,
Sat,
Sun
};
enum Day d=Mon;
3.2枚举的优点🎈
- 增加代码的可维护性和可读性
- 与#define相比更加严谨
- 防止了命名污染
- 便于调试
- 使用方便,可以一次定义多个常量
4.联合体(共用体)👾
4.1共用体的定义🎈
其特征就是将多个变量存储到一个内存单元当中,这些变量共同使用这部分内存单元,且这些变量的起始地址是一致的!
与结构体的定义类似:
union 共用体名
{
成员表列
}变量表列;
4.2共用体大小计算🎈
联合的成员是共用一块内存单员的,所以这样一个联合变量的大小最少应该是最大成员的大小即最长的成员的内存长度!
- 联合体的大小至少是最大成员的大小!
- 最大成员的大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍!
union un1
{
char c[5];
int i;
};
union un2
{
short arr[7];
int i;
};
int main()
{
printf("%d\n", sizeof (union un1));
printf("%d\n", sizeof (union un2));
return 0;
}
因此,第一个共用体占据8个字节,同理,第二个共用体占据16个字节。
4.3共用体的特点🎈
- 共用体中一般只使用其中一个变量,不会同时赋值每个变量,即存储单元中只有唯一一个内容
- C99标准,相同类型的共用体可以相互赋值
- C99标准后,可以使用共用体变量作为函数参数;之前,只可以让指向共用体的指针作为函数参数
5.Ending👾
本次自定义类型就到此为止了,内容还是比较晦涩的,希望本篇博客可以帮助到大家,好了,就这样吧🙋?♂?🙋?♂?
|