(九十九)函数指针
2015-12-26 19:05
169 查看
与数据项(比如int、string、char等)相似,函数也有地址。
函数的地址是存储其机器语言代码的内存的开始地址(不懂)。
这些地址对用户而言,没什么用,也不重要,但对程序而言,却很有用。
例如,可以编写将另外一个函数的地址作为参数的函数。这样,第一个函数将能够找到第二个函数,并运行它。
与直接调用第二个函数相比,这种方法很笨拙,但他允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。
函数指针和指针函数的区别:
函数指针:指向某个函数的指针(即这个指针指向函数的地址);
指针函数:返回值是指针的函数
函数指针的基础知识:
函数的地址:
函数名 就是函数的地址(像字符串一样);
声明函数指针:
有些像函数返回指针,但是要把*和函数名用括号括起来。例如:函数类型 (*函数名) (参数);
注意:①声明的函数指针,要和函数的特征标(例如类型、函数的参数类型)要相符;
②*函数名 此时是函数,而 函数名 此时是指针。
函数指针作为参数时:
函数类型 函数名(函数类型 (*函数名)(函数指针的参数类型) );
注:蓝色部分为函数指针作为参数时的写法。
调用函数指针时:
和声明函数指针类似,用函数指针的 (*函数名) 取代普通函数 函数名 即可。
如:(*函数名)(函数指针的参数) 这样。
也可以像使用普通函数那样,通过 函数名(参数) 这样调用函数,C++是允许这样做的。
给函数指针赋值时:
首先,函数指针和函数的特征标要相符。
然后例如代码节选:
void ab(int);//函数原型
void(*c)(int);
//函数指针原型
c = ab; //让函数指针c指向函数ab。这行代码一般在函数里
这样。
无返回值、将函数指针赋值、声明函数指针、调用函数指针,如代码:
输出:
解释:
①这行代码就是函数指针指向不同函数时,同样的变量作为参数,却执行不同的函数。
有返回值、将函数指针作为函数的参数、不同情况下指向不同的函数、使用函数的地址,如代码:
输出:
总结:
①以上两部分代码,涉及到了函数的地址(即函数名)、声明函数指针、函数指针作为参数、调用函数指针、给函数指针赋值 这五个概念;
②可以在函数内部声明函数指针,然后根据变量的不同,让指针指向其他不同的函数,再执行对应的函数,并将对应函数的返回值作为函数的返回值。
如代码:
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)。
当函数返回值为指针的函数指针:
可以发现,无论是指向普通函数,还是指向返回值为指针的函数,函数指针都是用(*指针名)替代函数名。就像(*m)替代abc 一样。
不同参数让指针指向不同的函数,代码:
输出:
解释:
①当参数i(根据for循环而变)能否被2整除,让函数指针指向不同的函数;
声明一个函数指针数组:
除了上面声明一个函数指针,利用if语句使得指针指向不同函数,也可以声明一个函数指针数组,然后修改数组的成员标号,来让其指向不同的函数。
例如:
函数类型* (*函数名[函数指针成员个数]) (参数类型) ={ 函数1, 函数2};
可以将上面的代码
改为:
输出结果是相同的,但减少了代码量。
原理:
①不同函数指针数组成员指向不同函数;
②然后利用成员编号(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; 这样的格式
重要:函数的名字是函数,字符串、数组的名字也只是字符串和数组;
只不过,在大多数情况下(例如不加地址运算符&),他被转化为指向他本身的指针。
如代码:
输出:
解释:
①正如代码前面红字绿字所说,无论是函数、字符串、数组或者其他什么的,他们的名字,并不是地址,只是大多数情况下,被转化为指向他自己的指针。因此,假如函数名字是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是一个函数指针。
如代码:
输出:
总结:
①个人推测:假如使用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
函数的地址是存储其机器语言代码的内存的开始地址(不懂)。
这些地址对用户而言,没什么用,也不重要,但对程序而言,却很有用。
例如,可以编写将另外一个函数的地址作为参数的函数。这样,第一个函数将能够找到第二个函数,并运行它。
与直接调用第二个函数相比,这种方法很笨拙,但他允许在不同的时间传递不同函数的地址,这意味着可以在不同的时间使用不同的函数。
函数指针和指针函数的区别:
函数指针:指向某个函数的指针(即这个指针指向函数的地址);
指针函数:返回值是指针的函数
函数指针的基础知识:
函数的地址:
函数名 就是函数的地址(像字符串一样);
声明函数指针:
有些像函数返回指针,但是要把*和函数名用括号括起来。例如:函数类型 (*函数名) (参数);
注意:①声明的函数指针,要和函数的特征标(例如类型、函数的参数类型)要相符;
②*函数名 此时是函数,而 函数名 此时是指针。
函数指针作为参数时:
函数类型 函数名(函数类型 (*函数名)(函数指针的参数类型) );
注:蓝色部分为函数指针作为参数时的写法。
调用函数指针时:
和声明函数指针类似,用函数指针的 (*函数名) 取代普通函数 函数名 即可。
如:(*函数名)(函数指针的参数) 这样。
也可以像使用普通函数那样,通过 函数名(参数) 这样调用函数,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); |
不同参数让指针指向不同的函数,代码:
#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
相关文章推荐
- 关于安卓你不知道的6件事
- lucene.net helper类 【结合盘古分词进行搜索的小例子(分页功能)】
- extern "c"作用
- SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed
- [DT] 数据结构术语中英文对照
- OC学习小结之数据类型间转化
- IPayablebillItf
- 平方之哈希表
- solr中文分词器
- Yocto i.MX6 (TQIMX6) (04) : 使用mjpg-streamer做一个WebCam Server
- 如何用C语言封装 C++的类,在 C里面使用
- python 语法常用 lambda
- 基础:从概念理解Lucene的Index(索引)文档模型
- Mac上的软件的一些对开发者有用的使用技巧(持续更新)
- Android项目欢迎界面实现方式及代码
- kbengine编译相关问题
- 设计模式目录
- lucene之排序、设置权重、优化、分布式搜索(转)
- 记JAVA的二三事(1)——"==","引用"
- 算法设计题2.16~2.18-线性表-第2章-《数据结构习题集》-严蔚敏吴伟民版