🍏向函数传递结构的信息
2021-1-25
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property
 
函数的参数把值传递给函数。每个值都是一个数字——可能是int类型、float类型,可能是ASCII字符码,或者是一个地址。然而,一个结构比一个单独的值复杂,所以难怪以前的C实现不允许把结构作为参数传递给函数。 当前的实现已经移除了这个限制,ANSI C允许把结构作为参数使用。所以程序员可以选择是传递结构本身,还是传递指向结构的指针。如果只关心结构中的某一部分,也可以把结构的成员作为参数。

传递结构成员

只要结构成员是一个具有单个值的数据类型(即,int及其相关类型、char、float、double或指针),便可把它作为参数传递给接受该特定类型的函数
当然,如果需要在被调函数中修改主调函数中成员的值,就要传递成员的地址。
 

传递结构的地址

这次把结构的地址作为参数
sum()函数使用指向funds结构的指针(money)作为它的参数。把地址&stan传递给该函数,使得指针money指向结构stan。然后通过->运算符获取stan.bankfundstan.savefund的值。由于该函数不能改变指针所指向值的内容,所以把money声明为一个指向const的指针。 虽然该函数并未使用其他成员,但是也可以访问它们。注意,必须使用&运算符来获取结构的地址。
 

传递结构

把指向struct funds类型的结构指针money替换成struct funds类型的结构变量moolah。调用sum()时,编译器根据funds模板创建了一个名为moolah的自动结构变量。然后,该结构的各成员被初始化为 stan结构变量相应成员的值的副本。因此,程序使用原来结构的副本进行计算,传递指针的程序使用的是原始的结构进行计算。由于moolah是一个结构,所以该程序使用moolah.bankfund,而不是moolah->bankfund。
 

其他结构特性

现在的C允许把一个结构赋值给另一个结构,但是数组不能这样做。也就是说,如果n_data和o_data都是相同类型的结构,可以这样做:
这条语句把n_data的每个成员的值都赋给o_data的相应成员。即使成员是数组,也能完成赋值。另外,还可以把一个结构初始化为相同类型的另一个结构:
 
现在的C(包括ANSI C),函数不仅能把结构本身作为参数传递,还能把结构作为返回值返回。把结构作为函数参数可以把结构的信息传送给函数;把结构作为返回值的函数能把结构的信息从被调函数传回主调函数。结构指针也允许这种双向通信,因此可以选择任一种方法来解决编程问题。
为了对比这两种方法,我们先编写一个程序以传递指针的方式处理结构,然后以传递结构和返回结构的方式重写该程序。
notion image
该程序把任务分配给3个函数来完成,都在main()中调用。每调用一个函数就把person结构的地址传递给它。 getinfo()函数把结构的信息从自身传递给main()。该函数通过与用户交互获得姓名,并通过pst指针定位,将其放入 person 结构中。由于 pst->lname意味着 pst 指向结构的 lname 成员,这使得pst->lname等价于char数组的名称,因此做s_gets()的参数很合适。注意,虽然getinfo()给main()提供了信息,但是它并未使用返回机制,所以其返回类型是void。 makeinfo()函数使用双向传输方式传送信息。通过使用指向 person 的指针,该指针定位了储存在该结构中的名和姓。该函数使用C库函数strlen()分别计算名和姓中的字母总数,然后使用person的地址储存两数之和。同样,makeinfo()函数的返回类型也是void。 showinfo()函数使用一个指针定位待打印的信息。因为该函数不改变数组的内容,所以将其声明为const。 所有这些操作中,只有一个结构变量 person,每个函数都使用该结构变量的地址来访问它。一个函数把信息从自身传回主调函数,一个函数把信息从主调函数传给自身,一个函数通过双向传输来传递信息。
现在,我们来看如何使用结构参数和返回值来完成相同的任务。第一,为了传递结构本身,函数的参数必须是person,而不是&person。那么,相应的形式参数应声明为struct namect,而不是指向该类型的指针。第二,可 以通过返回一个结构,把结构的信息返回给main()。
 
不使用指针的版本。
程序中的每个函数都创建了自己的person备份,所以该程序使用了4个不同的结构,不像前面的版本只使用一个结构。
例如,考虑makeinfo()函数。在第1个程序中,传递的是person的地址,该函数实际上处理的是person的值。在第2个版本的程序中,创建了一个新的结构info。储存在person中的值被拷贝到info中,函数处理的是这个副本。因此,统计完字母个数后,计算结果储存在info中,而不是person中。然而,返回机制弥补了这一点。makeinfo()中的这行代码:
与main()中的这行结合:
把储存在info中的值拷贝到person中。注意,必须把makeinfo()函数声明为struct namect类型,所以该函数要返回一个结构。
 

结构和结构指针的选择

把指针作为参数有两个优点:无论是以前还是现在的C实现都能使用这种方法,而且执行起来很快,只需要传递一个地址。缺点是无法保护数据。被调函数中的某些操作可能会意外影响原来结构中的数据。不过,ANSI C新增的const限定符解决了这个问题。例如,如果在程序中,showinfo()函数中的代码改变了结构的任意成员,编译器会捕获这个错误。 把结构作为参数传递的优点是,函数处理的是原始数据的副本,这保护了原始数据。另外,代码风格也更清楚。假设定义了下面的结构类型:
如果用vector类型的结构ans储存相同类型结构a和b的和,就要把结构作为参数和返回值:
对程序员而言,上面的版本比用指针传递的版本更自然。指针版本如下:
另外,如果使用指针版本,程序员必须记住总和的地址应该是第1个参数还是第2个参数的地址。 传递结构的两个缺点是:较老版本的实现可能无法处理这样的代码,而且传递结构浪费时间和存储空间。尤其是把大型结构传递给函数,而它只使用结构中的一两个成员时特别浪费。这种情况下传递指针或只传递函数所需的成员更合理。 通常,程序员为了追求效率会使用结构指针作为函数参数,如需防止原始数据被意外修改,使用const限定符。按值传递结构是处理小型结构最常用的方法。
 
 

结构中的字符数组和字符指针

是否可以使用指向 char 的指针来代替字符数组?
其中的结构声明是否可以这样写:
当然可以,考虑下面的代码:
对于struct names类型的结构变量veep,以上字符串都储存在结构内部,结构总共要分配40字节储存姓名。然而,对于struct pnames类型的结构变量treas,以上字符串储存在编译器储存常量的地方。结构本身只储存了两个地 址,在系统中共占16字节。尤其是,struct pnames结构不用为字符串分配任何存储空间。它使用的是储存在别处的字符串(如,字符串常量或数组中的字符串)。简而言之,在pnames结构变量中的指针应该只用来在程序中管理那些已分配和在别处分配的字符串。
 
就语法而言,这段代码没问题。但是,用户的输入储存到哪里去了?对于会计师(accountant),他的名储存在accountant结构变量的last成员中,该结构中有一个储存字符串的数组。对于律师(attorney),scanf()把字符串放 到attorney.last表示的地址上。由于这是未经初始化的变量,地址可以是任何值,因此程序可以把名放在任何地方。如果走运的话,程序不会出问题,少暂时不会出问题,否则这一操作会导致程序崩溃。实际上,如果程序能正常运行并不是好事,因为这意味着一个未被觉察的危险潜伏在程序中。因此,如果要用结构储存字符串,用字符数组作为成员比较简单。用指向 char 的指针也行,但是误用会导致严重的问题。

结构、指针和malloc()

如果用malloc()分配内存并使用指针储存该地址,那么在结构中使用指针处理字符串就比较合理。这样的优点是,可以请求malloc()为字符串分配合适的存储空间。可以要求用4字节储存"Joe"和用18字节储存 "Rasolofomasoandro"。
这两个字符串都未储存在结构中,它们储存在 malloc()分配的内存块中。然而,结构中储存着这两个字符串的地址,处理字符串的函数通常都要使用字符串的地址。
 
notion image
 
 

复合字面量和结构(C99)

C99 的复合字面量特性可用于结构和数组。如果只需要一个临时结构值,复合字面量很好用。例如,可以使用复合字面量创建一个数组作为函数的参数或赋给另一个结构。语法是把类型名放在圆括号中,后面紧跟一个用 花括号括起来的初始化列表。例如,下面是struct book类型的复合字面量:
 
还可以把复合字面量作为函数的参数。如果函数接受一个结构,可以把复合字面量作为实际参数传递:
 
如果函数接受一个地址,可以传递复合字面量的地址:
复合字面量在所有函数的外部,具有静态存储期;如果复合字面量在块中,则具有自动存储期。复合字面量和普通初始化列表的语法规则相同。这意味着,可以在复合字面量中使用指定初始化器。
 
 

伸缩型数组成员(C99)

C99新增了一个特性:伸缩型数组成员(flexible array member),利用这项特性声明的结构,其最后一个数组成员具有一些特性。第1个特性是,该数组不会立即存在。第2个特性是,使用这个伸缩型数组成员可以编写合适的代码,就好像它确实存在并具有所需数目的元素一样。
声明一个伸缩型数组成员有如下规则:
  • 伸缩型数组成员必须是结构的最后一个成员;
  • 结构中必须至少有一个成员;
  • 伸缩数组的声明类似于普通数组,只是它的方括号中是空的。
声明一个struct flex类型的结构变量时,不能用scores做任何事,因为没有给这个数组预留存储空间。实际上,C99的意图并不是让你声明struct flex类型的变量,而是希望你声明一个指向struct flex类型的指针,然后用malloc()来分配足够的空间,以储存struct flex类型结构的常规内容和伸缩型数组成员所需的额外空间。例如,假设用scores表示一个内含5个double类型值的数组,可以这样做:
现在有足够的存储空间储存count、average和一个内含5个double类型值的数组。可以用指针pf访问这些成员:
notion image
带伸缩型数组成员的结构确实有一些特殊的处理要求。第一,不能用结构进行赋值或拷贝:
这样做只能拷贝除伸缩型数组成员以外的其他成员。确实要进行拷贝,应使用memcpy()函数。 第二,不要以按值方式把这种结构传递给结构。原因相同,按值传递一个参数与赋值类似。要把结构的地址传递给函数。 第三,不要使用带伸缩型数组成员的结构作为数组成员或另一个结构的成员。 这种类似于在结构中最后一个成员是伸缩型数组的情况,称为struct hack。除了伸缩型数组成员在声明时用空的方括号外,struct hack特指大小为0的数组。然而,struct hack是针对特殊编译器(GCC)的,不属于C标准。这种伸缩型数组成员方法是标准认可的编程技巧。
 
 
 

匿名结构(C11)

匿名结构是一个没有名称的结构成员。
这里,name成员是一个嵌套结构,可以通过类似ted.name.first的表达式访问"ted":
在C11中,可以用嵌套的匿名成员结构定义person:
初始化ted的方式相同:
但是,在访问ted时简化了步骤,只需把first看作是person的成员那样使用它:
当然,也可以把first和last直接作为person的成员,删除嵌套循环。
 
 

使用结构数组的函数

假设一个函数要处理一个结构数组。由于数组名就是该数组的地址,所以可以把它传递给函数。另外,该函数还需访问结构模板。
notion image
数组名jones是该数组的地址,即该数组首元素(jones[0])的地址。因此,指针money的初始值相当于通过下面的表达式获得:
因为money指向jones数组的首元素,所以money[0]是该数组的另一个名称。与此类似,money[1]是第2个元素。每个元素都是一个funds类型的结构,所以都可以使用点运算符(.)来访问funds类型结构的成员。
 
可以把数组名作为数组中第1个结构的地址传递给函数。 然后可以用数组表示法访问数组中的其他结构。注意下面的函数调用与使用数组名效果相同:
因为jones和&jones[0]的地址相同,使用数组名是传递结构地址的一种间接的方法。 由于sum()函数不能改变原始数据,所以该函数使用了ANSI C的限定符const。
 
  • C
  • 指向结构的指针把结构内容保存到文件中
    目录