2013年8月19日 星期一

指向函數的指標(pointer to function)(aka: Function Pointer) V.S. 傳回指標的函數(function return a pointer)

From: 指標的藝術

Function Pointer:(指向函數的指標)

int (*pf)(int);

在指標的藝術一書(p.87)中又名Pointer to function。目的是經過宣告後,會有一個指標是指向函數的起始位址,當我們要使用某個函數的時候,只要將此指標指向那個函數,就可使用,其實函數名稱是一個位址,因此只要將函數名稱設定給此指標即可。

使用函數指標的時候必須注意指標的引入與回傳參數是否與原函式匹配,就說原函式引入、回傳的資料型態,引入參數個數,通通要一樣才可以。

例如三個副函式:

(1) int add(int);
(2) float add2(float);
(3) int add3(int,int);

並且宣告一個函式指標:
int (*pf)(int);

則:

(a.) pf = add;     //正確
(b.) pf = add2;   //錯誤,參數資料型態不匹配
(c.) pf = add3;   //錯誤,引入參數個數不匹配

#################################################################################

Function return a pointer:(回傳指標的函數)

int *pf(int); //少了一個括號cf: int (*pf)(int);

表示pf是一個函數,這個函數會引入一個整數型態的參數,經過函數運算後會回傳一個整數型態的指標(回傳的東西是一個位址,假設回傳x,則x可以是指標(x存的內容是指向的位址),也可以是變數的位址,若x是一般變數,則回傳&x;若x是陣列則回傳x。

#################################################################################

typedef與函數指標(from wiki typedef)

Using typedef with function pointers[edit source | editbeta]

Function pointers are somewhat different than all other types because the syntax does not follow the pattern typedef <old type name> <new alias>;. Instead, the new alias for the type appears in the middle between the return type (on the left) and the argument types (on the right). Consider the following code, which does not use a typedef:
int do_math(float arg1, int arg2) {
    return arg2;
}
 
int call_a_func(int (*call_this)(float, int)) {
    int output = call_this(5.5, 7);
    return output;
}
 
int final_result = call_a_func(do_math);
This code can be rewritten with a typedef as follows:
typedef int (*MathFunc)(float, int);
 
int do_math(float arg1, int arg2) {
    return arg2;
}
 
int call_a_func(MathFunc call_this) {
    int output = call_this(5.5, 7);
    return output;
}
 
int final_result = call_a_func(do_math);
Here, MathFunc is the new alias for the type. A MathFunc is a pointer to a function that returns an integer and takes as arguments a float followed by an integer.

typedef int (*MathFunc)(float, int);是別名的宣告,就只是在最前面加上typedef則以後就可以只用MathFunc這樣的名稱來宣告同類型的函數指標。


2013年8月16日 星期五

typedef用法

typedef用法小結- -
這兩天在看程序的時候,發現很多地方都用到typedef,在結構體定義,還有一些數組等地方都大量的用到.但是有些地方還不是很清楚,今天下午,就想好好研究一下.上網搜了一下,有不少資料.歸納一下

來源一:Using typedef to Curb Miscreant Code
Typedef
聲明有助於創建平台無關類型,甚至能隱藏複雜和難以理解的語法。不管怎樣,使用typedef能為代碼帶來意想不到的好處,通過本文你可以學習用typedef避免缺欠,從而使代碼更健壯。

typedef
聲明,簡稱typedef,為現有類型創建一個新的名字。比如人們常常使用typedef來編寫更美觀和可讀的代碼。所謂美觀,意指typedef能隱藏笨拙的語法構造以及平台相關的數據類型,從而增強可移植性和以及未來的可維護性。本文下面將竭盡全力來揭示typedef強大功能以及如何避免一些常見的陷阱。

如何創建平台無關的數據類型,隱藏笨拙且難以理解的語法?
使用typedefs為現有類型創建同義字。
定義易於記憶的類型名
  typedef
使用最多的地方是創建易於記憶的類型名,用它來歸檔程序員的意圖。類型出現在所聲明的變量名字中,位於''typedef''關鍵字右邊。例如:
typedef int size;
 
此聲明定義了一個int的同義字,名字為size。注意typedef並不創建新的類型。它僅僅為現有類型添加一個同義字。你可以在任何需要int的上下文中使用size
void measure(size * psz);
size array[4];
size len = file.getlength();
std::vector vs;
  typedef
還可以掩飾符合類型,如指針和數組。例如,你不用像下面這樣重複定義有81個字符元素的數組:
char line[81];
char text[81];
定義一個typedef,每當要用到相同類型和大小的數組時,可以這樣:
typedef char Line[81];
Line text, secondline;
getline(text);
同樣,可以像下面這樣隱藏指針語法:
typedef char * pstr;
int mystrcmp(pstr, pstr);
 
這裡將帶我們到達第一個typedef陷阱。標準函數strcmp()有兩個'const char *'類型的參數。因此,它可能會誤導人們像下面這樣聲明mystrcmp()
int mystrcmp(const pstr, const pstr);
 
這是錯誤的,按照順序,'const pstr'被解釋為'char * const'(一個指向char的常量指針),而不是'const char *'(指向常量char的指針)。這個問題很容易解決:
typedef const char * cpstr;
int mystrcmp(cpstr, cpstr); //
現在是正確的
記住:不管什麼時候,只要為指針聲明typedef,那麼都要在最終的typedef名稱中加一個const,以使得該指針本身是常量,而不是對象。

代碼簡化
 
上面討論的typedef行為有點像#define宏,用其實際類型替代同義字。不同點是typedef在編譯時被解釋,因此讓編譯器來應付超越預處理器能力的文本替換。例如:
typedef int (*PF) (const char *, const char *);
 
這個聲明引入了PF類型作為函數指針的同義字,該函數有兩個const char *類型的參數以及一個int類型的返回值。如果要使用下列形式的函數聲明,那麼上述這個typedef是不可或缺的:
PF Register(PF pf);
  Register()
的參數是一個PF類型的回調函數,返回某個函數的地址,其署名與先前註冊的名字相同。做一次深呼吸。下面我展示一下如果不用typedef,我們是如何實現這個聲明的:
int (*Register (int (*pf)(const char *, const char *)))
(const char *, const char *);
 
很少有程序員理解它是什麼意思,更不用說這種費解的代碼所帶來的出錯風險了。顯然,這裡使用typedef不是一種特權,而是一種必需。持懷疑態度的人可能會問:"OK,有人還會寫這樣的代碼嗎?",快速瀏覽一下揭示signal()函數的頭文件,一個有同樣接口的函數。

typedef
和存儲類關鍵字(storage class specifier
 
這種說法是不是有點令人驚訝,typedef就像autoexternmutablestatic,和register一樣,是一個存儲類關鍵字。這並是說typedef會真正影響對象的存儲特性;它只是說在語句構成上,typedef聲明看起來象staticextern等類型的變量聲明。

下面將帶到第二個陷阱:
typedef register int FAST_COUNTER; //
錯誤
 
編譯通不過。問題出在你不能在聲明中有多個存儲類關鍵字。因為符號typedef已經佔據了存儲類關鍵字的位置,在typedef聲明中不能用register(或任何其它存儲類關鍵字)。
促進跨平台開發
  typedef
有另外一個重要的用途,那就是定義機器無關的類型,例如,你可以定義一個叫REAL的浮點類型,在目標機器上它可以i獲得最高的精度:
typedef long double REAL;
在不支持long double的機器上,該typedef看起來會是下面這樣:
typedef double REAL;
並且,在連double都不支持的機器上,該typedef看起來會是這樣:
typedef float REAL; 


  
你不用對源代碼做任何修改,便可以在每一種平台上編譯這個使用REAL類型的應用程序。唯一要改的是typedef本身。在大多數情況下,甚至這個微小的變動完全都可以通過奇妙的條件編譯來自動實現。不是嗎?標準庫廣泛地使用typedef來創建這樣的平台無關類型:size_tptrdifffpos_t就是其中的例子。此外,象std::stringstd::ofstream這樣的typedef還隱藏了長長的,難以理解的模板特化語法,例如:basic_stringallocator>basic_ofstream>


來源二:( http://www.ccfans.net/bbs/dispbbs.asp?boardid=30&;id=4455 )
C
語言中typedef用法
1.
基本解釋
  typedef
C語言的關鍵字,作用是為一種數據類型定義一個新名字。這裡的數據類型包括內部數據類型(int,char等)和自定義的數據類型(struct等)。
 
在編程中使用typedef目的一般有兩個,一個是給變量一個易記且意義明確的新名字,另一個是簡化一些比較複雜的類型聲明。

 
至於typedef有什麼微妙之處,請你接著看下面對幾個問題的具體闡述。

2. typedef &
結構的問題
 
當用下面的代碼定義一個結構時,編譯器報了一個錯誤,為什麼呢?莫非C語言不允許在結構中包含指向它自己的指針嗎?請你先猜想一下,然後看下文說明:
typedef struct tagNode
{
char *pItem;
pNode pNext;
} *pNode; 


 
答案與分析:
  1
typedef的最簡單使用
typedef long byte_4;
 
給已知數據類型long起個新名字,叫byte_4


  2
typedef與結構結合使用
typedef struct tagMyStruct
{
int iNum;
long lLength;
} MyStruct; 


 
這語句實際上完成兩個操作:
  1)
定義一個新的結構類型
struct tagMyStruct
{
int iNum;
long lLength ;
};
 
分析:tagMyStruct稱為“tag”,即標籤,實際上是一個臨時名字,struct關鍵字和tagMyStruct一起,構成了這個結構類型,不論是否有typedef,這個結構都存在。
 
我們可以用struct tagMyStruct varName來定義變量,但要注意,使用tagMyStruct varName來定義變量是不對的,因為structtagMyStruct合在一起才能表示一個結構類型。

  2) typedef
為這個新的結構起了一個名字,叫MyStruct
typedef struct tagMyStruct MyStruct;
 
因此,MyStruct實際上相當於struct tagMyStruct,我們可以使用MyStruct varName來定義變量。
 
答案與分析
  C
語言當然允許在結構中包含指向它自己的指針,我們可以在建立鍊錶等數據結構的實現上看到無數這樣的例子,上述代碼的根本問題在於typedef的應用。
 
根據我們上面的闡述可以知道:新結構建立的過程中遇到了pNext域的聲明,類型是pNode,要知道pNode表示的是類型的新名字,那麼在類型本身還沒有建立完成的時候,這個類型的新名字也還不存在,也就是說這個時候編譯器根本不認識pNode
 
解決這個問題的方法有多種:
  1)

typedef struct tagNode
{
char *pItem;
struct tagNode *pNext;
} *pNode; 


  2)

typedef struct tagNode *pNode;
struct tagNode
{
char *pItem;
pNode pNext;
};
 
注意:在這個例子中,你用typedef給一個還未完全聲明的類型起新名字。C語言編譯器支持這種做法。

  3)
、規範做法:
struct tagNode
{
char *pItem;
struct tagNode *pNext;
};
typedef struct tagNode *pNode; 



3. typedef & #define
的問題
 
有下面兩種定義pStr數據類型的方法,兩者有什麼不同?哪一種更好一點?
typedef char *pStr;
#define pStr char *;
 
答案與分析:
 
通常講,typedef要比#define要好,特別是在有指針的場合。請看例子:
typedef char *pStr1;
#define pStr2 char *;
pStr1 s1, s2;
pStr2 s3, s4;
 
在上述的變量定義中,s1s2s3都被定義為char *,而s4則定義成了char,不是我們所預期的指針變量,根本原因就在於#define只是簡單的字符串替換而typedef則是為一個類型起新名字。
  #define
用法例子:
#define f(x) x*x
main( )
{
int a=6
b=2c
c=f(a) / f(b)

printf("%d \\n"
c)
}
 
以下程序的輸出結果是: 36
 
因為如此原因,在許多C語言編程規範中提到使用#define定義時,如果定義中包含表達式,必須使用括號,則上述定義應該如下定義才對:
#define f(x) (x*x)
 
當然,如果你使用typedef就沒有這樣的問題。


  4. typedef & #define
的另一例
 
下面的代碼中編譯器會報一個錯誤,你知道是哪個語句錯了嗎?
typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;
 
答案與分析:
 
p2++出錯了。這個問題再一次提醒我們:typedef#define不同,它不是簡單的文本替換。上述代碼中const pStr p2並不等於const char * p2const pStr p2const long x本質上沒有區別,都是對變量進行只讀限制,只不過此處變量p2的數據類型是我們自己定義的而不是系統固有類型而已。因此,const pStr p2的含義是:限定數據類型為char *的變量p2為只讀,因此p2++錯誤。

  #define
typedef引申談
  1) #define
宏定義有一個特別的長處:可以使用#ifdef ,#ifndef等來進行邏輯判斷,還可以使用#undef來取消定義。

  2) typedef
也有一個特別的長處:它符合範圍規則,使用typedef定義的變量類型其作用範圍限制在所定義的函數或者文件內(取決於此變量定義的位置),而宏定義則沒有這種特性。


  5. typedef &
複雜的變量聲明
 
在編程實踐中,尤其是看別人代碼的時候,常常會遇到比較複雜的變量聲明,使用typedef作簡化自有其價值,比如:
 
下面是三個變量的聲明,我想使用typdef分別給它們定義一個別名,請問該如何做?
>1
int *(*a[5])(int, char*);
>2
void (*b[10]) (void (*)());
>3. doube(*)() (* pa)[9]; 


 
答案與分析:
 
對複雜變量建立一個類型別名的方法很簡單,你只要在傳統的變量聲明表達式裡用類型名替代變量名,然後把關鍵字typedef加在該語句的開頭就行了。

>1
int *(*a[5])(int, char*);
//pFun
是我們建的一個類型別名
typedef int *(*pFun)(int, char*);
//
使用定義的新類型來聲明對象,等價於int* (*a[5])(int, char*);
pFun a[5]; 


>2
void (*b[10]) (void (*)());
/ /
首先為上面表達式藍色部分聲明一個新類型
typedef void (*pFunParam)();
//
整體聲明一個新類型
typedef void (*pFun)(pFunParam);
//
使用定義的新類型來聲明對象,等價於void (*b[10]) (void (*)());
pFun b[10]; 


>3. doube(*)() (*pa)[9];
//
首先為上面表達式藍色部分聲明一個新類型
typedef double(*pFun)();
//
整體聲明一個新類型
typedef pFun (*pFunParam)[9];
//
使用定義的新類型來聲明對象,等價於doube(* )() (*pa)[9];
pFunParam pa;