在前面的学习中,我们了解了整型、浮点型、数组等等,今天小编带着大家来学习一些新的构造类型,这些构造类型各有优劣,接下来小编带着大家一些学习了解这些构造类型。
目录
一、结构体
我们前面学习过数组,数组储存的时一组相同元素的集合,而接下来学习的结构体则是可以储存一组不同类型元素的集合。
1、结构体的声明
以下为结构体声明的基本语法:
struct struct_name
{
member_list;
}variable_list;
其中struct_name为结构体名,member_list为成员列表,variable_list为变量列表,结尾分号一定不能丢!!!
例如用结构体表示一名学生:
struct Stu
{
char name[20]; //姓名
int age; //年龄
char sex[5]; //性别
int Id[10]; //学号
}student1;
在声明以上结构体的同时,还创建了一个结构体全局变量student1。
2、匿名结构体
所谓匿名结构体一般指的是没有结构体名的结构体,该结构体一般在声明同时会创建结构体变量。
struct
{
int a;
char b;
double c;
}x;
3、结构体变量的定义与初始化
结构体变量的定义有如下两种;
//1、声明同时定义
struct A
{
int a;
char ch;
}x1;
//1、声明后定义
struct A x2;
结构体变量的初始化也有如下两种;
//1、声明同时初始化
struct B
{
int b;
char ch;
}n = { 4,'a' };
//2、定义变量时初始化
struct B n2 = { 5, 'b' };
4、结构体变量成员的访问
访问结构体变量的成员有如下两种方法;
struct Stu
{
char name[20];
int age;
};
int main()
{
struct Stu S = { "zhangsan", 20 };
struct Stu* ps = &S;
//通过结构体变量名访问结构体成员
printf("%s %d\n", S.name, S.age);
//通过结构体指针访问结构体成员
printf("%s %d\n", ps->name, ps->age);
return 0;
}
5、结构体的内存对齐 (重点)
关于计算结构体大小时,我们就不得不掌握结构体对齐的知识,以下我们结构体对齐的基本规则以及结构体对齐的意义来讲解。
(1)结构体的对齐规则
a、第一个成员在与结构体变量偏移量为0的地址处。
b、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
(对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。)
c、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
d、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
接下来我们通过实例一一讲解以上四条规则。
//exe1
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct S1));
return 0;
}
根据第一条规则,结构体成员的第一个变量放在偏移量为0处,如下图蓝色方块。
接着根据第二条规则,除了第一个元素以外的元素都放在对齐数整数倍处,而对齐数为数据大小与默认对齐数(VS为8)中较小值,即4与8中较小值,因此此处将i放在偏移为4的地方,如下图橙色方块。依次类推,字符c2也要放在对齐数的整数倍处,字符c2的对齐数则为1与8中较小值,即为1,则c2应放在1的整数倍处,则放在了偏移量为8处,即为绿色方块;
又根据第三条规则,结构体的大小为成员变量对齐数中的最大对齐数的整数倍,不难算出三个成员变量的对齐数分别为1、4、1,最大为4,故结构体应该为4的倍数,图中三个变量已经占了9个字节的空间,为了达到4的倍数,故补充3个字节,到达12。
//exe2
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S2));
}
还是根据如上规则,c1变量放在偏移量为0处,c2 对齐数为自己大小与默认对齐数(VS:8)较小值,即为1,最终放在偏移量为1处,i对齐数为4,即放在偏移为4的整数倍4处,放完所有元素后,发现占用了8个字节,而整个结构体大小应该为结构体成员变量中对齐数中的较大值的整数倍,即为4的整数倍,而8恰好为4的整数倍,故输出8。
//exe3
struct S3
{
double d;
char c;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S3));
}
根据对齐规则,d应该放在偏移为0处,c应该放在对齐数的整数倍偏移处,即1处,i应该放在偏移为对齐数整数倍处,即12处,此时三个成员变量占用了16字节,而结构体大小应该为成员变量中对齐数最大的那个的整数倍,即8的倍数,而16恰好为8的倍数,故输出16。
//exe4
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S4));
return 0;
}
根据对齐规则,c1放在了偏移为0处,第二个成员为结构体,根据第四条规则,结构体对齐到自己最大对齐数的整数倍处,而从第三题我们知s3最大的对齐数为8,故该结构体应该放在偏移为8处,该结构体s3的大小为32,由于太多故图上省略,第三个变量是double型,对齐数为8,故放在了偏移量为8的整数倍处,即24,所有变量加起来一共占用了32字节,而整个结构体的大小为最大对齐数的整数倍,最大对齐数为8,32恰好为8的整数倍,故输出32。
(2)结构体对齐的意义
- 平台原因:不是所有的计算机都可以随机访问任意地址的任意数据,某些特殊机器只能访问特定位置的数据。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
6、结构体传参
在传结构体参数中,我们有两种,分别为以下两种;
struct S
{
int data[1000];
int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
你认为哪种传参更合适呢?
实际上,传址传参更好,当我们传结构体时,形参创建一份结构体的临时拷贝,来保存这个给结构体,如果结构体很大,则浪费了很多的空间,而传址只需要保存一个地址即可,减少了内存的消耗,所以传址更合适。
二、位段
1、结构体实现位段
位段如下代码定义;在每个结构体成员后加一个冒号和数字;
struct A
{
int a : 2;
int b : 5;
int c : 10;
int d : 30;
};
位段中的位实际上是比特位的意思。
2、位段的内存分配
使用位段是须注意下几点;
1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
struct A
{
int a : 2;
int b : 5;
int c : 10;
int d : 30;
};
如上代码,在创建a时,因为a是int型,内存创建4个字节,而a却只用2个比特位,因此还有30个比特位,创建b变量时,b只需5个比特位,剩余30个比特位可以容纳,因此无需重新申请空间,给b分配5个比特位,还剩25个比特位,创建变量c时,c需要10个比特位,于是分给c十个比特位,还剩15比特位,而创建变量d时,d需要30比特位,而剩下15比特位不够用,因此,向内存再次申请4个字节空间,用来存放d的30比特位,而原来剩下来的15比特位空下来,放弃使用。(以上为VS中处理位段)。所以一共向内存申请了两次,每次申请了4字节,一共8字节。
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
用之间我们分析的方法,这里是字符型,因此,依次申请1字节,按上述推理,这里一共申请了3字节,并将申请内存初始化为0。如下图;
a使用3比特位,b使用了4比特位,c使用了5比特位,d使用了4比特位,接下来给a、b、c、d分别赋值,把10赋值给a,10转换成二进制有效位为1010,由于a只有三个比特位,储存进a中只有010被存了进去,12转换成二进制有效位为1100,可全部存入b中,3二进制有效位11,也可全部存入c中,4转换成二进制有效位为100,也可全部存入d中,因此这三个字节最终为0110 0010 0000 0011 0000 0100转换为16进制即为62 03 04。通过调试,我们确实在内存中看到了这一串数字。
3、位段的跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的。
三、枚举
所谓枚举则是一一列举
1、枚举类型的定义
//枚举星期
enum Day
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};//枚举颜色
enum Color
{
Red,
Green,
Yellow
};
以上均为Day、Color等均为枚举类型,其中每个枚举类型中的值成为枚举常量;
这些常量都有默认值,默认从0开始,比如上面Red的值为0,Green的值为1,Yellow的值为2,还可以赋初值,如下;
//枚举颜色
enum Color
{
Red,
Green = 5,
Yellow
};
这样赋值以后Green的值为5,Yellow的值为6,Red的值还是为0;
2、枚举的优点
有些人可能会问了,有了#define,枚举又有上面作用呢?
枚举的优点:
1、增加代码可读性
2、有类型检查
3、防止命名污染
4、便于调试
5、一次可定义多个枚举常量
枚举和#define定义的常量不同的是枚举常量可以用于调试,#define定义的常量在预编译过程中就将所有的数据进行了替换,因此无法调试,而枚举类型不会,可以进行调试,且枚举类型有类型检查。
四、联合体(共用体)
联合体,也有人叫共用体,所谓联合体就是共同使用一块空间,例如;
1、联合体的定义
union A
{
char ch;
int i;
};
int main()
{
printf("%d\n", sizeof(union A));
return 0;
}
当你计算他的大小时,你会发现他们只占4个字节。
实际上,他们在内存中是如下图所示;
我们可以打印出他们的地址,来验证;
2、联合体的大小计算
联合体的大小计算需要遵循以下规则;
1、联合的大小至少是最大成员的大小。
2、当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union Un1
{
char c[5];
int i;
};
int main()
{
printf("%d\n", sizeof(union Un1));
return 0;
}
根据我们结构体大小的计算经验,c的对齐数为1,i的对齐数为4,最大对齐数为4,结果应该为4的整数倍,故输出应该为4的倍数,故不输出5,输出8。
以上三种类型均为自定义类型,到了这里所有的C语言数据类型都以介绍完毕,后面我会带着大家做一个小项目来巩固该文的内容。