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

发布于 2021-09-28  684 次阅读


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限定符解决了这个问题。
把结构作为参数传递的优点是,函数处理的是原始数据的副本,还保护了原始数据。另外,代码风格也更清楚。假设定义了下面的结构类型:
struct vector(double x;doble y;);
如果用vector类型的结构ans储存相同类型的结构a和b的和,就要把结构作为参数和返回值;
struct vector ans,a,b;
struct vector sum_vect(struct vector,struct vector);
...
ans = sum_vect(a,b);
对程序员而言,上面版本用指针传递的版本更自然,指针版本如下:
struct venctor,a,b;
void sum_vect(const struct vector *,const struct vector *,struct vector *);
...
sum_vect(&a,&b,&ans);
另外,如果使用指针版本,程序员必须记住总和的地址应该是第1个参数还是第2个参数的地址。
传递结构的两个缺点是:
较老版本的实现可能无法处理这样的代码,而且传递结构浪费时间和存储空间,尤其是把大型结构传递给函数,而只是用结构中的一个两个成员时特别浪费。这种情况下传递指针或只传递函数所需的成员更合理。
通常,程序员为了追求效率会使用结构指针作为函数参数,如需防止原始数据被意外修改,使用const限定符。按值传递结构是处理小型结构最常用的方法。

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

到目前为止,我们都是使用字符数组来存储字符串。是否可以使用指向char的指针来代替字符串数组?
#define  LEN 20
struct names{
    char firest[LEN];
    char last[LEN];
};
其中的结构声明是否可以这样写?:
struct pnnames{
    char *first;
    char *last;
};
当然可以,但是如果不理解这样做的含义,可能会有麻烦。考虑下面代码:
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 pname结构不用为字符串分配仍和存储空间。它使用的是存储在别处的字符串(如,字符串常量或数组中的字符串)。简而言之,在pnames结构变量中的指针应该只用来在程序中管理哪些已分配和在别处分配的字符串。
看看这种限制在什么情况下出问题:
struct names accountant;
struct pnames attorney;
puts("Enter the last name of your accountant:");
scanf("%s",account.last);
puts("Enter the last name of your attorney:");
scanf("%s",attorney.last);        //这里有一个潜在的危险
就语法而言,这段代码没问题,但是,用户的输入存储到哪里去了?
对于会计师(accountant),他的名存储在了accountant结构变量的last成员中,该结构有一个存储字符串的数组。对于律师(attorney),scanf()把字符放到attorney.last表示的地址上,由于这是未经初始化的变量,地址可以是任何值,因此程序可以把名放在任何地方。如果走运的话,程序不会出问题,至少暂时不会出问题,否则这一操作会导致程序崩溃。
实际上,如果程序能正常运行的话并不是好事,因为这意味着一个未被察觉的危险潜伏在程序中。
因此,如果要用结构存储字符串,用字符数组作为成员比较简单。用指向char的指针也行,但是误用会导致严重的问题。

14.7.7 结构、指针和malloc()

如果使用malloc()分配内存使用指针存储该地址,那么在结构中使用指针处理字符串比较合理。这种做法的优点是,可以请求malloc()为字符串分配合适的存储空间。可以要求用4字节存储”Joe“和利用18字节存储"Rasolofomasoandro".用这种方法改写程序并不难,主要是更改结构声明(用指针代替数组。)和提供一个新版本的getinfo()函数。新的结构声明如下:
struct namect{
    char *fname;
    char *lname;
    int letters;
};
新版本的geinfo()把用户读入临时数组中,调用malloc()函数分配存储空间,并把字符拷贝到新分配的存储空间中。对名和姓都要这样做:
void getinfo(struct namect *pst)
{
    char temp[SLEM];
    printf("Please enter your first name.\n");
    s_gets(temp,SLEM);
    //分配内存储存名
    pst->fname = (char *)malloc(strlen(temp)+1);
    //把名拷贝到已分配的内存
    strcpy(pst->fname,temp);
    printf("Please enter your last name.\n");
    s_gets(temp,SLEM);
    pst->lname = (char *)malloc(strlen(temp)+1);
    strcpy(pst->lname,temp);
}
要理解这两个字符串都未存储在结构中,它们存储在malloc分配的内存块中。然而,结构中储存着这两个字符串的地址,处理字符串的函数通常都要使用字符串的地址。因此,不用修改程序中的其他函数。
建议:应该成双成对的使用malloc()和free()。因此,还要在程序中添加一个新的函数cleanup(),用于释放程序动态分配的内存。如下所示:
//name3.c   ---使用指针和malloc() 
#include<stdio.h>
#include<string.h>
#include<stdlib.h>    //malloc() free()
#define SLEN 81
struct namect{
    char *fname;
    char *lname;
    int letter;
}; 
void getinfo(struct namect *);      //分配内存
void makeinfo(struct namect *);
void showinfo(const struct namect *);
void cleanup(struct namect *);      //调用该函数时释放内存
char *s_gets(char *st,int n);

int main(void)
{
    struct namect person;

    getinfo(&person);
    makeinfo(&person);
    showinfo(&person);
    cleanup(&person);

    return 0;
} 
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("Pleasr enter your last name.\n");
    s_gets(temp,SLEN);
    pst->lname = (char *)malloc(strlen(temp)+1);
    strcpy(pst->lname,temp);
}
void makeinfo(struct namect *pst)
{
    pst -> letter = 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->letter);
}
void cleanup(struct namect *pst)
{
    free(pst->fname);
    free(pst->lname);
}
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

14.7.8 复合字面量和结构(C99)

C99的复合字面量特性可以用于结构和数组。如果只需要一个临时结构值,复合字面量很好用。例如,可以使用复合字面量创建一个数组作为函数的参数或者赋值给另一个结构。语法是把类型名放在圆括号中,后面紧跟一个用花括号括起来的初始化列表。例如,下面是struct book类型的复合字面量。
(struct book){"The Idiot","Fystoyevsky",6.99}
如下,使用复合字面量未一个结构变量提供两个可以替换的值。
//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 punishement",
                                  "Fyodor Dostoybsky",11.25
        };
    }
    else
        readfirst = (struct book){"Mr.Bouncy's Nice Ha'",
                                  "Fred Winsome",5.99
        };
        printf("Your assigned reading:\n");
        printf("%s by %s:$%.2f\n",readfirst.title,readfirst.title,readfirst.value);

        return 0;
}

file

file

还可以把复合字面量作为函数的参数。如果函数接受一个结构,可以把复合字面量作为实际参数传递:
struct rect{double x;double y;};
double ret_area(struct rect r)
{
    return r.x * r.y;
}
...
double area;
area = rect_area((struct rect){10.5,20.0});
值210被赋给area。
如果函数接受了一个地址,可以传递复合字面量的地址:
struct rect{double x;double y;};
double ret_areap(struct rect *rp)
{
    return rp->x * rp->y;
}
...
double area;
area = rect_areap(&(struct rect){10.5,20.0});
值210被赋值给area。
复合字面量在所有函数的外部,具有静态存储期;如果复合字面量在块中,则具有自动存储期。复合字面量和普通初始化列表的语法规则相同,这意味着,可以在复合字面量中使用指定初始化器。

14.7.9 伸缩型数组成员(C99)

C99新增了一个特性:伸缩型数组成员(flexible array member),利用这种特性声明的结构,其中最后一个数组成员具有一些特性。第一个特性是,该数组不会立即存在。第2个特性是,使用这个伸缩型数组成员可以编写合适的代码,就好像它确实存在并具有所需数目的元素一样。
声明一个绳索数组成员有如下的规则:
- 伸缩型数组成员必须是结构的最后一个成员;
- 结构中必须至少还有一个成员;
- 伸缩数组的声明类似于普通数组,只是它的方括号中是空的。
例如:
struct flex{
    int count;
    double average;
    double scores[];    //伸缩型数组成员
}
声明一个struct flex类型的结构变量时,不能用scores做任何事,因为没有给这个数组预留存储空间。实际上,C99的意图并不是让你声明struct flex类型的变量,而是希望你声明一个指向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;          //访问数组成员的一个元素
//flexmemb.c        --伸缩型数组成员(C99新特性)
#include<stdio.h>
#include<stdlib.h>

struct flex{
    size_t count;
//size_t是标准C库中定义的,在64位系统中为long long unsigned int,非64位系统中为long unsigned int。
    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);
}
void showFlex(const struct flex *p)
{
    int i;
    printf("Scores :");
    for(i = 0;i<p->count;i++)
    {
        printf("%g ",p->scores[i]);    //%d, %i,代表整数,%f-浮点,%s,字符串,%c,char. %p 指针,%fL 长long,%e科学计数,%g 小数或科学计数。
    }
    printf("\nAverage: %g\n",p->average);
}

file

带伸缩型数组成员的结构确实有一些特殊的处理要求,第一,不能用结构进行赋值或拷贝:
struct flex *pf1,*pf2;      //pf1和pf2都是结构
。。。
*pf1 = *pf2         //不要这样做
这样做只能拷贝除伸缩型数组以外的其他成员,确实要进行拷贝,应使用memcpy()函数。
第二,不要以按值方式把这种结构传递给结构。原因相同,按值传递一个参数与赋值类似,要把结构的地址传递给函数。
第三,不要使用带伸缩数组成员的结构作为数组成员或另外一个结构的成员。
这种类似于在结构中最后一个成员是伸缩型数组的情况,称为struct hack.除了伸缩型数组成员在声明时用空的方括号外,struct hack 特指大小为0的数组,然而,struct hack是针对特殊编译器(GCC)的,不属于C标准,这种伸缩型数组成员的方法是标准认可的编程技巧。

14.7.10 匿名结构(C11)

见结构和其他数据形式(3)


擦肩而过的概率