文档章节

第14章 结构和其他数据形式 14.14 函数和指针

idreamo
 idreamo
发布于 2017/03/30 05:15
字数 2339
阅读 18
收藏 1

典型用法是,一个函数的指针可以作为另一个函数的参数,告诉第二个函数使用哪一个函数。例如,对一个数组进行排序,涉及到比较两个元素以决定 哪个元素放在前面。如果元素是数字,可以使用>运算符。更普遍的是元素可能是一个字符串或是结构,C库里的qsort()函数是对任何类型的数组都适用的,只要告诉它用哪个函数来比较元素。为此,它接受一个指向函数的指针来作为参数。然后,无论数组元素的类型是整数、字符串还是结构,qsort()都使用这个函数对元素进行排序。

函数的指针是什么意思 ?假定一个指针指向一个int变量,它保存着这个int变量在内存中存储的地址。同样,函数也有地址,这是因为函数的机器语言实现是由载入到内存的代码组成。指向函数的指针中,保存着函数代码起始 处的地址。

其次,当声明一个数据指针时,必须要声明它指向的数据的类型。当声明一个函数指针时,必须要声明它指向的函数类型。要指定函数类型,就要指出函数的返回类型以及函数参量类型。例如,考虑以下原型:

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

函数ToUpper()的类型是“具有char*类型的参量,返回类型是void的函数”。要声明指向这种类型的函数的指针pf,可以这样做:

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

这意味着pf是一个指向函数的指针。这就使得(*pf)是一个函数,并使(char*)作为该函数的参量列表,void作为其返回类型。可能创建这个声明最简单的方法是注意到它用(*pf)来代替函数名ToUpper。因此,如果想要声明一个指向特定类型函数的指针,可以声明一个这种特定类型的函数,然后用一个(*pf)形式的表达式来替代函数名,以创建一个函数指针声明。由于有运算符优先级的规则,第一个圆括号是必须的。省略掉圆括号会导致完全不同的情况:

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

有了函数指针以后,可以把适当类型的函数的地址赋给它。在这种场合中,函数名可以用来表示函数的地址:

void ToUpper(char *);
void ToLower(char *);
int round(double);
void (*pf)(char*);
pf=ToUpper;  //合法,Toupper是函数Toupper()的地址
pf=ToLower;  //合法,ToLower是函数ToLower()的地址
pf=round;    //无效,round是错误类型的函数
pf=ToLower();  //无效,ToLower()不是地址 

最后一种赋值方法也是不正确的,因为不能在一个赋值的语句中使用一个void类型的函数。注意,指针pf可以指向任何一个使用char*参数并且返回类型为void的函数,而不能指向具有其他特性的函数。

正像可以使用一个数据指针来访问数据一样,也可以使用函数指针来访问函数。。奇怪的是有两个逻辑上不一致的语法规则来实现这样的操作,请看下面的举例说明:

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)

第一种方法:因为pf 指向ToUpper函数,*pf就是ToUpper函数,因此表达式(*pf)(mis)与ToUpper(mis)一样。从ToUpper和(*pf)的声明中就能看出ToUpper和(*pf)是等价的

第二种方法:因为函数名是一个指针 ,可以互换地使用函数名和指针。因此pf(mis)与ToLower(mis)一样。从pf的赋值语句就能提出pf和ToLower是等价的

正如数据指针最常风的用法是作为函数的参数一们,函数指针最常见的用法也是作为函数的参数。例如:

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

参量fp是一个函数指针,str是一个数据指针。到时候具体一点,fp指向接受一个char*参量且返回类型为void的函数,str指向一个char值。因此,给定前面的声明,可以使用像下面这样的函数调用:

show(ToUpper,mis);  /*show()使用ToLower()函数:fp=ToLower */

show(pf,mis);  /*show()使用由pf指向的函数:fp=pf */

show()如何使用传递过来的指针呢?它使用语法fp()或(*fp)()来调用函数:

void show(void(*fp)(char*),char *str)
{
    (*fp)(str);  /*把所选函数作用于str*/
    puts(str);  /*显示结果*/
}

这里show()首先把fp指向的函数作用于字符串str来转换str,然后显示转换后的字符串。

顺便提一句,带有返回值 的函数能以两种不同的方式作为其他函数的参数。

fun1(sprt);  /*传递sprt函数的地址*/

fun2(sqrt(4.0)); /*传递sprt函数的返回值*/

为了说明基本概念,程序清单14.16中的程序使用一个以各种各样的转换函数作为参数的show()函数。该程序清单也说明了一些处理菜单的有用技术。

程序清单14.16

#include <stdio.h>
#include <string.h>
#include <ctype.h>

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[81];
    char copy[81];
    char choice;
    void(*pfun)(char*);

    puts("Enter a string (empty line to quit): ");
    while(gets(line)!=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(pfun,copy);
        }
        puts("Enter a string (empty line to quit): ");
    }
    puts("Bye!");
    return 0;
}
char showmenu(void)
{
    char ans;
    puts("Enter munu 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);
    puts(str);
}

注意,函数ToUpper()、ToLower()、Transpose()和Dummy()都是相同类型的,因此4个函数 都 可以赋值给指针pfun。这个程序用pfun作为show的参数,但是也可以直接将4个函数名中的任何一个作为参数,就像show(Transpose,copy)一样。

在这种情况下您可以使用typedef。例如,示例程序还可以这样做:

typedef void(*V_FP_CHAR)(char  *);

void show(V_FP_CHAR fp,char*);

V_FP_CHAR pfun;

如果您具有探索精神 ,您可以声明并初始化一个这类指针的数组

V_FP_CHAR arpf[4] = {Toupper,ToLower,Transpose,Dummy};

然后修改函数showmune(),使它是int类型的,并且在用户键入u时返回0,键入l时返回1,键入t时返回2,等等。您就可以用下面的语句代替包含swith语句的循环:

index=showmenu();
while(index>=0 && index<=3)
{
    strcpy(copy,line);      //为show制作一份拷贝
    show(arpf[index],copy); //使用用户选择的函数  
    index=showmenu();
}

不能拥有一个函数的数组,但是可以拥有一个函数指针的数组。

现在您已经了解了使用函数名的所有4种方法:

定义函数、声明函数、调用函数以及作为指针。图14.4总结了这些用法:

原型声明中的函数名:    int comp(int x,int y);
函数调用中的函数名:    status=comp(q,r);
函数定义中的函数名:    int comp(int x,int  y)
                    {...
在赋值语句中用途指针的函数名:pfunct=comp;
用作指针参数的函数名:  slowsort(arr,n,comp);

至于处理菜单,函数showmenu()给出了几种技术:

ans=getchar();  //获取用户的响应

ans=tolower(ans);    //转换为小写

ans=tolower(getchar());

给出两种方法。这两种方法都可以将用户输入转换为一种小写形式,这样就不用即检测'u'又检测'U'等等。

函数eatline()剔除剩余的部分,这在两方面很有用。第一,要输入一个选择,用户会键入一个字母,然后按下回车键,这将产生一个换行符。如果不事先去掉这个换行符,它将作为下一个用户响应被读入。第二,假设用户键入整个单词uppercase而不是u作为响应,如果没有eatline()函数,程序会把单词uppercase的每个字符当作一个单独的响应。有了eatline(),程序只处理u,并丢弃输入行的其他部分。

其次,showmunu()函数只将正确的选择返回给程序。为了完成该任务,程序用了头文件string.h中的标准函数strchr():

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

这个函数在字符 串“ulton"中找出字符ans首次出现的位置,并返回一个指向该指针。如果没有找到这个字符,函数返回空指针。

需要检查的选择越多,使用strchr()就会越方便。

© 著作权归作者所有

idreamo
粉丝 18
博文 139
码字总数 224743
作品 0
青岛
产品经理
私信 提问
【书评:Oracle查询优化改写】第14章 结尾章

【书评:Oracle查询优化改写】第14章 结尾章 一.1 相关参考文章链接 前13章的链接参考相关连接: 【书评:Oracle查询优化改写】第一章 http://blog.itpub.net/26736162/viewspace-1652985/ 【书...

技术小胖子
2017/11/08
0
0
[备忘]《C++入门经典》各章小节

《C++入门经典》(即英文版“Beginning C++ The Complete Language”的中译版) 各章小节 第1章 基本概念 ·C++中的程序至少包含一个main()函数。 ·函数的可执行部分由包含在一对花括号中的...

leeoo
2011/12/24
122
0
C Primer Plus 第10章 数组和指针 10.6 保护数组的内容

这种技术也会带来一些新的问题。通常C传递数据的值,其原因是要保护原始数据的完整性。函数使用原始数据的一份拷贝,这样就不会意外的修改原始数据。但是,由于处理数组的函数直接操作原始数...

idreamo
2016/07/31
26
0
清华大学视频课件:基于Linux的C++(自主模式)

基于Linux的C++(自主模式) 课程简介 Linux操作系统开源的特性使得其获得越来越重要的地位,而Linux系统编程也向C++程序设计者提出了更高的要求。本课程由C/C++语言的共性与特性出发,在深入...

dragonscroll
2017/11/20
0
0
C语言基础-13-结构体

C语言的核心部分都说得七七八八了,相信大家已经对C语言的基本数据类型(charintfloat)、数组、指针都很熟悉了,今天来学习C语言中另外一种数据类型:结构体。在iOS开发中,结构体是经常用到的...

Corwien
2016/03/16
27
1

没有更多内容

加载失败,请刷新页面

加载更多

堆”和“栈

C++作为一款C语言的升级版本,具有非常强大的功能。它不但能够支持各种程序设计风格,而且还具有C语言的所有功能。我们在这里为大家介绍的是其中一个比较重要的内容,C++内存区域的基本介绍。...

SibylY
33分钟前
3
0
总结:Https

一、介绍 简单理解,https即在http协议的基础上,增加了SSL协议,保障数据传输的安全性。 它由以前的http—–>tcp,改为http——>SSL—–>tcp;https采用了共享密钥加密+公开密钥加密的方式 ...

浮躁的码农
36分钟前
4
0
数据库表与表之间的一对一、一对多、多对多关系

表1 foreign key 表2 多对一:表 1 的多条记录对应表 2 的一条记录 利用foreign key的原理我们可以制作两张表的多对多,一对一关系 多对多: 表1的多条记录可以对应表2的一条记录 表2的多条记...

Garphy
今天
7
0
MySQL 表崩溃修复

MySQL日志报错 2019-10-19 13:41:51 19916 [ERROR] /usr/local/mysql/bin/mysqld: Table './initread_hss/user_info' is marked as crashed and should be repaired2019-10-19 13:41:51 1......

雁南飞丶
今天
6
0
Error和Exception

1.Error类和Exception类都是继承Throwable类 2.Error(错误)是系统中的错误,程序员是不能改变的和处理的,是在程序编译时出现的错误,只能通过修改程序才能修正。一般是指与虚拟机相关的问...

大瑞清_liurq
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部