结构体(初阶)

大家好!我是保护小周,本期为大家带来的是结构体的基本操作(初阶)版本,这期很重要哦,想要学好数据结构,那就需要先学好结构体跟指针哦!快来跟着我一起来学习吧!


目录

一、结构体类型的声明

1.1为什么使用结构体

1.2结构体的声明

1.3结构体变量的定义

1.3.1先定义结构体类型,再定义结构体变量:

1.3.2在定义结构体类型的同时定义结构体变量:

二、结构体初始化

三、结构体成员访问 

四、结构体传参

结论:结构体传参的时候,尽量传结构体的地址。


一、结构体类型的声明

1.1为什么使用结构体

我们在程序开发中,常常需要描述由多个不同类型的数据项组成的数据,描述一个复杂的对象。

例如:一个学生信息需要使用学号,姓名,性别,年龄,电话,家庭住址等信息,这些数据项的类型不一定相同,如果我们定义多个数组去实现,就无法反映出各个数据项之间的关系,从而失去了整体性。


1.2结构体的声明

我们用数组与结构体做一个简单的对比:

数组:一组相同类型的元素的集合

结构体:是一些值得集合,这些值称之为成员变量,结构得每一个成员可以是不同类型得变量

(1)struct 是关键字,结构体类型名称的命名规则满足标识符命名规则。

(2)结构体类型中的成员有“{}”括起来,用来表示结构体有那些成员、成员数据类型。

(3)结构体类型定义末尾括号的分号“;”是万万不能丢的(一些好的编译器在打出“{}”时会自动生成分号“;”)。

结构体的成员可以是标量,数组,指针,也可以是结构体。

我们采用结构体定义一个学生基本信息看看:

这里我们定义了一个student的结构体类型,student中有6个成员拥有两种数据类型。

所谓类型,就像我们 int sum=0;定义一个整型类型的变量sum,初始化数据为0;

 所以我们在使用结构体类型的时候也需要定义一个结构体类型的变量,我定义是student类型。


1.3结构体变量的定义

1.3.1先定义结构体类型,再定义结构体变量:

1.3.2在定义结构体类型的同时定义结构体变量:

1.3.3直接定义结构体变量

我们一般使用先定义结构体类型,再定义结构体变量这种方式;

结构体变量定义后,编译器就会为其分配内存,结构体类型的所占用的实际字节数就是结构体成员所占用的字节数的总和。我们用代码来证实一下:

 

 这里你可能懵了,不对啊,通过计算明明应该是65个字节才对,为什么会是68呢!

原来数据在内存中是对齐存放的,结构体变量所占空间的大小一般是对齐参数大小的整数倍,如果不足时,就会在最后一个成员后填充若干字节使得所占空间大小是对齐参数大小的整数倍。

以我们的例题来讲,对齐参数为 4(这个要根据系统或者编译器来定义,在windowed(32)下各种类型的变量的自身对齐参数就是该类型变量所占的字节大小) ,对齐参数就是取结构体类型中所有成员对齐参数中最大值与系统默认的对其参数比较,取小的对齐参数作为结构体的对齐参数,有了上面的简单了解,这下我们知道为什是student结构类型是68个字节而不是65个字节了吧,

这个知识点在考试题也是经常出现的,这方面我们做一个简单的了解,数据对齐存放确实会浪费了一部分内存空间,但是对于读取数据等方面来说确实非常方便。

好了好了,我们言归正传,回到我们的结构体当中。


二、结构体初始化

我们采用图的形式观察一下结构体变量jake的存储结构:


三、结构体成员访问 

以上 我们完成了对结构体jake变量的初始化操作,那么我们要怎么去引用结构体变量中的一个成员呢?

圈起来:结构体变量访问成员,结构变量的成员是通过点操作符(.)访问的,其中“.”是一个运算符,它的优先级最高,从左到右结合。

引用结构体变量的语法结构是:

结构体变量名.成员名

我们来尝试一下将jake结构体变量中的数据打印出来感受一下:

 

是不是打印出来了,那么作为一个学生信息,怎么少的了学习成绩呢?

我们尝试一下给我们的student结构体类型增加一个成员,学习成绩,学习成绩又分许多科目的成绩,又需要各成绩之间需要有联系性,所以我们想要增加的成员可以是另一个结构体,我们来看看怎样实现:

是不是有点绕啊,咱不着急,慢慢来,理解了就好了,可以把代码拿去自己感受一下,尝试一下用scanf函数给结构体变量赋值。

#include<stdio.h>

//学生学习成绩类型定义
struct grade
{
	int Chinese;//语文
	int math;   //数学
	int English;//英语
};
//学生信息类型定义
struct student //定义一个student的结构体类型
{
	char number[12];//学号
	char name[12];  //姓名
	char sex[5];    //性别
	int age;        //年龄
	char tele[12];  //电话
	char addr[20];  //地址
	struct grade report;//成绩单
};
int main()
{
	//定义一个student类型的变量jake;
	struct student jake = { "21933321","张三","男" ,18,"18871342986","湖北-武汉",{80,90,100 }};
	//学生基本信息打印
	printf("%s %s %s %d %s %s\n",jake.number,jake.name,jake.sex,jake.age,jake.tele,jake.addr);
	//学生成绩单打印
	printf("%d %d %d",jake.report.Chinese,jake.report.math,jake.report.English);
	return 0;
}

除了刚刚我们用到的结构体引用符“.”可以访问结构体成员,还有另一种放式,我们可以通过结构体指针变量访问结构体变量中的成员。

而结构体指针变量访问结构体变量中成员时有两种方式,我们对比一下用法:

(1)(*结构体指针变量).成员名

(2)结构体指针变量->成员名

在实际运用中(1)=(2);两种表示方法的效果是差不多的。

实际运用到代码中观察他们的表现:

int main()
{
	//定义一个student类型的变量jake;
	struct student jake = { "21933321","张三","男" ,18,"18871342986","湖北-武汉",{80,90,100 }};
	//定义一个student类型的结构体指针ps指向student类型的变量jake
	struct student* ps = &jake;
	//(*结构体指针变量).成员名
	printf("%s %s %s %d %s %s\n",(*ps).number, (*ps).name, (*ps).sex, (*ps).age, (*ps).tele, (*ps).addr);
	//结构体指针变量->成员名
	printf("%d %d %d\n",ps->report.Chinese,ps->report.math,ps->report.English);
	return 0;
}

 无论是使用点运算符“.”还是“->”都是可以的


四、结构体传参

我们如果之前了解过函数,那么接下来的内容会容易理解很多。

我们写一个函数打印jake结构体变量中的内容,直接上代码:

//打印
void Print1(struct student ps)//实参传给形参,形参类型要与实参保持一致
{
	printf("%s %s %s %d %s %s %d %d %d\n",
		ps.number,ps.name,ps.sex,ps.age,
		ps.tele,ps.addr,ps.report.Chinese,
		ps.report.math,ps.report.English);
}
//打印
void Print2(struct student *ps)//用指针ps接收
{
	printf("%s %s %s %d %s %s %d %d %d\n",
		ps->number,ps->name,ps->sex,ps->age,
		ps->tele,ps->addr,ps->report.Chinese,
		ps->report.math,ps->report.English);
}
int main()
{
	//定义一个student类型的变量jake;
	struct student jake = { "21933321","张三","男" ,18,"18871342986","湖北-武汉",{80,90,100} };
	//打印结构体变量jake中的信息
	Print1(jake);//传值调用

	Print2(&jake);//传址调用

	return 0;
}

这里问问我们用print1()函数打印好还是print2()函数打印好?好在哪里?

如果我们采用Print1()函数打印,就是传值调用,实参传给形参时,我们的形参也需要开辟与实参jake相同大小的空间来接收jake里面的数据,jake占大多空间,我们的ps就有多大空间,需要准备一份额外的临时空间,这样就造成了空间上的浪费,从时间的角度来讲,我们传参也需要时间来接收。

如果我们采用Print2()函数打印,就是传址调用,我们直接传jake变量的地址给指针变量*ps,如果是32位平台,指针变量就占4个字节,如果是64位平台,指针变量就占8个字节,指针变量不需要额外的开辟开辟空间,只存实参的地址。

第二种方式我们传参的效率相对来说更高,而且形参是实参的拷贝,我们修改形参ps的数据并不会改变实参jake里的数据,如果我们使用指针变量*ps来接收实参jake的地址,*ps是有能力改变实参jake里的数据的。

结论结构体传参的时候,尽量传结构体的地址。

最后我们介绍一下函数调用,函数在传参的时候,参数需要压栈,栈是一种先进后出,后进先出的数据结构。

每一个函数调用都会在内存的栈区上开辟一块空间, 我们简单的了解一下“栈”:

 我们再来简单的了解一下参数压栈是怎么一回事:


到了这里通过对结构体(初阶)的学习,我们应该对结构体的结构,结构体基本操作有了一个初步的认识。大家可以自己动手敲敲代码,感受一下,希望对大家有所帮助。

 

 本期收录于博主的专栏——C语言,适用于编程初学者,感兴趣的朋友们可以订阅,查看其它“C语言基础知识”。C语言_保护小周ღ的博客-CSDN博客

感谢每一个观看本篇文章的朋友,更多精彩敬请期待:保护小周ღ  *★,°*:.☆( ̄▽ ̄)/$:*.°★* 

文章多处存在借鉴,如有侵权请联系修改删除!

  • 23
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

保护小周ღ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值