C语言 结构体struct总结

一.前言

C语言提供了一些由系统提供好的数据类型,如:int,float,char等,用户可以在程序中用他们定义变量,解决一般的问题,但是在实际问题中只有这些数据类型是不够的,有时候我们需要其中的几种一起来修饰某个变量,在此情况下,使用简单的基本数据类型甚至是数组都是很困难的;我们就需要自己建立一些数据类型,并用它来定义变量。

二.结构体的引入

1.结构体的定义:由不同类型数据组成的组合型的数据结构称为结构体。

2.结构体的优点:结构体不仅可以记录不同类型的数据,而且使得数据结构是“高内
聚,低耦合”的,更利于程序的阅读理解和移植,结构体的存储方式可以提高CPU对内
存的访问速度。

三.结构体声明

我们以一个学生信息为例来说明

struct Student{         //声明结构体
    char name[20];      //姓名
    char sex;			//性别
    int age;			//年龄
    int num;            //学号
    float score;        //成绩
};						//注意最后有一个分号

上述代码Student 是自定义的结构体名,与struct构成一个新的类型名,name ,sex,age,num,score是结构体内的各个成员,对于成员都应进行类型声明,即

类型名 成员名;

上述结构体的组织结构:
在这里插入图片描述

声明一个结构体类型的一般形式为:

struct 结构体名
{
	成员列表;
}

说明:

  1. 定义一个结构体类型只是进行了描述,编译时并不分配存储空间,所以不能对定义的结构体类型进行赋值。
  2. 大括号里的内容为结构体成员,结构体成员可以是简单变量,数组,指针,也可以是结构体共用体等。
  3. 结构体类型定义可以放在函数内部,也可以放在函数外部;放在内部:只在函数内部有效;放在外部:所有函数有效。(类似全局变量和局部变量
  4. 同一结构体内各成员不可以重名,成员名可以和程序中变量名相同,系统会识别。
  5. 最后的分号不要忘了这个分号也相当于是一个语句,有的编译器会自己加上。

对于说明的第二点进行举例
如果结构体中成员为结构体时:

struct Student{         //声明结构体
    char name[20];      //姓名
    char sex;			//性别
    struct data_type birthday //生日
    int num;            //学号
    float score;        //成绩
};		

这个结构体的组织结构为:
在这里插入图片描述
相比于最初的组织结构,如果结构体成员为结构体时也是合理可行的。

四.结构体变量的定义

之前结构体的声明只是告诉编译器该如何表示数据,但是它没有让计算机为其分配空间。我们要使用结构体,那么就需要创建变量,也就是结构体变量。
结构体变量定义的三种形式:

1.先定义类型,在定义变量:

struct Student{         //声明结构体
    char name[20];      //姓名
    int num;            //学号
    float score;        //成绩
};
struct Student stu1;    //定义结构体变量

最常用的方法,较直观。

2.定义类型的同时定义变量

struct Student{        
    char name[20];       
    int num;             
    float score;         
}stu1;                  //在定义之后跟变量名

是形式一的简略形式。

3.直接定义结构体变量,省略结构体名

struct {                //没有结构名
    char name[20];       
    int num;            
    float score;         
}stu1;  

但要注意的是这样的方式虽然简单,但不能再次定义新的结构体变量了。

说明:

  1. 结构体类型结构体变量是不同的概念,不可以混淆。只可以对变量赋值,存取或运算,不可以对一个类型进行上述操作。
  2. 编译时只对变量分配空间,对类型不分配空间
  3. 结构体变量中各成员是存储在一片连续单元内的。

五.结构体变量的初始化

所谓结构体变量初始化,就是定义结构体变量的同时,对其成员变量赋初值,在赋初值时应注意按顺序及类型依次为每个结构体成员指定初始值。

结构体变量的初始化一般格式如下:

struct 结构体名 结构体变量={初始化值的列表};
在这里插入图片描述

说明:

  1. 初始化数据之间用逗号分隔开。
  2. 初始化数据的个数一般与成员的个数相同,若小于成员数,则剩余成员数将被自动按类型初始化为0。(成员是字符型则被初始化为’\0’;成员是指针则被初始化为NULL)
  3. 初始化数据类型要与相应成员变量的类型一致
  4. 初始化只可以对整个结构体变量进行,不能对结构体类型定义中的各成员直接进行初始化赋值。

对于说明的第二点进行验证:
在这里插入图片描述

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

结构体就像一个超级数组,在这个超级数组内,一个元素可以是char类型,下个元素就可以是flaot类型,再下个还可以是int数组型,这些都是存在的。在数组里面我们通过下标可以访问一个数组的各个元素,那么如何访问结构体中的各个成员?
这里我们分为两种情况来说明

  1. 结构体普通变量
  2. 结构体指针变量

1.访问结构体普通变量成员的一般形式:

结构体变量名.成员名
在这里插入图片描述
2.访问结构体指针变量成员的一般形式:

结构体变量名->成员名
在这里插入图片描述
(1)’ . ‘是运算符,在所有运算符优先级中最高
(2)如果结构体的成员本身是一个结构体,则需要继续用’ . '运算符,直到最低一级的成员。
(3)可以引用结构体变量成员的地址,也可以引用结构体变量的地址。
printf(“%o”, student);(输出student的首地址)(%o 按八进制输出)

七.结构体存储原理

  1. 结构体整体空间是占用空间最大的成员(的类型)所占字节数的整数倍。
  2. 结构体的每个成员相对结构体首地址的偏移量(offset)都是最大基本类型成员字节大小的整数倍,如果不是编译器会自动补齐。
  3. 引入一个概念 偏移量:偏移量指的是结构体变量中成员的地址和结构体变量首地址的差。即偏移字节数,结构体大小等于最后一个成员的偏移量加上他的大小,第一个成员的偏移量为0。

结构体对齐规则:

  1. 结构体变量的首地址一定是这个结构体变量中最大的基础类型的成员大小的整数倍
  2. 结构体变量中每一个成员,相对于结构体首地址的偏移一定是该成员的基础数据类型大小的整数倍。
  3. 结构体变量的总大小一定是这个结构体变量中最大的基础类型的大小整数倍不包括结构体。
    在这里插入图片描述

分析以下结构体的大小:
1)

struct A
{
	char a; //1
	int b;  //4
	double c; //8
};

一般认为是13个字节其实不是;
在这里插入图片描述
这里char a 偏移量为1 之后为int b 因为偏移量1不为int(4)的整数倍,所以会自动补齐,而在 double c 时,偏移量为8 是double(8)的整数倍,所以不用自动补齐 最后求得结构体得大小为16。

2)

struct A
{
    char a;//1
    int b;//4
};

在这里插入图片描述
由上述图分析可知所以共占用8字节。

注意:
当一个结构体内声明同样的数据但是顺序变了的时候,那么他的内存对齐也还是不一样的。
例如:第一种情况结构体内依次为int char short
在这里插入图片描述
内存分配状态为:
在这里插入图片描述

第二种情况结构体内依次为char int short
在这里插入图片描述
内存分配状态为:
在这里插入图片描述

八.结构体数组

在上面的student类型的结构体 ,每本书就需要用一个student类型的结构体变量来描述,若是要描述两个学生,需要使用两个这样的变量,依次类推;因此要使用一个该结构体的数组,来表示这些学生;并且数组就是存储一组具有相同类型的数据,因此就有了结构体数组的出现。

8.1结构体数组的定义

结构体数组的定义方式与结构体变量的定义方式类似;
结构体数组与结构体变量区别只是将结构体变量替换为数组。
一般形式为:

struct 结构体名
{
结构体成员表列;
};
struct 结构体名 结构体数组名;

例如:定义一个结构体数组stud,它有五个元素,每个元素都是struct student类型。

struct student
{
	char num[20];
	char name[20];
	char sex[4];
	int age;
	float score;
};
struct student stud[5];

8.2结构体数组的初始化

结构体数组在定义时也可以进行初始化,一般形式是在定义之后用花括号括起来的多组初始数据。

struct 结构体名 结构体数组名 ={初始化值};

struct student stu[5]={ {"10001","张三","男",19,95}{"10002","李四","女",20,88}{"10003","王五","男",21,87} };

8.3结构体数组元素引用

一般形式:

数组名[下标].成员名

例如:

stud[1].score=89

说明:

  1. 可以将一个结构体数组元素的整体赋值给同一结构体数组中的另一个元素,或赋给同一类型的结构体变量。
  2. 不可以把结构体数组元素作为一个整体直接进行输入或输出。

九.结构体指针

所谓结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。如果把一个结构体变量的起始地址存放在一个指针变量中,那么,这个指针变量就指向该结构体变量。

我们知道,指针指向的是变量所占内存的首地址,在结构体中,指针指向的是结构体变量的起始地址,当然也可指向结构体变量的元素
这里我们分为三部分

9.1指向结构体变量的指针

定义形式一般为:

struct 结构体名 指针名

指针变量的基类型必须与结构体变量的类型相同。
struct student* p;

struct Student
{	
	char name[20];
 	int number;
 	char sex;  
}student1;
struct Student*p;
p=&student1;
//若为结构体数组则
struct Student stu1[5];
struct Student*p;
p=stu1;//因为stu1为结构体数组p=stu1直接是指向stu1的首地址不用再加&符

用结构体指针变量访问结构体变量成员的方法在六.结构体变量成员的访问中提到过有两种:

  1. (*p).name
  2. p->name

这两种表示方法均正确。

9.2指向结构体数组的指针

我们想要用指针访问结构体数组的第n个数据时可用

struct Student stu1[5];
struct Student*p;
p=stu[n];
(++p).number//是指向了结构体数组下一个元素的地址

注意区分(++p)->num 和 (p++)->num
(++p)->num: 先使p自然加1,然后得到p指向的元素中的num成员值。
(p++)->num:先求的p->num的值,然后再使p自加1,指向stu[1]。

9.3结构体作为函数参数

首先我们要注意的一点,使用结构体变量作为函数参数的时候,采取的是值传递的方式,将结构体所占内存单元的内容全部传递给形参,并且形参必须也要是同类型的结构体变量,在使用时,会自动创建一个结构体变量作为原变量的副本,并且也需要占内存,并且在调用期间如果修改(形参)结构体中成员的值,修改值是无效的,将一个结构体变量的值传递给一个函数,有3个方法:

  1. 用结构体变量的成员作参数。
  2. 用结构体变量作实参。
  3. 用指向结构体变量(或数组元素)的指针作实参,将结构体变量(或数组元素)的地址传给形参。
    在这里插入图片描述
    而如果用指针作为实参,传递给函数的形参,这时候传递的是结构体的地址,形参所指向的地址就是结构体变量的地址,这时候进行修改的话是可以修改的。
    在这里插入图片描述

十.共用体(联合体)

10.1什么是共用体类型

我们有的时候会想用同一段内存单元存放不同类型的变量,这种使几个不同的变量共享同一内存的结构,称为共用体类型的结构。
在这里插入图片描述
如上图:三个变量在内存中占的字节不同但是都从地址1000开始存放,也就是使用覆盖,后一个数据覆盖了前面的数据。

10.1.1定义共用体的一般形式为:

union 共用体名
{
成员表列
}变量表列;

上图的联合体:

union Data
{
int i;    //表示不同类型的变量
char ch;
float f;
}a,b,c;   //声明类型的同时定义变量

结构体变量所占内存长度为给成员所占内存长度的和,而共用体所占内存长度等于最长成员的长度,例如上面的union Data中a,b,c各占4个字节而不是9个字节。
共用体所占共享内存,所有成员均可访问,只不过最好有一人使用的话,其他人就不要再使用,以免发生错误

10.2引用共用体变量的方式

只有先定义了共用体后才可以引用,不能引用共用体变量,只能引用共用体变量中的成员
例如,前面定义a,b,c为共用体变量

a.i     //(引用共用体变量中的整型变量i)
a.ch	//(引用共用体变量中的字符变量ch)
a.f		//(引用共用体变量中的实型变量f)

printf("%d",a)   //error
printf("%d",a.i) //true

并且a的存储区可以按不同类型存放数据,有不同的长度仅写变量名,系统无法知道应输出哪一个成员的值,所以后面的写法为正确的。

10.3共用体的应用

判断平台是大端还是小端

10.3.1先明确大端小端的由来:

这里有个故事
“大端”和“小端”可以追溯到1726年的Jonathan Swift的《格列佛游记》,其中一篇讲到有两个国家因为吃鸡蛋究竟是先打破较大的一端还是先打破较小的一端而争执不休,甚至爆发了战争。1981年10月,Danny Cohen的文章《论圣战以及对和平的祈祷》(On holy wars and a plea for peace)将这一对词语引入了计算机界。这么看来,所谓大端和小端,也就是big-endian和little-endian,其实是从描述鸡蛋的部位而引申到计算机地址的描述,也可以说,是从一个俚语衍化来的计算机术语。

10.3.2明确大端和小端的区别:

  1. 大端:高地址存大数据
  2. 小端:低地址存低数据

10.3.3判断大小端的方法如下:

bool Islittle()
{
	union
	{
		char a;
		int b;
	}tmp;
	tmp.b = 1;
	return tmp.a == 1;
}

int main()
{
	bool flg = Islittle();
	if (flg)
	{
		printf("小端\n");
	}
	else
		printf("大端\n");
return 0;
}

十一.枚举类型

如果一个变量只有几种可能的值,则可以定义为枚举类型。

11.1什么是枚举类型

所谓枚举就是把可能的值一一列举出来,变量的值只限于列举出来的值的范围内。

11.2声明枚举类型

声明枚举类型的一般形式为:

enum [枚举名] {枚举元素列表};

例如:

enum week{sun,mon,tue,wed,thu,fri,sat};

说明:

  1. 枚举类型的枚举元素按常量处理,故称为枚举常量,不可以对它们赋值。
  2. 每一个枚举元素都代表一个整数,编译时按定义的顺序默认从0开始依次++。
  3. 由2中所说:枚举元素是常量,所以可以用来比较大小。

对说明2进行详细解释:
在对枚举类型赋值的时候

  1. 不指定值,枚举值默认从0开始,往后逐个加1。
  2. 全部给值,枚举值就为给定的值。
  3. 可以给一部分,并且给枚举值赋的值可以重复。

在这里插入图片描述

11.3枚举的特点

  1. 受到限制有符号类型
  2. 枚举值(元素)必须是整型类型 // 字符可以给如’A’ short //字符串’abc’ 浮点值 float double 1.2 指针不可以 。
  3. 枚举变量的值必须是在枚举值的集合。
  4. 给枚举值赋的值可以重复 ,但是枚举值不能重复

11.4枚举的优点

  1. 增加代码的可读性可维护性
  2. 和define不同,枚举有类型检查,更安全
  3. 封装性好。
  4. 便于调试。
  5. 使用方便以一次定义多个变量

十二.typedef声明类型名

typedef 作用:给已有的数据类型定义新类型名。

  1. 命名一个新的类型名代表结构体类型
typedef struct {
	int month;
	int day;
}Date;

Date today;  //定义结构体类型变量
Date *p;     //定义结构体指针变量 p,指向此结构体类型数据

  1. 命名一个新的类型名代表数组类型
typedef int Num[100];	//声明 Num为整形数组类型名
Num a;			//定义a为整形数组名,它有100个元素

  1. 命名一个新的类型名代表指针类型
typedef char* String;	//声明String为字符指针类型
String p,s[10];		    //定义p为字符指针变量,s为字符指针数组

  1. 命名一个新的类型名代表指向函数的指针类型
typedef int (* Pointer)();	//声明Pointer为指向函数的指针类型,函数返回整型值
Pointer p;			//p为Pointer类型的指针变量

说明:

  1. 用typedef只是对已经存在的类型指定一个新的类型名,并没有创作新的类型
  2. 用typedef声明数组类型、指针类型、结构体类型、共用体类型、枚举类型等,使得编程更加方便
  3. typedef与#define表面上有相似之处。
  4. 使用typedef名称有利于程序的通用与移植。有时程序会依赖于硬件特性,用typedef类型就便于移植。

小白一枚 可能有的地方会有一些错误 欢迎指正.
(写博客不易,喜欢的话就点赞评论吧.)

  • 9
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Mi ronin

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

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

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

打赏作者

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

抵扣说明:

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

余额充值