C++函数指针详解

简介: 来源:http://www.cnblogs.com/ggjucheng/archive/2011/12/13/2286391.html 指针的概念 指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。让我们分别说明。  先声明几

来源: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);       //定义一个函数指针 pFunc
    pFunc = 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;
}






相关实践学习
RocketMQ一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
目录
相关文章
|
存储 安全 算法
【C++智能指针 相关应用】深入探索C++智能指针:跨进程、动态库与最佳实践
【C++智能指针 相关应用】深入探索C++智能指针:跨进程、动态库与最佳实践
61 5
|
28天前
|
存储 安全 C++
在C++指针和引用
在C++指针和引用
|
1月前
|
安全 程序员 Linux
【C++】—— c++11之智能指针
【C++】—— c++11之智能指针
|
1月前
|
存储 程序员 C++
在C++编程语言中指针的作用类型
在C++编程语言中指针的作用类型
14 0
|
24天前
|
JSON JavaScript 前端开发
C++ 智能指针与 JSON 处理:高级编程技巧与常见问题解析
C++ 智能指针与 JSON 处理:高级编程技巧与常见问题解析
254 0
|
21天前
|
存储 编译器 C语言
【c++】类和对象(二)this指针
朋友们大家好,本节内容来到类和对象第二篇,本篇文章会带领大家了解this指针
【c++】类和对象(二)this指针
|
24天前
|
存储 安全 数据库连接
【C++智能指针】深入探究C++智能指针:自定义删除器的设计与选择
【C++智能指针】深入探究C++智能指针:自定义删除器的设计与选择
82 0
|
24天前
|
存储 安全 编译器
【C++ 函数设计的艺术】深挖 C++ 函数参数的选择 智能指针与 std::optional:最佳实践与陷阱
【C++ 函数设计的艺术】深挖 C++ 函数参数的选择 智能指针与 std::optional:最佳实践与陷阱
106 0
|
24天前
|
安全 算法 程序员
【C++ 空指针的判断】深入理解 C++11 中的 nullptr 和 nullptr_t
【C++ 空指针的判断】深入理解 C++11 中的 nullptr 和 nullptr_t
47 0
|
24天前
|
安全 算法 程序员
【C++智能指针 空指针判断】深入探索C++智能指针:nullptr与empty的微妙差异
【C++智能指针 空指针判断】深入探索C++智能指针:nullptr与empty的微妙差异
27 1