❥关于C++之函数与指针

Posted itzyjr

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了❥关于C++之函数与指针相关的知识,希望对你有一定的参考价值。

执行到函数调用指令时,程序将在函数调用后立即存储该指令的内存地址,并将函数参数复制到堆栈(为此保留的内存块),跳到标记函数起点的内存单元,执行完函数代码(也许还需将返回值放入到寄存器中),然后跳回到地址被保存的指令处。这与阅读文章时停下来看脚注,并在阅读完脚注后返回到以前阅读的地方类似。

与数据项相似,函数也有地址。函数的地址是存储其机器语言代码的内存的开始地址。

double pam(int); // 函数原型
double (*pf)(int); // 声明函数指针(参数为int型,返回值为double型)
pf = pam; // 函数名就是函数的地址,这时pf就是指向函数pam()的指针(参数、返回值类型都匹配)
double x = pam(4); // 用函数名调用
double x = (*pf)(5); // 使用函数指针调用

为何pam和(*pf)等价呢?由于pf是函数指针,那*pf是函数,因此可将(*pf)( )用作函数调用。

#include <iostream>
using namespace std;
double betsy(int);
double pam(int);
void estimate(int lines, double (*pf)(int));
int main() 
	int code;
	cout << "How many lines of code do you need? ";
	cin >> code;
	cout << "Here's Betsy's estimate:\\n";
	estimate(code, betsy);
	cout << "Here's Pam's estimate:\\n";
	estimate(code, pam);
	return 0;

double betsy(int lns)  return 0.05 * lns; 
double pam(int lns)  return 0.03 * lns + 0.0004 * lns * lns; 
void estimate(int lines, double (*pf)(int)) 
	cout << lines << " lines will take ";
	cout << (*pf)(lines) << " hour(s)\\n";

How many lines of code do you need? |100<Enter>
Here's Betsy's estimate:
100 lines will take 5 hour(s)
Here's Pam's estimate:
100 lines will take 7 hour(s)

可以看到,调用相同的estimate()函数,有不同的效果,这都是函数指针的功劳,它可以按需实现不同的功能。

下面看一个稍复杂点的示例:

#include <iostream>
const double* f1(const double ar[], int n);
const double* f2(const double[], int);
const double* f3(const double*, int);
int main() 
	using namespace std;
	double av[3] =  1112.3, 1542.6, 2227.9 ;
 
	typedef const double* (*p_fun)(const double*, int); // 创建类型别名
	
	p_fun p1 = f1;
	auto p2 = f2;  // C++0x automatic type deduction
	cout << "Using pointers to functions:\\n";
	cout << " Address  Value\\n";
	cout << (*p1)(av, 3) << ": " << *(*p1)(av, 3) << endl;
	cout << p2(av, 3) << ": " << *p2(av, 3) << endl;

	p_fun pa[3] =  f1,f2,f3 ;
	auto pb = pa;
	cout << "\\nUsing an array of pointers to functions:\\n";
	cout << " Address  Value\\n";
	for (int i = 0; i < 3; i++)
		cout << pa[i](av, 3) << ": " << *pa[i](av, 3) << endl;// 运算符优先级:[] > () > *
	cout << "\\nUsing a pointer to a pointer to a function:\\n";
	cout << " Address  Value\\n";
	for (int i = 0; i < 3; i++)
		cout << pb[i](av, 3) << ": " << *pb[i](av, 3) << endl;
		
	cout << "\\nUsing pointers to an array of pointers:\\n";
	cout << " Address  Value\\n";
	auto pc = &pa; // easy way to declare pc
	cout << (*pc)[0](av, 3) << ": " << *(*pc)[0](av, 3) << endl;
	p_fun (*pd)[3] = &pa;// slightly harder way to declare pd(1)
	const double* (*(*pd)[3])(const double *, int) = &pa;// hard way to declare pd(2)
	const double* pdb = (*pd)[1](av, 3);
	cout << pdb << ": " << *pdb << endl;
	cout << (*(*pd)[2])(av, 3) << ": " << *(*(*pd)[2])(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; 
Using pointers to functions:
 Address  Value
006FFC0C: 1112.3
006FFC14: 1542.6
 
Using an array of pointers to functions:
 Address  Value
006FFC0C: 1112.3
006FFC14: 1542.6
006FFC1C: 2227.9
 
Using a pointer to a pointer to a function:
 Address  Value
006FFC0C: 1112.3
006FFC14: 1542.6
006FFC1C: 2227.9
 
Using pointers to an array of pointers:
 Address  Value
006FFC0C: 1112.3
006FFC14: 1542.6
006FFC1C: 2227.9

代码里面最难理解的点在于如下这句代码:

p_fun pa[3] =  f1,f2,f3 ;
const double* (*(*pd)[3])(const double *, int) = &pa;

首先,运算符优先级:[ ] > () > *
T (*pd)[3](K)——pd是一个指针(函数指针),pd指向一个包含3个元素的数组,每个数组元素都是具有参数列表为K,返回类型为T特征的函数。由于函数名只可以作为指针(函数指针)使用,而不可能有其他函数名用法,所以此声明是无效的!在此只是作个比较说明而已。
T (*(*pd)[3])(K)——pd是一个指针(函数指针),pd指向一个包含3个元素的数组,每个数组元素都是具有参数列表为K,返回类型为T特征的指针(函数指针)。第一个星号作用于pd变量,第二个星号作用于数组元素。

以上图片是去掉最外层的*(),其就不能作为函数指针数据了,在此无任何意义。

程序解读:
对于函数原型f1、f2、f3这些函数的特征标看似不同,但实际上相同。首先,在函数原型中,参数列表const double ar [ ]与const double * ar的含义完全相同。其次,在函数原型中,可以省略标识符。因此,const double ar [ ]可简化为const double [ ],而const double * ar可简化为const double *。因此,上述所有函数特征标的含义都相同。

从运行结果,可以看到,同一函数的地址是固定不变的。

可以看到auto的强大,让程序员将主要精力放在设计而不是细节上。

注意到程序中:
p_fun pa[3] = f1,f2,f3 ;// f1、f2、f3都是函数名,也是代表着地址,都是指针变量
auto pb = pa;
cout << pa[i](av, 3);
对比:
auto pc = &pa;
cout << (*pc)[0](av, 3)
————————————
通过IDEA的提示,如下图,更直观比较pb与pc的赋值区别:

auto pb = pa;——对于数组pa是数组首元素地址,这时pb[0]=f1, pb[1]=f2, pb[2]=f3
auto pc = &pa;——对于数组&pa是整个数组的地址,两边加星号,则*pc=pa, (*pc)[0]=pa[0]=f1, (*pc)[1]=f2, (*pc)[2]=f3

下面这个示例,非常直观解释了以上疑惑:

int arr[3] = 3, 5, 7;
int (*pc)[3] = &arr;
cout << arr[0] << "," << arr[1] << "," << arr[2] << endl;
cout << pc[0] << "," << pc[1] << "," << pc[2] << endl;
cout << *pc[0] << "," << *pc[1] << "," << *pc[2] << endl;
cout << (*pc)[0] << "," << (*pc)[1] << "," << (*pc)[2];
3,5,7
0x3bc51ffafc,0x3bc51ffb08,0x3bc51ffb14
3,-987759876,59
3,5,7

arr是数组首元素地址,步长为1,所以可以直接下标访问。
pc被赋值为整个数组的地址,通过例子打印的地址也可佐证它步长为3,所以除了pc[0]外,其他下标访问的元素是无意义的。
pc指向整个数组的地址&arr,那(*pc)就恒等于arr了,这时(*pc)就是首元素地址了,可以直接用下标访问元素。

还可以看到:使用typedef,使程序编写简单很多:

typedef const double* (*p_fun)(const double*, int);

以上是关于❥关于C++之函数与指针的主要内容,如果未能解决你的问题,请参考以下文章

试图实现一个 C++ 库,需要一些关于如何与它交互的指针

❥关于C++之智能指针

❥关于C++之语言特别与限制

C++之静态

C++性能系列之函数与函数参数的几个原则

C与C++关于*与&的传参解析