您的位置:首页 > 其它

第14章 结构和其他数据形式 14.7 向函数传递结构信息

2017-03-23 00:00 489 查看
现在的C实现允许把结构作为参数传递,或把指向结构的指针作为参数传递,如果只关心结构的一部分,还可以把结构成员作为参数传递。首先,我们看看把结构成员作为参数传递。

14.7.1 传递结构成员

只要结构成员是具有单个值的数据类型(即int及其相关类型、char、float、double或指针),就可以把它作为参数传递给一个接受这个特定类型的函数。程序清单14.5所示的金融分析雏形程序就说明了这一点。这个程序把客户的银行账号加到他的储蓄和贷款账户中。

程序清单14.5 funds1.c程序

/*funds1.c  --把结构成员作为参数传递*/
#include <stdio.h>
#define FUNDLEN 50
struct funds {
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};
double sum(double,double);

int main(void)
{
struct funds stan = {
"Garlic-Melon Bank",
3024.72,
"Lucky's savings and Loan",
9237.11
};
printf("Stan has a total of $%.2f.\n",
sum(stan.bankfund,stan.savefund));
return 0;
}
double sum(double x,double y)
{
return(x+y);
}

注意,函数sum()即不知道也不关心实际参数是不是结构的成员,它只要求参数是double类型的。当然,如果想让被调函数影响调用函数中的成员的值,可以传递成员地址:

modify(&stan.bankfund);

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

下一个向函数传递结构信息的方法将使被调函数知道自己正在处理一个结构。

14.7.2 使用结构地址

我们还是解决前面的那个问题,只不过这一次我们把结构的地址作为参数。因为函数要处理funds结构,所以它也要使用funds声明。请看程序清单14.6:

程序清单14.6 funds2.c程序

/*funds2.c  --传递指向结构的指针*/
#include <stdio.h>
#define FUNDLEN 50
struct funds {
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};
double sum(const struct funds *);/*参数是一个指针*/

int main(void)
{
struct funds stan = {
"Garlic-Melon Bank",
3024.72,
"Lucky's savings and Loan",
9237.11
};
printf("Stan has a total of $%.2f.\n",
sum(&stan));
return 0;
}
double sum(const struct funds *money)
{
return(money->bankfund+money->savefund);
}

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

注意,必须使用&运算符才能得到结构的地址。和数组名不一样,单独的结构名不是该结构地址的同义词。

14.7.3 把结构作为参数传递

对于允许把结构作为参数传递的编译器来说,上一个例子可以改写为程序清单14.7所示的程序。

/*funds2.c  --把结构作为参数传递*/
#include <stdio.h>
#define FUNDLEN 50
struct funds {
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};
double sum(struct funds moolah);/*参数是一个结构*/

int main(void)
{
struct funds stan = {
"Garlic-Melon Bank",
3024.72,
"Lucky's savings and Loan",
9237.11
};
printf("Stan has a total of $%.2f.\n",
sum(stan));
return 0;
}
double sum(struct funds moolah)
{
return(moolah.bankfund+moolah.savefund);
}

调用sum()时,会根据funds模板创建一个自动变量moolah。然后,这个结构的成员被初始化为stan结构的相应成员取值的副本。因此将使用原有结构的副本完成计算,而前面的程序(使用指针的那个)使用的是原有结构本身。因为moolah是一个结构,所以程序用的是moolah.bankfund,而不是moolah->bankfund。

14.7.4 其他结构特性

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

也就是说n_data和o_data是同一类型的结构,可以像下面这样做:

o_data=n_data; //把一个结构赋值给另一个结构

这样就使o_data的每个成员都被赋成n_data相应成员的值 ,即使有一个成员是数组也照样完成赋值

也可以把一个结构初始化为另一个同样类型的结构

struct names right_field={"Ruthie","George"};

struct names captain=reght_field; //把一个结构初始化为一个结构

在现在的C中,结构不仅可以作为参数传递给函数,也可以作为函数返回值返回。

把结构作为函数参数可以把结构信息传递给一个函数,使用函数返回结构可以将结构信息从被调函数传递给调用函数。同时,结构指针也允许双向通信,因此可以使用任一种方法解决编程问题。

为了对比这两种方法,我们写一个用指针处理结构的简单程序,然后再用结构传递和结构返回来重写这个程序。程序本身要求您输入名和姓,然后告诉您姓名中的字母总数。

程序清单14.8给出了指针版的程序 。

程序清单14.8

/*funds2.c  --使用指向结构的指针*/
#include <stdio.h>
#include <string.h>

struct namect {
char fname[20];
char lname[20];
int letters;
};

void getinfo(struct namect *);
void makeinfo(struct namect *);
void showinfo(const struct namect *);

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");
gets(pst->fname);
printf("Please enter your last name.\n");
gets(pst->lname);
}

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);
}

程序的工作分配给3个由main()调用的函数来完成。person结构的地址被传递给了每一个函数。

getinfo()函数把信息从它自身传递给main()。具体地,它从用户处获取姓名,通过使用指针pst定位把姓名放入person结构中。回忆一下,pst->lname是pst所指向结构的lname成员。这就使pst->lname相当于一个char数组的名字,因此适合做gets()的参数。虽然getinfo()给主程序提供了信息,但是它并没有使用返回机制,因此它是void类型的。

函数makeinfo()执行自信的双向传递。它通过使用一个指向person的指针来确定结构中存储的姓和名的位置。它使用C函数库里的函数strlen()来计算姓和名中的字母总数,然后使用person的地址来存储这个总数。它的类型也是void型。最后,showinfo()使用一个指针定位要打印的信息。因为这个函数不改变结构的内容,因此它使用const。

在所有的操作中,只有一个结构变量person,每个函数都使用该结构的地址访问它。其中一个函数(getinfo())将信息从函数自身传递给调用函数,一个函数(showinfo())将信息从调用函数传递给函数自身,一个函数两个工作都做。

现在我们来看看,如何使用结构参数和返回值来完成这个任务。第一,为了传递结构本身,需要使用参数person而不是&person。这样相应的形式参数应被声明为struct namect类型,而不是声明为指向该类型的指针。第二,要把结构值提供给main()函数,可以返回一个结构。程序清单14.9给出了不使用指针的版本。

程序清单14.9 names2.c程序

/*funds2.c  --使用指向结构的指针*/
#include <stdio.h>
#include <string.h>

struct namect {
char fname[20];
char lname[20];
int letters;
};

struct namect getinfo(void);
struct namect makeinfo(struct namect);
void showinfo(struct namect);

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");
gets(temp.fname);
printf("Please enter your last name.\n");
gets(temp.lname);

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);
}

3个函数中的每一个都创建了自己的person副本,因此该程序不是使用了1个结构,而 是使用了4个不同的结构。

例如,考虑函数makeinfo()。在第二个版本中,创建了一个名为info的新的结构变量。person中存储的值被复制到info中,函数处理这个副本。因此在计算字母总数时,将把值存储到info里,而不是person里。然而,返回机制弥补了这一点。makeinfo()中的下面一行:

return info;

与main()中的行:

person=makeinfo(person);

相结合,将info里存储的值复制到person里。注意,必须把makeinfo()函数声明为struct namect类型,因为它返回一个结构。

14.7.5 结构还是指向结构的指针

把指针做为参数的方法有两个优点:它即工作在较早的C上,也工作在较新的C上,而且执行起来很快;只须传递一个单个地址。缺点是缺少对数据的保护 。被调函数中的一些操作可能不经意的影响到原来结构中的数据。不过,ANSI C新增了const限定词解决了这个问题。例如,如果在showinfo()函数中写入了改变结构中任何成员的代码,编译器就会把它作为一个错误捕获出来。

把结构作为参数 的一个优点是函数处理的是原始数据的副本,这就比直接处理原始数据安全。编程风格也往往更加清晰。

传递结构的两个主要缺点是早期的C实现可能不 处理这种代码,并且这样做浪费时间和空间。把很大的结构传递给函数,但函数只需要使用一个或两个 结构成员,这尤其浪费时间和空间。在这种情况下,传递指针或只将所需的成员作为参数传递会更合理。

通常,程序员为追求效率而使用结构指针作为函数参数;当需要保护数据防止意外数据时对指针使用const限定词。传递结构值是处理小型结构最常用的方法。

14.7.6 在结构中使用字符数组还是字符指针

前面的例子中都是在结构中使用字符数组来存储字符串。您可能想知道是否可以用指向字符 的指针代替字符 数组。例如,程序清单14.3中有这们的声明:

#define LEN 20
struct names {
char first[LEN];
char last[LEN];
};

可以改写成下面这样吗?

struct pnames{
char *fist;
char *last;
};

答案是可以,但是可能会遇到麻烦,除非您理解其含义。考虑下面的代码:

struct names veep = {"Talia","summers"};
struct pnames treas = {"Brad","Fallingjaw"};
printf("%s and %s\n",veep.first,treas.first);

这是一段正确的代码,也能正常运行,但是想想字符串存储在哪里?对于变量veep来说,字符串存储在结构内部;这个结构总共我不分配了40个字节来存放两个字符串。然而,对于pnames变量treas来说,字符串存储在编译器存储字符串常量的任何地方。这个结构中存储的只是两个地址而已,在我们的系统中它总共占用8个字节。struct pnames 结构不为字符串分配任何存储空间。它只适用于在另外地方已经为字符串分配 了空间(例如字符串常量或数组中的字符串)。简单地说,pnames结构中的指针应该只用来管理那些已经创建的而且在程序其他地方已经分配过空间的字符串。

我们来看看这个限定条件在什么情况下会升级为问题。考虑下面的代码:

struct names accountant;
struct pnames accorney;
puts("Enter the last name of your accountant: ");
scanf("%s",accountant.last);
puts("Enter the last name of your attorney: ");
scanf("%s",attorney.last); /*这里有一个潜在的风险*/

从语法上看,这段代码是正确的。但是把输入存储到哪里去了?对会计师,他的名字存储在accountant变量的最后一个成员中;这个结构有一个用来存放字符串的数组。对律师,scanf()把字符 串放到由attorney.last给出的地址中。因为这是个未经初始化的变量,所以该地址可能是任何值,程序就可以把名字放在任何地方。如果幸运的话,程序至少有时候可以正常运行。否则这个操作可能使程序彻底终止。实际上,如果程序运行,那是很不增的,因为程序中会含有您未沉冤到的编程错误。

因此,如果需要一个结构来存储字符串,请使用字符数组成员。存储字符指针有它们的用处,但也有被严重误用的可能。

14.7.7 结构、指针和malloc()

在结构中使用指针处理字符串的一个有意义的例子是使用malloc()分配 内存,并用指针来存放地址。这个方法的优点是可以请求malloc()分配刚好湖南路字符串需求数量的空间。

把程序清单14.9必定成这种方法不用很费劲。主要的两个变化是改变结构定义,使用指针而不是使用数组;然后给出getinfo()函数的新形式。

新的结构定义如下所示:

struct namect {
char *fname;
char *lname;
int letters;
};

getinfo()的新形式把输入读进一个临时数组 中;用malloc()分配 存储空间,然后把字符串复制到新分配的空间里。对每个名字都要这样做:

void getinfo(struct namect *pst)
{
char temp[81];
printf("Please enter your first name.\n");
gets(temp);
//分配用来存放名字的内存
pst->fname = (char*)malloc(strlen(temp)+1);
//把名字复制到已分配的内存中
strcpy(pst->fname,temp);
printf("Please enter your last name.\n");
gets(temp);
pst->lname = (char *)malloc(strlen(temp)+1);
strcpy(pst->lanme,temp);
}

要确保理解两个字符串都不是被存储在结构中,它们被保存在由malloc()管理的内存块中。然而,两个字符串的地址被保住在结构中,这些地址是字符串处理函数通常处理的对象。这样,程序中其余部分函数就不必做任何改变了。

但是像第12章中建议的那样,在调用malloc()之后还 应该调用free(),因此程序加入一个名字为cleanup()的新函数,在程序使用完内存后释放内存。在程序清单14.10中可以找到这个函数以及程序的其余部分。

//name3.c  --使用指针和malloc()函数
#include <stdio.h>
#include <string.h>  //为了使用strcpy(),strlen()
#include <stdlib.h>  //为了使用malloc(),free()

struct namect {
char *fname;    //使用指针
char *lname;
int letters;
};

void getinfo(struct namect *);    //分配内存
void makeinfo(struct namect *);
void showinfo(const struct namect *);
void cleanup(struct namect *);  //用完后释放内存

int main(void)
{
struct namect person;

getinfo(&person);
makeinfo(&person);
showinfo(&person);
cleanup(&person);
return 0;
}

getinfo(struct namect *pst)
{
char temp[81];  //临时数组
printf("Please enter your first name.\n");
gets(temp);
//用来分配存放名字的内存
pst->fname = (char *)malloc(strlen(temp)+1);
//把名字复制到已分配的内存
strcpy(pst->fname,temp);
printf("Please enter your last name.\n");
gets(temp);
pst->lname = (char *)malloc(strlen(temp)+1);
strcpy(pst->lname,temp);
}

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);
}
void cleanup(struct namect *pst)
{
free(pst->fname);
free(pst->lname);
}

14.7.8 复合文字和结构(C99)

c99新增的复合文字的特性不仅适用于数组,也适用于结构。可以使用复合文字创建一个被用来作为函数参数或被赋值给另一个结构的结构。语法是把类型名写到圆括号中,后跟一个用花括号括号括起来的初始化项目列表。例如,下面是一个struct book类型的复合文字:

(struct book){"The Idiot","Fyodor Dostoyevsky",6.99}

程序清单14.11 complit.c程序给出一个使用复合文字来有选择的 给结构变量赋值的例子。

/*complit.c  复合文字*/
#include <stdio.h>
#define MAXTITL 41
#define MAXAUTL 31

struct book {
char title[MAXTITL];
char author[MAXAUTL];
float value;
};

int main(void)
{
struct book readfirst;
int score;

printf("Enter test score: ");
scanf("%d",&score);

if(score>=84)
readfirst=(struct book){"Crime and Punishment",
"Fyodor Dostoyevsky",
9.99};

else
readfirst=(struct book){"Mr.Bouncy's Nice Hat",
"Fred Winsome",
5.99};
printf("Your assigned reading:\n");
printf("%s by %s:$%0.2f\n",readfirst.title,
readfirst.author,readfirst.value);
return 0;
}

也可以 把复合文字做为函数参数 使用。如果函数需要一个结构,可以做复合文字作为实际参数传递给它:

struct rect {double x;double y};
double rect_area(struct rect r)
{
return r.x * r.y;
}
...
double area;
area=rect_area((struct rect){10.5, 20.0});

这就把area赋值为210.0。

如果需要需要一个地址,可以把一个复合文字的地址传递给它

struct rect{double x;double y};
double rect_areap(struct rect *rp)
{
return rp->x * rp->y;
}
...
double area;
area = rect_areap(&(struct rect){10.5, 20.0});

这就把area赋值为210.0。

出现在所有函数外面的复合文字具有 静态存储时期而出现在一个代码块内部的复合文字具有 自动存储时期。适用于常规初始化项目列表的语法也适用于复合文字。这意味着,例如,可以在复合文字中 指定初始化项目。

14.7.9 伸缩型数组成员(C99)

C99具有一个称为伸缩型(flexible array member)数组成员的新特性。利用这一新特性可以声明最后一个成员是一个具有特殊属性的数组的结构。该数组成员的特殊属性之一是它不存在,至少不立即存在。第二个特殊属性是您可以编写适当的代码使用这个伸缩型数组成员,就像它确实存在并且拥有您需要的任何数目的元素一样。

首先,看看声明一个伸缩型数组成员的规则:

**伸缩型数组成员必须是最后一个数组成员;

**结构中必须至少有一个其他成员

**伸缩型数组就像普通数组一样被声明,除了它的方括号内是空的。

下面是一个说明这些规则的例子:

struct flex
{
int count;
double average;
double scores[];  //伸缩型数组成员
};

如果声明了一个struct flex类型的变量,您不能使用scores做任何事情,因为没有为它分配任何内存空间。实际上,c99的意图并不是让您声明struct flex类型的变量,而是希望您声明struct flex类型的指针,然后使用malloc()来分配足够的空间,以存放struct flex结构常规内容和伸缩型数组成员需要的任何额外空间。假如,现在想要scores表示含有5个double类型数值的数组,那么就要这样做:

struct flex *pf;  //声明一个指针
//请求一个结构和一个数组的空间
pf=malloc(sizeof(struct flex)+5*sizeof(double));

现在您已经有一块足够大的内存,以存储count、average和一个含有5个double型数值的数组。可以使用指针pf来访问这些成员:

pf->count=5; //设置count成员的值;

pf->scores[2]=18.5; //访问数组成员的一个元素;

程序清单14.12更进一步拓展了这个例子,让伸缩型数组成员在第一种情况下代表5个数值,在第二种情况下代表9个数值。它也说明了如何编写一个处理带有伸缩型数组成员 的结构的函数。

/*flexmemeb.c --伸缩型数组成员*/
#include <stdio.h>
#include <stdlib.h>

struct flex
{
int count;
double average;
double scores[];
};

void showflex(const struct flex *p);
int main(void)
{
struct flex *pf1, *pf2;
int n=5;
int i;
int tot=0;

//为结构和数组分配内存
pf1=malloc(sizeof(struct flex)+n*sizeof(double));
pf1->count=n;
for(i=0;i<n;i++)
{
pf1->scores[i]=20.0-i;
tot+=pf1->scores[i];
}
pf1->average=tot/n;
showflex(pf1);

n=9;
tot=0;
pf2=malloc(sizeof(struct flex)+n*sizeof(double));
pf2->count=n;
for(i=0;i<n;i++)
{
pf2->scores[i]=20.0-i/2.0;
tot+=pf2->scores[i];
}
pf2->average = tot/n;
showflex(pf2);

free(pf1);
free(pf2);

return 0;
}

void showflex(const struct flex *p)
{
int i;
printf("Scores: ");
for(i=0;i<p->count;i++)
printf("%g ",p->scores[i]);
printf("\nAverage: %g\n",p->average);
}

14.7.10 使用结构数组成员

假设需要用一个函数处理结构数组。因为数组的名称等同于它的地址,所以可以把数组名传递给函数。再一次函数需要访问结构模板。程序清单14.13将有关货币的程序扩展到两个人,以个含有两个funds结构的数组。

程序清单14.13 funds4.c

/*funds4.c  --向函数传递一个结构数组*/
#include <stdio.h>
#define FUNDLEN 50
#define N 2

struct funds {
char bank[FUNDLEN];
double bankfund;
char save[FUNDLEN];
double savefund;
};

double sum(const struct funds money[],int n);

int main(void)
{
struct funds jones
={
{
"Garlic - Melon Bank",
3024.72,
"Lucky's Savings and Loan",
9237.11
},
{
"Honest Jack's Bank",
3534.28,
"Party Time Savings",
3203.89
}
};
printf("The Joneses have a total of $%.2f.\n",sum(jones,N));
return 0;
}

double sum(const struct funds money[],int n)
{
double total;
int i;

for(i=0,total=0;i<n;i++)
total += money[i].bankfund+money[i].savefund;
return total;
}

数组名jones是数组的地址具体地说,它是数组第一个元素,即结构jones[0]的地址。因此,指针money最初是由这个表达式给出的:

money = &jones[0];

因为money指向数组jones的第一个元素,所以money[0]是该数组的第一个元素的另一名名称。同样money[1]是第二个元素。每个元素是一个funds结构,所以每个元素都可以使用(.)运算符来访问其结构成员。

下面这些是要点:

**可以用数组名把数组中第一个结构的地址传递给函数。

**然后可以使用数组的方括号来访问数组的后续结构。注意下面的函数调用和使用数组名有同样的效果:

sum(&jones[0],N)

因为二者都是指向同一地址。使用数组名只是传递结构地址的一种间接方法

**因为函数sum()不用来改变原来的数据,所以我们使用ANSI C 的限定词const。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息