在C语言中有三种常见的自定义类型:结构体,联合体,枚举。
1.1 结构体
1.1.1 结构体的声明
struct tag{
member-list; //成员清单
}variable-list; //变量清单
例如:我们创建一个结构体的变量,来描述一个学生。
struct Student{
char name[20];
int age;
char sex[5];
char id[20];
};
1.1.2 结构体的创建与初始化
#include<stdio.h>
struct Student {
char name[20];
int age;
char sex[5];
char id[20];
};
int main(){
struct Student s1={"张三",18,"男","2110000"};//按照顺序初始化
struct Student s2={.name="李四",.id="9850000",.age=18,.sex="女"};
//不按照顺序初始化
return 0;
}
1.1.3 结构体的不完全初始化
我们在创建一个结构体的时候,允许对结构体进行不完全的初始化。省掉结构体名称 tag。但是我们要在结构体的尾部创建变量,并且在后续程序中不能再创建相同结构体的变量了。
#include<stdio.h>
int main(){
struct {
int a;
double x;
chua c;
}x;
//这里创建一个结构体的时候创建了一个变量x,在后续的程序中不能再创建与x相同类型的结构体变量了。
return 0;
}
1.1.4 结构体的自引用
我们在创建一个结构体变量的时候,能不能将同意类型的结构体当成自己的成员呢?如下:
struct Stu{
int data;
struct Stu next;
};
如果上述代码可行的话,那么sizeof(struct Stu)的大小多大呢?我们不难发现我们无法计算出其大小,所以我们不能采用这种方式。但是我们可以用以下方式来实现结构体的自引用:
我们用存储下一个结构体变量的方式来实现自引用。
struct Stu{
int data;
struct Stu *next;
};
1.1.5 结构体的大小与内存对齐
我们现在已经掌握了结构体的基本使用方式,那么结构体在内存中的大小是多少呢?
那么我们必须要掌握一个十分重要的知识: 结构体的内存对齐。
结构体的对齐规则: 1.结构体的第一个变量对齐到结构体变量偏移位置为0的内存处。 2.其他的成员对齐到“对齐数”的整数倍的地方。
对齐数 = 编译器中默认的对齐数 与 变量大小中的较小值
( 在VS中编译器默认的对齐数是8,在Linux的gcc环境下没有默认对齐数。)
3.结构体的大小为最大对齐数(所有变量中最大的对齐数)的整数倍。
4.如果结构体中有结构体的嵌套,嵌套的结构体的最大对齐数是其中最大的对齐数。外层结构体的大小满足规则3.
下面以两个例子来深入体会结构体的对齐规则。
struct SSY{
char a;
int b;
double c;
};//exmple 1.
struct SSS{
int a;
char ch[5];
struct SSY w;
};//exmple 2.
上图为struct SSY在内存之中的存储,类似我们可以得到struct SSS的内存。
那为什么结构体要内存对齐呢?
(1)性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节内存块中。
(2)平台原因
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
总体来说:结构体的内存对齐就是以空间换时间。
1.1.6 结构体传参
我们用一段代码来引入:
#include<stdio.h>
struct SSY{
char a;
int b;
double c;
};
void Print1(struct SSY i){
printf("%c %d %lf ",i.a ,i.b ,i.c);
}
void Print2(struct SSY *j){
printf("%c %d %lf " ,j->a ,j->b ,j->c);
}
int main(){
struct SSY z={'t',4,2.5};
Print1(z);
Print2(&z);
return 0;
}
我们比较一下 Print1 和Print2 函数哪一个更好呢?
答案是:Print2.
我们在进行函数传参的时候要进行压栈的操作,要在栈区拷贝一份临时空间,当我们的结构体过大的时候我们的代码性能就会变差,所以我们在进行结构体传参数的时候进行传地址的操作。
2.2联合体
2.2.1 联合体的声明
联合体像结构体一样,联合体也是由一个或者多个变量构成,但是编译器只为最大的成员分配足够的内存空间。联合体的特点是所有成员共用同一块内存空间。所以联合体也叫:共用体。我们给联合体的其中一个成员赋值时,其他成员的值也跟着变。
#include<stdio.h>
//联合体的声明
union SSY{
char i;
int j;
};
int main(){
union SSY s;//定义一个联合体
s={1};//给联合体进行赋值
printf("%zd",sizeof(s));
//计算联合体的大小,结果为:4
return 0;
}
那我们计算的结果为什么就是4呢?
2.2.2 联合体的内存大小计算
我们通过对联合体和结构体的对比来看看联合体的大小。
struct s{
char c;
int i;
};
union u{
char c;
int i;
};
联合体的大小至少是最大成员的大小,当最大成员的大小不是最大的对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
#include<stdio.h>
int main(){
union un{
char c;
int i;
};
union un u={0};
u.i=0x11223344;
u.c=0x55;
printf("%u",u.i);
//结果为:11223355
return 0;
}
我们由这个例子可以体会到其中的奥妙,对于此结构体 union un u在内存中的存储如下图:
3.3 枚举
3.3.1 枚举的声明
枚举顾名思义就是一一列举,我们可以定义一个变量将它可能出现的值进行一一列举。
enum DAY{
Mon,
Tues,
Wen,
Thur,
Fri,
Sat,
Sun,
};
enum SEX{
male,
female;
};
这里我们举了两个例子,一个是今天是星期几,第二个是性别。其中enum DAY 和enum SEX是枚举类型,{}中的值是枚举常量。枚举常量在枚举类型中的编号默认是从0开始的,但是我们也可以人为的对其进行修改。
3.3.2 枚举类型的优点
我们从枚举类型的定义可以知道枚举和 # define很相似,其实枚举就是 # define 的plus版本。
那么我们为什么要用枚举呢?
标签:struct,自定义,int,C语言,枚举,内存,类型,对齐,结构 From: https://blog.csdn.net/2301_80758704/article/details/137060773<1>.增加代码的可读性和可维护性。
<2>.和#define定义的标识符比较枚举有类型检查,更加严谨。
<3>.便于调试,在编译器的预处理阶段会删除 # define定义的符号。
<4>.使用方便,一次可以定义多个常量。
<5>.枚举类型遵循作用域的规则。