前言
我们为什么需要结构体,结构体有什么用?因为在现实生活中有很多复杂的事物,并不能用基本的数据类型来很好的表示出来,为了满足实际需求,才有了结构体这一自定义数据类型。
一、结构体
1.什么是结构体?
结构体是一些值的的集合,这些值被称作成员变量。结构体的每一个成员可以是不同类型的变量。
2.结构体的声明
struct tag
{
member-list;
}variable-list;
例如,我们要描述一个学生,学生的信息有名字,年龄,学号。
struct Stu
{
char name[20];
int age;
char id[15];
};
3.特殊结构体的声明
结构体中有一种特殊的结构体,叫作匿名结构体,该结构体在声明的时候省略了结构体的名称。
struct
{
int a;
char c;
float f;
}x;
在使用匿名结构体的时候,我们不能再定义结构体变量,只能使用声明时已经定义的变量x来使用该结构体。
如果有两个匿名结构体,他们的成员变量相同,例如:
struct
{
int a;
char b;
float f;
}x;
struct
{
int a;
char b;
float f;
}* p;//*p是结构体指针,p指向该结构体。
在这种情况下,我们能否用 *p = &x?
答案是不能的,虽然他们的成员变量相同,但编译器把他们当作两个不同的类型。所以是非法的。
4.结构体的自引用
如果学过C语言的数据结构链表的知识,我们就能发现,链表的结点就用到了结构体的自引用。
结构体自引用的方式有三种:
struct Node
{
int data;
struct Node* next;
};
typedef struct Node
{
int date;
struct Node* next;
}Node;
struct Node;
typedef struct Node Node;
struct Node
{
int data;
Node* next;
};
这三种方式都是正确的,可以在自己编译器上尝试编译一下。
但下面两种方式都是错误的:
struct Node
{
int data;
struct Node next;
};
这种声明是错误的,这种声明实际上是一个无限循环,成员Node是一个结构体,Node的内部还有一个结构体,循环往复。在分配内存的时候,无法确定该结构体的大小,因此是非法的。
typedef struct Node
{
int data;
Node* next;
}Node;
这种写法看似是对的,实际上使用typedef重命名的类型名是从语句结束开始的,在结构体内部并不能使用。
我们在使用结构体自引用的时候,之所以用到结构体指针,是因为指针的大小是确定的,在32位机器上是4字节,62位机器上是8字节,所以在内存分配的时候,就有明确的大小。
5.结构体的定义和初始化
结构体的定义有多种方式,例如在声明结构体的同时,就定义了结构体变量,也可以在函数内部定义结构体变量。
struct A
{
int a;
char b;
float c;
}a1,a2;//a1,a2是全局变量,定义在{}外部
struct A a3;//全局变量
int main()
{
struct A a4,a5;//局部变量,定义在{}内部。
return 0;
}
结构体的初始化
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
struct Point p3 = {3,4};//定义变量的同时初始化。
//结构体变量的初始化也可以不按照声明的变量顺序来
p2 = {.y = 4, .x = 3};//也是可以的,可以自己去试一试。
结构体的嵌套初始化
struct Point
{
int x;
int y;
};
struct line
{
struct Point p1;
struct Point p2;
char type[20];
};
struct line l1 = { { 2,3 } , { 7,1 } , "straight line"};
6.结构体的内存对齐
结构体内存对齐是一个及其重要的考点。
下面有两个结构体,你认为它们的大小是什么?
struct A
{
char c1;
int a;
char c2;
};
struct B
{
char c1;
char c2;
int a;
};
可能有一些不太了解这个知识点的同学,可能就简单的认为c1是char类型占一个字节,a是int类型,占四个字节,c2是char类型占一个字节,加起来是6个字节,是这样的么,我们看一下程序运行的结果:
struct A
{
char c1;
int a;
char c2;
};
struct B
{
char c1;
char c2;
int a;
};
int main()
{
printf("%d", sizeof(struct A));
printf("%d", sizeof(struct B));
return 0;
}
我们可以看到程序运行的结果,结构体A的大小是12个字节,结构体B的大小是8个字节。
这是为什么呢?
这就要引出结构体内存对齐这个知识点了。
结构体对齐有几个规则,我们把这些规则理解了,结构体的大小也就不那么难以计算了。
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍偏移量的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小(字节数)的较小值。
VS默认是8。 - 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
了解了这几个规则,我们再来看一下上面的例子。
关于结构体B的字节大小,大家可以自己去尝试画一下。
下面我们来看一下存在结构体嵌套的结构体的大小该如何计算。
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
7.结构体传参
结构体传递参数,一般采用传址的方式,因为传参,如果结构体过于庞大的话,传参的时候要临时拷贝一份,会导致性能的下降。
总结
关于结构体的内存对齐,我们也可以采用#pragma这个预处理命令来改变默认对齐数,具体操作方式如下:
#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
有关结构体更多的细致的知识,因为篇幅原因,就不在这里和大家一一赘述了,希望大家都能好好学习C语言,每天都能更近一步。