C语言中的函数与指针

我们可以声明一个指向函数的指针。这个复杂的玩意儿到底有何用处?

通常,函数指针常用作另一个函数的参数,告诉该函数要使用哪一个函数。例如,排序数组涉及比较两个元素,以确定先后。如果元素是数字,可以使用>运算符;如果元素是字符串或结构,就要调用函数进行比较。C库中的qsort()函数可以处理任意类型的数组,但是要告诉qsort()使用哪个函数来比较元素。为此,qsort()函数的参数列表中,有一个参数接受指向函数的指针。然后,qsort()函数使用该函数提供的方案进行排序,无论这个数组中的元素是整数、字符串还是结构。 我们来进一步研究函数指针。首先,什么是函数指针?假设有一个指向int类型变量的指针,该指针存储着这个int类型变量存储在内存位置的地址。同样,函数也有地址,因为函数的机器语言实现由载入内存的代码组成。指向函数的指针中存储着函数代码的起始处的地址。 其次,声明一个数据指针时,必须声明指针所指向的数据类型。声明一个函数指针时,必须声明指针指向的函数类型。为了指明函数类型,要指明函数签名,即函数的返回类型和形参类型。例如,考虑下面的函数原型:

void ToUpper(char *);   // convert string to uppercase

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

void (*pf)(char *);     // pf a pointer-to-function

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

void *pf(char *);     // pf a function that returns a pointer

提示:

要声明一个指向特定类型函数的指针,可以先声明一个该类型的函数,然后把函数名替换成(*pf)形式的表达式。然后,pf就成为指向该类型函数的指针。


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


void ToUpper(char *);
void ToLower(char *);
int round(double);
void (*pf)(char *);
pf = ToUpper;        // valid, ToUpper is address of the function
pf = ToLower;        // valid, ToLower is address of the function
pf = round;          // invalid, round is the wrong type of function
pf = ToLower();      // invalid, ToLower() is not an address

最后一条语句是无效的,不仅因为ToLower()不是地址,而且ToLower()的返回类型是void,它没有返回值,不能在赋值语句中进行赋值。注意,指针pf可以指向其他带char*类型参数、返回类型是void的函数,不能指向其他类型的函数。既然可以用数据指针访问数据,也可以用函数指针访问函数。 奇怪的是,有两种逻辑上不一致的语法可以这样做,下面解释:

void ToUpper(char *);
void ToLower(char *);
void (*pf)(char *);
char mis[] = "Nina Metier";
pf = ToUpper;
(*pf)(mis);     // apply ToUpper to mis (syntax 1)
pf = ToLower;
pf(mis);        // apply ToLower to mis (syntax 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。fp形参是一个函数指针,str是一个数据指针。更具体地说,fp指向的函数接受char * 类型的参数,其返回类型为void;str指向一个char类型的值。因此,假设有上面的声明,可以这样调用函数:

show(ToLower, mis);  /* show() uses ToLower() function: fp = ToLower   */
show(pf, mis);       /* show() uses function pointed to by pf: fp = pf */

show()如何使用传入的函数指针?是用fp()语法还是(*fp)()语法调用函数:

void show(void (* fp)(char *), char * str)
{
    (*fp)(str); /* apply chosen function to str */
    puts(str);  /* display result               */
}

例如,这里的show()首先用fp指向的函数转换str,然后显示转换后的字符串。顺带一提,把带返回值的函数作为参数传递给另一个函数有两种不同的方法。例如,考虑下面的语句:

function1(sqrt);      /* passes address of sqrt function      */
function2(sqrt(4.0)); /* passes return value of sqrt function */

第1条语句传递的是sqrt()函数的地址,假设function1()在其代码中会使用该函数。第2条语句先调用sqrt()函数,然后求值,并把返回值(该例中是2.0)传递给function2()。程序func_ptr中的程序通过show()函数来演示这些要点,该函数以各种转换函数作为参数。该程序也演示了一些处理菜单的有用技巧。

Listing 14.16 The funcptr.c Program

// func_ptr.c -- uses function pointers
#include 
#include 
#include 
#define LEN 81
char * s_gets(char * st, int n);
char showmenu(void);
void eatline(void);     // read through end of line
void show(void (* fp)(char *), char * str);
void ToUpper(char *);   // convert string to uppercase
void ToLower(char *);   // convert string to uppercase
void Transpose(char *); // transpose cases
void Dummy(char *);     // leave string unaltered
​
int main(void)
{
    char line[LEN];
    char copy[LEN];
    char choice;
    void (*pfun)(char *); // points a function having a
                          // char * argument and no
                          // return value
    puts("Enter a string (empty line to quit):");
    while (s_gets(line, LEN) != NULL && line[0] != '0')
    {
        while ((choice = showmenu()) != 'n')
        {
            switch (choice   )  // switch sets pointer
            {
                case 'u' : pfun = ToUpper;   break;
                case 'l' : pfun = ToLower;   break;
                case 't' : pfun = Transpose; break;
                case 'o' : pfun = Dummy;     break;
            }
            strcpy(copy, line);// make copy for show()
            show(pfun, copy);  // use selected function
        }
        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();    // get response
    ans = tolower(ans); // convert to lowercase
    eatline();          // dispose of rest of line
    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)
{
    // leaves string unchanged
}
​
void show(void (* fp)(char *), char * str)
{
    (*fp)(str); // apply chosen function to str
    puts(str);  // display result
}
​
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');   // look for newline
        if (find)                  // if the address is not NULL,
            *find = '0';          // place a null character there
        else
            while (getchar() != 'n')
                continue;          // dispose of rest of line
    }
    return ret_val;
}
​

下面是该程序的输出示例:

Enter a string (empty line to quit): Does C make you feel loopy? Enter menu choice: u) uppercase l) lowercase t) transposed case o) original case n) next string t dOES c MAKE YOU FEEL LOOPY? Enter menu choice: u) uppercase l) lowercase t) transposed case o) original case n) next string l does c make you feel loopy? Enter menu choice: u) uppercase l) lowercase t) transposed case o) original case n) next string n Enter a string (empty line to quit):

Bye!

注意,ToUpper()、ToLower()、Transpose()和Dummy()函数的类型都相同,所以这4个函数都可以赋给pfun指针。该程序把pfun作为show()的参数,但是也可以直接把这4个函数中的任一个函数名作为参数,如show(Transpose, copy)。这种情况下,可以使用typedef。例如,该程序中可以这样写:

typedef void (*V_FP_CHARP)(char *);
void show (V_FP_CHARP fp, char *);
V_FP_CHARP pfun;

如果还想更复杂一些,可以声明并初始化一个函数指针的数组::

V_FP_CHARP arpf[4] = {ToUpper, ToLower, Transpose, Dummy};

然后把showmenu()函数的返回类型改为int,如果用户输入u,则返回0;如果用户输入l,则返回2;如果用户输入t,则返回2,以此类推。可以把程序中的switch语句替换成下面的while循环:

index = showmenu();
while (index >= 0 && index <= 3)
{
    strcpy(copy, line);       /* make copy for show()  */
    show(arpf[index], copy);  /* use selected function */
    index = showmenu();
}

虽然没有函数数组,但是可以有函数指针数组。以上介绍了使用函数名的4种方法:定义函数、声明函数、调用函数和作为指针。下图进行了总结。


C语言中的函数与指针

Uses for a function name.

至于如何处理菜单,showmenu()函数给出了几种技巧。首先,下面的代码:

ans = getchar();    // get response
ans = tolower(ans); // convert to lowercase

ans = tolower(getchar());

演示了转换用户输入的两种方法。这两种方法都可以把用户输入的字符转换为一种大小写形式,这样就不用检测用户输入的是'u'还是'U',等等。 eatline()函数丢弃输入行中的剩余字符,在处理这两种情况时很有用。第一,用户为了输入一个选择,输入一个字符,然后按下Enter键,将产生一个换行符。如果不处理这个换行符,它将成为下一次读取的第1个字符。第二,假设用户输入的是整个单词uppercase,而不是一个字母u。如果没有eatline()函数,程序会把uppercase中的字符作为用户的响应依次读取。有了eatline(),程序会读取u字符并丢弃输入行中剩余的字符。 其次,showmenu()函数的设计意图是,只给程序返回正确的选项。为完成这项任务,程序使用了string.h头文件中的标准库函数strchr():

while (strchr("ulton", ans) == NULL)

该函数在字符串"ulton"中查找字符ans首次出现的位置,并返回一个指向该字符的指针。如果没有找到该字符,则返回空指针。因此,上面的while循环头可以用下面的while循环头代替,但是上面的用起来更方便:

while (ans != 'u' && ans != 'l' && ans != 't' && ans != 'o' && ans != 'n')

待检查的项越多,使用strchr()就越方便。

展开阅读全文

页面更新:2024-03-19

标签:指针   函数   数组   表达式   字符串   语句   字符   元素   形式   声明   参数   语言   类型   地址   程序

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top