C++函数指针详解

来源:http://www.cnblogs.com/ggjucheng/archive/2011/12/13/2286391.html

指针的概念

指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。让我们分别说明。 

先声明几个指针放着做例子:  

int *ptr;  
char *ptr;  
int **ptr;  
int (*ptr)[3];  
int *(*ptr)[4];  

指针的类型

从语法的角度看,只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型这是指针本身所具有的类型

int *ptr; //指针的类型是int *  
char *ptr; //指针的类型是char *  
int **ptr; //指针的类型是 int **  
int (*ptr)[3]; //指针的类型是 int(*)[3]  
int *(*ptr)[4]; //指针的类型是 int *(*)[4]  
怎么样?找出指针的类型的方法是不是很简单?  

指针所指向的类型

当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:  

int *ptr; //指针所指向的类型是int  
char *ptr; //指针所指向的的类型是char  
int **ptr; //指针所指向的的类型是 int *  
int (*ptr)[3]; //指针所指向的的类型是 int()[3]  
int *(*ptr)[4]; //指针所指向的的类型是 int *()[4]  
在指针的算术运算中,指针所指向的类型有很大的作用。  

指针的类型(即指针本身的类型)指针所指向的类型是两个概念。当你对C越来越熟悉时,你会发现,把与指针搅和在一起的“类型”这个概念分成“指针的类型”和“指针所指向的类型”两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。

指针的值

指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。 

指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。 

指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。 

以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指向的类型是什么?该指针指向了哪里?  

指针本身所占据的内存区

指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。
指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。  

指针的算术运算

指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例如:

char a[20];  
int *ptr=a;  
...  
...  
ptr++;  
在上例中,指针ptr的类型是int*,它指向的类型是int,它被初始化为指向整形变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字节做单位的,故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。
由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,此时指向了数组a中从第4号单元开始的四个字节。
我们可以用一个指针和一个循环来遍历一个数组,看例子:  
int array[20];  
int *ptr=array;  
...  
//此处略去为整型数组赋值的代码。  
...  
for(i=0;i<20;i++)  
{  (*ptr)++;  ptr++;  
}  
这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1,所以每次循环都能访问数组的下一个单元。再看例子:  
char a[20];  
int *ptr = a;  
...  
...  
ptr += 5;  

在这个例子中,ptr被加上了5,编译器是这样处理的:将指针ptr的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20。由于地址的单位是字节,故现在的ptr所指向的地址比起加5后的ptr所指向的地址来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ptr指向数组a的第0号单元开始的四个字节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。 

如果上例中,ptr是被减去5,那么处理过程大同小异,只不过ptr的值是被减去5乘sizeof(int),新的ptr指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。 

总结一下,一个指针ptrold加上一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值增加了n乘sizeof(ptrold所指向的类型)个字节。就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向高地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。一个指针ptrold减去一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节,就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向低地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。

运算符&和*

这里&是“取地址”运算符,*是...书上叫做“间接运算符”。&a的运算结果是一个指针,指针的类型是a的类型加个*指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。*p的运算结果就五花八门了。总之*p的结果是p所指向的东西,这个东西有这些特点:它的类型是p指向的类型,它所占用的地址是p所指向的地址。

int a=12;  
int b;  
int *p;  
int **ptr;  
p=&a;//&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a的地址。  
*p=24;//*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地址,显然,*p就是变量a。
ptr=&p;//&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int**。该指针所指向的类型是p的类型,这里是int*。该指针所指向的地址就是指针p自己的地址。 
*ptr=&b;//*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以?amp;b来给*ptr赋值就是毫无问题的了。
**ptr=34;//*ptr的结果是ptr所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果就是一个int类型的变量。

指针表达式

一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表达式。下面是一些指针表达式的例子:  

int a,b;  
int array[10];  
int *pa;  
pa=&a;//&a是一个指针表达式。  
int **ptr=&pa;//&pa也是一个指针表达式。  
*ptr=&b;//*ptr和&b都是指针表达式。  
pa=array;  
pa++;//这也是指针表达式。
char *arr[20];  
char **parr=arr;//如果把arr看作指针的话,arr也是指针表达式  
char *str;  
str=*parr;//*parr是指针表达式  
str=*(parr+1);//*(parr+1)是指针表达式  
str=*(parr+2);//*(parr+2)是指针表达式  

指针表达式的结果是一个指针。
所以,指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。

好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。 在例七中,&a不是一个左值,因为它还没有占据明确的内存。*ptr是一个左值,因为*ptr这个指针已经占据了内存,其实*ptr就是指针pa,既然pa已经在内存中有了自己的位置,那么*ptr当然也有了自己的位置。

数组和指针的关系

如果对声明数组的语句不太明白的话,请参阅我前段时间贴出的文章<<如何理解c和c++的复杂类型声明>>。 数组的数组名其实可以看作一个指针。看下例: 

int array[10]={0,1,2,3,4,5,6,7,8,9},value;  
...  
...  
value=array[0];//也可写成:value=*array;  
value=array[3];//也可写成:value=*(array+3);  
value=array[4];//也可写成:value=*(array+4);  
上例中,一般而言数组名array代表数组本身,类型是int [10],但如果把array看做指针的话,它指向数组的第0个单元,类型是int *,所指向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪了。同理,array+3是一个指向数组第3个单元的指针,所以*(array+3)等于3。其它依此类推。 
char *str[3]={  
"Hello,this is a sample!",  
"Hi,good morning.",  
"Hello world"  
};  
char s[80];  
strcpy(s,str[0]);//也可写成strcpy(s,*str);  
strcpy(s,str[1]);//也可写成strcpy(s,*(str+1));  
strcpy(s,str[2]);//也可写成strcpy(s,*(str+2));  
上例中,str是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str当作一个指针的话,它指向数组的第0号单元,它的类型是char**,它指向的类型是char *。

*str也是一个指针,它的类型是char*,它所指向的类型是char,它指向的地址是字符串"Hello,this is a sample!"的第一个字符的地址,即'H'的地址。 str+1也是一个指针,它指向数组的第1号单元,它的类型是char**,它指向的类型是char *。 

*(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,good morning."的第一个字符'H',等等。  

下面总结一下数组的数组名的问题。声明了一个数组TYPE array[n],则数组名称array就有了两重含义:第一,它代表整个数组,它的类型是TYPE [n];第二,它是一个指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0号单元,该指针自己占有单独的内存区,注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。 

在不同的表达式中数组名array可以扮演不同的角色。  
在表达式sizeof(array)中,数组名array代表数组本身,故这时sizeof函数测出的是整个数组的大小。  
在表达式*array中,array扮演的是指针,因此这个表达式的结果就是数组第0号单元的值。sizeof(*array)测出的是数组单元的大小。  

表达式array+n(其中n=0,1,2,....。)中,array扮演的是指针,故array+n的结果是一个指针,它的类型是TYPE*,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。 

int array[10];  
int (*ptr)[10];  
ptr=&array;  
ptr是一个指针,它的类型是int (*)[10],他指向的类型是int [10],我们用整个数组的首地址来初始化它。在语句ptr=&array中,array代表数组本身。 
本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小?答案是前者。例如:
int (*ptr)[10];  
则在32位程序中,有:  
sizeof(int(*)[10])==4  
sizeof(int [10])==40  
sizeof(ptr)==4  
实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。  

指针和结构类型的关系

可以声明一个指向结构类型对象的指针。  

struct MyStruct  
{  
int a;  
int b;  
int c;  
}  MyStruct ss={20,30,40};//声明了结构对象ss,并把ss的三个成员初始化为20,30和40。
MyStruct *ptr=&ss;//声明了一个指向结构对象ss的指针。它的类型是
MyStruct*,它指向的类型是MyStruct。
int *pstr=(int*)&ss;//声明了一个指向结构对象ss的指针。但是它的类型和它指向的类型和ptr是不同的。
请问怎样通过指针ptr来访问ss的三个成员变量?  
答案:  
ptr->a;  
ptr->b;  
ptr->c;  
又请问怎样通过指针pstr来访问ss的三个成员变量?  
答案:  
*pstr;//访问了ss的成员a。  
*(pstr+1);//访问了ss的成员b。  
*(pstr+2)//访问了ss的成员c。 
呵呵,虽然我在我的MSVC++6.0上调式过上述代码,但是要知道,这样使用pstr来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元:
int array[3]={35,56,37};  
int *pa=array;  
通过指针pa访问数组array的三个单元的方法是:  
*pa;//访问了第0号单元  
*(pa+1);//访问了第1号单元  
*(pa+2);//访问了第2号单元  
从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。

所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,不同编译器可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个“填充字节”,这就导致各个成员之间可能会有若干个字节的空隙。这就是内存对齐。

所以,在例十二中,即使*pstr访问到了结构对象ss的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a和成员b之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节,嘿,这倒是个不错的方法。 通过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。

指针和函数的关系(函数指针)

可以把一个指针声明成为一个指向函数的指针。  

int fun1(char*,int);  
int (*pfun1)(char*,int);  
pfun1=fun1;  
....  
....  
int a = (*pfun1)("abcdefg",7);//通过函数指针调用函数。或者 int a = pfun1("abcdefg",7)。但是推荐使用int a = (*pfun1)("abcdefg",7)
与数据一样,函数也有地址,函数的地址就是内存中存放函数语言代码的起始地址函数指针就是指向这个地址。函数指针所指向的类型,就是函数本身。我们知道,指针所指向类型代表了指针所指向的内存区域的大小。所以函数指针所指向的类型,就是函数在内存中所占据内存的大小。知道了函数的起始地址和大小,所以函数指针可以很轻易的代替函数完成函数调用。

#include <string.h>  
#include <stdio.h>  
typedef int* PINNT;  
#define PP int*  int funcA(int a,int b);  
int funcB(int* a,int *b);  
int  main(int argc, char *argv[])  
{  int (*func)(int,int);  //func = &funcA;  func = funcA;  //两种赋值给函数指针的方法都可以  printf("%d",func(1,10));  //printf("%d",(*func)(1,10));  //两种调用函数指针的方法都可以  //两种赋值方法和两种调用方法可以任选一种组合        
}  int funcA(int a,int b)  
{  return a + b;  
}  int funcB(int* a,int *b)  
{  (*a) = (*a) + (*b);  return 1;  
}  
#include <iostream>
using namespace std;int test(int, int);
//自定义一个新类型,类型名是NewType,自身类型是int ()(int,int)
typedef int (NewType)(int,int);int main(void)
{int (*pFunc)(int,int);       //定义一个函数指针 pFuncpFunc = test;                //或者 pFunc = &test;cout<<(*pFunc)(10,20)<<endl; // 或者 pFunc(10,20);/* 注意 和 函数指针 区别 */NewType* newType = test;     //或者 NewType* newType = &test;cout<<newType(30,40)<<endl;  //或者 (*newType)(30,40);   return 0;
}int test(int a, int b)
{cout<<"a:"<<a<<endl<<"b:"<<b<<endl;return a+b;
}

一、普通函数指针

通常我们所说的函数指针指的是指向一般普通函数的指针。和其他指针一样,函数指针指向某种特定类型,所有被同一指针运用的函数必须具有相同的形参类型和返回类型。

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

这里,pf指向的函数类型是int (int, int),即函数的参数是两个int型,返回值也是int型。

注意:*pf两端的括号必不可少,如果不写这对括号,则pf是一个返回值为int指针的函数。

二、成员函数指针

定义:typedef 返回类型(类名::*新类型)(参数表)

一句话,使用类成员函数指针必须有“->*”或“.*”的调用。

成员函数指针(member function pointer)是指可以指向类的非静态成员函数的指针。类的静态成员不属于任何对象,因此无须特殊的指向静态成员的指针,指向静态成员的指针与普通指针没有什么区别。与普通函数指针不同的是,成员函数指针不仅要指定目标函数的形参列表和返回类型,还必须指出成员函数所属的类。因此,我们必须在*之前添加classname::以表示当前定义的指针指向classname的成员函数:

int (A::*pf)(int, int);  // 声明一个成员函数指针 

同理,这里A::*pf两端的括号也是必不可少的,如果没有这对括号,则pf是一个返回A类数据成员(int型)指针的函数。注意:和普通函数指针不同的是,在成员函数指向该成员的指针之间不存在自动转换规则。所以,必须显式地使用取址运算符(&)

pf = &A::add;  // 正确:必须显式地使用取址运算符(&) 
pf = A::add;  // 错误 

当我们初始化一个成员函数指针时,其指向了类的某个成员函数,但并没有指定该成员所属的对象——直到使用成员函数指针时,才提供成员所属的对象。下面是一个成员函数指针的使用示例:
class A;
typedef int (A::*pClassFun)(int, int); // 成员函数指针类型class A{
public:int add(int m, int n){cout << m << " + " << n << " = " << m+n << endl;return m+n;}int mns(int m, int n){cout << m << " - " << n << " = " << m-n << endl;return m-n;}int mul(int m, int n){cout << m << " * " << n << " = " << m*n << endl;return m*n;}int dev(int m, int n){cout << m << " / " << n << " = " << m/n << endl;return m/n;}int call(pClassFun fun, int m, int n){// 类内部接口return (this->*fun)(m, n);}
};int call(A obj, pClassFun fun, int m, int n)
{   // 类外部接口return (obj.*fun)(m, n);
}int call(A* pObj,pClassFun fun, int m, int n)
{   // 类外部接口return (pObj->*fun)(m, n);
}int main()
{A a;cout << "member function 'call':" << endl;a.call(&A::add, 8, 4);a.call(&A::mns, 8, 4);a.call(&A::mul, 8, 4);a.call(&A::dev, 8, 4);cout << "external function 'call(A obj, pClassFun fun, int m, int n)':" << endl;call(a, &A::add, 9, 3);call(a, &A::mns, 9, 3);call(a, &A::mul, 9, 3);call(a, &A::dev, 9, 3);cout << "external function 'call(A* pObj,pClassFun fun, int m, int n)':" << endl;call(&a, &A::add, 9, 3);call(&a, &A::mns, 9, 3);call(&a, &A::mul, 9, 3);call(&a, &A::dev, 9, 3);return 0;
}
如示例所示,我们一样可以使用typedef定义成员函数指针的类型别名。另外,我们需要留意函数指针的使用方法:对于普通函数指针,是这样使用(*pf)(arguments),因为要调用函数,必须先解引用函数指针,而函数调用运算符()的优先级较高,所以(*pf)的括号必不可少;对于成员函数指针,唯一的不同是需要在某一对象上调用函数,所以只需要加上成员访问符即可:
(obj.*pf)(arguments)     // obj 是对象 
(objptr->*pf)(arguments)   // objptr是对象指针  

三、函数表驱动

对于普通函数指针和指向成员函数的指针来说,一种常见的用法就是将其存入一个函数表(function table)当中。当程序需要执行某个特定的函数时,就从表中查找对应的函数指针,用该指针来调用相应的程序代码,这个就是函数指针在表驱动法中的应用。

表驱动法(Table-Driven Approach)就是用查表的方法获取信息。通常,在数据不多时可用逻辑判断语句(if…else或switch…case)来获取信息;但随着数据的增多,逻辑语句会越来越长,此时表驱动法的优势就体现出来了。

#include<iostream> 
#include<string> 
#include<map> 
using namespace std; class A; 
typedef int (A::*pClassFun)(int, int); class A{ 
public: A(){  // 构造函数,初始化表 table["+"] = &A::add; table["-"] = &A::mns; table["*"] = &A::mul; table["/"] = &A::dev; } int add(int m, int n){ cout << m << " + " << n << " = " << m+n << endl; return m+n; } int mns(int m, int n){ cout << m << " - " << n << " = " << m-n << endl; return m-n; } int mul(int m, int n){ cout << m << " * " << n << " = " << m*n << endl; return m*n; } int dev(int m, int n){ cout << m << " / " << n << " = " << m/n << endl; return m/n; } // 查找表,调用相应函数 int call(string s, int m, int n){ return (this->*table[s])(m, n); } 
private: map<string, pClassFun> table; // 函数表 
}; // 测试 
int main() 
{ A a; a.call("+", 8, 2); a.call("-", 8, 2); a.call("*", 8, 2); a.call("/", 8, 2); return 0; 
}
使用类型定义 
  可以用类型定义来隐藏复杂的成员指针语法。例如,下面的语句定义了PMA是一个指向A成员函数的指针,函数返回无类型值,函数参数类型为char * 和 const char *: 
  typedef void(A::*PMA)(char *, const char *); 
  PMA pmf= &A::strcat; // pmf是PMF类型(类A成员指针)的变量 
  下文会看到使用类型定义特别有利于声明成员指针数组。 
A a1, a2;
A *p= &a1; //创建指向A的指针
//创建指向成员的指针并初始化
void (A::*pmf)(char *, const char *) = &A::strcpy;
//要将成员函数绑定到pmf,必须定义呼叫的对象。
//可以用*号引导:void dispatcher(A a, void (A::*pmf)(char *, const char *))
{char str[4];(a.*pmf)(str, “abc”); //将成员函数绑定到pmf
}//或用A的指针表达方式指向成员指针:
void dispatcher(A * p, void (A::*pmf)(char *, const char *))
{char str[4]; (p->*pmf)(str, “abc”);
}//函数的调用方法为:
dispatcher(a, pmf);  // .* 方式1
dispatcher(&a, pmf); // ->* 方式2 
高级使用技巧 
  以上是成员函数的基本知识。现在介绍它的高级使用技巧。 
   成员指针数组  
  在下例,声明了一个含有二个成员指针的数组,并分配类的成员函数地址给成员指针: 
  PMA pmf[2]= {&A::strcpy, &A::strcat}; 
也就是
      void (A::*PMA[2])(char *, const char *)= {&A::strcpy, &A::strcat}; 
  这样的数组在菜单驱动应用中很有用。选择菜单项后,应用将调用相应的回叫函数,如下所示: 
enum MENU_OPTIONS { COPY, CONCAT };
int main()
{MENU_OPTIONS option; char str[4];//从外部资源读取选项switch (option){case COPY:        (pa->*pmf[COPY])(str, “abc”);        break;        case CONCAT:        (pa->*pmf[CONCAT])(str, “abc”);        break;        //…        }
} 
Const 类型的成员函数 
  成员指针的类型应该与成员函数类型一致。上面例子中的pmf 可以指向A的任意函数,只要该函数不是const类型。如下所示,如果将touppercase()的地址分配给pmf,将导致编译出错,因为touppercase() 的类型是const。 
class A
{    
public:    void strpcy(char *, const char *);    void strcat(char *, const char *);    void touppercase(char *, const char*) const;    
};pmf=&A::touppercase; //出错,类型不匹配//解决的方法是声明一个const类型的成员指针:
void (A::pcmf)(char *, const char *) const;pcmf=&A::touppercase; // 现在可以了 

一、最简单的函数指针

  变量都包括声明和赋值,指针不例外,函数指针也不例外。我们来看一个简单的函数:

void add(int a, int b){cout << a + b << endl;
}

一个简单的加法计算并输出到命令行的函数。那么如何通过函数指针来调用它呢?

1、声明:

void (*p1)(int a, int b);

函数指针的声明很简单,基本就是通过一个指针把函数名替换。指针p1的类型为void (*) (int a,int b),表明指针是一个指向某个函数的指针,指针指向的类型为void () (int a,int b)

2、赋值:

p1 = add;

3、也可以直接定义:

void (*p1)(int a, int b) = add;  

注意,函数void add(int a,int b)的函数名add就是函数的地址。将地址add赋值给指针p1,那么就可以通过函数指针p1直接调用函数了。

4、调用:

(*p1)(1, 2);
p1(1, 2);
注意!出于历史原因以上2种方式都是可以调用函数的。

二、包含多个函数指针的数组

  有时候有这种情况,有一个数组,数组中的每个元素都是一个函数指针,该怎么定义这个数组呢?

  1、解释*p[n]和(*p)[n]

  我们知道,[]运算符的优先级要高于*,所以,p[3]表示含有3个元素的数组,而*p[3] 前面的 " * " 指明了数组中元素的类型,即*p[3]表示一个指向3个指针的数组。 

  p[3]表示含有3个元素的数组,那么(*p)[3]就是用 *p 替换了 p,很容易想到,(*p)[3] 表示指向一个包含3个元素的数组的指针。

  2、声明:

void (*p2[2])(int a, int b);

  数组名为p2,数组大小为2,数组中元素类型为void (*)(int a, int b),表明元素是一个指向某个函数的指针,指针指向的类型为void () (int a,int b)。

  3、赋值: 

p2[1] = add;

  理解上跟上面是一样的。

  4、调用: 

p2[1](2,3);
(*p2[1])(3,4); 
  同样是2种方式都可以。

三、指向“包含多个函数指针的数组“的指针

  这个标题好像有点拗口。简而言之,这个指针指向上文中的 “包含多个函数指针的数组” 。其实很简单,说白了,就是把上文中的p2用一个指针来代替。

  1、声明:

void (*(*p3)[2])(int a, int b);

   可以看到,无非就是把p2用*p3代替。

  2、赋值,注意,既然是指针,使用前必须初始化:

p3 = &p2;
(*p3)[1] = add;

  注意!既然实质上就是把p2用*p3代替,c++11可以很简单的这样直接定义:auto p3 = &p2; 代替了void (*(*p3)[2])(int a, int b)= &p2;

  3、调用:

(*p3)[1](1, 2);
(*(*p3)[1])(1, 2);


1.     定义

每一个函数都占用一段内存单元,它们有一个起始地址,指向函数入口地址的指针称为函数指针。

2.     语法

指向函数的指针变量的一般定义形式为:数据类型 (*指针变量名)(参数表);

3.     说明

    1) 函数指针的定义形式中的数据类型是指函数的返回值的类型。

    2) 区分下面两个语句:

        int (*p)(int a, int b); //p是一个指向函数的指针变量,所指函数的返回值类型为整型

        int *p(int a, int b); //p是函数名,此函数的返回值类型为整型指针

    3) 指向函数的指针变量不是固定指向哪一个函数,只是定义一个这样类型的变量,专门用来存放函数入口地址;程序中把哪一个函数的地址赋给它,它就指向哪一个函数。

    4) 在给函数指针变量赋值时,只需给出函数名,而不必给出参数。

        如函数max的原型为:int max(int x, int y); 指针p的定义为:int (*p)(int a, int b); 则p = max;的作用是将函数max的入口地址赋给指针变量p。

        这时,p就是指向函数max的指针变量,也就是p和max都指向函数的开头。

    5) 在一个程序中,指针变量p可以先后指向不同的函数,但一个函数不能赋给一个不一致的函数指针(即不能让一个函数指针指向与其类型不一致的函数)。

        如有如下的函数:int fn1(int x, int y); int fn2(int x);

        定义如下的函数指针:int (*p1)(int a, int b); int (*p2)(int a);

        则

        p1 = fn1; //正确

        p2 = fn2; //正确

        p1 = fn2; //产生编译错误

    6) 定义了一个函数指针并让它指向了一个函数后,对函数的调用可以通过函数名调用,也可以通过函数指针调用(即用指向函数的指针变量调用)。

        如语句:c = (*p)(a, b); //表示调用由p指向的函数(max),实参为a,b,函数调用结束后得到的函数值赋给c。

    7) 函数指针只能指向函数的入口处,而不可能指向函数中间的某一条指令。不能用*(p+1)来表示函数的下一条指令。

    8) 函数指针变量常用的用途之一是把指针作为参数传递到其他函数。

#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
using namespace std;int max(int, int); //在“声明”时形参名可以省略,“定义”时只要形参类型相同,就认为是同一个函数
int min(int, int); 
int add(int x, int y); 
void process(int i, int j, int (*p)(int, int)); //应用函数指针int max(int x, int y){return x > y ? x : y;} //定义时指定 形参名
int min(int x, int y){return x > y ? y : x;}
int add(int x, int y){return x + y;}void process(int i, int j, int (*p)(int a, int b))
{/* 函数指针带不带*都可以操作所指向的函数 。 但是 最好 加上*号 以用来指示这是一个指针 */cout<<p(i, j)<<endl; //直接 不使用 * 操作cout<<(*p)(i, j)<<endl; // 使用 * 也可以操作cout<<"************************"<<endl;
}int main(void)
{int x=100, y=200;//cout<<"input x and y value(x y):";//cin>>x>>y;cout<<"Max is: ";process(x, y, max);cout<<"Min is: ";process(x, y, min);cout<<"Add is: ";process(x, y, add);getch();return 0;
}






本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/496792.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

增大表名最大长度_风电叶片材料的发展变迁史,及风力发电机叶片长度发展历程...

2020年&#xff0c;能源领域最火的行业是风电&#xff0c;由于中国出台相关文件&#xff0c;截止12月31日为止&#xff0c;之后装机并网的风电机组将不能够获得电价补贴。这个政策直接推动了国内风电市场的短期井喷。当然宏观因素背景是中国对于环保型电力能源的改革步伐在加快…

重磅,Intel考虑收购博通

来源&#xff1a;腾讯科技概要&#xff1a;3月10日消息&#xff0c;据外媒报道&#xff0c;《华尔街日报》援引知情人士的话报道称&#xff0c;英特尔&#xff08;Intel&#xff09;正在考虑一系列收购方案&#xff0c;包括收购芯片制造商博通&#xff08;Broadcom&#xff09;…

C++学习之路 | PTA乙级—— 1073 多选题常见计分法 (20 分)(精简)

1073 多选题常见计分法 (20 分) 批改多选题是比较麻烦的事情&#xff0c;有很多不同的计分方法。有一种最常见的计分方法是&#xff1a;如果考生选择了部分正确选项&#xff0c;并且没有选择任何错误选项&#xff0c;则得到 50% 分数&#xff1b;如果考生选择了任何一个错误的选…

Java并发编程实战~volatile

禁用CPU 缓存 告诉编译器&#xff0c;对这个变量的读写&#xff0c;不能使用 CPU 缓存&#xff0c;必须从内存中读取或者写入 /*** TODO 在此写上类的相关说明.<br>* author gqltt<br>* version 1.0.0 2020年4月8日<br>* see * since JDK 1.5.0*/ public c…

如何在SQL Server 2005中还原数据库

还原数据库的方式 有几种数据库备份的方式就将会有几种还原数据库的 方式&#xff1a; l 完整备份的还原&#xff1a;无论是完整备份、差异备份还是事务日志备份的还原&#xff0c;在第一步都要先做完整备份的还原。完整备份的还原只需要还原完整备份文件即可。 l 差异备份的还…

amd cpu不能在cmd环境下运行java代码_如何在Windows10中配置java的JDK环境

今天给大家分享一下如何配置java的JDK环境。操作步骤如下&#xff1a;1.下载好 jdk 的安装文件&#xff0c;我下载的是 jdk-10.0.1_windows-x64_bin.exe 这个版本的安装文件&#xff1b;2.使用鼠标双击该exe文件&#xff0c;该exe文件会运行安装界面&#xff0c;截图如下&#…

我国医疗机器人产业发展特征分析

来源&#xff1a;雷克世界导语&#xff1a;2016年4月&#xff0c;我国发布了《机器人产业发展规划&#xff08;2016-2020年&#xff09;》&#xff0c;该规划引导我国机器人产业快速健康可持续发展&#xff0c;增强技术创新能力和国际竞争能力&#xff0c;医疗机器人政策长期利…

位枚举(Bit Flags)

场景&#xff1a;如字体&#xff0c;一个字体可以同时拥有枚举里面所列举的一种或者多种风格&#xff0c;这时就需要位枚举 定义&#xff1a; [Flags] publicenumFontStyle { Bold 0x0001, Italic 0x0002, Regular 0x0004, …

C++学习之路 | PTA乙级—— 1074 宇宙无敌加法器 (20 分)(精简)

1074 宇宙无敌加法器 (20 分) 地球人习惯使用十进制数&#xff0c;并且默认一个数字的每一位都是十进制的。而在 PAT 星人开挂的世界里&#xff0c;每个数字的每一位都是不同进制的&#xff0c;这种神奇的数字称为“PAT数”。每个 PAT 星人都必须熟记各位数字的进制表&#xff…

Java并发编程实战~final

变量生而不变&#xff0c;允许优化&#xff0c;JDK1.5 pre 避免&#xff0c;逸出 final int x; // 错误的构造函数 public FinalFieldExample(){x 3;// 此处就是将this逸出global.obj this; }

华为鸿蒙麒麟玉兔_华为P50除了麒麟9000,还预装鸿蒙系统,比iPhone12值得买

2020年已经临近尾声&#xff0c;各大手机厂商的旗舰机均悉数亮相&#xff0c;消费者的目光也开始逐渐转向2021年的开年旗舰上。而在各大手机厂商的第一批开年旗舰中&#xff0c;华为P系列的新品无疑是最受关注的一款。这主要是因为华为目前仍处于美方制裁下&#xff0c;芯片危机…

2018年智能化发展趋势:语音交互全球开战、AI终端趋势显现

来源&#xff1a;雪球网 作者&#xff1a;西木财经美国知名研究机构CB Insights近日发布重磅报告《2018年必看的人工智能热门趋势》&#xff08;Top AI Trends To Watch In 2018&#xff09;&#xff0c;报告对AI行业发展现状进行了深入研究剖析&#xff0c;并给出了2018年AI…

C++ 对象的内存布局

来源&#xff1a;http://blog.csdn.net/haoel/article/details/3081328 前言 07年12月&#xff0c;我写了一篇《C虚函数表解析》的文章&#xff0c;引起了大家的兴趣。有很多朋友对我的文章留了言&#xff0c;有鼓励我的&#xff0c;有批评我的&#xff0c;还有很多问问题的。…

C++学习之路 | PTA乙级—— 1075 链表元素分类 (25 分)(精简)

1075 链表元素分类 (25 分) 给定一个单链表&#xff0c;请编写程序将链表元素进行分类排列&#xff0c;使得所有负值元素都排在非负值元素的前面&#xff0c;而 [0, K] 区间内的元素都排在大于 K 的元素前面。但每一类内部元素的顺序是不能改变的。例如&#xff1a;给定链表为 …

Java并发编程实战~Happens-Before 规则

Happens-Before 规则 前面一个操作的结果对后续操作是可见的 所以比较正式的说法是&#xff1a; Happens-Before 约束了编译器的优化行为&#xff0c;虽允许编译器优化&#xff0c;但是要求编译器优化后一定遵守Happens-Before 规则。 程序的顺序性规则 这条规则是指在一个线…

VB.NET 中的 As New 以及型別指定

常有人以為 VB.NET 程式的執行效能不如 C#&#xff0c;但根據 msdn 的說法&#xff0c;VB.NET 和 C# 都是編譯成 MSIL 中繼語言&#xff0c;因此基本上以二者所寫出來的應用程式也具有相同的效能。最常導致二者在 web 應用程式中&#xff0c;執行效能差異的罪魁禍首&#xff0c…

普华永道:2018 AI预测报告 将回答关于数据的重大问题

来源&#xff1a;网络大数据近日 &#xff0c;普华永道发布关于2018年人工智能趋势预测的报告&#xff0c;同时介绍人工智能对商业、政府和社会的等方面的影响。人工智能非常复杂&#xff0c;且发展迅速。AI 在一些领域做了很多&#xff0c;在另一些领域做得较少&#xff0c;这…

过渡效果_剪映教程:剪映怎么添加视频之间的过渡转场效果?

今天是国庆假期的第三天&#xff0c;直播apk小编今天确实郁闷的无比&#xff0c;因为今天小编所在的老家正在下雨&#xff0c;而且从上午一直下到现在下了整整一天!OMG&#xff0c;浪费了一天的假期。不过还好有你们&#xff0c;小编还可以静下心来给大家写教程&#xff0c;好了…

C++ 多继承和虚继承的内存布局

来源&#xff1a;http://www.oschina.net/translate/cpp-virtual-inheritance 来源&#xff1a;http://www.cnblogs.com/BeyondAnyTime/archive/2012/06/05/2537451.html C中的虚拟继承的一些总结 1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决…

C++学习之路 | PTA乙级—— 1076 Wifi密码 (15 分)(精简)

1076 Wifi密码 (15 分) 下面是微博上流传的一张照片&#xff1a;“各位亲爱的同学们&#xff0c;鉴于大家有时需要使用 wifi&#xff0c;又怕耽误亲们的学习&#xff0c;现将 wifi 密码设置为下列数学题答案&#xff1a;A-1&#xff1b;B-2&#xff1b;C-3&#xff1b;D-4&…