您的位置:首页 > 其它

(九十九)函数指针

2015-12-26 19:05 169 查看
与数据项(比如int、string、char等)相似,函数也有地址。

函数的地址是存储其机器语言代码的内存的开始地址(不懂)。

这些地址对用户而言,没什么用,也不重要,但对程序而言,却很有用。

例如,可以编写将另外一个函数的地址作为参数的函数。这样,第一个函数将能够找到第二个函数,并运行它。

与直接调用第二个函数相比,这种方法很笨拙,但他允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。

函数指针和指针函数的区别:

函数指针:指向某个函数的指针(即这个指针指向函数的地址);

指针函数:返回值是指针的函数

函数指针的基础知识:

函数的地址:

函数名 就是函数的地址(像字符串一样);

声明函数指针:

有些像函数返回指针,但是要把*和函数名用括号括起来。例如:函数类型 (*函数名) (参数);

注意:①声明的函数指针,要和函数的特征标(例如类型、函数的参数类型)要相符;

②*函数名 此时是函数,而 函数名 此时是指针。

函数指针作为参数时:

函数类型 函数名(函数类型 (*函数名)(函数指针的参数类型) );

注:蓝色部分为函数指针作为参数时的写法。

调用函数指针时:

和声明函数指针类似,用函数指针的 (*函数名) 取代普通函数 函数名 即可。

如:(*函数名)(函数指针的参数) 这样。

也可以像使用普通函数那样,通过 函数名(参数) 这样调用函数,C++是允许这样做的。

给函数指针赋值时:

首先,函数指针和函数的特征标要相符。

然后例如代码节选:

void ab(int);//函数原型

void(*c)(int);
//函数指针原型

c = ab; //让函数指针c指向函数ab。这行代码一般在函数里

这样。

无返回值、将函数指针赋值、声明函数指针、调用函数指针,如代码:

#include<iostream>
using namespace std;
void ab(int);	//函数1
void ef(int);	//函数2
void(*c)(int);	//声明函数指针,注意,这行代码放在main()函数内也可以

int main()
{
int a = 5;
c = ab; //函数指针指向函数1
c(a);	//调用函数指针(即执行函数1)
c = ef;	//函数指针指向函数2
(*c)(a);	//调用函数指针(此时执行函数2),注意c和(*c)是等价的,不能省略括号
system("pause");
return 0;
}

void ab(int m)
{
cout << "m = " << m << endl;
}

void ef(int m)
{
cout << "m = " << m * 2 << endl;
}


输出:

m = 5
m = 10
请按任意键继续. . .


解释:

①这行代码就是函数指针指向不同函数时,同样的变量作为参数,却执行不同的函数。

有返回值、将函数指针作为函数的参数、不同情况下指向不同的函数、使用函数的地址,如代码:

#include<iostream>
using namespace std;
int ab(int);	//函数1
int ef(int);	//函数2
int c(int(*x)(int),int y);	//引入函数指针作为函数c的参数

int main()
{
int a = 5;
int m, n;
m = c(ab, a);	//调用的时候,指定ab函数作为参数
n = c(ef, a);	//调用的时候,制定ef为指定的函数
cout << "m = " << m << endl;
cout << "n = " << n << endl;
system("pause");
return 0;
}

int ab(int m)
{
return m + 3;
}

int ef(int m)
{
return m * 3;
}

int c(int(*x)(int),int y)	//x指向某个函数,y为函数将使用的参数。因此,在调用的时候,需要指定使用哪个函数。
{
return x(y);	//返回 调用指针指向的函数(参数)的返回值
}


输出:

m = 8
n = 15
请按任意键继续. . .


总结:

①以上两部分代码,涉及到了函数的地址(即函数名)、声明函数指针、函数指针作为参数、调用函数指针、给函数指针赋值 这五个概念;

②可以在函数内部声明函数指针,然后根据变量的不同,让指针指向其他不同的函数,再执行对应的函数,并将对应函数的返回值作为函数的返回值。

如代码:

int c(int y)
//x指向某个函数,y为函数将使用的参数。因此,在调用的时候,需要指定使用哪个函数。

{

int(*x)(int);
//声明函数指针x

if(y<10)x=ab; else x=ef;
//不同参数y,让x指向不同的指针

return x(y);
//调用函数指针,y作为参数,返回值作为函数c的返回值

}

③A函数指针作为参数时,可以传递不同的函数名(例如B或者C)作为参数,使得在A函数中根据传递的函数名的不同,执行不同的函数(B或者C)。

当函数返回值为指针的函数指针:

函数

示例函数原型

普通函数

int abc(int);

函数指针

int (*m) (int);

返回值为指针的函数

int* abc (int);

返回值为指针的函数指针

int* (*m) (int);

可以发现,无论是指向普通函数,还是指向返回值为指针的函数,函数指针都是用(*指针名)替代函数名。就像(*m)替代abc 一样。

不同参数让指针指向不同的函数,代码:

#include<iostream>
using namespace std;
int*m(int);
int*n(int);

int main()
{
int*(*abc)(int);	//创建一个函数指针,面对对象是返回值是指针的函数
int *l;	//创建一个指针,将函数的返回值(指针)赋值给他。
for (int i = 0;i < 5;i++)
{
if (i % 2 == 0)	//函数指针能被2整除时,指向m,不能被2整除时,指向n
{
abc = m;
}
else
{
abc = n;
}
l = abc(i+1);	//执行函数指针,将返回值赋值给指针l。参数是i+1
cout << i+1 << "#:" << endl;	//第(i+1)#
cout << "l = " << l << endl << "  *l = " << *l << endl << endl;	//打印l(指针)和l指向的地址的值
}
system("pause");
return 0;
}

int *m(int a)
{
int *x = new int;	//new一个int地址出来
*x = a;		//新地址的值是参数a
return x;	//返回地址
}

int *n(int a)
{
int *x = new int;	//new一个int地址出来
*x = a*2;	//新地址的值是参数a*2
return x;	//返回地址
}


输出:

1#:
l = 00740A10
*l = 1

2#:
l = 00740A40
*l = 4

3#:
l = 00740670
*l = 3

4#:
l = 00740CA0
*l = 8

5#:
l = 00740CD0
*l = 5

请按任意键继续. . .


解释:

①当参数i(根据for循环而变)能否被2整除,让函数指针指向不同的函数;

声明一个函数指针数组:

除了上面声明一个函数指针,利用if语句使得指针指向不同函数,也可以声明一个函数指针数组,然后修改数组的成员标号,来让其指向不同的函数。

例如:

函数类型* (*函数名[函数指针成员个数]) (参数类型) ={ 函数1, 函数2};

可以将上面的代码

int*(*abc)(int);	//创建一个函数指针,面对对象是返回值是指针的函数
int *l;	//创建一个指针,将函数的返回值(指针)赋值给他。
for (int i = 0;i < 5;i++)
{
if (i % 2 == 0)	//函数指针能被2整除时,指向m,不能被2整除时,指向n
{
abc = m;
}
else
{
abc = n;
}
l = abc(i+1);	//执行函数指针,将返回值赋值给指针l。参数是i+1
……以下略……


改为:

int*(*abc[2])(int) = { m,n };	//创建一个 函数指针 数组,成员分别指向不同的函数
int *l;	//创建一个指针,将函数的返回值(指针)赋值给他。
for (int i = 0;i < 5;i++)
{
l = abc[i%2](i+1);	//利用i/2求余,调用不同的函数指针数组成员,并将返回值赋给指针l
……以下略……


输出结果是相同的,但减少了代码量。

原理:

①不同函数指针数组成员指向不同函数;

②然后利用成员编号(abc[i])的不同,通过i%2的结果,得到不同的函数指针成员,再指向不同的函数(m或者n)。

③使用函数指针数组,就像使用普通的指针数组那样,把“[ ]”加在函数名后即可。

④关于auto:auto的效果是根据赋值来源的类型,自动为变量提供类型推断。

例如int a=4; auto b=a; 则b也是int类型。

auto只能用于单值类型初始化,比如在上面那个代码中,加入:auto b=m;

则相当于 int*(*b)(int)=m; 这样

又因为auto只能单值,因此不能auto b={m,n}; 这样,在后面的代码是不能b[0]调用m,b[1]调用n的。

但是,可以:

int*(*abc[2])(int) = { m,n };
//创建一个 函数指针 数组,成员分别指向不同的函数

auto b = abc;

这样,在后面的代码,可以用b[i]代替abc[i]了;

函数指针的地址:

指针赋值:int a; int *b=&a; 这样的格式

重要:函数的名字是函数,字符串、数组的名字也只是字符串和数组;

只不过,在大多数情况下(例如不加地址运算符&),他被转化为指向他本身的指针。

如代码:

#include<iostream>
using namespace std;

int* a1(void);
int* a2(void);
int* a3(void);
int main()
{
int*(*b1)(void);	//b1是一个函数指针,他可以指向a1~a3中的某一个函数
b1 = a1;	//指针b1指向函数a1,则b1输出的是函数a1的地址,
//b1()是调用函数b1,然后得到其返回值(int指针)。
//*b1是b1指针指向的值(依然是函数a1,和b1等价)
//(*b1)()和b1()是等价的。都是函数a1的返回值(这里是一个指针)
//*b1()和*(*b1)()是等价的,都是返回值(是个指针)指向地址的值

auto b2 = a2;
cout << b1() << ": " << *b1() << endl;
cout << (*b2)() << ": " << *(*b2)() << endl;

int*(*c[3])(void) = { a1,a2,a3 };	//创建一个函数指针数组,分别指向a1,a2,a3
//运算符[]的优先级高于*,因此先是一个数组,然后才是指针,所以是一个指针数组。而这个指针数组又是指向函数的,所以是函数指针数组

auto d = c;	//c是一个函数指针数组,auto d=c,于是d也是一个同样类型的数组(函数指针数组,且包含3个成员)

int *x_0 = c[0]();	//c[0]是函数指针数组的第一个成员,他指向函数a1,因此c[0]后面加上()实际上就是调用a1。函数a1的返回值是一个int指针,所以被赋值给x_0这个指针
int *x_1 = (*d[1])();	//d[1]指向a2,加上*,函数a2被转化为一个指针,*d[1]就是*(a2),a2被转化为一个指向自己的函数,于是值还是自己。于是(*d[1])相当于a2,然后加上()就相当于调用函数a2
//然后a2函数的返回值(int类型指针)被赋值给x_1

int y_0 = *c[0]();	//由于()优先级比*高,因此是先调用函数a1,得到返回值(指针),然后解除指针运算,得到的是返回值指向的地址
int y_1 = *(*d[1])();	//同上

auto m = &c;	//m是一个指向(函数指针数组)的指针。
//与上面的d不同,上面的d指针数组被赋值给d,因此d也是一个指针数组。
//这里的m是指针数组的地址被赋值给m,因此m是一个指针(因为地址),他指向的是c这个指针数组。
//因此m(一个指针,指向指针数组的地址)和*m(指针的值——指针数组的地址——指针数组第一个成员的地址),c指针数组(通常被转化为指向这个指针数组的指针)
//&m表示为m指针的地址,&(*m)表示指针数组的地址,&c表示指针数组的地址(同第一个成员的地址),&c[0]指针数组第一个成员的地址
cout << &m << endl;	//m指针的地址
cout << &(*m) << endl;	//指针数组的地址
cout << &c << endl;	//指针数组的地址
cout << &c[0] << endl;	//指针数组第一个成员的地址,同上面
cout << endl;
cout << m << endl;	//指针(表示为指针指向的地址——指针数组的地址)
cout << *m << endl;	//指针的值(指向地址的值——是函数指针数组,又通常被转化为指向他本身的指针,因此同m)
cout << c << endl;	//指针数组,通常被转化为指向他的指针。c[0]同c
cout << endl;

int*(*(*n)[3])(void) = &c;	//n的类型同m
//首先把(*n)看做一个整体,*(*n)[3]中,(*n)这个整体是一个指针数组(包含3个元素的),而n是一个指针,这个指针指向这个整体的指针数组。
//注意对比:int*(*c[3])(void) = { a1,a2,a3 };
//因为n这个指针指向整体的这个指针数组,所以他的值是&c,c是这个指针数组整体的地址
//因为n指向c这个指针数组,所以*n就是指针数组(因为n指向它),因此(*n)[1]就是这个指针数组的第二个成员c[1](指向函数a2)
cout << (*n)[1] << "和" << a2 << endl;

system("pause");
return 0;
}

int*  a1(void)
{
static int a = 1;
int*m = &a;
return m;
}
int* a2(void)
{
static int a = 2;
int*m = &a;
return m;
}
int*  a3(void)
{
static int a = 3;
int*m = &a;
return m;
}


输出:

0087B034: 1
0087B038: 2
0039FE38
0039FE80
0039FE80
0039FE80

0039FE80
0039FE80
0039FE80

008713F2和008713F2
请按任意键继续. . .


解释:

①正如代码前面红字绿字所说,无论是函数、字符串、数组或者其他什么的,他们的名字,并不是地址,只是大多数情况下,被转化为指向他自己的指针。因此,假如函数名字是abc,那么abc、*abc(指向自己的指针的值,还是自己),**abc等,都是函数的地址(因为被转化为指向他自己的指针了)。

②&是显示他的地址。例如&函数名,显示的是函数名的地址,因此,函数a1和函数&a1(代码中没有展现),他们的值是相同的,只不过a1是通过指向他的指针(a1的地址)所展现,而&a1就是输出函数a1的地址。

③指针数组的声明方式是

int*指针数组名[成员个数] = {第一个成员指向的地址, 第二个成员指向的地址, ……}



int *指针名=数组名;

是一个指向数组的指针——他解除运算后,就不是指针了。

举个简单例子,如代码:

int a[3] = { 1,2,3 };

int*b[3] = { &a[0],&a[1],&a[2] };

int*c = a;

cout << *c[0] << endl; //编译器会提示c[0]不是指针

cout << *b[0] << endl;
//但这个b[0]就没问题

c[0]能显示a[0]值的原因在于,c是一个int类型的指针,他指向a[3]这个数组,相当于指向a[3]这个数组的第一个成员,因此c输出的是数组第一个成员的地址。

而c[0]则输出的是这个地址的值([0]有点类似*c),c[1]则是输出这个地址往右偏移一个int距离的地址的值。同理,b[0][0]和*b[0]展现出来的值是一样的。

是不是这个原因不确定,但是结果根据观测,是确定的。

这个代码,其中指针数组b[0],b[1],b[2]分别指向不同的数组a的成员的地址,但不代表其是连续的,即b[2]-b[1]不一定等于b[1]-b[0],因为是三个指针分别赋值(指向不同的地址)。

而c[0],c[1]是连续的,因为相当于偏移了一个int的内存地址距离。因此c[3]指向的是a[2]后面一个int距离的内存地址(输出的是这个地址的值),而b[3]由于没有声明和初始化,所以b[3]是不确定的(并不一定在b[2]后面一个int距离的内存地址)。

使用typedef进行简化:

之前有说过,typedef可以用一个别名替代原名:

例如: typedef 原名 别名;

在某些情况下,可以极大的简化输入。

例如上面,我们要表示一个函数指针,需要例如:

int *(*函数指针)(void); 这样

如果使用typedef,则可以简化,如:

typedef int*(*abc)(void);

这个时候,abc就表示是函数指针,例如abc x_1,表示,x_1是一个函数指针。

如代码:

#include<iostream>
using namespace std;

typedef int *(*abc)(void);	//使用abc,让abc表示函数指针
//另,好像不能用typedef的别名来代替函数原型和函数头

int* a_1(void);	//a_1函数

int main()
{
abc m1 = a_1;	//m1指向函数a_1
cout << *m1() << endl;	//m1()调用函数a_1(得到返回值,是一个指针),加*则解除指针,输出值

system("pause");
return 0;
}

int* a_1(void)
{
int a = 10;
int *b = &a;
return b;
}


输出:

10
请按任意键继续. . .


总结:

①个人推测:假如使用typedef,将例如abc之类的代码,放在原本应该是这个类型的名字(函数名、字符串名、变量名等)之类的位置。那么这个例如是abc的代码,就是该类型的别名,

使用方法是:abc之类的代码 def之类名字

就是指def是abc所表示的类型。

如上面代码中,abc表示是函数指针的别名,则abc m1也是函数指针。

②指针数组也可以用typedef来使用别名。例如代码:

typedef int *(*abc[2])(void);
//使用abc来作为此类指针数组的别名

abc m = { a_1,a_2 };
//m这个指针数组分别指向函数a_1和函数a_2
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: