了解结构体

一、结构体

1.结构体的声明

结构体类型是一种构造类型,它是由若干成员组成。而每一个成员既可以是基本数据类型也可以是构造类型(结构体里放结构体)

//下面我声明了一个叫Book的结构体类型,即我把含有括号内三个基本类型的构造类型称之为Book类型
//同时Book类型具有三个成员,分别是Book的价格,Book的名称,Book的编号
struct Book //struct 是结构体的关键字,而book是结构体的标志
{
	float price;
	char name[1000];
	int number;
};//末尾的分号不能掉

结构体的声明与函数的声明一样,需放在main函数之前。

 2.结构体变量的定义

 通过上述方法,我们声明一个叫Book的类型,那我们如何像其它基本类型一样定义变量呢?

其实方法大同小异,我们同样可以根据基本类型的定义模式来定义结构体变量。

int main(void)
{
	struct Book book1;类似于基本类型的类型 + 变量名
	return 0;
}
//可能有人会觉得这个struct让人觉得变扭
//不要忘了我们也可以自定义类型名称
typedef struct Book 
{
	float price;
	char name[1000];
	int number;
}Book;//我们将上述结构体类型自定义称之为Book类型
int main(void)
{
    Book book1;//此时我们就可以直接使用Book,而不使用struct关键字了
	return 0;
}

我们还有第二种定义方式

struct Book 
{
	float price;
	char name[1000];
	int number;
}book1, book2;//可以在声明末尾,分号之前进行定义变量

struct  
{
	float price;
	char name[1000];
	int number;
}book1, book2;//也可以直接定义,但此举对于后面继续操作结构体不太方便,一般不建议

 3.结构体变量的初始化

所谓结构体变量的初始化,就是给结构体变量中的成员进行一个最开是的赋值操作。由上述可知对于结构体变量的定义我们有两大类定义方式。故此,对于结构体变量的初始化也基本上有两种初始化方式。


#include<stdio.h>
typedef struct Book
{
	float price;
	char name[1000];
	int number;
}Book;
int main(void)
{
	Book book1 = {30.2,"C",0213};//按照成员顺序依次输入。
	return 0;
}
//若初始化数据少于成员数量,缺少数据的成员将会自动初始化为0;
//即我若只输入30.2,"C"那么number将会自动初始化为0;

同理我们可得第二种初始化方式

struct Book
{
	float price;
	char name[1000];
	int number;
}book1 = {66.3,"B",3256};

 4.结构体变量成员的访问

此时我们就要用到一个操作符“.”

我们可以通过   结构体变量.结构体成员这个模式来访问一个结构体成员,我们称“.”为成员运算符

#include<stdio.h>
struct Book
{
	float price;
	char name[1000];
	int number;
}book1 = {66.3,"B",3256};
int main(void)
{
	printf("%d", book1.number);
	return 0;
}

 输出结果为

 如果对指针了解的话,我们还有第二种访问方式


#include<stdio.h>
struct Book
{
	float price;
	char name[1000];
	int number;
}book1 = {66.3,"B",3256};
int main(void)
{
	struct Book* ch = &book1;
	printf("%d", ch->number);//运用指针来访问成员
	printf("%d", (*ch).number);//解引用指正来访问
	return 0;
}

输出的结果于上面是一样的 

int main(void)
{
	struct Book* book = &book1;
	scanf("%f %s %d", book->price, book->name, book->number);
	return 0;
}

至此,我们可以运用“.”操作符来对结构体变量进行输入

int main(void)
{
	scanf("%f %s %d", &book1.price, &book1.name, &book1.number);
	return 0;
}
int main(void)
{
	struct Book* book = &book1;
	scanf("%f %s %d", &book->price, &book->name, &book->number);
	return 0;
}

 这两种都是可行的。

综上,我们可以知道对于结构体变量的输入输出,都是以结构体变量成员为单位输入输出。

但是,若我想把一个结构体变量的值拷贝到另一个结构体变量上,那我可以

struct Book
{
	float price;
	char name[1000];
	int number;
}book1 = {30.2,"B", 1258}, book2;
int main(void)
{
	book2 = book1;
	return 0;
}

 前提是两个结构体变量的类型是相同的。

5.结构体类型的数组

结构体类型的数组本质上还是数组,只不过将数组中的元素类型从基本数据类型替换成了结构体类型。

1.结构体类型数组的定义方式

#include<stdio.h>
struct Book
{
	float price;
	char name[1000];
	int number;
};
int main(void)
{
	struct Book books[100];//定义了一个名字为books的结构体数组名,其中可以容纳100个结构体变量
	return 0;
}

 模式还是类似,数组元素类型 + 数组名 + 数组元素个数,相同的数组名指的是首元素地址,即数组中第一个结构体变量的地址。

对于结构体类型数组来说,数组名+下标,例:books[0],指的是数组中第一个结构体变量,若想访问其成员还得继续使用操作符“.”。

2.结构体数组的初始化

我们回忆起数组的初始化方式为

int main(void)
{
	int arr[5] = {1,2,3,4,5};//数组元素类型+数组名+数组元素个数={元素1,元素2.....}
	return 0;
}

 在上面我们也讲述了结构体变量的初始化方式,现在我们将两者结合起来就得到了结构体数组的初始化方式。

#include<stdio.h>
struct Book
{
	float price;
	char name[1000];
	int number;
};
int main(void)
{//定义了含有三个元素的结构体类型数组
	struct Book books[3] =
	{
		{3.1,"A",1},
		{3.1,"B",2},
		{3.1,"C",3}
	};//个人喜欢这样每个结构体变量换一次行,这样更为清晰,根据个人喜好编码就好
	for (int i = 0; i < 3; i++)//打印数组元素,本质是打印结构体变量成员
	{
		printf("%.1f %s %d\n", books[i].price, books[i].name, books[i].number);
	}
	return 0;
}

 根据数组的输入方式,大家可以自己结合上述内容写写结构体类型数组的输入代码。

6.结构体指针变量

 其实在本章的前面出现过结构体指针变量,顾名思义该变量就是一个指向结构体类型变量的指针

1.结构体指针变量的定义

我们也可根据指针的定义方式来类比结构体指针变量的定义 ,即

#include<stdio.h>
struct Book
{
	float price;
	char name[1000];
	int number;
}book1;
int main(void)
{
	struct Book* books = &book1;指向元素类型 *指针名 = &指向元素
	return 0;
}

2.通过指针来访问结构体成员 

前面已经出现过“->”操作符,本处就不再举例 。

注意“->”跟结构体指针变量搭配,名为指向运算符,“.”跟结构体变量搭配。

不要混淆,不可乱用。 

3.结构体指针变量指向一个结构体数组

 结构体指针与普通指针一样,也可以指向一个数组(什么类型指针指向什么类型数组),

此时该指针所指向的是该数组的首元素地址。因为本质上数组名在不添加“&”符号或者不在sizeof()括号内,就是☞数组首元素地址

使用结构体指针变量打印结构体数组

#include<stdio.h>
struct Book
{
	float price;
	char name[1000];
	int number;
};
int main(void)
{
	struct Book books[3] =
	{
		{3.1,"A",1},
		{3.1,"B",2},
		{3.1,"C",3}
	};
	struct Book* book = books;//指针指向结构体数组
	for (int i = 0; i < 3; i++)
	{
		printf("%.1f %s %d\n", (book + i)->price, (book + i)->name, (book + i)->number);
	}
	return 0;
}

 指向运算符“->”比较常见,下面有几个常见的表示方式及其含义(p表示指针,member表示成员,i表示某一整数)

  • p->member;得到p所指向的结构体变量中成员member的值
  • p->member++; 得到p所指向的结构体变量中成员member的值,然后member的值+1
  • ++p->member;使p所指向的结构体变量中的成员member的值+1,在引用这个新值
  • (++p)->member;使p的值+1,再得到p所指向的结构体变量中的成员member的值
  • (p++)->member;得到p所指向的结构体变量中成员member的值,然后p的值+1
  • (p+i)->member;使p的值+i后,再得到p所指向结构体变量中成员member的值

注意,指针的值改变,所指向的值不一定是原来的值。

7.结构体与函数 

1.结构体变量作为函数参数

结构体变量作为函数参数时,所采取的方式是值传递的方式,即将实参结构体变量的各个成员的值对应传递给形参结构体变量。

例 

#include<stdio.h>
struct Book//结构体类型的声明
{
	float price;
	char name[1000];
	int number;
};
void print(struct Book books)//函数参数为结构体变量
{//打印结构体变量
	printf("%.1f %s %d\n", books.price, books.name, books.number);
}
int main(void)
{
	struct Book books = { 3.1,"A",1 };//结构体变量的定义和初始化
	print(books);//调用函数
	return 0;
}

2.结构体指针变量作为函数参数

 上述用结构体变量作为函数参数的方式,显然在传递参数时,需要将每一个成员的值进行传递,耗费较多的空间和时间。而采用结构体指针变量进行传参(即址传递),则可以避免此类问题。

例 

#include<stdio.h>
struct Book
{
	float price;
	char name[1000];
	int number;
};
void print(struct Book* book1)//改用结构体指针变量作为参数
{
	printf("%.1f %s %d\n", book1->price, book1->name, book1->number);
}
int main(void)
{
	struct Book books = { 3.1,"A",1 };
	struct Book* book1 = &books;//定义和初始化结构体指针变量
	print(book1);//调用函数
	return 0;
}

8.结构体变量的存储方式 

结构体变量的大小计算与基本数据类型的计算是不一样的

首先我们来了解一些结构体大小的计算规则

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

 其中出现了一个词,对齐数。意思是某个成员地址要与某一个数对齐,这个数就称为对齐数。

举个例子

现在我们有下面一个结构体

struct O
{
	int a;
	char ch;
	double m;
}O;

首先根据规则,第一个成员位于结构体变量偏移量为0地址处,即距离地址的偏移量0,不就是起始地址吗?故我们第一个成员a起始放在0地址处

 假设这个方框的初始方框为0地址处,按照int类型为四个字节。

同时我们也可以根据规则二,默认对齐数8与int类型大小4进行比较,较小者为4.

故此时对齐数为4.

再者也是规则二,一个成员要对齐到对齐数的整数倍,故a在内存中的存放位置是如上图所示绿色部分。

再对ch分析可知此时对齐数为1,而a所占地址后的偏移量为4,是对齐数的整数倍,故ch如下图红色部分

 对m分析可知此时对齐数为8,而ch地址后的偏移量为5,不是对齐数的整数倍。此时我们就需要舍弃内存,向下继续寻找对齐数整数倍处。显而易见,偏移量为8处是对齐数的整数倍,我们就从偏移量为8处存放m。m的存放如蓝色部分

 我们已将所有成员放置内存中,最后我们要参考第三个规则,此时结构体所占用16个字节

每个成员的对齐数分别是4、1、8,其中最大的是8。结构体总大小正好是8的倍数,故规则三满足。结构体总大小就是16个字节

运行代码即可验证。

若最后结构体总大小不是最大对齐数的整数倍,我们要像存放m时一样一直往下寻找,直到找到符合规则。白色部分都是为了满足规则而浪费掉,虽然没有储存东西,但也算结构体的大小的一部分

有的人可能会觉得,为什么要这样储存结构体,这样不是浪费了很多空间吗。 

我们有下面两个原因

  • 设备

不同设备、平台读取内存的方式存在区别,不是想读什么就可以获取到什么的,某些硬件平台是按一定方式读取的

  • 效率

为了获取未对齐的内容,处理器可能会访问两次内存,但是我将其对齐的话,处理器就只需要访问一次。

一般编译器的默认对齐数是8;

我们也可通过下面方式手动修改默认对齐数

#pragma pack()//填入数字

故,在规定了这些规则的情况下,我们如何能更好的节约内存呢,我们只需要将内存大小相近的成员放在一起,将会在一定程度上改善我们结构体的内存大小。

若有错误,望告知,会即使纠正

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值