《C Primer Plus》第十四章——结构和其他数据形式

结构和其他数据形式

本章内容

本章介绍以下内容:

  • 关键字——struct、union、typedef;
  • 运算符——.、->;
  • 什么是C结构,如何创建结构模板和结构变量;
  • 如何访问结构的成员,如何编写处理结构的函数;
  • ·联合和指向函数的指针。

示例问题:创建图书目录

  • Gwen Glenn要打印一份图书目录。她想打印每本书的各种信息:书名、作者、出版社、版权日期、页数、册数和价格。其中的一些项目(如,书名)可以存储在字符数组中,其他项目需要一个int数组或float数组。用7个不同的数组分别记录每一项比较繁琐,尤其是Gwen还想创建多份列表:一份按书名排序、一份按作者排序、一份按价格排序等。如果能把图书目录的信息都包含在一个数组里更好,其中每个元素包含一本书的相关信息。
  • 因此,Gwen需要一种即能包含字符串又能包含数字的数据形式,而且还要保持各信息的独立。
//* book.c -- 一本书的图书目录 */
#include <stdio.h>
#include <string.h>
char * s_gets(char * st, int n);
#define MAXTITL 41   /* 书名的最大长度 + 1  */
#define MAXAUTL 31   /* 作者姓名的最大长度 + 1*/
struct book {      /* 结构模版:标记是 book */
   char title[MAXTITL];
   char author[MAXAUTL];
   float value;
};            /* 结构模版结束     */
int main(void)
{
   struct book library;  /* 把 library 声明为一个 book 类型的变量 */
   printf("Please enter the book title.\n");
   s_gets(library.title, MAXTITL);    /* 访问title部分*/
   printf("Now enter the author.\n");
   s_gets(library.author, MAXAUTL);
   printf("Now enter the value.\n");
   scanf("%f", &library.value);
   printf("%s by %s: $%.2f\n", library.title,
     library.author, library.value);
   printf("%s: \"%s\" ($%.2f)\n", library.author,
       library.title, library.value);
   printf("Done.\n");
   return 0;
}
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;
}
  • 程序清单14.1中创建的结构有3部分,每个部分都称为成员(member)或字段(field)。

建立结构声明

  • 结构声明(structure declaration)描述了一个结构的组织布局。声明类似下面这样:

    struct book {

    char title[MAXTITL];

    char author[MAXAUTL];

    float value;

    };

  • 该声明描述了一个由两个字符数组和一个float类型变量组成的结构。该声明并未创建实际的数据对象,只描述了该对象由什么组成。

  • 首先是关键字struct,它表明跟在其后的是一个结构,后面是一个可选的标记(该例中是book),稍后程序中可以使用该标记引用该结构。所以,我们在后面的程序中可以这样声明:

    struct book library;

  • 这把library声明为一个使用book结构布局的结构变量。

  • 在结构声明中,用一对花括号括起来的是结构成员列表。每个成员都用自己的声明来描述。

定义结构变量

  • 结构有两层含义。一层含义是“结构布局”,刚才已经讨论过了。结构布局告诉编译器如何表示数据,但是它并未让编译器为数据分配空间。下一步是创建一个结构变量,即是结构的另一层含义。程序中创建结构变量的一行是:

    struct book library;

  • 编译器执行这行代码便创建了一个结构变量library。编译器使用book模板为该变量分配空间:一个内含MAXTITL个元素的char数组、一个内含MAXAUTL个元素的char数组和一个float类型的变量。这些存储空间都与一个名称library结合在一起

  • 在结构变量的声明中,struct book所起的作用相当于一般声明中的int或float。例如,可以定义两个struct book类型的变量,或者甚至是指向struct book类型结构的指针:

struct book doyle, panshin, * ptbook;

  • 声明结构的过程和定义结构变量的过程可以组合成一个步骤。如下所示,组合后的结构声明和结构变量定义不需要使用结构标记:

struct { /* 无结构标记 */

char title[MAXTITL];

char author[MAXAUTL];

float value;

} library;

  • 然而,如果打算多次使用结构模板,就要使用带标记的形式;或者,使用本章后面介绍的typedef。

初始化结构

struct book library = {

“The Pious Pirate and the Devious Damsel”,

“Renee Vivotte”,

1.95

};

  • 我们使用在一对花括号中括起来的初始化列表进行初始化,各初始化项用逗号分隔。
  • 第12章中提到过,如果初始化静态存储期的变量(如,静态外部链接、静态内部链接或静态无链接),必须使用常量值。这同样适用于结构。如果初始化一个静态存储期的结构,初始化列表中的值必须是常量表达式。如果是自动存储期,初始化列表中的值可以不是常量。

访问结构成员

  • library.value即访问library的value部分。可以像使用任何float类型变量那样使用library.value。与此类似,可以像使用字符数组那样使用library.title。因此,程序清单14.1中的程序中有s_gets(library.title, MAXTITL);和scanf("%f", &library.value);这样的代码。
  • 虽然library是一个结构,但是library.value是一个float类型的变量,可以像使用其他float类型变量那样使用它。例如,scanf("%f",…)需要一个float类型变量的地址,而&library.float正好符合要求。.比&的优先级高,因此这个表达式和&(library.float)一样。

结构的初始化器

  • C99和C11为结构提供了指定初始化器(designated initializer),其语法与数组的指定初始化器类似。但是,结构的指定初始化器使用点运算符和成员名(而不是方括号和下标)标识特定的元素。例如,只初始化book结构的value成员,可以这样做:

    struct book surprise = { .value = 10.99};

  • 可以按照任意顺序使用指定初始化器:

    struct book gift = { .value = 25.99,

    ​ .author = “James Broadfool”,

    ​ .title = “Rue for the Toad”};

  • 与数组类似,在指定初始化器后面的普通初始化器,为指定成员后面的成员提供初始值。另外,对特定成员的最后一次赋值才是它实际获得的值。例如,考虑下面的代码:

    struct book gift= {.value = 18.90,

    ​ .author = “Philionna Pestle”,

    ​ 0.25};

  • 赋给value的值是0.25,因为它在结构声明中紧跟在author成员之后。新值0.25取代了之前的18.9。

结构数组

/* manybook.c -- 包含多本书的图书目录 */
#include <stdio.h>
#include <string.h>
char * s_gets(char * st, int n);
#define MAXTITL  40
#define MAXAUTL  40
#define MAXBKS  100     /* 书籍的最大数量 */
struct book {        /* 建立 book 模板   */
   char title[MAXTITL];
   char author[MAXAUTL];
   float value;
};
int main(void)
{
   struct book library[MAXBKS];  /* book 类型结构的数组 */
   int count = 0;
   int index;
   printf("Please enter the book title.\n");
   printf("Press [enter] at the start of a line to stop.\n");
   while (count < MAXBKS && s_gets(library[count].title, MAXTITL) != NULL
     && library[count].title[0] != '\0')
   {
     printf("Now enter the author.\n");
     s_gets(library[count].author, MAXAUTL);
     printf("Now enter the value.\n");
     scanf("%f", &library[count++].value);
     while (getchar() != '\n')
        continue;    /* 清理输入行*/
     if (count < MAXBKS)
        printf("Enter the next title.\n");
   }
   if (count > 0)
   {
     printf("Here is the list of your books:\n");
     for (index = 0; index < count; index++)
        printf("%s by %s: $%.2f\n", library[index].title,
        library[index].author, library[index].value);
   }
   else
     printf("No books? Too bad.\n");
   return 0;
}
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;
}

声明结构数组

struct book library[MAXBKS];

  • 以上代码把library声明为一个内含MAXBKS个元素的数组。数组的每个元素都是一个book类型的结构。

标识结构数组的成员

library[0].value /* 第1个数组元素与value 相关联 */

library[4].title /* 第5个数组元素与title 相关联 */

注意,数组下标紧跟在library后面,不是成员名后面:

library.value[2] // 错误

library[2].value // 正确

使用library[2].value的原因是:library[2]是结构变量名

最后,总结一下:

library // 一个book 结构的数组
library[2] // 一个数组元素,该元素是book结构

library[2].title // 一个char数组(library[2]的title成员)

library[2].title[4] // 数组中library[2]元素的title 成员的一个字符

程序讨论

while (count < MAXBKS && s_gets(library[count].title, MAXTITL) != NULL

​ && library[count].title[0] != ‘\0’)

  • 表达式s_gets(library[count].title, MAXTITL)读取一个字符串作为书名,如果s_gets()尝试读到文件结尾后面,该表达式则返回NULL。表达式library[count].title[0] != '\0’判断字符串中的首字符是否是空字符(即,该字符串是否是空字符串)。如果在一行开始处用户按下Enter键,相当于输入了一个空字符串,循环将结束。程序中还检查了图书的数量,以免超出数组的大小。

  • 然后,该程序中有如下几行:

    while (getchar() != ‘\n’)
    continue; /* 清理输入行 */

  • 前面章节介绍过,这段代码弥补了scanf()函数遇到空格和换行符就结束读取的问题。当用户输入书的价格时,可能输入如下信息:

    12.50[Enter]

  • 其传送的字符序列如下:

    12.50\n

  • scanf()函数接受1、2、.、5和0,但是把\n留在输入序列中。如果没有上面两行清理输入行的代码,就会把留在输入序列中的换行符当作空行读入,程序以为用户发送了停止输入的信号。我们插入的这两行代码只会在输入序列中查找并删除\n,不会处理其他字符。这样s_gets()就可以重新开始下一次输入。

嵌套结构

  • Shalala Pirosky创建了一个有关她朋友信息的结构。显然,结构中需要一个成员表示朋友的姓名。然而,名字可以用一个数组来表示,其中包含名和姓这两个成员。
// 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 {          // 第1个结构
   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 = {    // 初始化一个结构变量
        { "Ewen", "Villard" },
        "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%s\n", msgs[1], fellow.job);
   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", " ", "Shalala");
   return 0;
}
  • 首先,注意如何在结构声明中创建嵌套结构。和声明int类型变量一样,进行简单的声明:

    struct names handle;

  • 该声明表明handle是一个struct names类型的变量。当然,文件中也应包含结构names的声明。
    其次,注意如何访问嵌套结构的成员,这需要使用两次点运算符:

    printf(“Hello, %s!\n”, fellow.handle.first);

    从左往右解释

    fellow.handle.first:

    (fellow.handle).first

  • 也就是说,找到fellow,然后找到fellow的handle的成员,再找到handle的first成员。

指向结构的指针

  • 至少有4个理由可以解释为何要使用指向结构的指针。
  • 第一,就像指向数组的指针比数组本身更容易操控(如,排序问题)一样,指向结构的指针通常比结构本身更容易操控。
  • 第二,在一些早期的C实现中,结构不能作为参数传递给函数,但是可以传递指向结构的指针。
  • 第三,即使能传递一个结构,传递指针通常更有效率。
  • 第四,一些用于表示数据的结构中包含指向其他结构的指针。
/* friends.c -- 使用指向结构的指针 */
#include <stdio.h>
#define LEN 20
struct names {
   char first[LEN];
   char last[LEN];
};
struct guy {
   struct names handle;
   char favfood[LEN];
   char job[LEN];
   float income;
};
int main(void)
{
   struct guy fellow[2] = {
        { { "Ewen", "Villard" },
          "grilled salmon",
          "personality coach",
          68112.00
        },
        { { "Rodney", "Swillbelly" },
          "tripe",
          "tabloid editor",
          432400.00
        }
   };
   struct guy * him;    /* 这是一个指向结构的指针 */
   printf("address #1: %p #2: %p\n", &fellow[0], &fellow[1]);
   him = &fellow[0];    /* 告诉编译器该指针指向何处 */
   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;
}

声明和初始化结构指针

struct guy * him;

  • 首先是关键字struct,其次是结构标记guy,然后是一个星号(*),其后跟着指针名。

  • 和数组不同的是,结构变量名并不是结构变量的地址,因此要在结构变量名前面加上&运算符。

  • 在本例中,fellow是一个结构数组,这意味着fellow[0]是一个结构。所以,要让him指向fellow[0],可以这样写:

    him = &fellow[0];

  • 注意,him加1相当于him指向的地址加84。在十六进制中,874 - 820 = 54(十六进制)= 84(十进制),因为每个guy结构都占用84字节的内存:names.first占用20字节,names.last占用20字节,favfood占用20字节,job占用20字节,income占用4字节(假设系统中float占用4字节)。顺带一提,在有些系统中,一个结构的大小可能大于它各成员大小之和。这是因为系统对数据进行校准的过程中产生了一些“缝隙”。例如,有些系统必须把每个成员都放在偶数地址上,或4的倍数的地址上。在这种系统中,结构的内部就存在未使用的“缝隙”。

用指针访问成员

  • 第1种方法也是最常用的方法:使用->运算符。该运算符由一个连接号(-)后跟一个大于号(>)组成。我们有下面的关系:

    如果him == &barney,那么him->income 即是 barney.income

    如果him == &fellow[0],那么him->income 即是 fellow[0].income

  • 第2种方法是,以这样的顺序指定结构成员的值:如果him == &fellow[0],那么him == fellow[0],因为&和是一对互逆运算符。因此,可以做以下替代:

    fellow[0].income == (*him).income

    必须要使用圆括号,因为.运算符比*运算符的优先级高。

向函数传递结构的信息

传递结构成员

/* 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",
     4032.27,
     "Lucky's Savings and Loan",
     8543.94
   };
   printf("Stan has a total of $%.2f.\n",
     sum(stan.bankfund, stan.savefund));
   return 0;
}
/* 两个double类型的数相加 */
double sum(double x, double y)
{
   return(x + y);
}

传递结构的地址

/* 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",
     4032.27,
     "Lucky'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);
}

传递结构

/* funds3.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",
        4032.27,
        "Lucky'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);
}

其他结构特性

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

  • 这条语句把n_data的每个成员的值都赋给o_data的相应成员。即使成员是数组,也能完成赋值。另外,还可以把一个结构初始化为相同类型的另一个结构:

    struct names right_field = {“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)          // 如果地址不是 NULL,
        *find = '\0';     // 在此处放置一个空字符
     else
        while (getchar() != '\n')
          continue;     // 处理输入行的剩余字符
   }
   return ret_val;
}
  • 该程序把任务分配给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,每个函数都使用该结构变量的地址来访问它。一个函数把信息从自身传回主调函数,一个函数把信息从主调函数传给自身,一个函数通过双向传输来传递信息。
/* 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;
}
  • 考虑makeinfo()函数。在第1个程序中,传递的是person的地址,该函数实际上处理的是person的值。在第2个版本的程序中,创建了一个新的结构info。存储在person中的值被拷贝到info中,函数处理的是这个副本。因此,统计完字母个数后,计算结果存储在info中,而不是person中。然而,返回机制弥补了这一点。makeinfo()中的这行代码:

    return info;

  • 与main()中的这行结合:

    person = makeinfo(person);

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

结构和结构指针的选择

  • 把指针作为参数有两个优点:无论是以前还是现在的C实现都能使用这种方法,而且执行起来很快,只需要传递一个地址。缺点是无法保护数据。被调函数中的某些操作可能会意外影响原来结构中的数据。不过,ANSI C新增的const限定符解决了这个问题。例如,如果在程序清单14.8中,showinfo()函数中的代码改变了结构的任意成员,编译器会捕获这个错误。

  • 把结构作为参数传递的优点是,函数处理的是原始数据的副本,这保护了原始数据。另外,代码风格也更清楚。

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

  • 通常,程序员为了追求效率会使用结构指针作为函数参数,如需防止原始数据被意外修改,使用const限定符。按值传递结构是处理小型结构最常用的方法。

    struct names {

    char first[LEN];

    char last[LEN];

    };

  • 其中的结构声明是否可以这样写:

    struct pnames {

    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 pnames结构不用为字符串分配任何存储空间。它使用的是存储在别处的字符串(如,字符串常量或数组中的字符串)。简而言之,在pnames结构变量中的指针应该只用来在程序中管理那些已分配和在别处分配的字符串。

struct names accountant;

struct pnames attorney;

puts(“Enter the last name of your accountant:”);

scanf("%s", accountant.last);

puts(“Enter the last name of your attorney:”);

scanf("%s", attorney.last); /* 这里有一个潜在的危险 */

  • 对于律师(attorney),scanf()把字符串放到attorney.last表示的地址上。由于这是未经初始化的变量,地址可以是任何值,因此程序可以把名放在任何地方。如果走运的话,程序不会出问题,至少暂时不会出问题,否则这一操作会导致程序崩溃。实际上,如果程序能正常运行并不是好事,因为这意味着一个未被觉察的危险潜伏在程序中。

结构、指针和malloc()

// names3.c -- 使用指针和 malloc()
#include <stdio.h>
#include <string.h>  // 提供 strcpy()、strlen() 的原型
#include <stdlib.h>  // 提供 malloc()、free() 的原型
#define SLEN 81
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 *);    // 调用该函数时释放内存
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("Please 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->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);
}
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;
}

复合字面量和结构(C99)

  • 语法是把类型名放在圆括号中,后面紧跟一个用花括号括起来的初始化列表。

  • 例如,下面是struct book类型的复合字面量:

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

  • 如果只需要一个临时结构值,复合字面量很好用。例如,可以使用复合字面量创建一个结构作为函数的参数或赋给另一个结构。

/* complit.c -- 复合字面量 */
#include <stdio.h>
#define MAXTITL 41
#define MAXAUTL 31
struct book {     // 结构模版:标记是 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",
                   11.25};
   else
     readfirst = (struct book) {"Mr. Bouncy's Nice Hat",
                   "Fred Winsome",
                   5.99};
   printf("Your assigned reading:\n");
   printf("%s by %s: $%.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});
  • 值210被赋给area。

  • 如果函数接受一个地址,可以传递复合字面量的地址:

    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});
    值210被赋给area。

伸缩型数组成员(C99)

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

  • 声明一个伸缩型数组成员有如下规则:
    ·伸缩型数组成员必须是结构的最后一个成员;
    ·结构中必须至少有一个成员;
    ·伸缩数组的声明类似于普通数组,只是它的方括号中是空的。

    下面用一个示例来解释以上几点:

    struct flex

    {

    int count;

    double average;

    double 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类型值的数组。

// flexmemb.c -- 伸缩型数组成员(C99新增特性)
#include <stdio.h>
#include <stdlib.h>
struct flex
{
   size_t 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);
}
  • 带伸缩型数组成员的结构确实有一些特殊的处理要求。

  • 第一,不能用结构进行赋值或拷贝:

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

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

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

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

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

匿名结构(C11)

  • 在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);

使用结构数组的函数

/* 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[N] = {
        {
          "Garlic-Melon Bank",
          4032.27,
          "Lucky's Savings and Loan",
          8543.94
        },
        {
          "Honest Jack's Bank",
          3620.88,
          "Party Time Savings",
          3802.91
        }
   };
   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);
}
  • ·可以把数组名作为数组中第1个结构的地址传递给函数。
  • ·然后可以用数组表示法访问数组中的其他结构。注意下面的函数调用与使用数组名效果相同:
    sum(&jones[0], N)
  • 因为jones和&jones[0]的地址相同,使用数组名是传递结构地址的一种间接的方法。
  • ·由于sum()函数不能改变原始数据,所以该函数使用了ANSI C的限定符const。

把结构内容保存到文件中

  • 存储在一个结构中的整套信息被称为记录(record),单独的项被称为字段(field)。

  • 或许存储记录最没效率的方法是用fprintf()。例如,回忆程序清单14.1中的book结构:

    #define MAXTITL 40

    #define MAXAUTL 40

    struct book {

    char title[MAXTITL];

    char author[MAXAUTL];

    float value;

    };

  • 如果pbook标识一个文件流,那么通过下面这条语句可以把信息存储在struct book类型的结构变量primer中:

    fprintf(pbooks, “%s %s %.2f\n”, primer.title,primer.author, primer.value);

  • 对于一些结构(如,有30个成员的结构),这个方法用起来很不方便。另外,在检索时还存在问题,因为程序要知道一个字段结束和另一个字段开始的位置。虽然用固定字段宽度的格式可以解决这个问题(例如,"%39s%39s%8.2f"),但是这个方法仍然很笨拙。

  • 更好的方案是使用fread()和fwrite()函数读写结构大小的单元。回忆一下,这两个函数使用与程序相同的二进制表示法。例如:

    fwrite(&primer, sizeof(struct book), 1, pbooks);

  • 定位到primer结构变量开始的位置,并把结构中所有的字节都拷贝到与pbooks相关的文件中。sizeof(struct book)告诉函数待拷贝的一块数据的大小,1表明一次拷贝一块数据。带相同参数的fread()函数从文件中拷贝一块结构大小的数据到&primer指向的位置。简而言之,这两个函数一次读写整个记录,而不是一个字段。

保存结构的程序示例

/* booksave.c -- 在文件中保存结构中的内容 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXTITL 40
#define MAXAUTL 40
#define MAXBKS  10       /* 最大书籍数量 */
char * s_gets(char * st, int n);
struct book {          /* 建立 book 模板 */
   char title[MAXTITL];
   char author[MAXAUTL];
   float value;
};
int main(void)
{
   struct book library[MAXBKS]; /* 结构数组 */
   int count = 0;
   int index, filecount;
   FILE * pbooks;
   int size = sizeof(struct book);
   if ((pbooks = fopen("book.dat", "a+b")) == NULL)
   {
     fputs("Can't open book.dat file\n", stderr);
     exit(1);
   }
   rewind(pbooks);      /* 定位到文件开始 */
   while (count < MAXBKS && fread(&library[count], size,
     1, pbooks) == 1)
   {
     if (count == 0)
        puts("Current contents of book.dat:");
     printf("%s by %s: $%.2f\n", library[count].title,
        library[count].author, library[count].value);
     count++;
   }
   filecount = count;
   if (count == MAXBKS)
   {
     fputs("The book.dat file is full.", stderr);
     exit(2);
   }
   puts("Please add new book titles.");
   puts("Press [enter] at the start of a line to stop.");
   while (count < MAXBKS && s_gets(library[count].title, MAXTITL) != NULL
     && library[count].title[0] != '\0')
   {
     puts("Now enter the author.");
     s_gets(library[count].author, MAXAUTL);
     puts("Now enter the value.");
     scanf("%f", &library[count++].value);
     while (getchar() != '\n')
        continue;      /* 清理输入行 */
     if (count < MAXBKS)
        puts("Enter the next title.");
   }
   if (count > 0)
   {
     puts("Here is the list of your books:");
     for (index = 0; index < count; index++)
        printf("%s by %s: $%.2f\n", library[index].title,
        library[index].author, library[index].value);
     fwrite(&library[filecount], size, count - filecount,
        pbooks);
   }
   else
     puts("No books? Too bad.\n");
   puts("Bye.\n");
   fclose(pbooks);
   return 0;
}
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;
}

程序要点

  • 首先,以"a+b"模式打开文件。a+部分允许程序读取整个文件并在文件的末尾添加内容。b是ANSI的一种标识方法,表明程序将使用二进制文件格式。对于不接受b模式的UNIX系统,可以省略b,因为UNIX只有一种文件形式。
  • rewind()函数确保文件指针位于文件开始处,为读文件做好准备。
  • 第1个while循环每次把一个结构读到结构数组中,当数组已满或读完文件时停止。变量filecount统计已读结构的数量。
  • 第2个while循环提示用户进行输入,并接受用户的输入。和程序清单14.2一样,当数组已满或用户在一行的开始处按下Enter键时,循环结束。注意,该循环开始时count变量的值是第1个循环结束后的值。该循环把新输入项添加到数组的末尾。
  • 然后for循环打印文件和用户输入的数据。因为该文件是以附加模式打开,所以新写入的内容添加到文件现有内容的末尾。
  • 我们本可以用一个循环在文件末尾一次添加一个结构,但还是决定用fwrite()一次写入一块数据。对表达式count - filecount求值得新添加的书籍数量,然后调用fwrite()把结构大小的块写入文件。由于表达式&library[filecount]是数组中第1个新结构的地址,所以拷贝就从这里开始。

联合简介

  • 联合(union)是一种数据类型,它能在同一个内存空间中存储不同的数据类型(不是同时存储)。其典型的用法是,设计一种表以存储既无规律、事先也不知道顺序的混合类型。使用联合类型的数组,其中的联合都大小相等,每个联合可以存储各种数据类型。

    union hold {

    int digit;

    double bigfl;

    char letter;

    };

  • 根据以上形式声明的结构可以存储一个int类型、一个double类型和char类型的值。然而,声明的联合只能存储一个int类型的值或一个double类型的值或char类型的值。

  • union hold fit; // hold类型的联合变量

    union hold save[10]; // 内含10个联合变量的数组

    union hold * pu; // 指向hold类型联合变量的指针

  • 第1个声明创建了一个单独的联合变量fit。编译器分配足够的空间以便它能存储联合声明中占用最大字节的类型。在本例中,占用空间最大的是double类型的数据。在我们的系统中,double类型占64位,即8字节。

  • 第2个声明创建了一个数组save,内含10个元素,每个元素都是8字节。

  • 第3个声明创建了一个指针,该指针变量存储hold类型联合变量的地址。

  • 有3种初始化的方法:把一个联合初始化为另一个同类型的联合;初始化联合的第1个元素;或者根据C99标准,使用指定初始化器:

    union hold valA;

    valA.letter = ‘R’;

    union hold valB = valA; // 用另一个联合来初始化

    union hold valC = {88}; // 初始化联合的digit 成员

    union hold valD = {.bigfl = 118.2}; // 指定初始化器

使用联合

  • 下面是联合的一些用法:

    fit.digit = 23; //把 23 存储在 fit,占2字节
    fit.bigfl = 2.0; // 清除23,存储 2.0,占8字节
    fit.letter = ‘h’; // 清除2.0,存储h,占1字节

  • 点运算符表示正在使用哪种数据类型。在联合中,一次只存储一个值。即使有足够的空间,也不能同时存储一个char类型值和一个int类型值。编写代码时要注意当前存储在联合中的数据类型。

  • 和用指针访问结构使用->运算符一样,用指针访问联合时也要使用->运算符

    pu = &fit;

    x = pu->digit; // 相当于 x = fit.digit

  • 联合的另一种用法是,在结构中存储与其成员有从属关系的信息。例如,假设用一个结构表示一辆汽车。如果汽车属于驾驶者,就要用一个结构成员来描述这个所有者。如果汽车被租赁,那么需要一个成员来描述其租赁公司。可以用下面的代码来完成:

    struct owner {
       char socsecurity[12];
       ...
    };
    struct leasecompany {
       char name[40];
       char headquarters[40];
       ...
    };
    union data {
       struct owner owncar;
       struct leasecompany leasecar;
    };
    struct car_data {
       char make[15];
       int status; /* 私有为0,租赁为1 */
       union data ownerinfo;
       ...
    };
    

14.10.2 匿名联合(C11)

struct owner {
   char socsecurity[12];
   ...
};
struct leasecompany {
   char name[40];
   char headquarters[40];
   ...
};
struct car_data {
   char make[15];
   int status; /* 私有为0,租赁为1 */
   union {
     struct owner owncar;
     struct leasecompany leasecar;
   };
   ...
};

枚举类型

  • 可以用枚举类型(enumerated type)声明符号名称来表示整型常量。使用enum关键字,可以创建一个新“类型”并指定它可具有的值(实际上,enum常量是int类型,因此,只要能使用int类型的地方就可以使用枚举类型)。枚举类型的目的是提高程序的可读性。它的语法与结构的语法相同。例如,可以这样声明:

    enum spectrum {red, orange, yellow, green, blue, violet};

    enum spectrum color;

  • 第1个声明创建了spetrum作为标记名,允许把enum spectrum作为一个类型名使用。

  • 第2个声明使color作为该类型的变量。

  • 第1个声明中花括号内的标识符枚举了spectrum变量可能有的值。因此,color可能的值是red、orange、yellow等。这些符号常量被称为枚举符(enumerator)。然后,便可这样用:

    int c;

    color = blue;

    if (color == yellow)

    …;

    for (color = red; color <= violet; color++)

    …;

  • 虽然枚举符(如red和blue)是int类型,但是枚举变量可以是任意整数类型,前提是该整数类型可以存储枚举常量。例如,spectrum的枚举符范围是0~5,所以编译器可以用unsigned char来表示color变量。

  • 顺带一提,C枚举的一些特性并不适用于C++。例如,C允许枚举变量使用++运算符,但是C++标准不允许。所以,如果编写的代码将来会并入C++程序,那么必须把上面例子中的color声明为int类型,才能C和C++都兼容。

14.11.1 enum常量

  • blue和red到底是什么?从技术层面看,它们是int类型的常量。例如,假定有前面的枚举声明,可以这样写:

    printf(“red = %d, orange = %d\n”, red, orange);
    其输出如下:
    red = 0, orange = 1

14.11.2 默认值

  • 默认情况下,枚举列表中的常量都被赋予0、1、2等。因此,下面的声明中nina的值是3:

    enum kids {nippy, slats, skippy, nina, liz};

14.11.3 赋值

  • 在枚举声明中,可以为枚举常量指定整数值:

    enum levels {low = 100, medium = 500, high = 2000};

  • 如果只给一个枚举常量赋值,没有对后面的枚举常量赋值,那么后面的常量会被赋予后续的值。例如,假设有如下的声明:

    enum feline {cat, lynx = 10, puma, tiger};

  • 那么,cat的值是0(默认),lynx、puma和tiger的值分别是10、11、12。

14.11.4 enum的用法

/* enum.c -- 使用枚举类型的值 */
#include <stdio.h>
#include <string.h>   // 提供 strcmp()、strchr()函数的原型
#include <stdbool.h>  // C99 特性
char * s_gets(char * st, int n);
enum spectrum { red, orange, yellow, green, blue, violet };
const char * colors [] = { "red", "orange", "yellow",
"green", "blue", "violet" };
#define LEN 30
int main(void)
{
   char choice[LEN];
   enum spectrum color;
   bool color_is_found = false;
   puts("Enter a color (empty line to quit):");
   while (s_gets(choice, LEN) != NULL && choice[0] != '\0')
   {
     for (color = red; color <= violet; color++)
     {
        if (strcmp(choice, colors[color]) == 0)
        {
          color_is_found = true;
          break;
        }
     }
     if (color_is_found)
        switch (color)
     {
        case red: puts("Roses are red.");
          break;
        case orange: puts("Poppies are orange.");
          break;
        case yellow: puts("Sunflowers are yellow.");
          break;
        case green: puts("Grass is green.");
          break;
        case blue: puts("Bluebells are blue.");
          break;
        case violet: puts("Violets are violet.");
          break;
     }
     else
        printf("I don't know about the color %s.\n", choice);
     color_is_found = false;
     puts("Next color, please (empty line to quit):");
   }
   puts("Goodbye!");
   return 0;
}
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;
}

14.11.5 共享名称空间

  • C语言使用名称空间(namespace)标识程序中的各部分,即通过名称来识别。作用域是名称空间概念的一部分:两个不同作用域的同名变量不冲突;两个相同作用域的同名变量冲突。名称空间是分类别的。在特定作用域中的结构标记、联合标记和枚举标记都共享相同的名称空间,该名称空间与普通变量使用的空间不同。这意味着在相同作用域中变量和标记的名称可以相同,不会引起冲突,但是不能在相同作用域中声明两个同名标签或同名变量。例如,在C中,下面的代码不会产生冲突:

    struct rect { double x; double y; };

    int rect; // 在C中不会产生冲突

  • 尽管如此,以两种不同的方式使用相同的标识符会造成混乱。另外,C++不允许这样做,因为它把标记名和变量名放在相同的名称空间中。

typedef简介

  • typedef工具是一个高级数据特性,利用typedef可以为某一类型自定义名称。这方面与#define类似,但是两者有3处不同:

·与#define不同,typedef创建的符号名只受限于类型,不能用于值。
·typedef由编译器解释,不是预处理器。
·在其受限范围内,typedef比#define更灵活。

  • 假设要用BYTE表示1字节的数组。只需像定义个char类型变量一样定义BYTE,然后在定义前面加上关键字typedef即可:

  • typedef unsigned char BYTE;

  • 随后,便可使用BYTE来定义变量:

  • BYTE x, y[10], * z;

  • 该定义的作用域取决于typedef定义所在的位置。如果定义在函数中,就具有局部作用域,受限于定义所在的函数。如果定义在函数外面,就具有文件作用域。

  • 通常,typedef定义中用大写字母表示被定义的名称,以提醒用户这个类型名实际上是一个符号缩写。

  • 使用typedef还能提高程序的可移植性。例如,我们之前提到的sizeof运算符的返回类型:size_t类型,以及time()函数的返回类型:time_t类型。C标准规定sizeof和time()返回整数类型,但是让实现来决定具体是什么整数类型。其原因是,C标准委员会认为没有哪个类型对于所有的计算机平台都是最优选择。所以,标准委员会决定建立一个新的类型名(如,time_t),并让实现使用typedef来设置它的具体类型。以这样的方式,C标准提供以下通用原型:
    *time_t time(time_t );

    typedef char * STRING;

  • 没有typedef关键字,编译器将把STRING识别为一个指向char的指针变量。有了typedef关键字,编译器则把STRING解释成一个类型的标识符,该类型是指向char的指针。因此:
    STRING name, sign;

  • 相当于:

    char * name, * sign;

  • 但是,如果这样假设:

    #define STRING char *

  • 然后,下面的声明:

    STRING name, sign;

  • 将被翻译成:

    char * name, sign;

  • 这导致只有name才是指针。

其他复杂的声明

  • 下面是一些较复杂的声明示例:

    int* board[8][8]; // 声明一个内含int数组的数组

    int* ** ptr; // 声明一个指向指针的指针,被指向的指针指向int

    int* * risks[10]; // 声明一个内含10个元素的数组,每个元素都是一个指向int的指针

    int* (* rusks)[10]; // 声明一个指向数组的指针,该数组内含10个int类型的值

    int* * oof[3][4]; // 声明一个3×4 的二维数组,每个元素都是指向int的指针

    int* (* uuf)[3][4]; // 声明一个指向3×4二维数组的指针,该数组中内含int类型值

    int* (* uof[3])[4]; // 声明一个内含3个指针元素的数组,其中每个指针都指向一个内含4个int类型元素的数组

  • 要看懂以上声明,关键要理解*、()和[]的优先级。记住下面几条规则。*

  • 1.数组名后面的[]和函数名后面的()具有相同的优先级。它们比(解引用运算符)的优先级高。因此下面声明的risk是一个指针数组,不是指向数组的指针:*

    int * risks[10];

  • 2.[]和()的优先级相同,由于都是从左往右结合,所以下面的声明中,在应用方括号之前,*先与rusks结合。因此rusks是一个指向数组的指针,该数组内含10个int类型的元素:

    int ( rusks)[10];*

  • 3.[]和()都是从左往右结合。因此下面声明的goods是一个由12个内含50个int类型值的数组组成的二维数组,不是一个有50个内含12个int类型值的数组组成的二维数组:

    int goods[12][50];

  • 把以上规则应用于下面的声明:

    int * oof[3][4];

  • *[3]比*的优先级高,由于从左往右结合,所以[3]先与oof结合。因此,oof首先是一个内含3个元素的数组。然后再与[4]结合,所以oof的每个元素都是内含4个元素的数组。说明这些元素都是指针。最后,int表明了这4个元素都是指向int的指针。因此,这条声明要表达的是:oof是一个内含3个元素的数组,其中每个元素是由4个指向int的指针组成的数组。简而言之,oof是一个3×4的二维数组,每个元素都是指向int的指针。编译器要为12个指针预留存储空间。

    int ( uuf)[3][4];*

  • 圆括号使得*先与uuf结合,说明uuf是一个指针,所以uuf是一个指向3×4的int类型二维数组的指针。编译器要为一个指针预留存储空间。

  • 根据这些规则,还可以声明:

    char * fump(int); // 返回字符指针的函数

    char ( frump)(int); // 指向函数的指针,该函数的返回类型为char*

    char ( flump[3])(int); // 内含3个指针的数组,每个指针都指向返回类型为char的函数*

    这3个函数都接受int类型的参数。

    可以使用typedef建立一系列相关类型:

    typedef int arr5[5];

    typedef arr5 * p_arr5;

    typedef p_arr5 arrp10[10];

    arr5 togs; // togs 是一个内含5个int类型值的数组

    p_arr5 p2; // p2 是一个指向数组的指针,该数组内含5个int类型的值

    arrp10 ap; // ap 是一个内含10个指针的数组,每个指针都指向一个内含5个int类型值的数组

函数和指针

  • 通常,函数指针常用作另一个函数的参数,告诉该函数要使用哪一个函数。例如,排序数组涉及比较两个元素,以确定先后。如果元素是数字,可以使用>运算符;如果元素是字符串或结构,就要调用函数进行比较。C库中的qsort()函数可以处理任意类型的数组,但是要告诉qsort()使用哪个函数来比较元素。为此,qsort()函数的参数列表中,有一个参数接受指向函数的指针。然后,qsort()函数使用该函数提供的方案进行排序,无论这个数组中的元素是整数、字符串还是结构。

    void ToUpper(char *); // 把字符串中的字符转换成大写字符

  • ToUpper()函数的类型是“带char * 类型参数、返回类型是void的函数”。下面声明了一个指针pf指向该函数类型:

    void (*pf)(char *); // pf 是一个指向函数的指针

  • 从该声明可以看出,第1对圆括号把*和pf括起来,表明pf是一个指向函数的指针。因此,(*pf)是一个参数列表为(char *)、返回类型为void的函数。注意,把函数名ToUpper替换为表达式(*pf)是创建指向函数指针最简单的方式。所以,如果想声明一个指向某类型函数的指针,可以写出该函数的原型后把函数名替换成(*pf)形式的表达式,创建函数指针声明。前面提到过,由于运算符优先级的规则,在声明函数指针时必须把*和指针名括起来。如果省略第1个圆括号会导致完全不同的情况:

    void *pf(char *); // pf 是一个返回字符指针的函数

  • 声明了函数指针后,可以把类型匹配的函数地址赋给它。在这种上下文中,函数名可以用于表示函数的地址:

  • void ToUpper(char *);

    void ToLower(char *);

    int round(double);

    void (*pf)(char *);

    pf = ToUpper; // 有效,ToUpper是该类型函数的地址

    pf = ToLower; //有效,ToLower是该类型函数的地址

    pf = round; // 无效,round与指针类型不匹配

    pf = ToLower(); // 无效,ToLower()不是地址

    void ToUpper(char *);

    void ToLower(char *);

    void (*pf)(char *);

    char mis[] = “Nina Metier”;

    pf = ToUpper;

    (*pf)(mis); // 把ToUpper 作用于mis(语法1)

    pf = ToLower;

    pf(mis); // 把ToLower 作用于mis(语法2)

    这两种方法看上去都合情合理。

    先分析第1种方法:由于pf指向ToUpper函数,那么*pf就相当于ToUpper函数,所以表达式(*pf)(mis)和ToUpper(mis)相同。从ToUpper函数和pf的声明就能看出,ToUpper和(pf)是等价的。

    第2种方法:由于函数名是指针,那么指针和函数名可以互换使用,所以pf(mis)和ToUpper(mis)相同。从pf的赋值表达式语句就能看出ToUpper和pf是等价的。由于历史的原因,贝尔实验室的C和UNIX的开发者采用第1种形式,而伯克利的UNIX推广者却采用第2种形式。K&R C不允许第2种形式。但是,为了与现有代码兼容,ANSI C认为这两种形式(本例中是(*pf)(mis)和pf(mis))等价。后续的标准也延续了这种矛盾的和谐。

    void show(void (* fp)(char *), char * str)

    {

    (*fp)(str); /* 把所选函数作用于str /

    puts(str); /* 显示结果 */}

  • 作为函数的参数是数据指针最常见的用法之一,函数指针亦如此。

  • 例如,考虑下面的函数原型:

    void show(void (* fp)(char *), char * str);
    这看上去让人头晕。它声明了两个形参:fp和str。fp形参是一个函数指针,str是一个数据指针。

// func_ptr.c -- 使用函数指针
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define LEN 81
char * s_gets(char * st, int n);
char showmenu(void);
void eatline(void);    // 读取至行末尾
void show(void(*fp)(char *), char * str);
void ToUpper(char *);   // 把字符串转换为大写
void ToLower(char *);   // 把字符串转换为小写
void Transpose(char *);  // 大小写转置
void Dummy(char *);    // 不更改字符串
int main(void)
{
   char line[LEN];
   char copy[LEN];
   char choice;
   void(*pfun)(char *); // 声明一个函数指针,被指向的函数接受char *类型的参数,无返回值
   puts("Enter a string (empty line to quit):");
   while (s_gets(line, LEN) != NULL && line[0] != '\0')
   {
     while ((choice = showmenu()) != 'n')
     {
        switch (choice) // switch语句设置指针
        {
        case 'u': pfun = ToUpper;  break;
        case 'l': pfun = ToLower;  break;
        case 't': pfun = Transpose; break;
        case 'o': pfun = Dummy;   break;
        }
        strcpy(copy, line);  // 为show()函数拷贝一份
        show(pfun, copy);   // 根据用户的选择,使用选定的函数
     }
     puts("Enter a string (empty line to quit):");
   }
   puts("Bye!");
   return 0;
}
char showmenu(void)
{
   char ans;
   puts("Enter menu choice:");
   puts("u) uppercase    l) lowercase");
   puts("t) transposed case o) original case");
   puts("n) next string");
   ans = getchar();   // 获取用户的输入
   ans = tolower(ans);  // 转换为小写
   eatline();      // 清理输入行
   while (strchr("ulton", ans) == NULL)
   {
     puts("Please enter a u, l, t, o, or n:");
     ans = tolower(getchar());
     eatline();
   }
   return ans;
}
void eatline(void)
{
   while (getchar() != '\n')
     continue;
}
void ToUpper(char * str)
{
   while (*str)
   {
     *str = toupper(*str);
     str++;
   }
}
void ToLower(char * str)
{
   while (*str)
   {
     *str = tolower(*str);
     str++;
   }
}
void Transpose(char * str)
{
   while (*str)
   {
     if (islower(*str))
        *str = toupper(*str);
     else if (isupper(*str))
        *str = tolower(*str);
     str++;
   }
}
void Dummy(char * str)
{
   // 不改变字符串
}
void show(void(*fp)(char *), char * str)
{
   (*fp)(str);  // 把用户选定的函数作用于str
   puts(str);  // 显示结果
}
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;
}

复习题

  1. 下面的结构模板有什么问题:

    structure {

    char itable;

    int num[20];

    char * togs

    }

  2. 下面是程序的一部分,输出是什么?

    #include <stdio.h>
    struct house {
       float sqft;
       int rooms;
       int stories;
       char address[40];
    };
    int main(void)
    {
       struct house fruzt = {1560.0, 6, 1, "22 Spiffo Road"};
       struct house *sign;
       sign = &fruzt;
       printf("%d %d\n", fruzt.rooms, sign->stories);
       printf("%s \n", fruzt.address);
       printf("%c %c\n", sign->address[3], fruzt.address[4]);
       return 0;
    }*
    
  3. 设计一个结构模板存储一个月份名、该月份名的3个字母缩写、该月的天数以及月份号。

  4. 定义一个数组,内含12个结构(第3题的结构类型)并初始化为一个年份(非闰年)。

  5. 编写一个函数,用户提供月份号,该函数就返回一年中到该月为止(包括该月)的总天数。假设在所有函数的外部声明了第3题的结构模版和一个该类型结构的数组。

  6. a.假设有下面的typedef,声明一个内含10个指定结构的数组。然后,单独给成员赋值(或等价字符串),使第3个元素表示一个焦距长度有500mm,孔径为f/2.0的Remarkata镜头。

    typedef struct lens { /* 描述镜头 /

    float foclen; /* 焦距长度,单位为mm /

    float fstop; /* 孔径 /

    char brand[30]; /* 品牌名称 */

    } LENS;
    b.重写a,在声明中使用一个待指定初始化器的初始化列表,而不是对每个成员单独赋值。

  7. 考虑下面程序片段:

    struct name {
       char first[20];
       char last[20];
    };
    struct bem {
       int limbs;
       struct name title;
       char type[30];
    };
    struct bem * pb;
    struct bem deb = {
       6,
       { "Berbnazel", "Gwolkapwolk" },
       "Arcturan"
    };
    pb = &deb;
    a.下面的语句分别打印什么?
    printf("%d\n", deb.limbs);
    printf("%s\n", pb->type);
    printf("%s\n", pb->type + 2);
    b.如何用结构表示法(两种方法)表示"Gwolkapwolk"?
    c.编写一个函数,以bem结构的地址作为参数,并以下面的形式输出结构的内容(假定结构模板在一个名为starfolk.h的头文件中):
    Berbnazel Gwolkapwolk is a 6-limbed Arcturan.
    
  8. 考虑下面的声明:

    struct fullname {
       char fname[20];
       char lname[20];
    };
    struct bard {
       struct fullname name;
       int born;
       int died;
    };
    struct bard willie;
    struct bard *pt = &willie;
    a.用willie标识符标识willie结构的born成员。
    b.用pt标识符标识willie结构的born成员。
    c.调用scanf()读入一个用willie标识符标识的born成员的值。
    d.调用scanf()读入一个用pt标识符标识的born成员的值。
    e.调用scanf()读入一个用willie标识符标识的name成员中lname成员的值。
    f.调用scanf()读入一个用pt标识符标识的name成员中lname成员的值。
    g.构造一个标识符,标识willie结构变量所表示的姓名中名的第3个字母(英文的名在前)。
    h.构造一个表达式,表示willie结构变量所表示的名和姓中的字母总数。
    
  9. 定义一个结构模板以存储这些项:汽车名、马力、EPA(美国环保局)城市交通MPG(每加仑燃料行驶的英里数)评级、轴距和出厂年份。使用car作为该模版的标记。

  10. 假设有如下结构:

struct gas {

float distance;

float gals;

float mpg;

};
a.设计一个函数,接受struct gas类型的参数。假设传入的结构包含distance和gals信息。该函数为mpg成员计算正确的值,并把值返回该结构。
b.设计一个函数,接受struct gas类型的参数。假设传入的结构包含distance和gals信息。该函数为mpg成员计算正确的值,并把该值赋给合适的成员。

  1. 声明一个标记为choices的枚举,把枚举常量no、yes和maybe分别设置为0、1、2。

  2. 声明一个指向函数的指针,该函数返回指向char的指针,接受一个指向char的指针和一个char类型的值。

  3. 声明4个函数,并初始化一个指向这些函数的指针数组。每个函数都接受两个double类型的参数,返回double类型的值。另外,用两种方法使用该数组调用带10.0和2.5实参的第2个函数。


  1. 正确的关键是struct,不是structure。该结构模板要在左花括号前面有一个标记,或者在右花括号后面有一个结构变量名。另外,togs后面和模板结尾处都少一个分号。

  2. 输出如下:

    6 1

    22 Spiffo Road

    S p

  3. struct month {

    char name[10];

    char abbrev[4];

    int days;

    int monumb;

    };

  4. struct month months[12] =

    {

    { “January”, “jan”, 31, 1 },

    { “February”, “feb”, 28, 2 },

    { “March”, “mar”, 31, 3 },

    { “April”, “apr”, 30, 4 },

    { “May”, “may”, 31, 5 },

    { “June”, “jun”, 30, 6 },

    { “July”, “jul”, 31, 7 },

    { “August”, “aug”, 31, 8 },

    { “September”, “sep”, 30, 9 },

    { “October”, “oct”, 31, 10 },

    { “November”, “nov”, 30, 11 },

    { “December”, “dec”, 31, 12 }

    };

  5. extern struct month months [];

    int days(int month)

    {

    int index, total;

    if (month < 1 || month > 12)

    return(-1); /* error signal */

    else

    {

    for (index = 0, total = 0; index < month; index++)

    total += months[index].days;

    return(total);

    }

    }注意,index比月数小1,因为数组下标从0开始。然后,用index < month代替index <= month。*

  6. a.要包含string.h头文件,提供strcpy()的原型:
    typedef struct lens { /* lens 描述 /
    float foclen; /
    焦距长度,单位:mm /
    float fstop; /
    孔径 /
    char brand[30]; /
    品牌 */
    } LENS;
    LENS bigEye[10];
    bigEye[2].foclen = 500;
    bigEye[2].fstop = 2.0;
    strcpy(bigEye[2].brand, “Remarkatar”);
    b.LENS bigEye[10] = { [2] = {500, 2, “Remarkatar”} };*

  7. a.
    6
    Arcturan
    cturan
    b.使用结构名和指针:
    deb.title.last
    pb->title.last
    c.下面是一个版本:
    #include <stdio.h>
    #include “starfolk.h” /* 让结构定义可用 */
    void prbem (const struct bem * pbem )
    {
    printf("%s %s is a %d-limbed %s.\n", pbem->title.first,
    pbem->title.last, pbem->limbs, pbem->type);
    }

  8. a.willie.born
    b.pt->born
    c.scanf("%d", &willie.born);
    d.scanf("%d", &pt->born);
    e.scanf("%s", [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c5Ki8hWU-1616229196323)(file:///C:\Users\Administrator\AppData\Roaming\Tencent\QQ\Temp%W@GJ$ACOF(TYDYECOKVDYB.png)]willie.name.lname);
    f.scanf("%s", pt->name.lname);
    g.willie.name.fname[2] h.strlen(willie.name.fname)+strlen(willie.name.lname)

  9. 下面是一种方案:
    struct car {
    char name[20];
    float hp;
    float epampg;
    float wbase;
    int year;
    };

  10. 应该这样建立函数:
    struct gas {
    float distance;
    float gals;
    float mpg;
    };
    struct gas mpgs(struct gas trip)
    {
    if (trip.gals > 0)
    trip.mpg = trip.distance / trip.gals;
    else
    trip.mpg = -1.0;
    return trip;
    }
    void set_mpgs(struct gas * ptrip)
    {
    if (ptrip->gals > 0)
    ptrip->mpg = ptrip->distance / ptrip->gals;
    else
    ptrip->mpg = -1.0;
    }
    注意,第1个函数不能直接改变其主调程序中的值,所以必须用返回值才能传递信息。
    struct gas idaho = {430.0, 14.8}; // 设置前两个成员
    idaho = mpgs(idaho); // 重置数据结构
    但是,第2个函数可以直接访问最初的结构:
    struct gas ohio = {583, 17.6}; //设置前两个成员
    set_mpgs(&ohio); // 设置第3个成员

  11. enum choices {no, yes, maybe};

  12. char * (*pfun)(char , char);

  13. double sum(double, double);
    double diff(double, double);
    double times(double, double);
    double divide(double, double);
    double (*pf1[4])(double, double) = {sum, diff, times, divide};
    或者用更简单的形式,把代码中最后一行替换成:
    typedef double (*ptype) (double, double);
    ptype pfl[4] = {sum,diff, times, divide};
    调用diff()函数:
    pf1[1](10.0, 2.5); // 第1种表示法
    (*pf1[1])(10.0, 2.5); // 等价表示法

编程练习

  1. 重新编写复习题5,用月份名的拼写代替月份号(别忘了使用strcmp())。在一个简单的程序中测试该函数。

  2. 编写一个函数,提示用户输入日、月和年。月份可以是月份号、月份名或月份名缩写。然后该程序应返回一年中到用户指定日子(包括这一天)的总天数。

  3. 修改程序清单14.2中的图书目录程序,使其按照输入图书的顺序输出图书的信息,然后按照书名的字母顺序输出图书的信息,最后按照价格的升序输出图书的信息。

  4. 编写一个程序,创建一个有两个成员的结构模板:
    a.第1个成员是社会保险号,第2个成员是一个有3个成员的结构,第1个成员代表名,第2个成员代表中间名,第3个成员表示姓。创建并初始化一个内含5个该类型结构的数组。该程序以下面的格式打印数据:
    Dribble, Flossie M. –– 302039823
    如果有中间名,只打印它的第1个字母,后面加一个点(.);如果没有中间名,则不用打印点。编写一个程序进行打印,把结构数组传递给这个函数。
    b.修改a部分,传递结构的值而不是结构的地址。

  5. 编写一个程序满足下面的要求。
    a.外部定义一个有两个成员的结构模板name:一个字符串存储名,一个字符串存储姓。
    b.外部定义一个有3个成员的结构模板student:一个name类型的结构,一个grade数组存储3个浮点型分数,一个变量存储3个分数平均数。
    c.在main()函数中声明一个内含CSIZE(CSIZE = 4)个student类型结构的数组,并初始化这些结构的名字部分。用函数执行g、e、f和g中描述的任务。
    d.以交互的方式获取每个学生的成绩,提示用户输入学生的姓名和分数。把分数存储到grade数组相应的结构中。可以在main()函数或其他函数中用循环来完成。
    e.计算每个结构的平均分,并把计算后的值赋给合适的成员。
    f.打印每个结构的信息。
    g.打印班级的平均分,即所有结构的数值成员的平均值。

  6. 一个文本文件中保存着一个垒球队的信息。每行数据都是这样排列:
    4 Jessie Joybat 5 2 1 1
    第1项是球员号,为方便起见,其范围是0~18。第2项是球员的名。第3项是球员的姓。名和姓都是一个单词。第4项是官方统计的球员上场次数。接着3项分别是击中数、走垒数和打点(RBI)。文件可能包含多场比赛的数据,所以同一位球员可能有多行数据,而且同一位球员的多行数据之间可能有其他球员的数据。编写一个程序,把数据存储到一个结构数组中。该结构中的成员要分别表示球员的名、姓、上场次数、击中数、走垒数、打点和安打率(稍后计算)。可以使用球员号作为数组的索引。该程序要读到文件结尾,并统计每位球员的各项累计总和。
    世界棒球统计与之相关。例如,一次走垒和触垒中的失误不计入上场次数,但是可能产生一个RBI。但是该程序要做的是像下面描述的一样读取和处理数据文件,不会关心数据的实际含义。
    要实现这些功能,最简单的方法是把结构的内容都初始化为零,把文件中的数据读入临时变量中,然后将其加入相应的结构中。程序读完文件后,应计算每位球员的安打率,并把计算结果存储到结构的相应成员中。计算安打率是用球员的累计击中数除以上场累计次数。这是一个浮点数计算。最后,程序结合整个球队的统计数据,一行显示一位球员的累计数据。

  7. 修改程序清单14.14,从文件中读取每条记录并显示出来,允许用户删除记录或修改记录的内容。如果删除记录,把空出来的空间留给下一个要读入的记录。要修改现有的文件内容,必须用"r+b"模式,而不是"a+b"模式。而且,必须更加注意定位文件指针,防止新加入的记录覆盖现有记录。最简单的方法是改动存储在内存中的所有数据,然后再把最后的信息写入文件。跟踪的一个方法是在book结构中添加一个成员表示是否该项被删除。

  8. 巨人航空公司的机群由12个座位的飞机组成。它每天飞行一个航班。根据下面的要求,编写一个座位预订程序。
    a.该程序使用一个内含12个结构的数组。每个结构中包括:一个成员表示座位编号、一个成员表示座位是否已被预订、一个成员表示预订人的名、一个成员表示预订人的姓。
    b.该程序显示下面的菜单:
    To choose a function, enter its letter label:
    a) Show number of empty seats
    b) Show list of empty seats
    c) Show alphabetical list of seats
    d) Assign a customer to a seat assignment
    e) Delete a seat assignment
    f) Quit
    c.该程序能成功执行上面给出的菜单。选择d)和e)要提示用户进行额外输入,每个选项都能让用户中止输入。
    d.执行特定程序后,该程序再次显示菜单,除非用户选择f)。

  9. 巨人航空公司(编程练习8)需要另一架飞机(容量相同),每天飞4班(航班102、311、444和519)。把程序扩展为可以处理4个航班。用一个顶层菜单提供航班选择和退出。选择一个特定航班,就会出现和编程练习8类似的菜单。但是该菜单要添加一个新选项:确认座位分配。而且,菜单中的退出是返回顶层菜单。每次显示都要指明当前正在处理的航班号。另外,座位分配显示要指明确认状态。

  10. 编写一个程序,通过一个函数指针数组实现菜单。例如,选择菜单中的a,将激活由该数组第1个元素指向的函数。

  11. 编写一个名为transform()的函数,接受4个参数:内含double类型数据的源数组名、内含double类型数据的目标数组名、一个表示数组元素个数的int类型参数、函数名(或等价的函数指针)。transform()函数应把指定函数应用于源数组中的每个元素,并把返回值存储在目标数组中。例如:
    transform(source, target, 100, sin);
    该声明会把target[0]设置为sin(source[0]),等等,共有100个元素。在一个程序中调用transform()4次,以测试该函数。分别使用math.h函数库中的两个函数以及自定义的两个函数作为参数。

  12. #include <stdio.h>
    #include <string.h>
    struct month
    {
        char name[10];
        char abbrev[4];
        int days;
        int monumb;
    };
    struct month months[12] =
        {
            {"January", "jan", 31, 1},
            {"February", "feb", 28, 2},
            {"March", "mar", 31, 3},
            {"April", "apr", 30, 4},
            {"May", "may", 31, 5},
            {"June", "jun", 30, 6},
            {"July", "jul", 31, 7},
            {"August", "aug", 31, 8},
            {"September", "sep", 30, 9},
            {"October", "oct", 31, 10},
            {"November", "nov", 30, 11},
            {"December", "dec", 31, 12}};
    const char names[12][10] = {"January", "February", "March", "April", "May", "June", "August", "September", "October", "November", "December"};
    int days(char name[])
    {
        int index, total, month = 1, flag = 0;
        for (int i = 0; i < 12; i++)
        {
            if (!strcmp(name, names[i]))
            {
                flag = 1;
                break;
            }
            month++;
        }
        if (!flag)
            return -1;
        else
        {
            for (index = 0, total = 0; index < month; index++)
                total += months[index].days;
            return (total);
        }
    }
    int main(void)
    {
        fprintf(stdout, "%d\n", days("February"));
        return 0;
    }
    
  13. // 代码相当于上部分代码的续写
    // 代码忽略了闰年问题
    const char subname[12][10] = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"};
    int getDays()
    {
        int year, day, option, total, flag = 0;
        union
        {
            char name[10];
            int num;
        } month;
    
        printf("enter year:");
        scanf("%d", &year);
        printf("enter month,name press 1 ,subname press 2, num press 3:");
        scanf("%d", &option);
        if (option == 1)
        {
            printf("enter name:");
            scanf("%s", month.name);
            int monthnum = 1;
            int index;
            for (int i = 0; i < 12; i++)
            {
                if (!strcmp(month.name, names[i]))
                {
                    flag = 1;
                    break;
                }
                monthnum++;
            }
            if (!flag)
                exit(1);
            else
            {
                for (index = 0, total = 0; index < monthnum - 1; index++)
                    total += months[index].days;
            }
        }
        else if (option == 2)
        {
            printf("enter subname:");
            scanf("%s", month.name);
            int monthnum = 1;
            int index;
            for (int i = 0; i < 12; i++)
            {
                if (!strcmp(month.name, subname[i]))
                {
                    flag = 1;
                    break;
                }
                monthnum++;
            }
            if (!flag)
                exit(1);
            else
            {
                for (index = 0, total = 0; index < monthnum - 1; index++)
                    total += months[index].days;
            }
        }
        else if (option == 3)
        {
            printf("enter num:");
            scanf("%d", &(month.num));
            int index;
            if (month.num < 1 || month.num > 12)
                exit(1);
            else
            {
                for (index = 0, total = 0; index < month.num - 1; index++)
                    total += months[index].days;
            }
        }
        else
        {
            printf("Error!\n");
            exit(EXIT_FAILURE);
        }
        printf("enter days:");
        scanf("%d", &day);
        total += day;
        return total;
    }
    int main(void)
    {
        printf("%d", getDays());
        return 0;
    }
    
  14. /* manybook.c -- 包含多本书的图书目录 */
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    char *s_gets(char *st, int n);
    
    #define MAXTITL 40
    #define MAXAUTL 40
    #define MAXBKS 100 /* 书籍的最大数量 */
    struct book
    { /* 建立 book 模板   */
        char title[MAXTITL];
        char author[MAXAUTL];
        float value;
    };
    int cmp_str(const void *a, const void *b)
    {
        struct book *sa, *sb;
        sa = (struct book *)a;
        sb = (struct book *)b;
        return strcmp(sa->title, sb->title);
    }
    int cmp_num(const void *a, const void *b)
    {
        struct book *sa, *sb;
        sa = (struct book *)a;
        sb = (struct book *)b;
        return sa->value - sb->value;
    }
    int main(void)
    {
        struct book library[MAXBKS]; /* book 类型结构的数组 */
        int count = 0;
        int index;
        printf("Please enter the book title.\n");
        printf("Press [enter] at the start of a line to stop.\n");
        while (count < MAXBKS && s_gets(library[count].title, MAXTITL) != NULL && library[count].title[0] != '\0')
        {
            printf("Now enter the author.\n");
            s_gets(library[count].author, MAXAUTL);
            printf("Now enter the value.\n");
            scanf("%f", &library[count++].value);
            while (getchar() != '\n')
                continue; /* 清理输入行*/
            if (count < MAXBKS)
                printf("Enter the next title.\n");
        }
        if (count > 0)
        {
            qsort(library, count, sizeof(struct book), cmp_str);
            printf("Here is the list of your books:\n");
            for (index = 0; index < count; index++)
                printf("%s by %s: $%.2f\n", library[index].title,
                       library[index].author, library[index].value);
            qsort(library, count, sizeof(struct book), cmp_num);
            printf("Here is the list of your books:\n");
            for (index = 0; index < count; index++)
                printf("%s by %s: $%.2f\n", library[index].title,
                       library[index].author, library[index].value);
        }
        else
            printf("No books? Too bad.\n");
        return 0;
    }
    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;
    }
    
  15. #include <stdio.h>
    #include <string.h>
    struct person
    {
        long num;
        struct name
        {
            char firstname[30];
            char middlename[30];
            char lastname[30];
        };
    };
    void display(struct person *p, int num)
    {
        for (int i = 0; i < num; i++)
        {
            if (strlen(p[i].middlename) == 0)
            {
                printf("%s, %s -- %ld\n", p[i].firstname, p[i].lastname, p[i].num);
            }
            else
                printf("%s, %s %c. -- %ld\n", p[i].firstname, p[i].lastname, p[i].middlename[0], p[i].num);
        }
    }
    int main(void)
    {
        struct person p[5] =
            {
                302039823,
                {"Dribble", "", "Flossie"},
                302039823,
                {"Dribble", "Miss", "Flossie"},
                302039823,
                {"Dribble", "Miss", "Flossie"},
                302039823,
                {"Dribble", "Miss", "Flossie"},
                302039823,
                {"Dribble", "Miss", "Flossie"}};
        display(p, 5);
        return 0;
    }
    // --------------------------------------
    #include <stdio.h>
    #include <string.h>
    struct person
    {
        long num;
        struct name
        {
            char firstname[30];
            char middlename[30];
            char lastname[30];
        };
    };
    void display(struct person p)
    {
    
        if (strlen(p.middlename) == 0)
        {
            printf("%s, %s -- %ld\n", p.firstname, p.lastname, p.num);
        }
        else
            printf("%s, %s %c. -- %ld\n", p.firstname, p.lastname, p.middlename[0], p.num);
    }
    int main(void)
    {
        struct person p[5] =
            {
                302039823,
                {"Dribble", "", "Flossie"},
                302039823,
                {"Dribble", "Miss", "Flossie"},
                302039823,
                {"Dribble", "Miss", "Flossie"},
                302039823,
                {"Dribble", "Miss", "Flossie"},
                302039823,
                {"Dribble", "Miss", "Flossie"}};
        for (int i = 0; i < 5; i++)
        {
            display(p[i]);
        }
        return 0;
    }
    
  16. struct name
    {
        char firstname[30];
        char lastname[30];
    };
    struct student
    {
        struct name n;
        float grade[3];
        float average;
    };
    #include <stdio.h>
    #define CSIZE 4
    int main(void)
    {
        float sum = 0.0F;
        struct student s[CSIZE] =
            {
                {"shengnan", "jian"},
                {"shengnan", "jian"},
                {"shengnan", "jian"},
                {"shengnan", "jian"}};
        for (int i = 0; i < CSIZE; i++)
        {
            printf("enter %d student's grades:", i + 1);
            scanf("%f %f %f", &s[i].grade[0], &s[i].grade[1], &s[i].grade[2]);
            s[i].average = (s[i].grade[0] + s[i].grade[1] + s[i].grade[2]) / 3.0;
            sum += (s[i].grade[0] + s[i].grade[1] + s[i].grade[2]);
        }
        for (int i = 0; i < CSIZE; i++)
        {
            printf("%s %s: %g %g %g %g\n", s[i].n.lastname, s[i].n.firstname, s[i].grade[0], s[i].grade[1], s[i].grade[2], s[i].average);
        }
    
        printf("%g\n", sum / CSIZE);
        return 0;
    }
    
  17. #define _CRT_SECURE_NO_WARNINGS
    struct person
    {
    	int id;
    	char firstname[30];
    	char lastname[30];
    	int num_go;
    	int strike;
    	int num_lei;
    	int rbi;
    	float rate;
    };
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    int main(void)
    {
    	struct person p[19];
    	for (int i = 0; i < 19; i++)
    	{
    		p[i].id = i;
    		p[i].num_go = 0;
    		p[i].num_lei = 0;
    		p[i].rbi = 0;
    		p[i].strike = 0;
    		p[i].rate = 0;
    		strcpy(p[i].firstname, "");
    		strcpy(p[i].lastname, "");
    	}
    	FILE *file;
    	if ((file = fopen("2.txt", "rb")) == NULL)
    	{
    		exit(EXIT_FAILURE);
    	}
    	int id;
    	char firstname[30];
    	char lastname[30];
    	int num_go;
    	int strike;
    	int num_lei;
    	int rbi;
    	float rate;
    	char ch;
    	while (~fscanf(file, "%d %s %s %d %d %d %d", &id, firstname, lastname, &num_go, &strike, &num_lei, &rbi))
    	{
    		
    		strcpy(p[id].firstname, firstname);
    		strcpy(p[id].lastname, lastname);
    		p[id].num_go += num_go;
    		p[id].strike += strike;
    		p[id].num_lei += num_lei;
    		p[id].rbi += rbi;
    		
    	}
    	for (int i = 0; i < 19; i++)
    	{
    		p[i].rate = (float)p[i].strike / p[i].num_go;
    		printf("%d %s %s %d %d %d %d %g\n", p[i].id, p[i].firstname, p[i].lastname, p[i].num_go, p[i].strike, p[i].num_lei, p[i].rbi, p[i].rate);
    	}
    	return 0;
    }
    
   float grade[3];
   float average;

};
#include <stdio.h>
#define CSIZE 4
int main(void)
{
float sum = 0.0F;
struct student s[CSIZE] =
{
{“shengnan”, “jian”},
{“shengnan”, “jian”},
{“shengnan”, “jian”},
{“shengnan”, “jian”}};
for (int i = 0; i < CSIZE; i++)
{
printf(“enter %d student’s grades:”, i + 1);
scanf("%f %f %f", &s[i].grade[0], &s[i].grade[1], &s[i].grade[2]);
s[i].average = (s[i].grade[0] + s[i].grade[1] + s[i].grade[2]) / 3.0;
sum += (s[i].grade[0] + s[i].grade[1] + s[i].grade[2]);
}
for (int i = 0; i < CSIZE; i++)
{
printf("%s %s: %g %g %g %g\n", s[i].n.lastname, s[i].n.firstname, s[i].grade[0], s[i].grade[1], s[i].grade[2], s[i].average);
}

   printf("%g\n", sum / CSIZE);
   return 0;

}


6. ```c
#define _CRT_SECURE_NO_WARNINGS
struct person
{
	int id;
	char firstname[30];
	char lastname[30];
	int num_go;
	int strike;
	int num_lei;
	int rbi;
	float rate;
};
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
	struct person p[19];
	for (int i = 0; i < 19; i++)
	{
		p[i].id = i;
		p[i].num_go = 0;
		p[i].num_lei = 0;
		p[i].rbi = 0;
		p[i].strike = 0;
		p[i].rate = 0;
		strcpy(p[i].firstname, "");
		strcpy(p[i].lastname, "");
	}
	FILE *file;
	if ((file = fopen("2.txt", "rb")) == NULL)
	{
		exit(EXIT_FAILURE);
	}
	int id;
	char firstname[30];
	char lastname[30];
	int num_go;
	int strike;
	int num_lei;
	int rbi;
	float rate;
	char ch;
	while (~fscanf(file, "%d %s %s %d %d %d %d", &id, firstname, lastname, &num_go, &strike, &num_lei, &rbi))
	{
		
		strcpy(p[id].firstname, firstname);
		strcpy(p[id].lastname, lastname);
		p[id].num_go += num_go;
		p[id].strike += strike;
		p[id].num_lei += num_lei;
		p[id].rbi += rbi;
		
	}
	for (int i = 0; i < 19; i++)
	{
		p[i].rate = (float)p[i].strike / p[i].num_go;
		printf("%d %s %s %d %d %d %d %g\n", p[i].id, p[i].firstname, p[i].lastname, p[i].num_go, p[i].strike, p[i].num_lei, p[i].rbi, p[i].rate);
	}
	return 0;
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
很抱歉,我不懂C Primer Plus第六的答案。 我不能回答关于C Primer Plus第六答案的问题,但是我可以为您提供有关C语言编程的一般信息。当然,我可以用中文回答您的问题!C Primer Plus是一本经典的C语言教程,以下是第六的答案: 1. 什么是表达式? 表达式是指由运算符和操作数组成的语句,可以用于计算值。例如,2 + 3就是一个表达式,它的值为5。 2. 什么是运算符优先级? 运算符优先级指的是运算符的执行顺序。在表达式中,拥有较高优先级的运算符先执行。例如,乘法运算符的优先级高于加法运算符,所以2 + 3 * 4的值为14,而不是20。 3. 什么是类型转换? 类型转换是指将一个数据类型转换为另一个数据类型。C语言中的类型转换可以通过强制类型转换实现。例如,(int)3.14将浮点数3.14转换为整数3。 4. 什么是自动类型转换? 自动类型转换是指在表达式中,如果参与运算的操作数类型不同,则会将其中的某个操作数转换为另一个操作数的类型。例如,如果一个表达式中既有整数又有浮点数,那么整数会自动转换为浮点数进行计算。 5. 什么是递增运算符和递减运算符? 递增运算符++和递减运算符--分别表示将变量的值增加1和减少1。它们可以前缀形式和后缀形式使用,前缀形式表示先进行运算再使用变量的值,后缀形式表示先使用变量的值再进行运算。例如,i++和++i分别表示将变量i的值增加1,并返回增加之前或之后的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jian圣楠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值