第14章 结构和其他数据形式(2)

发布于 20 天前  28 次阅读


14.5 嵌套结构

有时,在一个结构中包含另外一个结构(即嵌套结构)很方便。例如,创建一个有关朋友信息的结构。结构中需要一个成员表示朋友的姓名。
名字可以利用一个数组表示。其中包含名和姓这两个成员。
//friend.c  --嵌套结构
#include<stdio.h>
#define LEN 20
const char *msgs[5] = 
{
    "  Thank you for the wonderful evening,",
    "You certainly prove that a",
    "is a special kind of guy.We must get together",
    "over a delicious",
    " and have a few laughs"
};

struct names {
    char first[LEN];
    char last[LEN];
};
struct guy{                 //第2个结构 
    struct names handle;    //嵌套结构 
    char favfood[LEN];
    char job[LEN];
    float income;
};

int main(void)
{
    struct guy fellow ={
        {"Li","Yaa"},
        "grilled salmon",
        "personality coach",
        68112.00
    };
    printf("Dear %s,\n\n",fellow.handle.first);
    printf("%s%s.\n",msgs[0],fellow.handle.first);
    printf("%s\n",msgs[2]);
    printf("%s%s%s",msgs[3],fellow.favfood,msgs[4]);
    if(fellow.income > 150000.0)
        puts("!!");
    else if(fellow.income >75000.0)
        puts("!");
    else
        puts(".");
    printf("\n%40s%s\n"," ","See you soon,");
    printf("%40s%s\n"," ","GuoAn");

    return 0;
}

file

首先,注意如何在结构声明中创建嵌套结构。和声明int类型变量一样,进行简单声明:
struct names handle;
该声明表示handle是一个struct name类型的变量。当然,文件中也应该包含结构names的声明。
其次,注意如何访问嵌套结构的成员,这需要使用两次点运算符:
printf("Hello,%s!\n",fellow.handle.first);
从左往右解释fellow.handle.first:
(fellow.handle).first
也就是说,找到fellow,然后找到fellow中的handle的成员,再找到handle的first成员

14.6 指向结构的数组

喜欢使用指针的人一定很高心能使用指向结构的指针。至少有4个理由可以解释为为何要使用指向结构的的指针。第一,就像指向数组的指针比数组本身更容易操控。(如排序问题。)一样,指向结构的指针通常比结构本身更容易操控。第二,在一些早期的C实现中。结构不能作为参数传递给函数,但是可以传递指向结构结构的指针。第三,即使能传递一个结构,传递指针通常更有效率。第四,一些用于表示数据的结构中包含指向其他结构的指针。
下面程序演示了如何定义指向结构和如何使用这样的指针访问结构的成员。
//friends.c --使用指向结构的指针
#include<stdio.h>
#define LEN 20

struct names{
    char firest[LEN];
    char last[LEN];
}; 

struct guy{
    struct names handle;
    char favfood[LEN];
    char last[LEN];
    float income;
};

int main(void)
{
    struct guy fellow[2]={
        {
            {"Ewen","Villard"},
            "grilled salmon",
            "Personality coach",
            68112.00
        },
        {
            {"Rodeny","Swillbelly"},
            "tripe",
            "tabloid editor",
            432400.00
        }
    };

    struct guy *him;

    printf("address #1:%p #2:%p\n",&fellow[0],&fellow[1]);
    him = fellow;
    printf("pointer #1:%p #2:%p\n",him,him+1);
    printf("him->income is $%.2f:(*him).income is $%.2f.\n",him->income,(*him).income);
    him++;
    printf("him -> favfood is %s:him->handle.last is %s\n",him->favfood,him->handle.last);

    return 0;
}

file

14.6.1 声明和初始化结构指针

声明结构指针很简单:
struct guy *him;
首先是关键字struct,其次是结构标记guy,然后是一个星(*),其后跟着指针名。这个语法和其他指针声明一样。
该声明并未创建一个新的结构,但是指针him现在可以指向仍何现有的guy类型的结构。例如,如果barney是一个guy类型的结构变量,可以这样写:
him = &barney;
和数组不同的是,结构变量名不是结构的地址,因此要在结构变量名前面加上&运算符。
在本例中,fellow是一个结构数组,这意味着fellow[0]是一个结构,所以要让him指向fellow[0],可以这样写:
him = &fellow[0];
输出的前两行说明赋值成功,比较这两行发现。him指向fellow[0],him+1指向fellow[1]。
注意,him+1相当于him指向的地址加84.在16进制中,874-820 = 54(十六进制) = 84(十进制),因为每个guy结构都占用84字节的内存:names.first占用20字节,name.last占用20字节,favfood占用20字节,job占用20字节,income占用4字节(假设系统中float占用4字节)。
在一些系统中,一个结构的大小可能大于它各个成员大小之和。这是因为系统对数据进行校准的过程中产生了一些”缝隙“。
例如:有些系统必须把每个成员都放在偶数地址上,或4的倍数的地址上。在这种系统中,结构的内部就存在未使用的”缝隙“。

14.6.2 用指针访问成员

指针him指向结构变量fellow[0],如何通过him获取fellow[0]的成员的值?
第一种方法也是最常用的方法:使用->运算符。
如果:
him == &barney,那么him->income即是barney.income.
him == &fellow[0],那么him->income即是fellow[0].income
换句话说,指向结构的指针后面的->运算符和结构变量后面的.运算符工作方式相同(不能写成him.income,因为him不是结构体名)
这里要着重理解him是一个指针,但是him->income是该指针所指向结构体的一个成员。所以在该例中,him->income是一个float类型的变量。
第2中方法是,以这样的顺序指定结构成员的值:如果him == &fellow[0],那么*him ==fellow[0],因为&和*是一对互逆运算符。因此可以做以下替代:
fellow[0].income == (*him).income
必须要使用圆括号,因为运算符.比*运算符的优先级更高。
总之,如果him是指向guy类型结构barney的指针,下面的关系恒成立:
barney.income == (*him).income == him ->income   //假设him == &barney

14.7 向函数传递结构的信息

函数的参数把值传递给函数。每个值都是一个数字--可能是int类型、float类型,可能是ASCII字符码,或者是一个地址。然而,一个结构比一个单独的值复杂,所以难怪以前的C不允许把结构作为参数传递给函数。当前的实现已经移除了这个限制,ANSI C允许把结构作为参数使用,所以程序员可以选择是传递结构本身,还是传递指向结构的指针。如果你只关心结构中的某一部分,也可以把结构的成员作为参数。我们接下来将分析这3种传递方式,首先介绍以结构成员作为参数的情况。

14.7.1 传递结构成员

只要结构成员是一个具有单个值的数据类型(即,int及其相关类型、char、float、double或指针),便可把它作为参数传递给接受特定类型的函数。
//该程序把客户的银行账户添加到他/她的储蓄和贷款账户中。
//funds1.c  --把结构成员作为参数传递
#include<stdio.h>
#define LEN 50

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

double sum(double,double);
int main(void)
{
    struct funds stan={
        "CHINA bank",
        4032.27,
        "Lunck's Savings and Loan",
        8543.94
    };
    printf("Stan has a total of $%.2f.\n",sum(stan.bankfund,stan.savefund));
    return 0;
}
double sum(double x,double y)
{
    return x+y;
}

file

14.7.2 传递结构的地址

我们继续解决前面的问题,但是这次是把结构的地址作为参数,由于函数要处理funds结构,所以必须声明funds结构。
//funds2.c  --传递指向结构的指针
#include<stdio.h>
#define LEN 50

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

double sum(const struct funds *);

int main(void)
{
    struct funds stan ={
        "CHINA bank",
        4032.27,
        "Lunck's Savings and Loan",
        8543.94
    };
    printf("Stan has a total of $%.2f.\n",sum(&stan));

    return 0;
}
double sum(const struct funds *money)
{
    return (money->bankfund+money->savefund);
}

file

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

14.7.3 传递结构

对于允许把结构作为参数的编译器
//funds3.c  --传递指向结构的指针
#include<stdio.h>
#define LEN 50

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

double sum(struct funds moolah);

int main(void)
{
    struct funds stan ={
        "CHINA bank",
        4032.27,
        "Lunck's Savings and Loan",
        8543.94
    };
    printf("Stan has a total of $%.2f.\n",sum(stan));

    return 0;
}
double sum(struct funds moolah)
{
    return (moolah.bankfund+moolah.savefund);
}

file

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

14.7.4 其他结构特征

现在C允许把一个结构赋值给另一个结构,但是数组不能这样做。也就是说,如果n_data和o_data都是相同类型的数据结构,可以这样做:
o_data = n_data;    //把一个结构赋值给另外一个结构
这条语句把n_data的每个成员的值都赋给o_data的相应成员。即使成员是数组。也能完成赋值。另外,还可以把一个结构初始化为相同类型的另一个结构:
struct names right_friend = {"Ruthie","George"};
struct names captain = right_field; //把一个结构初始化为另一个结构
现在的C(ANSI 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)
            *find = '\0';
        else
            while(getchar()!='\n')
                continue;
    }
    return ret_val;
}

file

该程序把任务分配给3个函数完成,都在main()中调用。每调用一个函数就把person结构的地址传递给它。
getinfo()函数把结构的信息从自身传递给main()。该函数通过与用户交互获得姓名,并且通过与用户交互获得姓名,并通过pst指针定位,将其放入person结构中。由于pst->lname意味者pst指向结构的lname成员,这使得pst->lname等价于char数组的名称,因此做s_gets()的参数很合适。注意,虽然getinof()给main()提供了信息,但是它并未使用返回机制,所以其返回类型是void.
makeinfo()函数使用了双向传输方式传送信息。通过使用指向person的指针,该指针定位了储存在该结构中的名和姓。该函数使用C库函数strlen()分别计算名与姓中的字母总和,然后使用person的地址存储两数之和。同样,makeinfo()函数返回类型也是void。
showinfo()函数使用一个指针定位待打印的信息。因为该函数不改变数组的内容,所以将其声明为const。
所有这些操作中,只有一个结构变量person,每个函数都使用该结构变量的地址来访问它。一个函数把信息从自生传回主调函数,一个函数把信息从主调函数传给自生,一个函数通过双向传输来传递信息。
现在,来看如何使用结构参数和返回值来完成相同的任务。第一,为了传递结构本身,函数的参数必须是person,而不是&person。那么,相应的形式参数应为struct namect,而不是指向该类型的指针。第二,可以通过返回一个结构,把结构的信息返回给main()。
//names2.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);
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;
}
//void getinfo(struct namect *pst)
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;

}
//void makeinfo(struct namect *pst)
struct namect makeinfo(struct namect temp)
{
    temp.letters = strlen(temp.fname) + strlen(temp.lname);
    return temp;
}
//void showinfo(const struct namect *pst)
void showinfo(struct namect temp)
{
    printf("%s %s,your name contains %d letters.\n",temp.fname,temp.lname,temp.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)
            *find = '\0';
        else
            while(getchar()!='\n')
                continue;
    }
    return ret_val;
}

file

两个版本的输出相同,但是运用了不同的方式。程序中的每个函数都创建了自己的person备份,所以该程序使用了4个不同的结构,不像前面的版本只使用一个结构。
例如,考虑makeinfo()函数,在第一个程序中,传递的是person的地址,该函数实际上处理的是person的值,在第2个版本中,创建了一个新的结构info。储存在person中的值被拷贝到info中,函数处理的是这个副本。因此,统计完了字母个数之后,计算结果储存在info中,而不是person中。然而,返回机制弥补了这一点,makeinfo()中的这行的代码:
return info;
与main()中的这行结合:
person = makeinfo(person);
把存储在info中的值拷贝到person中。注意,必须把makeinfo()函数声明为struct namect类型,所以该函数要返回一个结构。

14.7.5 结构和结构指针的选择

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

擦肩而过的概率