zoukankan      html  css  js  c++  java
  • C++回调,函数指针

    想要理解回调机制,先要理解函数指针

    函数指针

    函数指针指向的是函数而非对象,和其他指针一样,函数指针指向某种特定的类型

    函数的类型由他的返回类型和参数类型共同决定,与函数名无关,如:

    bool lengthCompare(const string &, const string &);
    

    该函数的类型是bool (const string &, const string &),要声明一个可以指向该函数的指针,只需要用指针替换函数名:

    //pf指向一个函数,该函数的参数是两个const string的引用,返回值是bool类型
    bool (*pf)(const string &, const string &);
    

    pf是指针,右侧是形参列表,表示pf指向的是函数;左侧返回类型是布尔值,因此pf就是一个指向函数的指针,其中函数的参数是两个const string的引用,返回值是bool类型

    (*pf) 少了括号,pf就是一个 返回值为bool指针的函数

    使用函数指针

    当我们把函数名作为一个值使用时,该函数自动转换成指针:

    bool lengthCompare(const string &, const string &);
    bool (*pf)(const string &, const string &);
    
    pf = lengthCompare;  //pf指向名为lengthCompare的函数
    pf = &lengthCompare; //等价的赋值语句,&是可选的
    

    直接使用指向函数的指针调用该函数:

    bool b1 = pr("hello","goodbye");			//调用lengthCompare函数
    bool b2 = (*pf)("hello","goodbye");			//等价调用
    bool b3 = lengthCompare("hello","goodbye"); //等价调用
    

    函数的类型由他的返回类型和参数类型共同决定,指向不同函数类型的指针间不存在转换规则,但可以为指针赋一个nullptr或者值为0的整形常量表达式,表示指针没有指向任何一个函数:

    bool (*pf)(const string &, const string &);
    string::size_type sumLength(const string&, const string&);
    bool cstringCompare(const char*, const char*);
    bool lengthCompare(const string &, const string &);
    
    pf = 0;				 //正确:pf不指向任何函数
    pf = sumLength; 	 //错误:返回类型不匹配
    pf = cstringCompare; //错误:形参类型不匹配
    pf = lengthCompare;  //正确:函数和指针的类型精确匹配
    

    重载函数指针

    当我们使用重载函数时,上下文必须清晰地界定到底该选用哪个函数,如果定义了指向重载函数的指针:

    void ff(int*);
    void ff(unsigned int);
    
    void (*pf1)(unsigned int) = ff; //pf1指向ff(unsigned int);
    

    编译器通过指针类型决定选用哪个函数,指针类型必须与重载函数其中之一精确匹配

    void (*pf2)(int) = ff;	  //错误:没有任何一个ff与该形参列表匹配
    double (*pf3)(int*) = ff; //错误:ff和pf3的返回类型不匹配
    

    函数指针形参

    和数组类似,不能定一函数类型的形参,但形参可以是指向函数的指针

    此时形参看起来像函数类型,实际上被当成指针使用

    //第三个形参是函数类型,会自动转换成指向函数的指针
    void useBigger(const string &s1, const string &s2, 
                   bool pf(const string &, const string &));//看起来像函数类型,实际上被当成指针使用
    
    //等价的声明:显示地将形参定义成指向函数的指针
    void useBigger(const string &s1, const string &s2, 
                   bool (*pf)(const string &, const string &));
    

    我们也可以直接把函数作为实参使用,此时它会自动转换成指针:

    //自动将函数lengthCompare转换成指向该函数的指针
    useBigger(s1,s2,lengthCompare);
    

    typedef和decltype

    简化使用了函数指针的代码

    //Func和Func2是函数类型
    typedef bool Func(const string&, const string&);
    typedef decltype(lengthCompare) Func2;   //等价的类型
    
    //FuncP和FuncP2是指向函数的指针
    typedef bool (*FuncP)(const string&, const string&);
    typedef decltype(lengthCompare) *FuncP2; //等价的类型
    

    我们使用typedef定义自己的类型,Func和Func2是函数类型,而FuncP和FuncP2是指针类型

    decltype返回函数类型,此时不会将函数类型自动转换成指针类型,因为decltype的结果是函数类型,所以只有在结果前面加上 * 才能得到指针,可以使用如下形式重新声明useBigger:

    // useBigger的等价声明,其中使用了类型别名 
    void useBigger(const string&, const string&, Func);//自动将Func表示的函数类型转换成指针
    void useBigger(const string&, const string&, FuncP2);
    

    返回指向函数的指针

    和数组类似,虽然不能返回一个函数,但是能返回指向函数类型的指针,我们也必须把返回类型写成指针形式,因为编译器不会自动地将函数返回类型当成对应的指针类型处理:

    using F = int(int*, int);	  //F是函数类型,不是指针
    using PF = int(*)(int*, int); //PF是指针类型
    

    我们使用类型别名将F定义成函数类型,将PF定义成指向函数类型的指针
    和函数类型的形参不一样,但会类型不会自动地转换成指针,必须显示地将返回类型定为指针

    PF f1(int);	 //正确:PF是指向函数的指针,f1返回指向函数的指针
    F f1(int);	 //错误:F是函数类型,f1不能返回一个函数
    F *f1(int);	 //正确:显示地制定返回类型是指向函数的指针
    

    我们也能用如下形式直接声明f1:

    int (*f1(int)(int, int));
    

    按照由内向外地顺序阅读:f1有形参列表,所以f1是个函数;f1前有*,所以f1返回一个指针;进一步观察发现,指针的类型本身也包含形参列表,因此指针指向函数,该函数返回的类型是int

    回调函数

    Callback方式(C的回调函数)

    Callback的本质是设置一个函数指针进去,然后在需要需要触发某个事件时调用该方法,比如Windows的窗口消息处理函数就是这种类型

    //callbackTest.c
    //1.定义函数onHeight(回调函数)
    //@onHeight 函数名
    //@height   参数
    //@contex   上下文
    void onHeight(double height, void* contex)
    {
    	sprint("current height is %lf",height);
    }
    
    //2.定义onHeight函数的原型
    //@CallbackFun 指向函数的指针类型
    //@height      回调参数,当有多个参数时,可以定义一个结构体
    //@contex      回调上下文,在C中一般传入nullptr,在C++中可传入对象指针
    typedef void (*CallbackFun)(double height, void* contex);
    
    //3.定义注册回调函数
    //@registHeightCallback 注册函数名
    //@callback             回调函数原型
    //@contex               回调上下文
    void registHeightCallback(CallbackFun callback, void* contex)
    {
    	double h=100;
    	callback(h,nullptr);
    }
    
    //4.main函数
    void main()
    {
    	//注册onHeight函数,即通过registHeightCallback的参数将onHeight函数指针
    	//传入给registHeightCallback函数,在registHeightCallback函数中调用
    	//callback就相当于调用onHeight函数。
    	registHeightCallback(onHeight,nullptr);
    }
    

    我们就可以用来实现一个小需求:Download完成时需要触发一个通知外面的事件:

    //Callback方式
    
    typedef void (__stdcall *DownloadCallback)(const char *pURL,bool OK);
    
    void DownLoadFile(const char *pURL,DownloadCallback callback)
    {
        std::cout<<"downloading..."<<pURL<<""<<std::endl;
        callback(pURL,true);
    }
    
    void __stdcall onDownloadFinished(const char* pURL,bool bOK)
    {
        std::cout<<"onDownloadFinished..."<<pURL<<"   status:"<<bOK<<std::endl;
    }
    
    void main()
    {
        DownLoadFile("http://wwww.baidu.com",onDownloadFinished);
        system("pause");
    }
    

    Sink方式

    Sink的本质是按对方的需求实现一个C++接口,然后把实现的接口设置给对方,对方需要触发事件时调用该接口, COM中连接点就是基于这种方式,上面下载文件的需求,如果用Sink实现,代码如下:

    //sink方式
    
    class IDownloadSink
    {
    public:  
        virtual void OnDownloadFinished(const char *pURL,bool bOK) = 0;
    };
    
    class CMyDownloader
    {
    public:  
        CMyDownloader (IDownloadSink *pSink)
            :m_pSink(pSink)
        {
    
        }
    
        void DownloadFile(const char* pURL)
        {
            std::cout<<"downloading..."<<pURL<<""<<std::endl;
            if(m_pSink!=NULL)
            {
                m_pSink->OnDownloadFinished(pURL,true);
            }
        }
    
    private:
        IDownloadSink *m_pSink;
    };
    
    
    class CMyFile:public IDownloadSink
    {
    public:  
        void download()
        {
            CMyDownloader downloader(this);
            downloader.DownloadFile("www.baidu.com");
        }
    
        virtual void OnDownloadFinished(const char *pURL,bool bOK) 
        {
            std::cout<<"onDownloadFinished..."<<pURL<<"   status:"<<bOK<<std::endl;
        }
    };
    
    void main()
    {
        CMyFile *file = new CMyFile();
        file->download();
        system("pause");
    }
    

    Delegate方式

    Delegate的本质是设置成员函数指针给对方,然后让对方在需要触发事件时调用,C#中用Delegate的方式实现Event,C++中因为语言本身的关系,要实现Delegate还是很麻烦的,我不会写,鸽了

  • 相关阅读:
    Java多线程之“同步”
    50 道 Java 线程面试题(转载自牛客网)
    Ubuntu14.04搭建Caffe(仅CPU)
    【译】OkHttp3 拦截器(Interceptor)
    OkHttp3 使用详解
    JSP ---- 声明、表达式、脚本、注释
    dom解析和sax解析的区别及优缺点
    一个java源文件中为什么只能有一个public类。
    js传入参数为字符串问题
    java project中 xml文件路径问题
  • 原文地址:https://www.cnblogs.com/zhxmdefj/p/11191407.html
Copyright © 2011-2022 走看看