【C语言】结构体

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

image.gifimage.gif

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

image.gif


一、结构体类型的声明

1.1为什么使用结构体

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

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


1.2结构体的声明

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

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

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

image.gif

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

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

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

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

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

image.gif

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

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

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


1.3结构体变量的定义

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

image.gif

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

image.gif

1.3.3直接定义结构体变量

image.gif编辑

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

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

image.gif

image.gif

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

image.gif

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

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

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

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


二、结构体初始化

image.gif

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

image.gif


三、结构体成员访问

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

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

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

结构体变量名.成员名

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

image.gif

image.gif

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

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

image.gif

是不是有点绕啊,咱不着急,慢慢来,理解了就好了,可以把代码拿去自己感受一下,尝试一下用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;
}

image.gif

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

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

(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;
}

image.gif

image.gif

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


四、结构体传参

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

我们写一个函数打印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;
}

image.gif

image.gif

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

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

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

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

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

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

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

image.gif

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

image.gif


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

image.gif

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

遇见了你,所有的星星都落在了我的头上……

相关文章
|
17天前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
26 10
|
16天前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
21天前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
21天前
|
存储 C语言
C语言:结构体与共用体的区别
C语言中,结构体(struct)和共用体(union)都用于组合不同类型的数据,但使用方式不同。结构体为每个成员分配独立的内存空间,而共用体的所有成员共享同一段内存,节省空间但需谨慎使用。
|
25天前
|
编译器 C语言 C++
C语言结构体
C语言结构体
22 5
|
1月前
|
C语言
C语言结构体链式结构之有头单链表
文章提供了一个C语言实现的有头单链表的完整代码,包括创建链表、插入、删除和打印等基本操作。
21 1
|
1月前
|
C语言
初识C语言6——结构体
初识C语言6——结构体
18 3
|
1月前
|
存储 编译器 C语言
【C语言】探索结构体基础知识:简明概要
【C语言】探索结构体基础知识:简明概要
|
26天前
|
编译器 Linux C语言
C语言 之 结构体超详细总结
C语言 之 结构体超详细总结
14 0
|
1月前
|
存储 编译器 Linux
深入C语言:探索结构体的奥秘
深入C语言:探索结构体的奥秘