自定义类型包括结构体、枚举、联合等,本文着重讲解结构体。
我们知道变量类型有整型、字符型、浮点型等,但是在生活中,我们不局限于这几种类型,比如描述:人、书,树等。这些都不能用以前单一的类型来呈现。所以我们就有了自定义类型。首先,我们来看结构体。
1 结构体
1.1 介绍
结构体是一些值的集合,这些值称为成员变量。结体构的每个成员可以是不同类型的变量。
1.2 声明
例如:
假设定义一个学生类型
//定义结构体类型
struct Stu
{
//成员变量
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}; //分号不能丢
1.3 变量的定义
我们已经介绍了如何创建一个结构体,那么如何使用呢?下面来看:
说,创建结构体变量,可以这样创建(这里我们使用上面创建好的结构体):
int main()
{
//创建结构体变量
struct Stu s1;
struct Stu s2;
return 0;
}
也可以:
//定义结构体类型
struct Stu
{
//成员变量
char name[20];
int age;
char sex[5];
char id[20];
}s3,s4;// 结构体变量
这两种创建方式在于前面的是局部变量,后面的是全局变量。
1.4 特殊的声明
在声明结构体的时候,可以不完全的声明。
比如:
//匿名结构体
struct
{
int a;
char c;
}s1;//匿名结构体变量只能在这后面定义。
因为是匿名结构体,省略掉了类型名,所以无法在其余位置定义变量,只能在这后面定义。
我们现在看,这两种代码:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
我们发现,这两个结构体除了后面的变量名有所区别,其余基本一致。
那么,在上述代码的前提下,这个代码能否实现?
p = &x;
我们在编译后发现,编译器会发出警告。
警告:编译器会把上面的两个声明当成完全不同的两个类型。 所以是非法的。
现在,我们了解了匿名结构体,那我们想,匿名结构体可不可拥有名字呢?
答案当然是可行的。
我们来看:
typedef struct
{
int a;
char c;
}S;//注意:这里的S是用typedef重定义的类型名
要注意的是,S和我们前面的s1是不一样的!
1.5 结构体的自引用
我们想,在结构中包含一个类型为该结构本身的成员是否可以呢?
什么意思呢?
比如:
将同种结构体类型的变量,用这种链条式的结构串联起来,就叫结构体的自引用。
先看代码:
//代码1
struct Node
{
int data;
struct Node next;
};
好,问题来了,这样是否可行?
如果可行的话,那么sizeof(struct Node)是多少呢?
我们发现,无法求出它的大小,我们在结构体成员中定义了一个此结构体的变量,那如果我们要求结构体的大小,在结构体成员中int类型的大小是确定的,但是next变量的大小无法确定,因为我们还不知道结构体的大小。这样一来就自相矛盾了。所以上述代码是不对的。
我们应该这样写代码:
//正确的自引用方式:
//代码2
struct Node
{
int data;
struct Node* next;
};
定义一个此结构体类型的指针变量,我们知道指针就是地址,大小是固定的。所以此结构体的大小就固定。
同时,我们就可以这样来完成自引用:
struct Node
{
int data;
struct Node* next;
};
int main()
{
struct Node n1;
struct Node n2;
n1.next = &n2;//将n2的地址传个n1的指针中
//实现n1调用n2
return 0;
}
现在,我们了解了如何自引用,那我们来想一个问题:
之前,我们知道了结构体是可以匿名的,匿名后又可以用typedef来重起一个名字。现在,又会了自引用,两者结合,看这样的代码可不可行?
代码:
typedef struct
{
int data;
Node* next;
}Node;
我们来看这段代码,我们知道匿名的结构体是无法在其他位置创建变量的,但现在,我们重命名了一下,使得它拥有名字,然后在成员里创建了一个这样类型的指针变量。那可不可行呢?
答案是不行。
这样创建有一个问题,就是,你的名字是在后面创建好的,而你现在要在成员里先用,那就存在一个先后矛盾的问题。所以,不行。
像这种奇怪的代码有很多,一定要注意思考!
1.6 结构体内存对齐
结构体我们已经基本了解了,现在,我们思考一个问题:计算结构体的大小
我们来看这样两个结构体:
struct s1
{
char c1;
int a;
char c2;
};
struct s2
{
char c1;
char c2;
int a;
};
这两个结构体中的成员变量一模一样,那他们的大小又是多少呢?相等?还是不等?
int main()
{
printf("s1:%d\n", sizeof(struct s1));
printf("s2:%d\n", sizeof(struct s2));
return 0;
}
我们通过代码来计算得到:
我们发现,大小是不一样的。这是为什么呢?
下面,来讲解一下内存对齐的问题:
首先,如何计算?
结构体的对齐规则:
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
- VS编译器中默认的值为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
我们以一开始的两个结构体为例:
同理,我们来分析 s2:
那么如果遇到结构体嵌套结构体时,又该如何求大小呢?
这时,就要用到我们的第四条规则:
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
例如:
struct S2
{
char c1;
char c2;
int a;
};
struct S4
{
char c1;
struct S2 s2;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S4));
return 0;
}
运行结果:
分析:
通过一系列的代码,我们了解了结构体是如何内存对齐的,那么为什么要内存对齐呢?
为什么存在内存对齐?
大部分的参考资料都是如是说的:
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
1.7 修改默认对齐数
默认对齐数是可以修改的。
通过使用#pragma 这个预处理指令就可以修改。
如:
#pragma pack(1)//设置默认对齐数为1
struct s1
{
char c1;
int a;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct s1));
return 0;
}
#pragma pack()//取消设置的默认对齐数,还原为默认
结果:
结束语
关于结构体,就说到这里,感谢观看,谢谢!!!