一. 什么是结构体

编程的一大意义是解决生活中的问题,而生活中的一些对象我们并不能单单通过C语言中的某一个变量类型来描述。比如一本书我们该如何用编程语言描述呢?一个整型变量?浮点型变量?肯定是不行的。所以C语言提供了结构体,由一系列具有相同类型或不同类型的数据构成的数据集合,是一种数据结构。描述一本书,我们得通过描述它的价格(浮点型变量),书名(字符串),作者(字符串),出版社(字符串)等等。

二. 结构体的声明与定义

2.1 一般情况

想要通过结构体来描述一本书,我们得学会结构体的定义与声明。
提供四种方式
第一种:

struct book
{
	char name[20];
	float price;
	char author[20];
	char publish[20];
};

第二种:

struct book
{
	char name[20];
	float price;
	char author[20];
	char publish[20];
} b1, b2, b3;//顺便定义了三个struct book类型的变量

注意如果struct book如果定义在main函数外部时,b1,b2,b3都是全局变量。
第三种:

struct book
{
	char name[20];
	float price;
	char author[20];
	char publish[20];
};
int main()
{
	struct book b1, b1, b3;
	return 0;
}

第四种:只声明,不定义。
以下代码只是举例一个场景,变量名没有实际意义

struct book;
struct store
{
	struct book n[20];
	char name[20];
};
struct book
{
	struct store a;
	float price;
};

该场景就是两个结构体都需要用到对方,所以可以先声明一个结构体以免编译出错。

2.1 特殊的声明(匿名结构体)

//匿名结构体类型
struct
{
	int a;
	float b;
}x;
struct
{
	int a;
	float b;
}*p, n[20];

注意:这样写代码,编译器会将x与n当成两种不同的类型

三. 结构体的自引用

即结构体中包含一个类型为该结构体本身的成员。该怎么写呢?

//自引用
struct book
{
	float price;
	struct book next;
};

这样写可以吗?如果可以,那么sizeof(struct book)又是多少呢?答案是算不出来的。所以这样写有一定的问题。实际该这样写:

//自引用的正确写法
struct book
{
	float price;
	struct book* next;//写成指针
};

四. 结构体的初始化

第一种:

struct student
{
	char name[20];
	int age;
};
int main()
{
	struct student s1 = { "zhangsan",20 };//定义时初始化
	return 0;
}

第二种方法:

struct student
{
	char name[20];
	int age;
}s2 = { "lisi",16 };//结构体声明的同时定义变量并且初始化

第三种方法:

#include <stdio.h>
struct student
{
	int id;
	int age;
};
int main()
{
	struct student s3 = { .age = 12,.id = 20 };//通过.操作符来选择先初始化哪个成员
	printf("%d %d\n", s3.id, s3.age);
}

程序运行结果:
在这里插入图片描述

五. 结构体的使用

结构体的使用主要就是通过.操作符来访问成员。

#include <stdio.h>
#include <string.h>
struct book
{
	float price;
	char name[20];
};
int main()
{
	struct book b1 = { 23.5f,"Cplus" };
	printf("%.2f %s\n", b1.price, b1.name);
	b1.price = 33.6f;
	strcpy(b1.name, "haha");
	printf("%.2f %s\n", b1.price, b1.name);
	return 0;
}

对于结构体指针来说,可以通过->操作符来访问结构体成员。

#include <stdio.h>
#include <string.h>
struct book
{
	float price;
	char name[20];
};
int main()
{
	struct book b1 = { 23.5f,"Cplus" };
	struct book* sb1 = &b1;
	printf("%.2f %s\n", sb1->price, sb1->name);//写法1
	b1.price = 33.6f;
	strcpy(b1.name, "haha");
	printf("%.2f %s\n", (*sb1).price, (*sb1).name);//写法2
	return 0;
}

六. 结构体的内存对齐

对于如何定义与使用结构体,我们已经大概了解了。现在又遇到了难题:如何计算结构体占用内存的大小?

6.1 内存对齐规则

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

6.2 计算练习

#include <stdio.h>
int main()
{
	struct S2
	{
		char c1;
		int i;
		char c2;
	};
	printf("%d\n", sizeof(struct S2));
	return 0;
}

在这里插入图片描述

#include <stdio.h>
int main()
{
	struct S2
	{
		char c1;
		char c2;
		int i;
	};
	printf("%d\n", sizeof(struct S2));
	return 0;
}

在这里插入图片描述

内存分布大概就是这样

6.3 内存对其的意义

  1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
  2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总体来说:结构体的内存对齐是拿空间来换取时间的做法。
所以我们在声明结构体时,比较节约内存空间的方法是:让占用空间小的成员尽量集中在一起。

6.4 修改默认对其数

主要用到#pragma这个预处理指令

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

程序运行结果:
在这里插入图片描述

七. 结构体传参

结构体传参主要以两种形式,结构体传参和结构体地址传参。

#include <stdio.h>
struct S
{
	int count[20];
	int num;
};
struct S s = { {1,2,3,4}, 20 };
//结构体传参
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;
}

程序运行结果:
在这里插入图片描述
可以看到两种方式得到的结果都是一样的。但是请想一想,如果结构体本身的成员就占用着很大的内存呢?(比如一个很大的数组)这时再使用结构体传参就会有很大的内存消耗。
结构体传参直接传结构体的话,内存消耗和时间消耗都会更大。
所以建议使用结构体指针传参。

总结

了解和学会使用结构体有助于我们使用编程来对一个对象进行描述,以便解决生活中的问题。
本篇博客主要介绍了结构体的大概内容,希望对大家有帮助。
结构体部分的难点是自主运用和结构体占用空间大小,建议多练点题来熟悉。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐