2022-11-24

向函数传递结构的信息

函数的参数把值传递给函数。 每个值都是一个数字——可能是int类型、float类型, 可能是ASCII字符码, 或者是一个地址。 然而, 一个结构比一个单独的值复杂, 所以难怪以前的C实现不允许把结构作为参数传递给函数。当前的实现已经移除了这个限制, ANSI C允许把结构作为参数使用。 所以程序员可以选择是传递结构本身, 还是传递指向结构的指针

传递结构成员

只要结构成员是一个具有单个值的数据类型(即, int及其相关类型、char、 float、 double或指针) , 便可把它作为参数传递给接受该特定类型的函数。

sum()函数既不知道也不关心实际的参数是否是结构的成员, 它只要求传入的数据是double类型。

如果需要在被调函数中修改主调函数中成员的值, 就要传递成员的地址

modify(&stan.bankfund);

这是一个更改银行账户的函数。

把结构的信息告诉函数的第2种方法是, 让被调函数知道自己正在处理一个结构。

传递结构的地址

sum()函数使用指向funds结构的指针(money) 作为它的参数。 把地址&stan传递给该函数, 使得指针money指向结构stan。 然后通过->运算符获取stan.bankfund和stan.savefund的值。 由于该函数不能改变指针所指向值的内容, 所以把money声明为一个指向const的指针。

必须使用&运算符来获取结构的地址。 和数组名不同, 结构名只是其地址的别名。

传递结构

程序中指向struct funds类型的结构指针money替换成struct funds类型的结构变量moolah。 调用sum()时, 编译器根据funds模板创建了一个名为moolah的自动结构变量。 然后, 该结构的各成员被初始化为 stan结构变量相应成员的值的副本。

其他结构特性

C允许把一个结构赋值给另一个结构, 但是数组不能这样做。

现在的C(包括ANSI C) , 函数不仅能把结构本身作为参数传递, 还能把结构作为返回值返回。 把结构作为函数参数可以把结构的信息传送给函数; 把结构作为返回值的函数能把结构的信息从被调函数传回主调函数。 结构指针也允许这种双向通信, 因此可以选择任一种方法来解决编程问题。

该程序把任务分配给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()。

考虑makeinfo()函数。 在第1个程序中, 传递的是person的地址,该函数实际上处理的是person的值。 在第2个版本的程序中, 创建了一个新的结构info。 储存在person中的值被拷贝到info中, 函数处理的是这个副本。因此, 统计完字母个数后, 计算结果储存在info中, 而不是person中。 然而, 返回机制弥补了这一点。

return info;

与main()中的这行结合:

person = makeinfo(person);

把储存在info中的值拷贝到person中。 注意, 必须把makeinfo()函数声明为struct namect类型, 所以该函数要返回一个结构。

结构和结构指针的选择

把指针作为参数有两个优点: 无论是以前还是现在的C实现都能使用这种方法, 而且执行起来很快, 只需要传递一个地址。 缺点是无法保护数据。被调函数中的某些操作可能会意外影响原来结构中的数据。 不过,ANSI C新增的const限定符解决了这个问题。

把结构作为参数传递的优点是, 函数处理的是原始数据的副本, 这保护了原始数据。

传递结构的两个缺点是: 较老版本的实现可能无法处理这样的代码, 而且传递结构浪费时间和存储空间。 尤其是把大型结构传递给函数, 而它只使用结构中的一两个成员时特别浪费。 这种情况下传递指针或只传递函数所需的成员更合理。

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

struct names veep = {"Talia", "Summers"};

struct pnames treas = {"Brad", "Fallingjaw"};

printf("%s and %s\n", veep.first, treas.first);

对于struct names类型的结构变量veep, 以上字符串都储存在结构内部,结构总共要分配40字节储存姓名。 然而, 对于struct pnames类型的结构变量treas, 以上字符串储存在编译器储存常量的地方。 结构本身只储存了两个地址, 在我们的系统中共占16字节。 尤其是, struct pnames结构不用为字符串分配任何存储空间。 它使用的是储存在别处的字符串(如, 字符串常量或数组中的字符串) 。 简而言之, 在pnames结构变量中的指针应该只用来在程序中管理那些已分配和在别处分配的字符串。

如果要用结构储存字符串, 用字符数组作为成员比较简单。

结构指针和malloc()

如果使用malloc()分配内存并使用指针储存该地址, 那么在结构中使用指针处理字符串就比较合理。 这种方法的优点是, 可以请求malloc()为字符串分配合适的存储空间。 可以要求用4字节储存"Joe"和用18字节储存"Rasolofomasoandro"。

struct namect {

char * fname; // 用指针代替数组

char * lname;

int letters;

};

新版本的getinfo()把用户的输入读入临时数组中, 调用malloc()函数分配存储空间, 并把字符串拷贝到新分配的存储空间中。

void getinfo (struct namect * pst)

char temp[SLEN];

printf("Please enter your first name.\n");

s_gets(temp, SLEN);

// 分配内存储存名

pst->fname = (char *) malloc(strlen(temp) + 1);

// 把名拷贝到已分配的内存

strcpy(pst->fname, temp);

printf("Please enter your last name.\n");

s_gets(temp, SLEN);

1054pst->lname = (char *) malloc(strlen(temp) + 1);

strcpy(pst->lname, temp);

}

要理解这两个字符串都未储存在结构中, 它们储存在 malloc()分配的内存块中。 然而, 结构中储存着这两个字符串的地址, 处理字符串的函数通常都要使用字符串的地址。 因此, 不用修改程序中的其他函数。

应该成对使用malloc()和free()。 因此, 还要在程序中添加一个新的函数cleanup(), 用于释放程序动态分配的内存。

复合字面量和结构(C99)

C99 的复合字面量特性可用于结构和数组。 如果只需要一个临时结构值, 复合字面量很好用。

使用复合字面量为一个结构变量提供两个可替换的值。

复合字面量在所有函数的外部, 具有静态存储期; 如果复合字面量在块中, 则具有自动存储期。 复合字面量和普通初始化列表的语法规则相同。 这意味着, 可以在复合字面量中使用指定初始化器。

伸缩型数组成员(C99)

C99新增了一个特性: 伸缩型数组成员(flexible array member),利用这项特性声明的结构, 其最后一个数组成员具有一些特性。 第1个特性是,该数组不会立即存在。 第2个特性是, 使用这个伸缩型数组成员可以编写合适的代码, 就好像它确实存在并具有所需数目的元素一样。

声明一个伸缩型数组成员有如下规则:

伸缩型数组成员必须是结构的最后一个成员;

结构中必须至少有一个成员;

伸缩数组的声明类似于普通数组, 只是它的方括号中是空的。

struct flex

int count;

double average;

double scores[]; // 伸缩型数组成员

};

声明一个struct flex类型的结构变量时, 不能用scores做任何事, 因为没有给这个数组预留存储空间。 实际上, C99的意图并不是让你声明struct flex类型的变量, 而是希望你声明一个指向struct flex类型的指针, 然后用malloc()来分配足够的空间, 以储存struct flex类型结构的常规内容和伸缩型数组成员所需的额外空间。

让伸缩型数组成员在第1种情况下表示5个值, 在第2种情况下代表9个值。

带伸缩型数组成员的结构确实有一些特殊的处理要求。 第一, 不能用结构进行赋值或拷贝。

struct flex * pf1, *pf2; // *pf1 和*pf2 都是结构

...

*pf2 = *pf1; // 不要这样做

这样做只能拷贝除伸缩型数组成员以外的其他成员。 确实要进行拷贝,应使用memcpy()函数。

第二, 不要以按值方式把这种结构传递给结构。 原因相同, 按值传递一个参数与赋值类似。 要把结构的地址传递给函数。

第三, 不要使用带伸缩型数组成员的结构作为数组成员或另一个结构的成员。

这种类似于在结构中最后一个成员是伸缩型数组的情况, 称为struct hack。 除了伸缩型数组成员在声明时用空的方括号外, struct hack特指大小为0的数组。 然而, struct hack是针对特殊编译器(GCC) 的, 不属于C标准。这种伸缩型数组成员方法是标准认可的编程技巧。

匿名结构(C11)

匿名结构是一个没有名称的结构成员。

struct names

char first[20];

char last[20];

};

struct person

int id;

struct names name;// 嵌套结构成员

};

struct person ted = {8483, {"Ted", "Grass"}};

name成员是一个嵌套结构, 可以通过类似ted.name.first的表达式访问"ted"

puts(ted.name.first);

在C11中, 可以用嵌套的匿名成员结构定义person:

struct person

int id;

struct {char first[20]; char last[20];}; // 匿名结构

};

初始化ted的方式相同:

struct person ted = {8483, {"Ted", "Grass"}};

但是, 在访问ted时简化了步骤, 只需把first看作是person的成员那样使用它。

puts(ted.first);

当然, 也可以把first和last直接作为person的成员, 删除嵌套循环。 匿名特性在嵌套联合中更加有用

你可能感兴趣的:(2022-11-24)