20-其他结构特性
14.7.4 其他结构特性
现在的C允许把一个结构赋值给另一个结构,但是数组不能这样做。也就是说,如果 n_data 和 o_data 都是相同类型的结构,可以这样做:
o_data = n_data; // 把一个结构赋值给另一个结构
这条语句把 n_data 的每个成员的值都赋给 o_data 的相应成员。即使成员是数组,也能完成赋值。另外,还可以把一个结构初始化为相同类型的另一个结构:
struct names right_field = {"Ruthie", "George"};
struct names captain = right_field; // 把一个结构初始化为另一个结构
现在的C(包括ANSI C),函数不仅能把结构本身作为参数传递,还能把结构作为返回值返回。把结构作为函数参数可以把结构的信息传送给函数;把结构作为返回值的函数能把结构的信息从被调函数传回主调函数。结构指针也允许这种双向通信,因此可以选择任一种方法来解决编程问题。我们通过另一组程序示例来演示这两种方法。
为了对比这两种方法,我们先编写一个程序以传递指针的方式处理结构(见程序清单14.8),然后以传递结构和返回结构的方式重写该程序。
程序清单14.8 names1.c 程序
/* names1.c -- 使用指向结构的指针 */
#include <stdio.h>
#include <string.h>
#define NLEN 30
struct namect {
char fname[NLEN];
char lname[NLEN];
int letters;
};
void getinfo(struct namect *);
void makeinfo(struct namect *);
void showinfo(const struct namect *);
char * s_gets(char * st, int n);
int main(void)
{
struct namect person;
getinfo(&person);
makeinfo(&person);
showinfo(&person);
return 0;
}
void getinfo(struct namect * pst)
{
printf("Please enter your first name.\n");
s_gets(pst->fname, NLEN);
printf("Please enter your last name.\n");
s_gets(pst->lname, NLEN);
}
void makeinfo(struct namect * pst)
{
pst->letters = strlen(pst->fname) +strlen(pst->lname);
}
void showinfo(const struct namect * pst)
{
printf("%s %s, your name contains %d letters.\n",
pst->fname, pst->lname, pst->letters);
}
char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n'); // 查找换行符
if (find) // 如果地址不是 NULL,
*find = '\0'; // 在此处放置一个空字符
else
while (getchar() != '\n')
continue; // 处理输入行的剩余字符
}
return ret_val;
}
下面是编译并运行该程序后的一个输出示例:
Please enter your first name.
Viola
Please enter your last name.
Plunderfest
Viola Plunderfest, your name contains 16 letters.
该程序把任务分配给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() 。程序清单14.9演示了不使用指针的版本。
程序清单14.9 names2.c 程序
/* names2.c -- 传递并返回结构 */
#include <stdio.h>
#include <string.h>
#define NLEN 30
struct namect {
char fname[NLEN];
char lname[NLEN];
int letters;
};
struct namect getinfo(void);
struct namect makeinfo(struct namect);
void showinfo(struct namect);
char * s_gets(char * st, int n);
int main(void)
{
struct namect person;
person = getinfo();
person = makeinfo(person);
showinfo(person);
return 0;
}
struct namect getinfo(void)
{
struct namect temp;
printf("Please enter your first name.\n");
s_gets(temp.fname, NLEN);
printf("Please enter your last name.\n");
s_gets(temp.lname, NLEN);
return temp;
}
struct namect makeinfo(struct namect info)
{
info.letters = strlen(info.fname) + strlen(info.lname);
return info;
}
void showinfo(struct namect info)
{
printf("%s %s, your name contains %d letters.\n",
info.fname, info.lname, info.letters);
}
char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n'); // 查找换行符
if (find) // 如果地址不是 NULL,
*find = '\0'; // 在此处放置一个空字符
else
while (getchar() != '\n')
continue; // 处理输入行的剩余部分
}
return ret_val;
}
该版本最终的输出和前面版本相同,但是它使用了不同的方式。程序中的每个函数都创建了自己的 person 备份,所以该程序使用了4个不同的结构,不像前面的版本只使用一个结构。
例如,考虑 makeinfo() 函数。在第1个程序中,传递的是 person 的地址,该函数实际上处理的是 person 的值。在第2个版本的程序中,创建了一个新的结构 info 。存储在 person 中的值被拷贝到 info 中,函数处理的是这个副本。因此,统计完字母个数后,计算结果存储在 info 中,而不是 person 中。然而,返回机制弥补了这一点。 makeinfo() 中的这行代码:
return info;
与 main() 中的这行结合:
person = makeinfo(person);
把存储在 info 中的值拷贝到 person 中。注意,必须把 makeinfo() 函数声明为 struct namect 类型,因为该函数要返回一个结构。