结构名称{
会员名单
};
其中,成员列表中的每个成员都应该进行类型声明,即:
输入 name 成员名称;
比如我们需要在程序中记录一个学生()的数据,包括学号(num)、姓名(name)、性别(sex)、年龄(age)、年级(score)、地址(addr)、等等,如下图所示:
如果我们想要在图中表示数据结构,但是C语言没有提供这样现成的数据类型,所以我们需要定义一个结构体类型来表示。
结构{
整数;
字符名称[20];
炭性;
年龄;
;
字符地址[30];
};
上面定义了一个新的结构体类型(注意,它是一个键,声明结构体类型时必须使用,不能省略)。 它向编译系统声明这是一个“结构类型”,其中包括num、name、sex、age、score、addr等不同类型的数据项。
应该说,这里是一个类型名,它和系统提供的标准类型(如int、char、float等)作用相同,可以用来定义变量的类型。
结构变量
之前我们只是声明了一个结构体类型,相当于一个模型,但是里面并没有具体的数据,编译系统也没有给它分配实际的内存单元。 为了在程序中使用结构体类型数据,我们应该定义结构体类型变量并在其中存储特定的数据。 定义结构体类型变量主要有以下三种方式:
结构体类型名 结构体变量名;
比如上面我们定义了一个结构体类型,我们可以用它来声明变量:
, ;
和 类型的变量被定义,它们有一个类型结构,我们可以稍后初始化它们。
{
整数;
字符名称[20];
炭性;
年龄;
;
字符地址[30];
} , ;
其作用与第一种方法相同,即定义了两种类型的变量。 这种形式的定义的一般形式是:
结构名称{
会员名单
} 变量名列表;
{
会员名单
} 变量名列表;
关于结构类型,需要补充说明一点:
类型和变量是不同的概念,不要混淆。 我们只能对变量进行赋值、访问或操作,而不能对类型进行赋值、访问或操作。 编译时,不为类型分配空间,只为变量分配空间。
简单地说,我们可以将“结构类型”和“结构变量”理解为面向对象语言中“类”和“对象”的概念。
另外,结构体的成员也可以是结构体变量。 例如,我们首先声明一个结构体日期:
日期 {
整月;
国际日;
整数年;
};
然后将其应用到声明中:
{
整数;
字符名称[20];
炭性;
年龄;
;
日期 ;
字符地址[30];
} , ;
最后解释一下阅读大型开源代码(比如-C源码)时容易引起疑惑的一点:下面两个结构体和声明的变量在内存中实际上是一模一样的。 原因是该结构体本身没有任何额外的附加信息:
{
整数a;
整数b;
整数c;
};
{
整数a;
第1部分 {
整数b;
};
第2部分 {
整数c;
};
};
结构体变量的引用
引用结构体变量中的成员的方法是:
结构体变量名.成员名
例如,.num 表示变量中的 num 成员。 我们可以给结构体变量的成员赋值:.num = 10010;。
如果成员本身属于结构体类型,则需要使用多个成员运算符(点.)逐级查找最低级别的成员,例如:
..月= 9;
另外,还可以像普通变量一样对结构体变量的成员进行各种操作。 还可以使用地址运算符&来引用结构体变量成员的地址,或者引用结构体变量的地址。
结构变量的初始化
与其他类型变量一样,结构体变量可以在定义时指定其初始值,并用大括号括起来:
{
整数;
字符名称[20];
炭性;
年龄;
字符地址[30];
} a = {10010, "李雷", 'M', 18, " "};
结构体和数组
如果数组的元素是结构类型,则称为“结构数组”。 结构体数组与之前介绍的数值数组的区别在于,每个数组元素都是一个结构体类型数据,并且它们都包含单独的成员项。
与定义结构体变量的方法类似,只需将其声明为数组即可,例如:
{
整数;
字符名称[20];
炭性;
年龄;
;
字符地址[30];
};
斯图[3];
上面定义了一个数组stu。 该数组有3个元素,均为类型数据,如下所示:
与其他类型的数组一样,结构体数组可以被初始化,例如:
{
整数;
字符名称[20];
炭性;
年龄;
;
字符地址[30];
} Stu[3] = {{10101, "李林", 'M', 18, 87.5, ""},
{10102, "艾米", 'M', 17, 92, ""},
{10103, "宾果", 'F', 20, 100, ""}};
从上面可以看出,结构体数组初始化的一般形式是添加“={初始值表列};” 在数组定义之后。
结构体数组中的每个元素也在内存中连续存储,如下所示:
结构体和指针
结构体变量的指针是该变量所占用的内存段的起始地址。 您可以设置一个指针变量来指向结构体变量。 此时,指针变量的值就是结构体变量的起始地址。 指针变量也可用于指向结构体数组中的元素。
{
整数;
字符名称[20];
炭性;
年龄;
;
字符地址[30];
};
Stu1 = {...};
*p;
p = &stu1;
上面的代码首先声明了结构体类型,然后定义了一个类型变量stu1,同时还定义了一个指针变量p,它指向一个类型的数据。 最后将结构体变量stu1的起始地址赋值给指针变量p,如图所示:
这时可以使用*p来访问结构体变量stu1的值,使用(*p).num来访问stu的成员变量。 在C语言中为了使用起来方便直观,可以将(*p).num的定义替换为p->num,它代表p指向的结构体变量中的num成员。
换句话说,以下三种形式是等效的:
{
整数;
字符名称[20];
炭性;
年龄;
;
字符地址[30];
};
Stu[3] = {{10101, "李林", 'M', 18, 87.5, ""},
{10102, "艾米", 'M', 17, 92, ""},
{10103, "宾果", 'F', 20, 100, ""}};
*p = 斯图;
此时指针变量p指向数组第一个元素的地址,即&stu[0],也就是数组名stu。
结构体指针使用场景
(1)函数参数:使用指向结构体变量(或数组)的指针作为实参,将结构体变量(或数组)的地址传递给形参。
无效(*p);
因为如果我们直接使用结构体变量(而不是结构体指针)作为实参,由于采用“值传递”的方式,结构体变量占用的内存单元的内容都会依次传递给形参。 它们还必须是同一类型的结构变量。
函数调用过程中,形参也占用内存单元。 这种传递方式会带来较大的时间和空间开销,而且也不利于将函数执行过程中改变的形参结构的值(结果)返回给主机。 调用函数,所以一般不太可能直接“用结构体变量作为实参”,而是用指针。
(2)链表
链表是一种常见且重要的数据结构,一般用于动态存储分配。 常见的有单链表和双链表。 一般来说,可以使用结构体来表示链表节点。 以下是常见的“单链表”节点的声明:
{
整数值;
*下一个;
};
其中,val构成链表节点的值,next指针用于指向链表的下一个节点。
比如面试中经常考到的“反转单链表”的问题:
*( *头) {
如果(头== NULL){
;
if(头->下一个== NULL) {
;
* = 空;
* = 空;
* = 头;
while(!= NULL) {
* = ->下一个;
如果(==空){
= ;
->下一个=;
= ;
= ;
;
(3)二叉树
{
整数值;
*左边;
*正确的;
};
其中val表示二叉树的叶子节点的值,left指向该节点的左子树,right指向该节点的右子树。
比如之前引起轩然大波的面试题“翻转二叉树”:
*( *根) {
如果(根== NULL){
;
根->左 = (根->左);
根->右 = (根->右);
*temp = 根->左;
根->左=根->右;
根->右=临时;
;
动态分配和释放内存空间
前面提到,链表结构动态分配存储,即只有在需要时才打开节点的存储单元。 那么,如何动态打开和释放存储单元呢? C语言编译系统的库函数提供了以下相关功能。
无效*(大小);
它的作用是在内存的动态存储区域(堆)中分配一块长度为size的连续空间。 该函数的返回值是一个指向分配域起始地址的指针(类型为void *,即空指针类型。使用时可转换为其他指针数据类型)。 如果该函数执行失败(例如内存空间不足),则返回空指针NULL。
使用示例:
int * = (2 * (int));
*节点= (( ));
上面是在堆上分配的长度为2的数组。 它和int[2]的区别; 就是后者分配在内存栈区域。 而node是一个指针变量,指向一类数据的起始地址(也是分配在堆上)。
无效*(n,大小);
它的作用是在内存的动态存储区域中分配n个连续的长度为size的空间。 该函数返回一个指向分配域起始地址的指针。 如果分配不成功,则返回NULL。
无效*(无效*p,大小);
它的作用是将p指向的分配的动态内存区域的大小改为size。 大小可以大于或小于最初分配的空间。 该函数返回一个指向已分配内存区域起始地址的指针。 同样,如果分配不成功,则返回NULL。
如果传入的p为NULL,其效果与函数相同,即分配size字节的内存空间。
如果传入的size值为0,那么p指向的内存空间会被释放,但由于还没有开辟新的内存空间,所以会返回空指针NULL,类似于调用free函数。
无效自由(无效* p);
它的作用是释放p指向的内存区域,以便这部分内存区域可以被其他变量使用。 p一般是调用上述函数的返回值。 free 函数没有返回值。
工会/工会
有时,我们需要在同一个内存单元中存储几种不同类型的变量。 例如,可以将一个整型变量(2个字节)、一个字符变量(1个字节)和一个实数变量(4个字节)放在内存单元的同一起始地址处,如下图所示:
以上三个变量在内存中占用的字节数不同,但都是从同一个地址开始存储的,即几个变量相互覆盖。 这种允许多个不同变量占用同一内存的结构称为“union”类型结构,也称为“union”。
联合变量的定义
定义联合类型变量的一般形式是:
工会 工会名称 {
会员名单
} 多变的
列表; 例如:
联合数据{
整数我;
字符c;
;
} a、b、c;
也可以将类型声明与变量的定义分开:
联合数据{
整数我;
字符c;
;
};
联合数据a、b、c;
即先声明一个联合数据类型,然后将a、b、c定义为联合数据类型。 另外,还可以省略联合名称,直接定义联合变量:
联盟{
整数我;
字符c;
;
} a、b、c;
可见,“联合”和“结构”的定义形式相似,但含义不同:
引用联合变量
与结构体类似,联合体变量中成员的引用方法为:
联合变量名。 成员名字
联合变量只有先定义后才能被引用,并且不能直接引用联合变量,只能引用联合变量的成员。 例如,如果之前定义了联合变量 a,则:
但你不能只引用联合变量,例如 ("%d", a); 是错误的,因为a有多种类型的存储区域,每种类型占用不同长度的字节。 只写联合变量名a是很困难的。 允许系统确定输出哪个成员的值。
联合类型数据的特点
使用union类型数据时,应注意以下特点:
艾 = 1;
交流=“F”;
af = 2.5;
执行完以上三个赋值语句后,此时只有af有效,而ai和ac则无意义。 因此,在引用联合变量的成员时,程序员必须非常清楚联合变量中当前存储的是哪个成员。
联盟{
整数我;
字符c;
;
} a = {1, 'a', 1.5}; // 无法初始化联合体
a = 1; // 不能给联合变量赋值
m = a; // 无法引用联合变量名来获取值
社区总是让人感觉像是计算机开发早期的遗迹,当时内存空间非常宝贵。
总结
本文简要介绍C语言中结构体和联合体的概念和应用。 如有不当之处,敬请指出。