在C语言中,一个结构体(struct)是一个或多个变量的集合,这些变量可能是不同的类型,为了方便处理,这些变量被组织在一个名称下.结构有助于组织复杂的数据,尤其是在大型程序中,因为它们将一组相关变量视为一个单元而不是单独的实体。联合,也称为联合体,用于保存不同类型和长度(在不同时间)的变量,它提供了一种在单个存储区域中管理不同类型数据的方法。今天就来介绍一下C语言中结构体和联合体的相关概念和用法。struct/struct结构的定义声明一个结构类型的一般形式是:struct结构名{成员列表};其中,成员列表中的每个成员都需要进行类型声明,即:类型名成员名;例如,我们需要在程序中记录一个学生(student)的数据,包括学号(num)、姓名(name)、性别(sex)、年龄(age)、年级(score)、地址(addr)等,如下图示例:如果我们想在图中表示数据结构,但是C语言没有提供这种现成的数据类型,所以我们需要定义一个结构类型来表示它。tructstudent{intnum;charname[20];charsex;intage;floatscore;charaddr[30];};上面定义了一个新的结构类型structstudent(注意struct是关键,在声明结构类型的时候一定要用到,不能省略),它向编译系统声明这是一个“结构类型”,它包含了不同的类型num,name,sex,age,score,addr等数据项。??应该说这里的structstudent是一个类型名,和系统提供的标准类型(比如int,char,float,double,等等),可以用来定义变量的类型。结构体变量只是声明了一个结构体类型,相当于一个模型,但是里面并没有具体的数据,编译系统也不会为其分配实际的内存单元。为了在程序中使用结构类型的数据,我们应该定义一个结构类型的变量,并在其中存储具体的数据。结构类型变量的定义有以下三种方式:先声明结构类型,再定义变量名结构类型名结构变量名;比如我们上面定义了一个结构类型structstudent,我们可以用它来声明变量:structstudentstudent1,student2;student1和student2被定义为structstudent类型的变量,他们有一个structstudent类型的结构体,后面我们可以对他们进行初始化。在声明类型的同时定义变量例如:structstudent{intnum;charname[20];charsex;intage;floatscore;charaddr[30];}student1,student2;它的作用和第一种方法一样,就是定义了两个struct类型的变量student1,student2。这种形式定义的一般形式是:struct结构名{成员列表}变量名列表;直接定义省略结构体名称的结构体类型变量,一般形式为:struct{memberlist}变量名列表;关于结构体类型,需要补充一点:类型和变量是不同的概念,不要混淆。我们只能对变量进行赋值、访问或操作,而不能对类型进行赋值、访问或操作。在编译时,没有空间分配给类型,只有空间分配给变量。简单地说,我们可以将“structtype”和“structvariable”理解为面向对象语言中“类”和“对象”的概念。另外,结构体的成员也可以是结构体变量。比如我们先声明一个结构体structdate:structdate{intmonth;intday;intyear;};然后将其应用于语句structstudent:structstudent{intnum;charname[20];charsex;intage;floatscore;structdatebirthday;charaddr[30];}student1,student2;最后解释一下在阅读大型开源代码(如Objective-CRuntime源码)时容易产生疑问的一点:下面两个结构体SampleA和SampleB声明的变量在内存中其实是完全一样的,原因是因为结构本身不携带任何额外的附加信息:structSampleA{inta;intb;intc;};structSampleB{inta;structPart1{intb;};structPart2{intc;};};结构变量结构变量中成员的引用方式为:结构变量名。成员名称比如student1.num代表student1变量中的num个成员,我们可以给结构体变量的成员赋值:student1.num=10010;。如果成员本身属于结构类型,则需要使用多个成员运算符(点号)逐一查找最底层的成员,例如:student1.birthday.month=9;另外,对于结构体成员变量可以像普通变量一样进行各种操作,也可以使用地址运算符&来引用结构体变量成员的地址,或者引用结构体变量的地址。结构变量的初始化和其他类型的变量一样。对于结构变量,可以在定义时指定其初始值,用大括号括起来:structstudent{intnum;charname[20];charsex;intage;charaddr[30];}a={10010,"LiLei",'M',18,"北京海淀"};结构和数组如果数组的元素是结构类型,则称为“结构数组”。结构数组与前面介绍的数值数组的区别在于,每个数组元素都是一种结构类型的数据,它们都包括单独的成员项。定义结构体数组的方法与定义结构体变量类似,声明为数组即可,例如:structstudent{intnum;charname[20];charsex;intage;floatscore;charaddr[30];};structstudentstu[3]];上面定义了一个数组stu,它有3个元素,都是structstudent类型的数据,如下图:结构体数组的初始化和其他类型的数组一样,结构体数组可以进行初始化,例如:structstudent{intnum;charname[20];charsex;intage;floatscore;charaddr[30];}stu[3]={{10101,"LiLin",'M',18,87.5,"北京"},{10102,"Amey",'M',17,92,"上海"},{10103,"宾果",'F',20,100,"福建"}};从上面可以看出,结构体数组初始化的一般形式是在定义数组时添加“={初始值列表};”在它之后。结构体数组中的每个元素也在内存中连续存储,如下图所示:结构体和指针结构体变量的指针是该变量所占用的内存段的起始地址。指针变量可以设置为指向结构变量。此时指针变量的值就是结构变量的起始地址。指针变量也可用于指向结构数组中的元素。指向结构体变量的指针structstudent{intnum;charname[20];charsex;intage;floatscore;charaddr[30];};structstudentstu1={...};structstudent*p;p=&stu1;上面的代码首先声明了structstudent结构类型,然后定义一个structstudent类型的变量stu1,同时定义一个指针变量p,指向一个structstudent类型的数据,最后将结构变量stu1的起始地址赋给该指针变量p,如图:这时候可以用*p访问结构体变量stu1的值,用(*p).num访问stu的成员变量。为了C语言的方便和直观,定义可以将(*p).num改为p->num,表示p指向的结构变量中的num成员。也就是说,以下三种形式是等价的:结构变量。成员名:stu1.num(*指针变量名)。成员名:(*p).num指针变量名->成员名:p->num指向结构体数组的指针也可以由结构体数组及其元素的指针变量指向,例如:structstudent{intnum;charname[20];charsex;intage;floatscore;charaddr[30];};structstudentstu[3]={{10101,"LiLin",'M',18,87.5,"北京"},{10102,"Amey",'M',17,92,"上海"},{10103,"宾果",'F',20,100,"福建"}};structstudent*p=stu;此时指针变量p指向数组首元素的地址,即&stu[0],即数组名stu。结构指针使用场景(1)函数参数:使用指向结构变量(或数组)的指针作为实参,将结构变量(或数组)的地址传递给形参。voidprintStudentInfo(structstudent*p);因为如果我们直接使用结构体变量(不是结构体指针)作为实参,由于采用“传值”方式,结构体变量所占内存单元的内容都是序列传递给形参,形参也必须是相同类型的结构变量。在函数调用过程中,形参也占用内存单元。这种传递方式会带来较大的时间和空间开销,也不利于将函数执行过程中改变的形参结构的值(结果)返回给宿主。所以一般不太可能直接“用结构变量作为实参”,而是用指针代替。(2)链表链表是一种常见且非常重要的数据结构,一般用于动态存储分配。常见的有单链表和双链表等,一般可以用结构体来表示链表的节点。下面是普通“单链表”节点的声明:structListNode{intval;structListNode*next;};其中,val形式的链表节点值,next指针用于指向链表的下一个节点。比如面试中经常考察的“反向单链表”题目:structListNode*reverseList(structListNode*head){if(head==NULL){returnNULL;}if(head->next==NULL){returnhead;}??structListNode*reversedHead=NULL;structListNode*prevNode=NULL;structListNode*currentNode=head;while(currentNode!=NULL){structListNode*nextNode=currentNode->next;if(nextNode==NULL){reversedHead=currentNode;}currentNode->next=prevNode;prevNode=currentNode;currentNode=nextNode;}returnreversedHead;}??(3)二叉树structTreeNode{intval;structTreeNode*left;structTreeNode*right;};其中val表示二叉树的叶子节点的值,left指向节点树的左孩子,right指向右子树。比如之前闹得沸沸扬扬的Google面试的“InvertingaBinaryTree”题目:structTreeNode*invertTree(structTreeNode*root){if(root==NULL){returnNULL;}root->left=invertTree(root->left);root->right=invertTree(root->right);structTreeNode*temp=root->left;root->left=root->right;root->right=temp;returnroot;}动态开和内存空间的释放链表结构是动态分配存储的,即一个节点的存储单元只有在需要的时候才打开。那么,如何动态开发和释放存储单元呢?C语言编译系统的库函数提供了以下相关函数。malloc函数void*malloc(unsignedsize);它的作用是在内存的动态存储区(堆)中分配一个长度为size的连续空间,该函数的返回值是一个指向分配域起始地址的指针(类型为void*,即,void指针类型,使用时可以转换为其他指针数据类型)。如果该函数执行失败(例如内存空间不足),则返回空指针NULL。使用示例:int*result=malloc(2*sizeof(int));structListNode*node=malloc(sizeof(structListNode));上面的result是分配在堆上的一个长度为2的数组,和intresult[2]是一样的;不同的是后者分配在内存栈区。而node是一个指针变量,指向一个structListNode类型数据的起始地址(也是分配在堆上)。calloc函数void*calloc(unsignedn,unsignedsize);它的作用是在内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配域起始地址的指针。如果分配不成功,则返回NULL。realloc函数void*realloc(void*p,unsignedsize);它的作用是将p指向的已分配动态内存区的大小更改为size,size可以比原来分配的空间更大或更小。该函数返回一个指向已分配内存区起始地址的指针,如果分配不成功,也返回NULL。如果传入的p为NULL,其作用与malloc函数相同,即分配size字节的内存空间。如果传入的size值为0,p指向的内存空间会被释放,但是由于没有新的内存空间被打开,所以会返回一个空指针NULL,类似于调用free函数。自由函数voidfree(void*p);它的作用是释放p指向的内存区域,让这部分内存区域可以被其他变量使用,p一般就是调用上面函数的返回值。free函数不返回任何值。Union/union有时,我们需要在同一个内存单元中存储几种不同类型的变量。例如,可以把一个整型变量(2字节),一个字符变量(1字节),一个实型变量(4字节)放在同一个起始地址的内存单元中,如下图:以上三个变量在内存中占用的字节数不同,但都是从同一个地址存储的,即几个变量相互覆盖。这种允许几个不同变量共享同一内存的结构称为“共同体”类型的结构,也称为“联合体”。联合变量的定义定义联合类型变量的一般形式为:联合联合名称{成员列表}变量列表;例如:uniondata{inti;charc;floatf;}a,b,c;类型声明也可以和变量分开定义:uniondata{inti;charc;floatf;};uniondataa,b,c;即先声明一个union数据类型,然后定义a,b,c为union数据类型。另外,也可以省略联合体名称,直接定义联合体变量:union{inti;charc;floatf;}a,b,c;可见“union”和“structure”的定义形式类似,但它们的含义不同:结构变量占用的内存长度(总字节数)是每个占用的内存长度之和member,每个member独占自己的内存单元。联合变量占用的内存长度等于最长成员的长度。比如上面定义的联合变量a、b、c各占4个字节(因为其中最长的实型变量占4个字节),而不是各占2+1+4=7个字节。联合变量的引用类似于结构体的引用。联合变量中成员的引用方式为:联合变量名。只有先定义了union变量才能引用成员名,不能直接引用union变量,只能引用body变量的common成员。比如上面定义了联合变量a,那么:a.i指联合变量中的整型变量ia.c指联合变量中的字符变量ca.f指联合变量中的实型变量f但是不能只引用联合变量是错误的,例如printf("%d",a);因为a有几种类型的存储区,各自占用的字节长度不同,只用联合变量名a是有难度的。系统决定输出哪个成员的值。union类型数据的特点在使用union类型数据时,要注意以下特点:同一个内存段可以用来存放几种不同类型的成员,但每个瞬间只能存放其中一个,不能同时存放。同时存储多个。也就是说,每一时刻只有一个成员工作,其他成员不工作,即联合体中的成员并不是同时存在和工作的。联合变量的有效成员为上次存储的成员。存储新成员后,原成员将失去其功能。例如有如下赋值语句:a.i=1;a.c='F';a.f=2.5;执行完以上三个赋值语句后,此时只有a.f有效,a.i和a.c无意义。因此,在引用联合变量的成员时,程序员必须非常清楚联合变量中当前存储的是哪个成员。联合变量的地址和其成员的地址是同一个地址,比如&a,&a.i,&a.c,&a.f都是同一个地址值,道理很明显。不能直接给联合变量名赋值,也不能试图引用变量名获取值,定义联合变量时不能初始化。例如下面都是错误的:union{inti;charc;floatf;}a={1,'a',1.5};//不能为union初始化a=1;//不能将m赋值给union变量=a;//无法引用union变量名取值不能使用union变量作为函数参数,函数也不能返回union类型的变量,但是可以使用指向union变量(和指向结构体变量的指针用法类似,这里不再赘述)。联合类型可以出现在结构类型定义中,也可以定义联合数组。反过来,结构也可以出现在联合类型定义中,数组也可以作为联合的成员。社区总是感觉像是早期计算的遗迹,那时内存很昂贵。小结本文简要介绍了C语言中结构体和联合体的概念和应用。如有不当之处,敬请指出。
