订阅专栏:自定义类型
关注博主:翻斗花园第一代码手牛爷爷
gitee仓库:牛爷爷爱写代码
C语言给我们提供了很多基本类型,类如int
,char
,float
等等类型,但是在生活中,我们往往需要去描述一个复杂的对象,例如一个人,一个物,单单一种属性是不能够描述一个复杂对象的,因此C语言提供给我们一种能够自定义的类型——结构体,本篇文章我们就来介绍结构体。
结构体的声明非常简单,例如,当我们需要描述一本书的时候,书的描述就有书名,作者,定价,编号等等。因此我们就需要用到结构体类型,如下代码。
struct book
{
char book_name[20];
char author[20];
int price;
char num[15];
};
该代码中我们进行了结构体类型的声明,结构体类型为struct book
,里面包括了书名,作者,定价,编号。
在声明了结构体类型后,我们就可以定义结构体变量了。我们有两种方式对结构体变量进行定义,第一种方式是我们在结构体声明的时候对结构体变量进行定义,如下代码。
struct book
{
char book_name[20];
char author[20];
int price;
char num[15];
}book_1, book_2;
代码中我们定义了两个变量(两本书),分别为book_1
和book_2
。另外一种方式是在主函数或者是需要的时侯我们再进行变量的定义,如下代码。
struct book
{
char book_name[20];
char author[20];
int price;
char num[15];
};
int main()
{
struct book book_3;
struct book book_4;
return 0;
}
该代码中我们也定义了两个变量(两本书),分别为book_3
和book_4
,两种方式都可以进行结构体变量的定义,但是不同的是book_1
和book_2
是全局变量,book_3
和book_4
是局部变量。
我们在对结构体声明的时候可以省略结构体的名称,如下代码。
struct
{
char book_name[20];
char author[20];
int price;
char num[15];
}book_1, book_2;
struct
{
char book_name[20];
char author[20];
int price;
char num[15];
}book_3, book_4;
代码中我们可以省略名称book
,例如这种声明叫做匿名结构体声明,像是这种结构体的声明在创建变量的时候我们只能在结构体声明的时候创建变量,不能够在之后创建变量,如struct book_1
,这种创建是错误的。注意:在创建两个匿名结构体类型的时候,哪怕两个匿名结构体里面的成员完全相同,但是在编译器看来这两个匿名结构体类型还是两个不同的类型,如上面代码中book_1
和book_2
就与book_3
和book_4
类型不同。
结构体的自引用我们经常在链表的使用中用到,它的变量结构通常分为数据域和指针域,我们一般把这样的一个变量叫做节点,如下图。
代码如下:
struct node
{
int data;
struct node* next;
};
int main()
{
struct node book_1;
return 0;
}
当我们创建多个节点时,我们就可以利用指针域将多个节点串联起来,我们只需要知道第一个节点的起始地址就可以找到所有的节点,如下图。
结构体的打印分两种情况,第一种是利用结构体变量进行打印时,我们需要用到.
操作符,如下代码。
struct book
{
char book_name[20];
char author[20];
int price;
char num[15];
};
int main()
{
struct book book_1 = { "高质量C/C++编程", "林锐", 99, "HX15935" };
printf("%s %s %d %s\n", book_1.book_name, book_1.author, book_1.price, book_1.num);
return 0;
}
我们在创建一个变量的同时对其进行初始化然后打印,打印结果如下图。
第二种是利用结构体指针进行打印,我们需要用到->
操作符,如下代码。
struct book
{
char book_name[20];
char author[20];
int price;
char num[15];
};
int main()
{
struct book book_1 = { "高质量C/C++编程", "林锐", 99, "HX15935" };
struct book* p = &book_1;
printf("%s %s %d %s\n", p->book_name, p->author, p->price, p->num);
return 0;
}
我们创建一个结构体指针指向结构体变量,然后打印,打印结果如下。
我们知道一个int
类型占4个字节,一个char
占1个字节,那么我们自定义的结构体类型又占多少个字节呢?如下代码。
struct book1
{
char x;
int y;
char z;
};
struct book2
{
char x;
char z;
int y;
};
int main()
{
printf("%d\n", sizeof(struct book1));
printf("%d\n", sizeof(struct book2));
return 0;
}
我们定义了两个结构体,并且结构体成员都一样,只不过成员的存放顺序不一样,我们用sizeof
来看一下两个结构体的大小有什么区别,运行结果如下。
通过上图我们可以看到,两个结构体的大小分别为12个字节和8个字节,为什么会产生这种结果呢?那是因为结构体内存要对齐,接下来我们来介绍结构体内存对齐的规则。
在了解完内存对齐的规则之后,我们再来分析上面的代码为什么会是不同结果,我们画图来分析,如下图。
变量x
的直接存放到结构体变量开辟的空间的起始位置,类型为char
占一个字节。然后放变量y
,因为从第二个表变量开始,就要存放到对齐数的整数倍偏移处,对齐数为4。因此从偏移量为4的位置处开始存放,y
的类型为int
,占4个字节。最后存放变量Z
,此时的对齐数为1,直接在y
的后面进行存放,类型为char
,占一个字节。又因为结构体总大小为最大对齐数的整数倍,最大对齐数为4,所以继续往后占用内存,直到偏移量为11,其中红色的都为浪费的字节,此时结构体大小为12字节。
我们再来画图分析book2
,如下图。
变量x
直接存放偏移量为0的位置处,占一个字节。变量z
的对齐数为1,直接存放到x
的后面,占一个字节。变量y
的对齐数为4,因此从偏移量为4的位置处开始存放,占4个字节,结构体大小为8。红色的都为浪费的字节。
当我们一个结构体嵌套另一个结构体的时候,这时候大小又是多少呢?如下代码。
struct book1
{
char x;
int y;
char z;
};
struct book2
{
char x;
struct book1 z;
int y;
};
此时的book2
的大小为多少字节呢?我们还是画图来分析,如下图。
我们知道变量x
直接存放变量起始位置,占一个字节,嵌套的结构体的要对齐到自己的最大对齐数的整数倍处,而book1
的最大对齐数为4,因此从偏移量为4的位置处开始存放,book1
占12个字节。变量y
的对齐数为4,刚好从book1
的后面,偏移量为16的位置处开始存放,占4个字节,结构体的总大小为20个字节。红色的为浪费的字节。我们来看一下运行结果,如下图。
本片文章介绍了结构体的内容,后续会继续更新自定义类型专栏,大家觉得有帮助的话可以点点订阅。由于作者水平有限,如果发现内容有误,还希望大家多多指正。如果有什么问题的话,欢迎大家评论区留言评论。