一、函數指針
先看一個例子:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define ERROR 0
#define OK 1
int add(int a,int b){
return a+b;
}
int multiply(int a,int b){
return a*b; } int main()
{ int (*fun)(int,int); fun=add; printf("%d\n",fun(5,6)); //5+6=11 fun=multiply; printf("%d\n",fun(5,6));//5*6=30
return 0; }
函數指針的本質是一個指針,該指針變量的值是地址,指向某一個函數的地址,所以它是指向函數的指針。函數的定義是存在於代碼段,因此,每個函數在代碼段中,也有着自己的入口地址,函數指針就是指向代碼段中函數入口地址的指針。
函數指針的聲明方法為:
返回值類型 ( * 指針變量名) ([形參列表]);
注1:“返回值類型”說明函數的返回類型;“(指針變量名 )”中的括號不能省,括號改變了運算符的優先級。若省略整體則成為
一個函數說明,說明了一個返回的數據類型是指針的函數;后面的“形參列表”表示指針變量指向的函數所帶的參數列表。例如:
int func(int x); /* 聲明一個函數 */
int (*f) (int x); /* 聲明一個函數指針 */
f=func; /* 將func函數的首地址賦給指針f , f(x)==func(x)等價,為什么還要用函數指針呢?比如+ - * /四個函數,主函數要對兩個數進行計算,但不確定那鍾運算,那代碼就等着不寫嗎,不,可以先寫,用函數指針f寫好代碼,將來想用+,則將其賦值給f(類似數組傳參數傳的是地址,函數指針就是用來接收某一函數的地址,間接調用該函數,函數名太固定了,不靈活,函數指針相當於一個變量,其它函數可以賦值給它)*/回想高中知識f(x)表示一函數,具體是什么函數就看你怎么賦值,程序中,我可以先用f(x)參與代碼編寫,將來需要f(x)實現什么功能,就賦值給它相應的功能代碼即可。
或者使用下面的方法將函數地址賦給函數指針:
f = &func; //一般用上面的,如同數組不用取址了
賦值時函數func不帶括號,也不帶參數,由於func代表函數的首地址,因此經過賦值以后,指針f就指向函數func(x)的代碼的首地址。
注2:函數括號中的形參可有可無,視情況而定。
如果覺得函數名直接調和函數指針調用函數沒什么卻別,我們來看有一段二叉樹中序遍歷非遞歸的代碼來理解。
Status visit(TElemType e)
{
printf("%d ", e);
return OK;
}
Status InOrderTraverse( BiTree T, Status (*visit)(TElemType e ))
{
InitStack( S ); p = T;
while( p || !StackEmpty( S ))
{
if( p )// 根指針進棧,遍歷左子樹
{
Push( S, p );
p = p>pLChild;
}
else // 根指針出棧,訪問根結點,遍歷右子樹
{
Pop( S, p );
if( !(*Visit)( p->data))
return ERROR;
p = p->pRChild; // 右子樹
}
}
return OK;
}
其中作為形參的status (*visit)(TElemType e )這個函數指針 代表了訪問節點的方法 這里使用函數指針的主要原因就是此時只是訪問結點,但是不知道是將來是打印或者是別的什么操作,而我們命名函數時的習慣是將名字起為函數的作用,但是這里不知道具體的訪問的操作,因此寫visit函數指針更加合理。所以,可以使代碼的封裝性和邏輯性更加合理。
還有一個作用就是可以將實現同一功能的很多個模塊統一起來標識,這樣一來更容易后期的維護,系統結構更加清晰。或者歸納為:便於分層設計、利於系統抽象、降低耦合度以及使接口與實現分開。
可以看下面對冒泡排序的封裝,讓整個代碼的層次和模塊化有了質的提升
#include<iostream>
using namespace std;
void sort(int arr[], int size, bool(*cmp)(int,int));
bool up(int a, int b);
bool down(int a, int b);
int main()
{
int arr[10];
for (int i = 0;i < 10;++i)
cin >> arr[i];
sort(arr, 10,down);
for (int i = 0;i < 10;++i)
cout << arr[i] << " ";
return 0;
}
void sort(int arr[],int size,bool(*cmp)(int,int))//簡單冒泡排序
{
int temp;
for (int i = 0;i < size-1; ++i)
{
for (int j = i;j < size-1;++j)
{
if (cmp(arr[i], arr[j + 1]))//cmp是一個函數指針,變量,功能是比較,是從小到大排序還是從大到小排序不知道, 主函數把up復制給它,它就是up;把down賦值給它,它就down;
{
temp = arr[i];
arr[i] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
bool up(int a, int b)
{
if (a > b)
return 1;
else
return 0;
}
bool down(int a, int b)
{
if (a > b)
return 0;
else
return 1;
}
二、指針函數
指針函數是一個函數,返回某種類型指針的地址,使其指向某個地址單元。
返回指針的函數,一般定義格式為:
類型標識符 *函數名(參數表)
int *f(x,y);
其中x,y是形式參數,f是函數名,調用后返回一個指向整型數據的地址指針。f(x,y)是函數,其值是指針。
如:char *ch();表示的就是一個返回字符型指針的函數,請看下面的例題:
例:將字符串1(str1)復制到字符串2(str2),並輸出字符串2.
#include "stdio.h"
void main()
{
char *ch(char *,char *); //子函數在main下,所以先聲明后使用
char str1[]="I am glad to meet you!";
char str2[]="Welcom to study C!";
printf("%s",ch(str1,str2));
}
char *ch(char *str1,char *str2)
{
int i;
char *p;
p=str2
if(*str2==NULL) exit(-1);
do
{
*str2=*str1;
str1++;
str2++;
}while(*str1!=NULL);
return(p);
}
通過分析可得:
函數指針是一個指向函數的指針,或者說是一個指針變量,可以用來指向一個函數;而指針函數是一個返回值為指針的函數, 只不過返回值類型為指針型。