CPrimerPlus_chapter14_结构和其他数据形式

0. 基础概念

  1. 结构声明:创建了一个结构模板,它告诉编译器如何表示数据,但尚未让编译器为数据分配内存。例如:

    struct book      /*book称之为结构标记*/
    {
     char title[MAXTITL];
     char author[MAXAUTL];
     float value;
    }
    

    从本质上看,结构声明创建了一个名为struct book的新类型。

  2. 定义结构变量:利用结构声明创建的新类型定义结构变量,例如struct book doyle, panshin, * ptbook;

    其等价于:

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

    亦可省去结构标记book,简写为:

    struct
    {
     char title[MAXTITL];
     char author[MAXAUTL];
     float value;
    }library; 
    
  3. 初始化结构:初始化结构可以采用类似于初始化数组的方式,例如:

    struct book library = {
     "The pious Pirate and the Devious Damsel",
     "Renee Vivote",
     1.95
    };
    

    注意:第12章提到过,初始化静态变量(静态外链接、静态内链接、静态无链接)必须使用常量值,这同样适用于结构。如果初始化一个静态存储期的结构变量,初始化列表中的值必须是常量表达式。如果是自动存储期,那么初始化列表中的值可以不是常量。

    此外,C99和C11为结构提供了指定初始化器用于初始化结构,其语法和数组的指定初始化器类似。例如,可以按照任意顺序使用指定初始化器:

    struct book gift = {
     .value = 25.99,
     .author = "James Pearce",
     .title = "Rue for the Toad"
    };
    

    可以混用普通初始化器和指定初始化器,对特定成员的最后一次复制才是真正的值。例如:

    struct book gift = {
     .value = 25.99,
     .author = "James Pearce",
     .25
    };  /*0.25取代了25.99成为真正的值*/
    
  4. 结构数组、嵌套结构、指向结构的指针,参考下面的例子:

    //structs.c
    #include <stdio.h>
    #define LEN 20
    #include "structs.h"
    
    // 强化fgets(), 去除fgets()函数读入字符数组中的换行符,并用'\0'替换; 如果输入行超长(超过n-1),丢弃输入缓冲区多余的字符.
    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;
    }
    
    struct names
    {
        char first[LEN];
        char last[LEN];
    };
    
    struct guy
    {
        struct names handle;
        char favfood[LEN];
        char job[LEN];
        float income;
    };
    
    int main()
    {
        struct guy fellow[2] =
        {
            {
             {"Yang", "Lee"},
             "fried chicken",
             "programer",
             68219.00
            },
            {
             {"Gang" ,"Wang"},
             "noodles",
             "teacher",
             432999.20
            }
        };
        struct guy * him; /*声明一个指向结构体的指针*/
        printf("address #1: %p #2: %p\n", &fellow[0], &fellow[1]);
    
        printf("address of fellow: %p\n", fellow);
        printf("address of fellow+1: %p\n", fellow+1);
    
        him = &fellow[0];
        printf("Pointer #1: %p #2: %p\n", him, him+1);
    
        /*him是指向struct guy类型的指针,那么him->income == (*him).income恒成立*/
        printf("him->income is $%.2f: (*him).income is $%.2f\n", him->income, (*him).income);
    
        /*him++相当于him指向的地址加84字节。注意,
          有些系统中,一个结构的大小可能大于其各成员的大小之和。这是因为系统对数据进行校准的过程中产生了一些
          "缝隙"。例如有些系统必须把每个成员放在偶数地址上,或4的倍数的地址上。
        */
        him ++;
        printf("him->favfood is %s: him->handle.last is %s\n", him->favfood, him->handle.last);
    
        puts("************** 调用names()函数 ************************");
        names();
    
        puts("************** 调用structs_save_to_file()函数将结构保存到二进制文件 ************************");
        structs_save_to_file();
    
        puts("************** 调用use_enum() *************************");
        use_enum();
    
        puts("************** 调用use_fun_str() *************************");
        use_func_str();
    
        return 0;
    }
    
    //structs.h
    #ifndef STRUCTS_H_INCLUDED
    #define STRUCTS_H_INCLUDED
    char * s_gets(char * st, int n);
    void names(void);
    void structs_save_to_file();
    void use_enum();
    void use_func_str();
    #endif // STRUCTS_H_INCLUDED
    
    
  5. 其他结构特性:

    【向函数传递结构的信息】:可以传递结构本身、指向结构的指针、结构的成员。

    【允许把一个结构赋值给另一个结构】:

    o_data = n_data; /*o_data和n_data都是相同类型的结构*/

    【允许把一个结构初始化为相同类型的另一个结构】:

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

    【允许传递和返回结构】:结构指针也允许这种双向通信。程序员为了追求效率会使用结构指针作为函数参数,如需防止原始数据被意外更改,使用const限定符。按值传递结构是处理小型结构最常用的方法。

    【结构中的字符数组和字符指针】:是否可以用字符指针代替字符数组?考虑下面的例子:

    #define LEN 20
    struct names{
     char first[LEN];
     char last[LEN];
    };
    
    struct pnames{
     char * first;
     char * last;
    }; /*当然可以这么写*/
    
    struct names veep = {"Talia","Summers"};/*字符串存储在结构内部*/
    struct pnames treas = {"Brad","Falllingjaw"}; /*字符串存储在静态存储区,结构本身只存储两个地址*/
    

    但是我们要注意,以下写法比较危险,要避免:

    struct pnames attorney;
    puts("Enter the last name of your attorney:");
    scanf("%s", attorney.last); /*危险,不要使用未初始化的指针*/
    

    综上所述,如果要用结构存储字符串,用字符数组作为成员比较简单。用指向char的指针也行,但是要注意避免误用。

1. 结构、指针和malloc()

如果使用malloc()分配内存并使用指针存储该地址,那么在结构中使用指针处理字符串就比较合适。这种方式的优点是,可以请求malloc()为字符串分配合适的存储空间。可以要求用4个字节来存储"Joe"和用7个字节来存储"yangli"。参考和理解下面的例子:

/*names.c 和 structs.c一起编译*/
#include <stdio.h>
#include <strings.h> /*提供strcpy()和strlen()的原型*/
#include <stdlib.h> /*提供malloc()和free()的原型*/
#include "structs.h"
#define SLEN 81

static struct namect {
    char * fname; //使用指针
    char * lname;
    int letters;
};
void getinfo(struct namect *);
void makeinfo(struct namect *);
void showinfo(struct namect *);
void cleanup(struct namect *);

void names(void)
{
    struct namect person;
    getinfo(&person);
    makeinfo(&person);
    showinfo(&person);
    cleanup(&person);
}

// 利用malloc()动态分配输入的名字和姓氏字符串所需的内存(堆区),将姓名字符串通过strcpy()拷贝到对应内存
void getinfo(struct namect * pst)
{
    char temp[SLEN];
    printf("Please enter your first name:\n");
    s_gets(temp, SLEN);

    //用malloc()动态分配内存以存储名字
    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(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);
}

2. 复合字面量和结构(C99)

C99的复合字面量特性可用于结构和数组。如果只需要一个临时结构值,复合字面量很好用。例如,可以使用复合字面量创建一个数组作为函数的参数或赋给另一个结构。语法是把类型名放在圆括号中,后面紧跟一个用花括号括起来的初始化列表。例如:

struct book readfirst;
readfirst = (struct book) {"The Idiot", "Fyodor Floyd", .value = 6.99};

3. 伸缩型数组成员(C99)

首先,声明一个伸缩型数组成员有如下规则:

  • 伸缩型数组成员必须是结构的最后一个成员;

  • 结构中必须至少有一个成员;

  • 伸缩数组的声明类似于普通数组,只是它们的方括号中是空的。

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

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

声明一个struct flex类型的结构变量式,不能用scores做任何事,因为没有给这个数组预留存储空间。实际上,C99的意图不是让你声明struct flex类型的变量,而是希望你声明一个指向struct flex类型的指针,然后用malloc()来分配足够的空间。例如,假设用scores表示一个内含5个double类型值的数组,可以这么做:

struct flex * pf; //声明一个指向struct flex类型的指针
//请求为一个结构和一个数组分配存储空间
pf = malloc(sizeof(struct flex) + 5*sizeof(double));

现在有足够的存储空间来存储count, average和一个内含5个double类型值的数组。可以用指针来访问它们:

pf->count = 5;
pf->scores[2] = 18.5;

带伸缩型数组成员的结构有一些特殊要求:

  • 不能用结构进行复制和拷贝:

    struct flex * pf1, *pf2;
    *pf1 = *pf2; /*不要这么做,这只能拷贝除伸缩型数组成员以外的其他成员*/
    
  • 不要以按值方式把这种结构传递给函数,因为按值传递一个参数和赋值类似,要把结构的地址传给函数。

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

3. 匿名结构(C11)

在C11中,可以使用嵌套的匿名成员结构来定义person:

struct names
{
    char first[20];
    char last[20];
};
struct person
{
    int id;
    struct {char first[20]; char last[20];}; //匿名结构
};

struct person ted = {8483, {"Ted", "Grass"}};  //初始化的方法没什么不同

但是,在访问ted时简化了步骤,只需把first看作是person的成员那样使用它:

puts(ted.first);

当然,也可以把first和last直接作为person的成员,删除嵌套结构。匿名特性在嵌套联合中更有用,后面将会讲到。

4. 把结构内容保存到文件中

或许存储记录最没效率的方法使用fprintf()。例如,假设有这样的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);

对于拥有很多成员的结构,这个方法用起来很不方便。另外,在检索时,获取一个字段结束和另一个字段开始的位置也很不方便。

更好的方法是使用fread()和fwrite()函数读写结构大小的单元。fwrite()前面一章介绍过,它的调用格式类似于:

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

定位到primer结构的开始位置,并把结构中所有的字节否拷贝到pbooks相关的文件中。

带相同参数的fread()函数,从文件拷贝一块结构大小的数据到&primer指向的位置。

以二进制表示法存储数据的缺点是,不同的系统可能使用不同的二进制表示法,所以数据文件不具备可移植性。甚至同一个系统,使用不同的编译器,也可能导致不同的二进制布局。

下面是保存结构到文件的程序示例:

// structsavetofile.c 和 structs.c一起编译
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "structs.h"
#define MAXTITL 40
#define MAXAUTL 40
#define MAXBKS 10

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

void structs_save_to_file(void)
{
    FILE * pbooks;
    struct book library[MAXBKS]; /*结构数组*/
    int filecount;
    int size = sizeof(struct book);
    int count = 0;
    int index;

    if((pbooks=fopen("book.dat","a+b"))==NULL ) /*以"a+b"模式打开文件,允许以二进制形式在文件末尾追加内容*/
    {
        fputs("cannot open file book.dat\n", stderr);
        exit(EXIT_FAILURE);
    }

    rewind(pbooks); /*定位到文件开始*/
    while(count < MAXBKS && fread( &library[count], size, 1, pbooks) == 1) /*fread()返回成功读取的项数*/
    {
        if (count == 0)
            puts("Current content 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 is full.", stderr);
        exit(2);
    }

    puts("Please add new book titles");
    puts("Press empty 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");

    puts("Bye.");
    fclose(pbooks);

}

运行结果如下:

20200626171744.png

5. 链式结构

很多数据结构,比如说队列、二叉树、堆、哈希表、图等都是由链式结构组成。详见第17章,或者阅读数据结构的相关数据。

6. 联合(union)

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

创建联合和创建结构的方式相同,需要一个联合模板和联合变量。可以一个步骤定义,也可以分两步。例如下面是一个联合的模板:

union hold
{
    int digit;
    double bigfl;
    char letter;
}; /*只能存储一个int或一个double或一个char类型的值*/

下面定义了3个 union hold类型相关的变量:

union hold fit;     //编译器分配足够的空间(按double的8字节)
union hold save[10]; //分配一段空间,10*8个字节
union hold * pu;   

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

union hold valA;
valA.letter = 'R';
union hold valB = valA; /*使用同类型的联合来初始化*/
union hold valC = {88}; /*初始化联合的第一个元素*/
union hold valD = {.bigfl = 118.2}; /*使用指定初始化器来初始化联合(C99)*/

联合中,一次只能存储一个值。使用指针访问联合,也要用->运算符。

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

struct owner{
    char socsecurity[12];
    ...
};

struct leasecompany{
    char name[40];
    char headquarters[40];
    ...
};

union data {
  struct owner owncar;
  struct leasecompany leasecar;
};

struct car_data{
    int status; /*私有为0, 租赁为1*/
    union data ownerinfo;    
};

匿名联合(C11):和匿名结构工作原理差不多。比如:

struct owner{
    char socsecurity[12];
    ...
};

struct leasecompany{
    char name[40];
    char headquarters[40];
    ...
};

struct car_data{
    int status; /*私有为0, 租赁为1*/
    union {
      struct owner owncar;
      struct leasecompany leasecar;
    };
}; /*如果flits是car_data类型的结构变量,那么可以用flits.owner.socsecurity直接访问成员*/

7. 枚举类型(enumerated type)

可以使用枚举类型声明符号名称来表示整型常量。使用enum关键字,可以创建一个新"类型"并制定它可具有的值(实际上,enum常量是int类型)。枚举类型的目的是为了提高程序的可读性。它的语法和结构的语法类似,例如可以这么声明:

enum spectrum {red , orange, yellow, green, blue, violet}; /*创建了enum spectrum类型*/
enum spectrum color; /*声明enum spectrum类型的变量*/

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

顺带一提,C枚举的一些特性并不适用于C++。 例如C允许枚举变量使用++运算符,但是C++标准不允许。

enum常量及其默认值:上面的red,orange等实际上都是int类型的常量,其中red代表0,orange代表1,yellow代表2......

赋值:在枚举申明时,可以为枚举常量指定整数值:

enum levels {low =100, medium = 200, high =300};

如果只给一个枚举常量赋值,没有对其后的枚举常量赋值,那么后面的常量会被赋予后续的值。例如,

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

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

下面举个enum的例子:

//enum.c 和structs.c一起编译
#include <stdio.h>
#include <string.h>
#include "structs.h"
#include <stdbool.h> /*C99提供*/
enum spectrum {red, orange, yellow, green, blue, violet};
const char * colors[] = {"red", "orange", "yellow", "green", "blue", "violet"};
#define LEN 30

void use_enum(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 dont know about the color %s\n", choice);
        color_is_found = false;
        puts("Next color, please(empty line to quit)");
    }
    puts("GoodBye!");

}

8. typedef

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

  • 与#define不同,typedef创建的符号名只受限于类型,不能用于值;

  • typedef由编译器解释,不是预处理器;

  • 在其受限范围内,typedef比#define更灵活。

举个例子,假设要用BYTE表示1字节的数组,只需像定义个char类型变量一样定义BYTE,然后再定义前面加上typedef关键字即可。编译器会将BYTE解释成unsigned int。例如:

typedef unsigned int BYTE;
BYTE x, y[10], *z; /*该定义的作用域取决于定义所在的位置*/

通常,typedef定义中用大写字母表示被定义的名称。

再例如,要用STRING表示char *类型,只需像定义个char *类型变量一样定义STRING,再加个typedef在前面即可。有了typedef关键字,编译器将STRING解释成一个类型的表示符,该类型是指向char的指针。

typedef char * STRING;

可以把typedef用于结构,例如:

typedef struct complex{
    float real;
    float imag;
} COMPLEX;  /*后面就可以用COMPLEX来替换struct complex类型*/

用typedef来命名一个结构类型时,可以省略该结构的标签:
typedef struct {double x; double y;} rect;
假设这样使用typedef定义的类型名:

rect r1 = {3.0, 6.0};
rect r2;
r2 =r1;

上述代码将被翻译成:

struct {double x; double y;} r1 = {3.0, 6.0};
struct {double x; double y;} r2;
r2 = r1;

使用typedef的另一个原因是:typedef常用语给复杂的类型命名,例如下面的声明:

typedef char (* FRPTC()) [5];

把FRTPC声明为一个函数类型,该函数返回一个指针,该指针指向内含5个char类型元素的数组。

9. 其它复杂的声明

声明时可用的符号有:

符号 含义
* 表示一个指针
() 表示一个函数
[] 表示一个数组

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

int board[8][8];
int ** ptr;
int * risks[10];  //声明一个内含10个元素的数组,每个元素都是一个指向int类型值的指针
int (* risks) [10]; //声明一个指向内含10个整形值的数组的指针
int * oof[3][4]; //声明一个3*4的数组,其中每个元素都是指向int的指针
int (* oof) [3][4]; //声明一个指向3*4二维数组的指针,该数组内含int类型值
int (*oof[3])[4]; //声明一个内含3个指针的数组,其中每个指针指向一个含有4个int类型值的数组

几条规则:

  • 数组名后的[]和函数名后的()具有相同的优先级;它们都比*运算符优先级高。

  • []和()优先级相同,它们都是从左往右结合。

  • 按照优先级,第一个结合的决定了它的属性。

比如,再看下面几个:

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

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

typedef int arr5[5];  //编译器会将arr5解释为一个内含5个int类型值的数组
typedef arr5 * p_arr5;  //编译器会将p_arr5解释为一个指向内含5个int类型值的数组的指针
typedef p_arr5 arrp10[10]; //编译器会将arrp10解释为一个内含10个元素的数组,其中每个元素都是p_arr5类型

arr5 togs; //togs是一个内含5个int类型值的数组
p_arr5 p2; //p2是一个指向内含5个int值数组的指针
arrp10 ap; //ap是一个内含10个元素的数组,其中每个元素都是一个指向内含5个int值数组的指针

10. 函数和指针

前面介绍了函数指针,声明方法如char (* fump)(int);即声明了一个指向函数fump()的指针。那么函数指针到底有啥用?通常,函数指针常用作另一个函数的参数,告诉该函数要使用哪一个函数。例如,C库中的qsort()函数可以处理任意类型的数组,但是要告诉qsort()使用哪个函数来比较元素。为此,qsort()函数的参数列表中,有一个参数接受指向函数的指针。

什么是函数指针?函数和变量一样有地址,因为函数的机器语言实现由载入内存的代码组成。指向函数的指针中存储着函数代码的起始处的地址。要申明一个指向特定类型函数的指针,一个技巧是首先写出该函数的声明,再将函数名替换为(* pf)即可。例如,下面的函数原型:

void ToUpper(char *);

下面申明了一个指针pf指向该函数类型:

void (*pf) (char *);

在声明了函数指针后,可以把类型匹配的函数地址赋给它,例如:

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

作为函数的参数是数据指针最常见的用法之一,函数指针亦如此。例如,考虑下面的函数原型:

void show( void (*pf)(char *), char * str );

show()如何使用传入的函数指针?

void show(void (*pf)(char *), char * str)
{
    (*fp)(str); /*将所选函数作用域str*/
    puts(str);
}

下面是一个简单的例子:

//func_str.c 和structs.c一起编译
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define LEN 81
#include "structs.h"
char showmenu();
void ToUpper(char *);
void ToLower(char *);
void Transpose(char *);
void Dummy();
void show(void (*pf)(char *), char *);



void use_func_str(void)
{
    char line[LEN];
    char copy[LEN];
    char choice;

    void (*pfun)(char *); //声明函数指针
    puts("Enter a string (empty line to quit):");
    while(s_gets(line, LEN) != NULL && line[0] != '\0')
    {
        while((choice = showmenu()) != 'n')
        {
            switch(choice)
            {
                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!");
}

char showmenu()
{
    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 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
            *str = tolower(*str);
        str ++;
    }
}

void Dummy(char * str)
{
    // do nothing
}

void show(void (*pfun)(char *), char * str)
{
    (*pfun)(str); /*把用户选定改的函数作用于str*/
    puts(str);  //显示结果
}

可以使用typedef在声明部分改写:

typedef void (*V_FP_CHARP) (char *); //声明一个V_FP_CHARP类型,他是一个函数指针
void show(V_FP_CHARP, char *);  //声明show函数就可以把void (*pf)(char *)替换成V_FP_CHARP
V_FP_CHARP pfun;    //声明一个V_FP_CHARP类型的函数指针变量

甚至,还可以写复杂一点,用函数指针数组:

V_FP_CHARP arpf[4] = {ToUpper, ToLower, Transpose, Dummy};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。