- #include
- #include
-
- /*
- * 函数指针
- * 函数的地址是存储其机器语言代码的内存的开始地址.
- * 可以编写将另一个函数的地址作为参数的函数,这样第一个函数能够找到第二个函数.这种方法的优势在于,可以在不同的场景传入不同的函数地址,提高函数的灵活性
- * 1、获取函数地址
- * 获取函数的地址很简单:只要使用函数名(不带参数)即可,例:void think(); 是一个函数,则 think就是该函数的地址
- * 2、声明函数指针
- * 函数指针的声明应像函数原型一样指出函数的相关信息,包括函数的返回类型,以及函数的特征标志(参数列表)
- * 例如: 某函数原型为 double pam(int); 则该函数的正确指针类型声明为 double (*pf)(int);
- * 可以看到该函数指针与原型极其相似,只是将pam替换为(*pf),由于pam是函数,因此(*pf)也是函数,则pf就是函数指针
- * 这里涉及到指针相关的知识比如 * () 的优先级,右左原则
- * 括号的优先级比 * 高,因此 *pf(int) 意味着pf()是一个返回指针的函数,而(*pf)(int)意味着pf是一个指向函数的指针
- * double (*pf)(int);
- * double pam(int);
- * pf = pam;
- *
- * 函数说明 : 该函数模拟了需要写lines行代码,不同的人需要多少时间
- * 入参:
- lines : 行数
- pf : 函数指针,每个人把自己需要的时间计算出来
- * 出参: 无
- * 返回值: void
- void estimate(int lines, double (*pf)(int));
- * 3、使用指针来调用函数
- * 如何来调用被指针指向的函数,也就是如何知道自己该传怎样一个函数做参数才能正确调用 estimate函数呢
- * 线索来自指针声明,在2中说了,(*pf)扮演的角色与函数名相同,因此使用(*pf)时,只需将它看做函数名即可
- * double pam(int);
- * double (*pf)(int);
- * pf = pam;
- * double x = pam(4);
- * double y = (*pf)(4);
- * 指针使用函数还有另一种方法: C++允许像使用函数名一样使用pf;
- * double y = pf(4);
- * 关于如上两种函数指针的用法,其由来可以往前追溯历史: 为何 pf 和 (*pf)等价呢?
- * 一种学派认为,由于pf是函数指针,所以*pf是函数,因此调用函数应为 (*pf)()
- * 另一种学派认为,由于函数名本身就是指向该函数的指针,所以指向函数的指针的行为应该是跟函数名相似的,而pf是指向函数的指针,因此可以将pf()当函数名来使用.
- * 以上两种方式C++均支持且运行一致.
- */
- using std::cout;
- using std::cin;
-
- double pam(int);
- double tony(int);
-
- void estimate(int lines, double (*pf)(int));
-
-
- int main()
- {
- int code;
- cout << "你需要多少行代码? ";
- cin >> code;
- cout << "pam 的预估:\n";
- estimate(code, pam);
- cout << "tony 的预估:\n";
- estimate(code, tony);
- return 0;
- }
-
- double pam(int lns)
- {
- return lns * 0.05;
- }
-
- double tony(int lns)
- {
- return 0.03 * lns + 0.004 * lns * lns;
- }
-
- void estimate(int lines, double (*pf)(int))
- {
- cout << lines << "行需要 ";
- cout << pf(lines) << " 小时 \n";
- }
上述代码运行结果:
键盘输入数字9
运行结果如下:
你需要多少行代码? 9
pam 的预估:
9行需要 0.45 小时
tony 的预估:
9行需要 0.594 小时
- #include
- #include
- /*
- * 深入探讨函数指针
- * 如下三个函数原型
- * 1、const double * f1(const double ar[],int n);
- * 2、const double * f2(const double [],int);
- * 3、const double * f3(const double *,int);
- * 这些函数的特征标志(参数列表)看似不同,但实际上相同.C++的函数原型,参数 const double ar[] 跟 const double * ar 含义完全一样,其次,函数原型可以省略标识符,
- * 因此 const double ar[] 可以简化为const double [],const double * ar可以简化为 const double *
- * 不过对于函数定义,必须提供显式的参数名,因此必须使用const double ar[]或者const double * ar
- * 有了函数原型,那么定义函数指针就十分方便了
- * const double * (*p1)(const double *,int); 表示指向上述三个函数的函数指针
- * 可在声明的同时进行初始化
- * const double * (*p1)(const double *,int) = f1;
- * 使用C++的自动类型推导,代码将要简单很多
- * auto p2 = f2;
- * 下面看如下语句
- * cout << (*p1)(av,3) << ":" << *(*p1)(av,3) << endl;
- * cout << p2(av,3) << ":" << *p2(av,3) << endl;
- * 如何理解上述语句? 根据前面函数指针的原理,这里可以知道 (*p1)(av,3) 和 p2(av,3) 都调用指向的函数,也就是 f1() 跟 f2(),av,3是两个参数,
- * 这俩函数返回值是double * ,因此显示出来的是地址,那后半部分(*(*p1)(av,3) 和 *p2(av,3))显示的就是这些地址里面的值
- * 因为上述三个函数都需要使用函数指针,那选择就有两种,一是声明三个函数指针分别指向三个函数,二是通过函数指针数组
- * 如何声明这样的一个数组呢? 这种数组的声明应该类似单个函数指针的声明,但是必须在某个地方加上[3]用于指出这是一个包含三个函数指针的数组,答案如下:
- * const double * (*pa[3])(const double *,int) = {f1,f2,f3};
- * 为何这样声明呢? 这涉及到 () [] * 三者的优先级以及指针的右左原则
- * 首先可以确定的是我们需要一个数组,因此必须有 pa[3],由于[] 的优先级高于 * ,因此 *pa[3] 表示pa是一个包含三个指针的数组,
- * 其余的特征标志(参数列表)和返回类型用以修饰该指针数组的指针类型(可通过指针的右左原则来反解pa的类型)
- * 这里可否使用auto来简化函数指针数组的声明呢? 不行,自动类型推导只能使用于单值初始化,不能用于初始化列表,但声明pa后可通过auto声明同类型的pb:
- * auto pb = pa;
- * 因为数组名是指向数组第一个元素的指针,所以这里的pa,pb 是指向函数指针的指针
- * 如何使用pa,pb 调用函数呢?
- * const double * px = pa[0](av,3);
- * const double * py = (*pb[1])(av,3);
- * 如何理解上述语句?
- * 在上面的 函数指针跟范例中解释了两种函数指针用法的历史
- * 因为函数名本身就是指向该函数地址的指针,因此调用函数有两个理解: 1、函数指针调用函数 2、函数名调用函数
- * 因为pa数组的每个元素都是函数指针,因此可以直接使用数组元素(函数指针)调用函数,即pa[0](av,3)
- * pb跟pa一样,每个元素都是函数指针,既然元素是指针,那通过对指针解引用即可得到函数名本身,通过函数名调用即 (* pb[1])(av,3)
- * 现在尝试来做另一件事: 创建一个指向pa数组的指针.
- * 由于数组名pa本身是指向函数指针的指针,因此指向该数组的指针将是一个指向 指针的指针 的指针
- * 上面的话听起来就够绕了,不过幸运的是,由于可以使用单个的值而不是初始化列表对其初始化,因此可以使用 auto:
- * auto pc = &pa;
- * 那么该如何手动声明这个类型呢? 假设指针名叫pd
- * 参考pa的声明 : const double * (*pa[3])(const double *,int)
- * 现在知道了pd是一个指针,由于*的优先级相对()[]而言最低,因此 一定存在 (*pd),其指向一个数组,根据右左原则,一定会有(*pd)[3],
- * 这个数组的元素都是指针,则 (* (*pd)[3]),数组元素指针的类型可以参考pa的声明,则最终的结果如下:
- * const double * (* (*pd)[3])(const double *,int) = &pa;
- * 那么该如何使用pd调用函数呢? 既然pd指向一个数组,那*pd就是数组,而(*pd)[i]就是数组元素,也就是函数指针,因此可以简单的使用函数指针方式调用
- * 即 (*pd)[i](av,3),那么 *(*pd)[i](av,3)就是函数返回的指针指向的值
- * 也可以使用第二种方法 (*(*pd)[i])(av,3) ,该方法使用的是函数名,那么 *(*(*pd)[i])(av,3)就是函数返回的double值
- * 需要注意的是pa 跟 &pa的区别
- * pa是一个数组名,在大多数情况下,数组名就是数组第一个元素的地址,即pa = &pa[0],因此,pa是单个指针的地址
- * 但是 &pa是整个数组(即三个指针块)的地址.从数字上说,pa和&pa的值相同,但是它们的类型不同.
- * 区别是,pa+1为数组中下一个元素的地址,而&pa + 1 表示的则是pa后面12个字节内存块的地址(对指针使用sizeof的结果为4,数组一共三个指针)
- * 另一个区别是,要得到第一个元素的值,只需要对pa解除一次引用,但需要对&pa解除两次引用
- * **&pa = *pa = pa[0];
- */
-
-
- using std::cout;
- using std::cin;
- using std::endl;
-
- const double* f1(const double ar[], int n);
- const double* f2(const double[], int);
- const double* f3(const double*, int);
-
- typedef const double* (*p_fun)(const double*, int);
-
- int main()
- {
- double av[3]{ 1112.3,1542.6,2227.9 };
- const double* (*p1)(const double*, int) = f1;
- auto p2 = f2;
- cout << "使用函数指针 \n";
- cout << "函数返回地址 函数返回值 \n";
- cout << (*p1)(av, 3) << " : " << *(*p1)(av, 3) << endl;
- cout << p2(av, 3) << " : " << *p2(av, 3) << endl;
-
- const double* (*pa[3])(const double*, int) { f1, f2, f3 };
- auto pb = pa;
- cout << "使用函数指针数组\n";
- cout << "函数返回地址 函数返回值 \n";
- for (int i = 0;i < 3;i++)
- {
- cout << pa[i](av, 3) << " : " << *pa[i](av, 3) << endl;
- }
- for (int i = 0; i < 3; i++)
- {
- cout << pb[i](av, 3) << " : " << *pb[i](av, 3) << endl;
- }
-
- cout << "使用函数指针数组指针\n";
- cout << "函数返回地址 函数返回值 \n";
- //auto pc = &pa;
- const double* (*(*pc)[3])(const double*, int){&pa};
- cout << (*pc)[0](av, 3) << " : " << *(*pc)[0](av, 3) << endl;
- auto pd = &pa;
- const double* pdb = (*pd)[1](av, 3);
- cout << pdb << " : " << *pdb << endl;
- cout << (*(*pd)[2])(av, 3) << " : " << *(*(*pd)[2])(av, 3) << endl;
-
- cout << "使用函数指针别名\n";
- cout << "函数返回地址 函数返回值 \n";
- p_fun pf1 = f1;
- cout << pf1(av, 3) << " : " << *pf1(av, 3) << endl;
- p_fun pe[3]{ f1,f2,f3 };
- cout << "使用函数指针别名数组\n";
- cout << "函数返回地址 函数返回值 \n";
- for (int i = 0; i < 3; i++)
- {
- cout << pe[i](av, 3) << " : " << *pe[i](av, 3) << endl;
- }
- cout << "使用函数指针别名数组指针\n";
- cout << "函数返回地址 函数返回值 \n";
- p_fun(*pf)[3]{ &pe };
- for (int i = 0; i < 3; i++)
- {
- cout << (*pf)[i](av, 3) << " : " << *(*pf)[i](av, 3) << endl;
- }
- return 0;
- }
-
- const double* f1(const double ar[], int n)
- {
- return ar;
- }
-
- const double* f2(const double ar[], int n)
- {
- return ar + 1;
- }
-
- const double* f3(const double* ar, int n)
- {
- return ar + 2;
- }
上述代码运行如下 :
使用函数指针
函数返回地址 函数返回值
0000009ACE70FA38 : 1112.3
0000009ACE70FA40 : 1542.6
使用函数指针数组
函数返回地址 函数返回值
0000009ACE70FA38 : 1112.3
0000009ACE70FA40 : 1542.6
0000009ACE70FA48 : 2227.9
0000009ACE70FA38 : 1112.3
0000009ACE70FA40 : 1542.6
0000009ACE70FA48 : 2227.9
使用函数指针数组指针
函数返回地址 函数返回值
0000009ACE70FA38 : 1112.3
0000009ACE70FA40 : 1542.6
0000009ACE70FA48 : 2227.9
使用函数指针别名
函数返回地址 函数返回值
0000009ACE70FA38 : 1112.3
使用函数指针别名数组
函数返回地址 函数返回值
0000009ACE70FA38 : 1112.3
0000009ACE70FA40 : 1542.6
0000009ACE70FA48 : 2227.9
使用函数指针别名数组指针
函数返回地址 函数返回值
0000009ACE70FA38 : 1112.3
0000009ACE70FA40 : 1542.6
0000009ACE70FA48 : 2227.9
上述的示例看起来比较深奥,但是指向函数指针数组的指针并不少见,类的虚方法实现通常都采用了这种技术,不过这些细节由编译器实现
typedef 可以将p_fun声明为函数指针类型,通过使用p_fun大大减少输入量,让代码更不容易出错,易于理解