1. 程式人生 > >函式指標 和 函式物件

函式指標 和 函式物件

函式指標:

函式指標:指向函式的指標變數。本身首先應是指標變數,只不過該指標變數指向函式。這正如用指標變數可指向整型變數、字元型、陣列一樣,這裡是指向函式。如前所述,C在編譯時,每一個函式都有一個入口地址,該入口地址就是函式指標所指向的地址。有了指向函式的指標變數後,可用該指標變數呼叫函式,就如同用指標變數可引用其他型別變數一樣,在這些概念上是一致的。
函式指標有兩個用途:呼叫函式和做函式的引數。
函式指標的宣告方法為:
  資料型別標誌符 (指標變數名) (形參列表);
  注1:“函式型別”說明函式的返回型別,由於“()”的優先順序高於“*”,所以指標變數名外的括號必不可少,後面的“形參列表”表示指標變數指向的函式所帶的引數列表。例如:
  int func(int x); /* 宣告一個函式 */
  int (*f) (int x); /* 宣告一個函式指標 */
  f=func; /* 將func函式的首地址賦給指標f */
  賦值時函式func不帶括號,也不帶引數,由於func代表函式的首地址,因此經過賦值以後,指標f就指向函式func(x)的程式碼的首地址
  注2:函式括號中的形參可有可無,視情況而定。
注意,指向函式的指標變數沒有++和--運算,用時要小心

      1.定義函式指標型別:
  typedef int (*fun_ptr)(int,int);
  2.申明變數,賦值:
  fun_ptr max_func=max;
  也就是說,賦給函式指標的函式應該和函式指標所指的函式原型是一致的。
  例二、
  #include<stdio.h>
  void FileFunc()
  {
  printf("FileFunc\n");
  }
  void EditFunc()
  {
  printf("EditFunc\n");
  }
  void main()
  {
  typedef void (*funcp)();
  funcp=FileFunc;
  (*funcp)();
  funcp=EditFunc;
  (*funcp)();
  }
指標函式和函式指標的區別  1,這兩個概念都是簡稱,指標函式是指帶指標的函式,即本質是一個函式。我們知道函式都有返回型別(如果不返回值,則為無值型),只不過指標函式返回型別是某一型別的指標。
  其定義格式如下所示:
  返回型別識別符號 *返回名稱(形式引數表)
  { 函式體 }
實上,每一個函式,即使它不帶有返回某種型別的指標,它本身都有一個入口地址,該地址相當於一個指標。
比如函式返回一個整型值,實際上也相當於返回一個指標變數的值,不過這時的變數是函式本身而已,而整個函式相當於一個“變數”。
關於函式指標陣列的定義  關於函式指標陣列的定義方法,有兩種:一種是標準的方法;一種是矇騙法。
  第一種,標準方法:
  {
  分析:函式指標陣列是一個其元素是函式指標的陣列。那麼也就是說,此資料結構是是一個數組,且其元素是一個指向函式入口地址的指標。
  根據分析:首先說明是一個數組:陣列名[]
  其次,要說明其元素的資料型別指標:*陣列名[].
  再次,要明確這每一個數組元素是指向函式入口地址的指標:函式返回值型別 (*陣列名[])().請注意,這裡為什麼要把“*陣列名[]”用括號擴起來呢?因為圓括號和陣列說明符的優先順序是等同的,如果不用圓括號把指標陣列說明表示式擴起來,根據圓括號和方括號的結合方向,那麼 *陣列名[]() 說明的是什麼呢?是元素返回值型別為指標的函式陣列。有這樣的函式數祖嗎?不知道。所以必須括起來,以保證陣列的每一個元素是指標。

函式物件:

        儘管函式指標被廣泛用於實現函式回撥,但C++還提供了一個重要的實現回撥函式的方法,那就是函式物件。函式物件(也稱“算符”)是過載了“()”操作符的普通類物件。因此從語法上講,函式物件與普通的函式行為類似。

用函式物件代替函式指標有幾個優點,首先,因為物件可以在內部修改而不用改動外部介面,因此設計更靈活,更富有彈性。函式物件也具備有儲存先前呼叫結果的資料成員。在使用普通函式時需要將先前呼叫的結果儲存在全程或者本地靜態變數中,但是全程或者本地靜態變數有某些我們不願意看到的缺陷。
    其次,在函式物件中編譯器能實現內聯呼叫,從而更進一步增強了效能。這在函式指標中幾乎是不可能實現的。

下面舉例說明如何定義和使用函式物件。首先,宣告一個普通的類並重載“()”操作符:

class Negate
{
public:
int operator() (int n) { return -n;}
};

    過載操作語句中,記住第一個圓括弧總是空的,因為它代表過載的操作符名;第二個圓括弧是引數列表。一般在過載操作符時,引數數量是固定的,而過載“()”操作符時有所不同,它可以有任意多個引數。

    因為在Negate中內建的操作是一元的(只有一個運算元),過載的“()”操作符也只有一個引數。返回型別與引數型別相同-本例中為int。函式返回與引數符號相反的整數。

使用函式物件

   我們現在定義一個叫Callback()的函式來測試函式物件。Callback()有兩個引數:一個為int一個是對類Negate的引用。Callback()將函式物件neg作為一個普通的函式名:

#include <iostream>
using std::cout;

void Callback(int n, Negate & neg)
{
int val = neg(n); //呼叫過載的操作符“()”
cout << val;
}

不要的程式碼中,注意neg是物件,而不是函式。編譯器將語句

int val = neg(n);

轉化為

int val = neg.operator()(n);

   通常,函式物件不定義建構函式和解構函式。因此,在建立和銷燬過程中就不會發生任何問題。前面曾提到過,編譯器能內聯過載的操作符程式碼,所以就避免了與函式呼叫相關的執行時問題。

為了完成上面個例子,我們用主函式main()實現Callback()的引數傳遞:

int main()
{
Callback(5, Negate() ); //輸出 -5
}

本例傳遞整數5和一個臨時Negate物件到Callback(),然後程式輸出-5。

模板函式物件

    從上面的例子中可以看出,其資料型別被限制在int,而通用性是函式物件的優勢之一,如何建立具有通用性的函式物件呢?方法是使用模板,也就是將過載的操作符“()”定義為類成員模板,以便函式物件適用於任何資料型別:如double,_int64或char:

class GenericNegate
{
public:
template <class T> T operator() (T t) const {return -t;}
};

int main()
{
GenericNegate negate;
cout<< negate(5.3333); // double
cout<< negate(10000000000i64); // __int64
}

如果用普通的回撥函式實現上述的靈活性是相當困難的。

標準庫中函式物件

   C++標準庫定義了幾個有用的函式物件,它們可以被放到STL演算法中。例如,sort()演算法以
判斷物件(predicate object)作為其第三個引數。判斷物件是一個返回Boolean型結果的
模板化的函式物件。可以向sort()傳遞greater<>或者less<>來強行實現排序的升序或降序:

#include <functional> // for greater<> and less<>
#include <algorithm> //for sort()
#include <vector>
using namespace std;

int main()
{
vector <int> vi;
//..填充向量
sort(vi.begin(), vi.end(), greater<int>() );//降序( descending )
sort(vi.begin(), vi.end(), less<int>() ); //升序 ( ascending )
}

考慮使用函式物件代替函式作演算法的引數
一個關於用高階語言程式設計的抱怨是抽象層次越高,產生的程式碼效率就越低。事實上,Alexander Stepanov(STL的發明者)有一次作了一組小測試,試圖測量出相對C,C++的“抽象懲罰”。其中,測試結果顯示基本上操作包含一個double的類產生的程式碼效率比對應的直接操作一個double的程式碼低。因此你可能會奇怪地發現把STL函式物件——化裝成函式的物件——傳遞給演算法所產生的程式碼一般比傳遞真的函式高效。

比如,假設你需要以降序排序一個double的vector。最直接的STL方式是通過sort演算法和greater<double>型別的函式物件:

vector<double> v;
...
sort(v.begin(), v.end(), greater<double>());
如果你注意了抽象懲罰,你可能決定避開函式物件而使用真的函式,一個不光是真的,而且是內聯的函式:

inline
bool doubleGreater(double d1, double d2)
{
return dl > d2;
}
...
sort(v.begin(), v.end(), doubleGreater);
有趣的是,如果你比較了兩個sort呼叫(一個使用greater<double>,一個使用doubleGreater)的效能,你基本上可以明確地知道使用greater<double>的那個更快。實際來說,我在四個不同的STL平臺上測量了對一百萬個double的vector的兩個sort呼叫,每個都設為針對速度優化,使用greater<double>的版本每次都比較快。最差的情況下,快50%,最好的快了160%。抽象懲罰真多啊。

這個行為的解釋很簡單:內聯。如果一個函式物件的operator()函式被宣告為內聯(不管顯式地通過inline或者隱式地通過定義在它的類定義中),編譯器就可以獲得那個函式的函式體,而且大部分編譯器喜歡在呼叫演算法的模板例項化時內聯那個函式。在上面的例子中,greater<double>::operator()是一個行內函數,所以編譯器在例項化sort時內聯展開它。結果,sort沒有包含一次函式呼叫,而且編譯器可以對這個沒有呼叫操作的程式碼進行其他情況下不經常進行的優化。(內聯和編譯器優化之間互動的討論,參見《Effective C++》的條款33和Bulka與Mayhew的《Efficient C++》[10]的第8-10章。)

使用doubleGreater呼叫sort的情況則不同。要知道有什麼不同,我們必須知道不可能把一個函式作為引數傳給另一個函式。當我們試圖把一個函式作為引數時,編譯器默默地把函式轉化為一個指向那個函式的指標,而那個指標是我們真正傳遞的。因此,這個呼叫

sort(v.begin(), v.end(), doubleGreater);不是真的把doubleGreater傳給sort,它傳了一個doubleGreater的指標。當sort模板例項化時,這是產生函式的宣告:

void sort(vector<double>::iterator first,    // 區間起點
   vector<double>::iterator last,   // 區間終點
   bool (*comp)(double, double));   // 比較函式
因為comp是一個指向函式的指標,每次在sort中用到時,編譯器產生一個間接函式呼叫——通過指標呼叫。大部分編譯器不會試圖去內聯通過函式指標呼叫的函式,甚至,正如本例中,那個函式已經宣告為inline而且這個優化看起來很直接。為什麼不?可能因為編譯器廠商從來沒有覺得值得實現這個優化。你得稍微同情一下編譯器廠商。他們有很多需求,而他們不能做每一件事。你的需要並不能讓他們實現那個優化。

把函式指標作為引數會抑制內聯的事實解釋了一個長期使用C的程式設計師經常發現卻難以相信的現象:在速度上,C++的sort實際上總是使C的qsort感到窘迫。當然,C++有函式、例項化的類模板和看起來很有趣的operator()函式需要呼叫,而C只是進行簡單的函式呼叫,但所有的C++“開銷”都在編譯期被吸收。在執行期,sort內聯呼叫它的比較函式(假設比較函式已經被宣告為inline而且它的函式體在編譯期可以得到)而qsort通過一個指標呼叫它的比較函式。結果是sort執行得更快。在我的測試的一百萬個double的vector上,它快670%,但不要光相信我的話,親自試試。很容易證實當比較函式物件和真的函式作為演算法的引數時,那是一個純紅利。

還有另一個使用函式物件代替函式作為演算法引數的理由,而它與效率無關。它涉及到讓你的程式可以編譯。對於任何理由,STL平臺經常完全拒絕有效程式碼,即使編譯器或庫或兩者都沒問題。比如,一個廣泛使用的STL平臺拒絕了下面(有效的)程式碼來從cout打印出set中每個字串的長度:

set<string> s;
...
transform(s.begin(), s.end(),
    ostream_iterator<string::size_type>(cout, "\n"),
    mem_fun_ref(&string::size));
這個問題的原因是這個特定的STL平臺在處理const成員函式時有bug(比如string::size)。一個變通方法是改用函式物件:

struct StringSize:
public unary_function<string, string::size_type>{ // 參見條款40
string::size_type operator()(const string& s) const
{
   return s.size();
}
};

transform(s.begin(), s.end(),
   ostream_iterator<string::size_type>(cout, "\n"),
   StringSize());
解決這問題的還有其他變通辦法,但這個不僅在我知道的每個STL平臺都可以編譯。而且它簡化了string::size的內聯呼叫,那是幾乎不會在上面把mem_fun_ref(&string::size)傳給transform的程式碼中發生的事情。換句話說,仿函式類StringSize的創造不僅避開了編譯器一致性問題,而且可能會帶來效能提升。

另一個用函式物件代替函式的原因是它們可以幫助你避免細微的語言陷阱。有時候,看起來合理程式碼被編譯器由於合法性的原因——但很模糊——而拒絕。有很多這樣的情況,比如,當一個函式模板例項化的名字不等價於函式名。這是一種這樣的情況:

template<typename FPType>      // 返回兩個
FPTypeaverage(FPType val1, FPType val2)    // 浮點數的平均值
{
return (val1 + val2) / 2;
}

template<typename InputIter1,
    typename InputIter2>
void writeAverages(InputIter1 begin1,    // 把兩個序列的
     InputIter1 end1,   // 成對的平均值
     InputIter2 begin2,   // 寫入一個流
     ostream& s)
{
transform(
   begin1, end1, begin2,
   ostream_iterator<typename iterator_traits
   <lnputIter1>::value_type> (s, "\n"),
   average<typename iteraror_traits
     <InputIter1>::value_type> // 有錯?
);
}
很多編譯器接受這段程式碼,但是C++標準傾向於禁止它。理由是理論上有可能存在另一帶有一個型別引數的函式模板叫做average。如果有,表示式average<typename iterator_traits<lnputIter1>::value_type>將是模稜兩可的,因為它不清楚將例項化哪個模板。在這個特殊的例子裡,不存在模稜兩可,但是無論如何,有些編譯器會拒絕這段程式碼,而且那麼做是允許的。沒有關係。解決這個問題的方法是依靠一個函式物件:

template<typename FPType>
struct Average:
public binary_function<FPType, FPType, FPType>{   // 參見條款40
FPType operator()(FPType val1. FPType val2) const
{
return average(val 1 , val2);
}

template<typename InputIter1, typename InputIter2>
void writeAverages(lnputIter1 begin1, InputIter1 end1,
     InputIter2 begin2, ostream& s)
{
transform(
   begin1, end1, begin2,
   ostream_iterator<typename iterator_traits<lnputIter1>
       ::value_type>(s, "\n"),
   Average<typename iterator_traits<InputIter1>
     ::value_type>()
);
}
每個編譯器都會接受這條修正的程式碼。而且在transform裡呼叫Average::operator()符合內聯的條件,有些事情對於例項化上面的average不成立,因為averate是一個函式模板,不是函式物件。

把函式物件作為演算法的引數所帶來的不僅是巨大的效率提升。在讓你的程式碼可以編譯方面,它們也更穩健。當然,真函式很有用,但是當涉及有效的STL程式設計時,函式物件經常更有用。

相關推薦

函式指標函式物件詳解

一、函式指標 在C++中,指標本質就是記憶體中的某個地址,如果該記憶體地址中存放的是某個資料,那麼這個指標就是常見的資料指標,如果這個記憶體地址中存放的是某個函式,那麼這個指標就是函式指標。 C++中每一個函式都有一個入口地址,該入口地址就是函式指標所指向的

C++中的函式指標函式物件總結

篇一、函式指標函式指標:是指向函式的指標變數,在C編譯時,每一個函式都有一個入口地址,那麼這個指向這個函式的函式指標便指向這個地址。函式指標的用途是很大的,主要有兩個作用:用作呼叫函式和做函式的引數。函式指標的宣告方法:資料型別標誌符 (指標變數名) (形參列表);一般函式的宣告為:int func ( in

C++函式指標函式物件

C++裡有函式指標和函式物件,讓我們來看下這2個的區別 一 函式指標 我們在學習C/C++時,有時會要寫一個函式,這個函式的引數是另外一個函式,一種寫法如下, #include <iostream> using namespace std; vo

回撥函式函式指標函式物件

  對於回撥函式的編寫始終是寫特殊處理功能程式時用到的技巧之一。先介紹一下回調的使用基本方法與原理。   在這裡設:回撥函式為A()(這是最簡單的情況,不帶引數,但我們應用的實際情況常常很會複雜),使用回撥函式的操作函式為B(), 但B函式是需要引數的,這個引數就是指向函

函式指標 函式物件

函式指標: 函式指標:指向函式的指標變數。本身首先應是指標變數,只不過該指標變數指向函式。這正如用指標變數可指向整型變數、字元型、陣列一樣,這裡是指向函式。如前所述,C在編譯時,每一個函式都有一個入口地址,該入口地址就是函式指標所指向的地址。有了指向函式的指標變數後,可用該

函式指標函式物件的比較

一、函式指標 函式指標:是指向函式的指標變數,在C編譯時,每一個函式都有一個入口地址,那麼這個指向這個函式的函式指標便指向這個地址。 函式指標的用途是很大的,主要有兩個作用:用作呼叫函式和做函式的引數。 函式指標的宣告方法: 資料型別標誌符 (指標變數名

C++中的函式指標函式物件總結(轉)

篇一、函式指標函式指標:是指向函式的指標變數,在C編譯時,每一個函式都有一個入口地址,那麼這個指向這個函式的函式指標便指向這個地址。函式指標的用途是很大的,主要有兩個作用:用作呼叫函式和做函式的引數。函式指標的宣告方法:資料型別標誌符 (指標變數名) (形參列表);一般函式的宣告為: int func ( i

函式指標函式指標型別

參考:https://blog.csdn.net/candyliuxj/article/details/6339414 函式指標 1.     定義 每一個函式都佔用一段記憶體單元,它們有一個起始地址,指向函式入口地址的指標稱為函式指標。

C++複習筆記(六)之函式指標函式模板、類模板

一、函式指標 函式指標在C語言中的作用類似於c++中的多型,都是可以實現框架的搭建,程式碼的相容性高。 函式三要素:名稱、引數、返回值 C語言可以通過typedef為函式型別重新命名,語法 typedef  返回值型別(型別名稱)(引數列表);如下程式碼所示: #in

函式指標函式指標陣列及其應用

1. 函式指標 先來看一個簡單的例子。 int a=3; void *p=&a; 這是一個基礎的不能再基礎的例子。相信學過指標的都能看得懂。P是一個指標,指向a。a 是一個整形變數。 函式指標和其類似,只不過其指向的不是一個變數,而是一個函式,僅此而已。話不多

靜態成員函式、this指標物件指標動態物件使用小結

·靜態成員函式就是使用static 關鍵字宣告的成員函式  在類外實現時不加static關鍵字,只有在類內宣告時才加static·靜態成員函式是類的一部分,作用是為了處理靜態資料成員  沒有this指標·靜態成員函式可以直接訪問該類的靜態成員,但不能直接訪問類中的非靜態成員·若想在靜態成員fun中使用非靜態成

有n個整數,指定位置m處插入g個值(用指標函式

#include <stdio.h> void main() {     void move(int *p,int *s,int n,int m,int g);  int a[30],b[20];     i

使用函式指標map載入不確定配置檔案的實現

#include<iostream> #include<string> #include<map> typedef void (*pFunc)(); //用於指向具體載入配置檔案的函式 using namespace std; enum TYPE

函式指標指標函式用法區別

函式指標和指標函式用法和區別   前言 函式指標和指標函式,在學習 C 語言的時候遇到這兩個東西簡直頭疼,當然還有更頭疼的,比如什麼函式指標函式、指標函式指標、陣列指標、指標陣列、函式指標陣列等等,描述越長其定義就越複雜,當然理解起來就越難,特別是剛開始學習這門

c語言的指標陣列陣列指標函式指標

#include <stdio.h> #include <stdlib.h> int func(int x){ return x; } int* func2(int x){ int *p=&x; return

C語言指標回顧——函式指標指標函式

函式指標 函式指標和指標函式兩個詞很像,但實際上只要看後兩個詞就行,函式指標是一個指標,指標是用來存放變數地址的,函式指標存放的地址是函式的入口地址。那麼函式指標如何使用呢,請看下面的程式碼: #include <iostream> using namespa

c/c++中的函式指標指標函式

定義 1.指標函式,本質是函式,返回值為指標,形如,int *pfun(int, int),由於“*”的優先順序低於“()”的優先順序,所以等同於int *(pfun(int, int)) 2.函式指標

函式指標指標函式的區別

1.指標函式 _type_ *function(int, int) _type_ *function(int, int)與普通函式int function(int,int)類似,只是返回的資料型別不一樣而已,_type_ *function(int, int)返回的是指標地址,int function(int

函式指標函式物件

    今天看c++中vector資料結構的底層實現,發現遍歷操作的實現之一用到了函式物件,花時間又複習了一下函式指標和函式物件。 函式指標:是指向函式的指標變數,在C編譯時,每一個函式都有一個入口地址,那麼這個指向這個函式的函式指標便指向這個地址。 函式指標的用途是很大的

C語言 函式指標 typedef

函式 函式一般遵循的格式:函式的返回型別、函式名、引數列表; void func(void) --> 對應的指標 void (*P)(void) typedef定義函式指標 typedef int (*funptr)(int,int) // typedef行 int (*