为什么有自定义类型?
C语言中的数据类型有int,float,double,short,char等,但是对于一些复杂的结构体我们还是不能用我们已有的类型来进行定义。比如说你想要描述一个人,你可能需要描述身高,电话,肤色,住址,工作等特征,所以我们可以自定义一个类型,类型中包含很多我们需要的特征。比如说:结构体类型,枚举类型,联合类型。
1.结构是一些值的集合,这些值成为成员变量,结构的每个成员可以是不同类型的变量。可以是整型,浮点型,函数,结构体
struct name
{
成员变量;
}变量;
如:
- struct 为结构体关键字 Stu 为结构体标签 struct Stu 为结构体类型(可以类比于int)
struct Stu
{
// 成员变量
char name[20];
short age;
char tele[12];
char sex[5];
}s1,s2,s3;//s1,s2,s3是三个全局的结构体变量
int main()
{
struct Stu s;//s为局部变量
return 0;
}
- 另一种方式
typedef struct Stu
{
// 成员变量
char name[20];
short age;
char tele[12];
char sex[5];
}Stu;(在此处Stu是类型不是变量)
此句话的意思就是说我可以给struct Stu 起另一个名字叫做Stu。在主函数中,定义一个结构变量就可以写成Stu n;或者struct Stu n;
结构体在定义的时候,是不能结构体中包含一个结构体的,但是可以包含一个结构体的地址。
比如:struct node
{
int a;
struct node* su;
}
2.在结构体中包含另一个结构体的时候,在初始化的时候,结构体要用大括号括起来。
如下:
使用两种不同的访问成员的方式打印:
printf1和printf2哪个更好些?
首选printf2,因为函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能下降。
3.结构体内存对齐
结构体对齐规则:
1.第一个成员在与结构体变量偏移量为0的地址处。
2.其他成员变量要对齐到对齐数的整数倍的地址处。
对齐数=编译器默认的一个对齐数与改成员大小的较小值。
VS中默认的值为8(gcc编译器是没有对齐数的)
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
为什么存在内存对齐?
1.平台原因(移植原因):不是所有的硬件平台都能访问任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问金需要一次访问。
总体来说:结构体的内存对齐是拿空间换取时间的做法。
那么在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的变量尽量集中在一起。
#pragma pack(对齐数的值) //设置默认对齐数
#pragma pack() // 取消设置默认对齐数
4.offsetof(类型名,结构体成员) 可以计算结构体成员偏移量
offsetof是一个宏,不是一个函数,所在头文件为#include <stddef.h>
等到学到宏的时候再加以补充
5.什么是位段?
位段的声明和结构是类似的,有两个不同:
- 位段的成员必须是int,unsigned int或者 signed int 还可以是short, char
- 位段的成员名后面有一个冒号和一个数字。
比如:struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
a本来是占4个字节的,但是他用不了那么多空间,只用给他留2个空间就够了。所以位段的作用可以理解成是为了节省空间。
A就是一个位段类型,一个特殊的结构体类型。
位段的内存分配:
1.位段的成员必须是int,unsigned int或者 signed int 还可以是short(属于整型家族)
2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
3.位段设计很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
6.枚举:顾名思义,就是一一列举的意思。
枚举的定义
enum Day
{
//枚举的可能取值:枚举常量
//默认的初值为0,1,2,往后依次增加1。但是有时默认的值并不是我们想要的值,我们可以这样写:Mnotallow=2,这样是给常量赋初值。
Mon, //0
Tues, //1
Wed, //2
Thur, //3
Fri, //4
Sat, //5
Sun //6
};
int main()
{
enum Day c=Mon;
return 0;
}
为什么使用枚举?
我们可以使用#define定义常量,为什么非要使用枚举?枚举的优点:
1.增加代码的可读性和可维护性
2.和#define定义的表示符比较,枚举有类型检查,更加严谨
3.防止了命名污染(封装)
4.便于测试
5.使用方便,一次可以定义多个常量
7.联合(共用体)
union Un
{
char c;
int i;
}
int main()
{
union Un u;
printf("%d\n",sizeof(u)); //4
printf("%p\n",&u); //O0F1F90C
printf("%p\n",&(u.c)); //O0F1F90C
printf("%p\n",&(u.i)); //O0F1F90C
}
可以观察到u,u.c,u.i他们三者共用一块空间,所以就要求不能同时使用联合体里面的成员。
8.存储的大小端字节序问题
一个16进制数字
int a=0x00000001
低地址……………………………………>高地址
……[][][][][][][00][00][00][01][][][][][]…… 大端字节存储模式
……[][][][][][][01][00][00][00][][][][][]…… 小端字节存储模式
讨论一个数据,放在内存中的存放的字节顺序
我们在写程序判断到底是大端还是小端的存储方式的时候,我们可以比较第一个字节里面的数字,如果第一个字节内的数字是1,那么就是小端存储。
int main()
{
int a=1;
(char*) &a;
if((*(char*)&a)==1) //此处借助一个char*的强制类型转化,因为转换成char类型后,可以访问一个字节的长度。
{
printf("存储方式为小端字节序存储模式");
}
else
{
printf("存储方式为大端字节序存储模式");
}
return 0;
}
同时也可以用联合体的方式来编写代码
联合大小的计算:
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
比如:union Un()
{
int a; //4 默认对齐数是8 ,所以4和8相比取4,
char arr[5]; // 1,默认的对齐数8, ,1和8相比取1,综合对齐数应该是4, 数组虽然是占用了5个空间,但是他还是要满足是4的倍数,所以最终的值取8.
};
int main()
{
union Un u;
printf("%d\n",sizeof(u)); //8
}