moc 发表于 2018-9-9 20:35:49

038-C/C++之函数类型和函数指针

本帖最后由 moc 于 2018-9-12 20:11 编辑

1、函数类型
        在C语言中,函数也是一种类型,同样也可以定义指向函数类型的指针。
函数三要素:名称、参数、返回值。
其中:函数的名称就代表函数的入口地址;函数名本身就是一个指针。
与数组类型相仿,也可以自定义函数类型:
        typedef type name(parameter list);
int test(int a)
{
        cout << a*a << endl;
        return a*a;
}

typedef int Func(int);   // 定义一个函数类型
Func *myfun = NULL;   // 用函数类型定义一个函数指针
myfun = test;   // 函数指针指向一个函数
myfun(2);          //通过函数指针调用函数

myfun = *(*test);
myfun = &(&(&test));
扩展:对函数名取多少次地址或*p操作,都是一样的,其结果都是他本身。
2、函数指针
除了通过上面的函数类型定义函数指针外,还可以通过定义函数指针类型来直接定义函数指针。
函数指针类型:
        typedef type(*name)(parameter list);
也可直接定义函数指针:
        type (*pointer)(parameter list);
typedef int(*MyPFun)(int);// 定义一个指向函数类型的指针类型
MyPFun aa = test;         // 通过函数指针类型定义函数指针
aa(10);    // 调用函数
int(*myf1)(int) = f;//直接定义一个函数指针并且赋值
myf1(20);
3、函数指针做函数参数
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

int add(int a, int b) { return a + b; }

// pDis是add的入口地址;函数指针做函数参数
int libfun(int(*pDis)(int a, int b))
{
        int a, b;
        a = 1;
        b = 2;
        printf("%d\n", pDis(a, b));   // 执行了add的调用
}

int main()
{
        int(*pfun)(int a, int b);   // 直接定义了一个函数指针
        pfun = add;         // 函数名赋给函数指针,即把函数入口地址赋给pfun
        libfun(pfun);         //函数指针做函数实参

        system("pause");
        return 0;
}
像上面函数指针做函数参数,如果放在一个函数中,没有任何意义,函数指针主要有两种应用场景:
        ① 正向调用    用于动态库加载
        ② 反向调用    用于回调函数
4、正向调用
动态库的加载有两种方式,除了前面的通过编译器加载(把lib文件放入链接器的输入的附加依赖项)中,还可以通过函数指针手动的加载。。
难点:
        理解被调用函数是什么机制被调用起来的。   --->框架。
        框架提前设置了被调用函数的入口(框架提供了第三方模块入口的集成功能)。
        框架具备调用第三方模块的入口函数。
MFC演示手工动态库加载:
typedef int(*CltSocketInit)(void *handle);// 定义函数指针类型
HINSTANCE hIstance = NULL;
hIstance = ::LoadLibrary(TEXT("d:/socketclient.dll"));   // MFC框架提供加载动态库的函数LoadLibrary
// 从动态库取得函数的入口地址赋给函数指针
CltSocketInit cltSocketInit = (CltSocketInit)::GetProAddress(hIstance, "cltSocketInit");// MFC框架通过函数名找到动态库里的函数的入口地址。

void *handle = NULL;
int ret = cltSocketInit(handle); // 调用动态库里的函数
5、反向调用
回调机制的原理:
        ① 当具体事件发生时,调用者通过函数指针调用具体函数;
        ② 回调机制将调用者和被调函数分开,两者互不依赖;
        ③ 任务的实现 和 任务的调用可以 解耦合 (提前进行接口的封装和设计)。
如何把一个普通的动态库变成一个业务模型框架:
1. 把第三方的业务入口传入框架(dll)(传入回调函数的入口地址)
        传入的方式有两种:
                ① 回调函数的入口地址缓存到框架(dll)中;
                ② 回调函数入口(函数名)直接放入框架函数参数中。
2. 实现动态库 和 业务模型的抽象
6、指向成员函数的函数指针
结论1: Teacher::printA 和 &Teacher::printA 虽然在值上是一样的,都表示成员函数的入口地址,但是不加&属于不规范操作,在给指向成员函数的函数指针赋值时会报错。
结论2: ps1 = &Teacher::printA;(ps1为函数指针)指向成员函数的函数指针正确的赋值操作,类的成员函数加括号取地址再给函数指针属于非法操作(Vs2015下);
结论3:自定义类型、类也有作用域,只在自己的命名空间下有效。
结论4:指向类的成员函数通过函数指针的正确调用方法:(*this.*psf1)();
class Student
{
public:
        Student(char* buf, int age, bool sex, int sID)
        {
                strcpy(name, buf);
                this->age = age;
                this->sex = sex;
                this->sID = sID;
        }
        void getnName() { cout << name << endl; }
        void getAge() { cout << age << endl; }
        void getSex() { cout << sex << endl; }
        void getsID() { cout << sID << endl; }
        void printMessage();

private:
        char name;
        int age;
        bool sex;
        int sID;
};

typedef void(Student::*pStuFun)();// 定义指向成员函数的函数指针类型,可放在类中
void Student::printMessage()
{   // 函数指针数组   注意必须是&类名::成员函数名{注意:中间不能加括号 &(类名::成员函数名)}
        pStuFun psf1 = { &Student::getnName, &Student::getAge, &Student::getSex, &Student::getsID };
        for (int i = 0; i < 4; i++)
        {
                (*this.*psf1)();   //或 (this->*psf1)();

                //错误写法
                //*this.*psf1();
                //*this.(*psf1)();
                //(*this.(*psf1))();
        }
}

int main()
{
        Student s1("小明", 23, 1, 1802);
        s1.printMessage();

        system("pause");
        return 0;
}
结论5:动态联编、多态的深入理解
        所谓的动态联编:就是说函数在运行时才确定函数地址!(是通过函数指针来实现的); 也就是在调用成员函数通过    (对象.*函数指针)(形参).
        多态是C++编译器通过vptr指针去查找虚函数表然后隐式帮我们完成了这个过程。
本小节参考博文。
页: [1]
查看完整版本: 038-C/C++之函数类型和函数指针