曾經碰到過讓你迷惑不解、類似于int* (* (*fp1) (int) ) [10];這樣的變量聲明嗎?本文将由易到難,一步一步教會你如何了解這種複雜的C/C++聲明。
我們将從每天都能碰到的較簡單的聲明入手,然後逐漸加入const修飾符和typedef,還有函數指針,最後介紹一個能夠讓你準确地了解任何C/C++聲明的“右左法則”。
需要強調一下的是,複雜的C/C++聲明并不是好的程式設計風格;我這裡僅僅是教你如何去了解這些聲明。注意:為了保證能夠在同一行上顯示代碼和相關注釋,本文最好在至
少1024x768分辨率的顯示器上閱讀。
讓我們從一個非常簡單的例子開始,如下:
int n;
這個應該被了解為“declaren as an int”(n是一個int型的變量)。接下去來看一下指針變量,如下:
int*p;
這個應該被了解為“declarep as an int *”(p是一個int*型的變量),或者說p是一個指向一個int型變量的指針。我想在這裡展開讨論一下:我覺得在聲明一個指針(或引用)類型的
變量時,最好将*(或&)寫在緊靠變量之前,而不是緊跟基本類型之後。這樣可以避免一些了解上的誤區,比如:
再來看一個指針的指針的例子:
char **argv;
理論上,對于指針的級數沒有限制,你可以定義一個浮點類型變量的指針的指針的指針的指針,再來看如下的聲明:
int RollNum[30][4];
int (*p)[4]=RollNum;
int *q[5];
這裡,p被聲明為一個指向一個4元素(int類型)數組的指針,而q被聲明為一個包含5個元素(int類型的指針)的數組。另外,我們還可以在同一個聲明中混合實用*和&,如下:
int **p1;
//p1 is a pointer to a pointer to an int.
int *&p2;
//p2 is a reference to a pointer to an int.
int &*p3;
//ERROR: Pointer to a reference is illegal.
int &&p4;
//ERROR: Reference to a reference is illegal.
注:p1是一個int類型的指針的指針;p2是一個int類型的指針的引用;p3是一個int類型引用的指針(不合法!);p4是一個int類型引用的引用(不合法!)。
const修飾符
當你想阻止一個變量被改變,可能會用到const關鍵字。在你給一個變量加上const修飾符的同時,通常需要對它進行初始化,因為以後的任何時候你将沒有機會再去改變它。例
如:
const int n=5;
int const m=10;
上述兩個變量n和m其實是同一種類型的——都是const int(整形恒量)。因為C++标準規定,const關鍵字放在類型或變量名之前等價的。我個人更喜歡第一種聲明方式,因為它更
突出了const修飾符的作用。當const與指針一起使用時,容易讓人感到迷惑。例如,我們來看一下下面的p和q的聲明:
const int *p;
int const *q;
他們當中哪一個代表const int類型的指針(const直接修飾int),哪一個代表int類型的const指針(const直接修飾指針)?實際上,p和q都被聲明為constint類型的指針。而int類型的
const指針應該這樣聲明:
int* const r= &n;
//n has been declared as an int
這裡,p和q都是指向const int類型的指針,也就是說,你在以後的程式裡不能改變*p的值。而r是一個const指針,它在聲明的時候被初始化指向變量n(即r=&n;)之後,r的值将不
再允許被改變(但*r的值可以改變)。
組合上述兩種const修飾的情況,我們來聲明一個指向constint類型的const指針,如下:
const int * const p=&n
//n has been declared as const int
下面給出的一些關于const的聲明,将幫助你徹底理清const的用法。不過請注意,下面的一些聲明是不能被編譯通過的,因為他們需要在聲明的同時進行初始化。為了簡潔起見,
我忽略了初始化部分;因為加入初始化代碼的話,下面每個聲明都将增加兩行代碼。
char ** p1;
// pointer to pointer to char
const char **p2;
// pointer to pointer to const char
char * const * p3;
// pointer to const pointer to char
const char * const * p4;
// pointer to const pointer to const char
char ** const p5;
//const pointer to pointer to char
constchar ** const p6;
//const pointer to pointer to const char
char* const * const p7;
//const pointer to const pointer to char
const char * const * const p8;
//const pointer to const pointer to const char
注:p1是指向char類型的指針的指針;p2是指向const char類型的指針的指針;p3是指向char類型的const指針;p4是指向const char類型的const指針;p5是指向char類型的指針的
const指針;p6是指向const char類型的指針的const指針;p7是指向char類型const指針的const指針;p8是指向const char類型的const指針的const指針。
typedef的妙用
typedef給你一種方式來克服“*隻适合于變量而不适合于類型”的弊端。你可以如下使用typedef:
typedef char * PCHAR;
PCHAR p, q;
這裡的p和q都被聲明為指針。(如果不使用typedef,q将被聲明為一個char變量,這跟我們的第一眼感覺不太一緻!)下面有一些使用typedef的聲明,并且給出了解釋:
typedef char * a;
//a is a pointer to a char
typedef a b();
//b is a function that returns
//a pointer to a char
typedef b *c;
//c is a pointer to a function
//that returns a pointer to a char
typedef c d();
//d is a function returning
//a pointer to a function
//that returns a pointer to a char
typedef d *e;
//e is a pointer to a function
//returning a pointer to a
//function that returns a
//pointer to a char
e var[10];
//var is an array of 10 pointers to
//functions returning pointers to
//functions returning pointers to chars.
typedef經常用在一個結構聲明之前,如下。這樣,當建立結構變量的時候,允許你不使用關鍵字struct(在C中,建立結構變量時要求使用struct關鍵字,如struct tagPOINT a;而
在C++中,struct可以忽略,如tagPOINT b)。
typedefstruct tagPOINT
{
intx;
inty;
}POINT;
POINT p;
函數指針
函數指針可能是最容易引起了解上的困惑的聲明。函數指針在DOS時代寫TSR程式時用得最多;在Win32和X-Windows時代,他們被用在需要回調函數的場合。當然,還有其它很
多地方需要用到函數指針:虛函數表,STL中的一些模闆,WinNT/2K/XP系統服務等。讓我們來看一個函數指針的簡單例子:
int(*p)(char);
這裡p被聲明為一個函數指針,這個函數帶一個char類型的參數,并且有一個int類型的傳回值。另外,帶有兩個float類型參數、傳回值是char類型的指針的指針的函數指針可以聲
明如下:
char ** (*p)(float, float);
那麼,帶兩個char類型的const指針參數、無傳回值的函數指針又該如何聲明呢?參考如下:
void * (*a[5])(char * const, char * const);
“右左法則”是一個簡單的法則,但能讓你準确了解所有的聲明。這個法則運用如下:從最内部的括号開始閱讀聲明,向右看,然後向左看。當你碰到一個括号時就調轉閱讀的方
向。括号内的所有内容都分析完畢就跳出括号的範圍。這樣繼續,直到整個聲明都被分析完畢。
對上述“右左法則”做一個小小的修正:當你第一次開始閱讀聲明的時候,你必須從變量名開始,而不是從最内部的括号。
下面結合例子來示範一下“右左法則”的使用。
int * (* (*fp1) (int) ) [10];
閱讀步驟:
1.從變量名開始——fp1
2.往右看,什麼也沒有,碰到了),是以往左看,碰到一個*——一個指針
3.跳出括号,碰到了(int)——一個帶一個int參數的函數
4.向左看,發現一個*——(函數)傳回一個指針
5.跳出括号,向右看,碰到[10]——一個10元素的數組
6.向左看,發現一個*——指針
7.向左看,發現int——int類型
總結:fp1被聲明成為一個函數的指針,該函數傳回指向指針數組的指針.
再來看一個例子:
int *( *( *arr[5])())();
閱讀步驟:
1.從變量名開始——arr
2.往右看,發現是一個數組——一個5元素的數組
3.向左看,發現一個*——指針
4.跳出括号,向右看,發現()——不帶參數的函數
5.向左看,碰到*——(函數)傳回一個指針
6.跳出括号,向右發現()——不帶參數的函數
7.向左,發現*——(函數)傳回一個指針
8.繼續向左,發現int——int類型
還有更多的例子:
float( * ( *b()) [] )();
//b is a function that returns a
//pointer to an array of pointers
//to functions returning floats.
void * ( *c) ( char, int (*)());
//c is a pointer to a function that takes
//two parameters:
//a char and a pointer to a
//function that takes no
//parameters and returns
//an int
//and returns a pointer to void.
void ** (*d) (int &,
char **(*)(char *, char **));
//d is a pointer to a function that takes
//two parameters:
//a reference to an int and a pointer
//to a function that takes two parameters:
//a pointer to a char and a pointer
//to a pointer to a char
//and returns a pointer to a pointer
//to a char
//and returns a pointer to a pointer to void
float( * ( * e[10])(int&) ) [5];
//e is an array of 10 pointers to
//functions that take a single
//reference to an int as an argument
//and return pointers to
//an array of 5 floats.