函数指针一站式理解(C++ primer plus),包括指针数组,数组指针,胆小慎入

         本文的所有文字均需仔细阅读,本文知识点来源于C++ primer plus指针篇,其重要性不言而喻,文中加入了些许本人的观点简化阅读障碍,但也可能因此产生表述不清的问题,如有疑问,请直接阅读C++ primer plus 第六版第198页7.10函数指针

函数指针原理跟范例

#include <iostream>
#include <windows.h>

/*
* 函数指针
* 函数的地址是存储其机器语言代码的内存的开始地址.
* 可以编写将另一个函数的地址作为参数的函数,这样第一个函数能够找到第二个函数.这种方法的优势在于,可以在不同的场景传入不同的函数地址,提高函数的灵活性
* 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 <iostream>
#include <windows.h>
/*
* 深入探讨函数指针
* 如下三个函数原型
* 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大大减少输入量,让代码更不容易出错,易于理解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值