1. 程式人生 > >C語言中星號的使用

C語言中星號的使用

C語言中星號的使用

1、乘法運算子

2、定義指標

int *p = 0; 還是 int* p = 0;

後一種比較容易這樣理解:定義了一個變數p,它是指標型的(更詳細一點,是指向int的指標型),相比而言,前面一種定義似乎是定義了*P這個奇怪的東西。但是後面一種寫法會帶來一個容易產生的誤解:

int* p1, p2;

這兒給人的感覺似乎是定義了兩個指標型變數p1p2,但是,事實上,這種直覺是錯誤的,正確的理解方式是int *p1, p2;p1是指標型的,而p2確是整型的。

MS VC++ 6.0中,是按照後面一種格式寫的。

3、何謂指標?

指標僅僅表示一個記憶體中的某個地址?

非也,注意到,我們在定義指標的時候,都關聯了一個型別,如intchar,或者是string等等,如果說指標僅僅表示一個記憶體中的地址,那何必要關聯這麼多變化的東西呢?完全可以DWORD p0;這樣解決問題。

關聯了的資料型別是作何用的呢?

它可以指示編譯器怎樣解釋特定地址上記憶體的內容,以及該記憶體區域應該跨越多少記憶體單元。如 int *p;

編譯器可以從這個定義中獲得資訊:

1、p指向的記憶體存放的是整型資料,

2、由於該記憶體區域只存放了一個數據,跨越的記憶體區域為4個位元組,即p+1的效果是跳過了四個位元組。

另一個複雜一點的例子,如

struct a

{int x1;

short x2;

a *next;

}

定義指標 a *p;那麼編譯器對這個指標又作何解釋呢?

1p指向的記憶體區域依次存放了三種類型的資料,分別是intshort和一個指標型資料。

2p指向的記憶體區域跨越了12個位元組,即p+1的效果是跳過了12個位元組。(為何不是10?對齊的原因)

但是,C++中定義了一種特殊的指標,它去除了一般指標中對記憶體區域內容以及大小的解釋,以滿足特定的需要,如我們只需要某塊記憶體的首地址,不需要考慮其中的資料型別以及大小。這種形式為 void *; 這種型別的指標可以被任意資料型別的指標賦值,如上面的a* 型,void *q = p; 

唯一例外的是,不能把函式指標賦給它。

4、關於const修飾符

const遇到指標,麻煩事就來了,

看:const int* p; int* const p; const int* const p;

這三個表示式,

第一個表示p是一個指標,p本身平凡無比,但是p所指向的物件是一個特殊的物件--整型常量;

第二個表示:這個p指標不是一個普通的指標,它是個常量指標,即只能對其初始化,而不能賦值,另外,這個指標所指向的物件是一平凡的int型變數;

第三個則結合了前兩者:指標和指向的物件都非同尋常,都是常量。

有了const,賦值的問題就變得麻煩起來,

首先,對於 const int* p;這兒由於p指向的物件是個常量,所以在通過p來引用這個物件的時候不可對其進行賦值!對於一個常量物件,不可以用普通的指標指向,而必須用這種指向常量的指標,原因很簡單,通過普通指標可以改變指向的那個值,但是對於一個非常量物件,即普通變數,可不可以將其地址賦給指向常量的指標呢?是可以的,但是一旦這樣指向之後,由於這個指標本身定義的是指向常量的指標,因而編譯器統一認為其是指向常量的,因而此時不可以通過該指標修改所指向的物件的值。

第二,對於 int* const p;這兒p本身是個常量指標,所以根本就不能賦值,所以不存在賦值的問題。不可以用常量對其進行初始化,因為這個指標不是指向常量的;只能用變數對其初始化。

第三,對於 const int* const p;這兒,只能初始化,不能賦值。可以利用常量進行初始化;也可以利用變數對其初始化,不過不可以利用該指標對該變數進行賦值。

const int* p這種指向常量物件的指標常用來用作某些函式的形參,用意是從編譯器的角度防止使用者在函式中將傳遞進去的引數修改,雖然使用者本身也可以避免,但是這樣更可靠一點--當用戶不小心作出修改實參的行為時,編譯器發現並阻止這種行為。

this指標是const xx* const型的。

5、函式與指標

指向函式的指標:可以利用它代替函式名來呼叫函式。

如何定義函式指標,由於一個程式中可以用多個函式名相同的情形(即函式的過載),因而,定義函式指標的時候,必須包含函式的引數,這樣才能準確地將指標指向某函式。

定義:int (*p)(const char*, int); 表示p是一個指向函式的指標,該函式的兩個引數為const char* int,另外該函式返回int型值。

容易混淆的是:int *p(const char *, int); 缺少了一個括號,此時編譯器的解釋是 int* p(const char*, int);即其含義是一個函式的宣告,函式名為p,返回一個指向int型的指標。

那麼 int* (*p)(const char*, int);則是定義了一個函式指標p,它指向一個函式,該函式的兩個引數為const char*int,該函式返回一個指向int型的指標

函式指標的初始化與賦值

函式名如同陣列名,編譯器將其解釋為指向該型別函式的指標,故而,可以領用函式名,或者&函式名對函式指標進行初始化或者賦值,另外,可以用另一個函式指標對該指標進行初始化以及賦值。重要的一點是指標與函式名,指標與指標必須具有完全相同的引數表和返回型別(必須完全完全一樣,任何一點不同都不可以)。不存在隱式的型別轉換,使用者必須保證完全的一致性。

初始化或者賦值為0,表示不指向任何函式。

利用函式指標呼叫函式是可以pxy)這樣呼叫,也可以(*p)(x,y)這樣呼叫,前提是p已經正確的賦值或者初始化。

函式返回指標:可以返回一個非基本型別的物件。

6、陣列與指標

int a[3] = {1,2,3};

考慮 aa[0], &a, 以及 &a[0]這三個表示式的含義:

首先這三個表示式的數值結果是一樣的--陣列的首地址(即陣列中第0個元素的地址),但是編譯器對三者的解釋不同:

對於a,編譯器將其解釋為一個指標,指向的是一個整型資料,因而利用a1即指向陣列中的第一的元素,a2指向第二個元素。

對於a這個指標有些特殊的性質:

a不是一個普通的指標,它同時是一個數組名,即關聯了一個數組,因而某些性質上與普通的指標不同。

普通的指標可以被賦值,即可以用一個地址或者另一個指標修改當前指標的指向,然而對於a這種關聯了一個數組的指標,如果允許這樣賦值的話,那麼陣列中的元素將無法被訪問,所以不允許對陣列名代表的指標進行賦值。在這一點上a相當於指標常量,即只能被初始化,不可以進行賦值。

   雖然a不可以被賦值,但是將a賦給其他的元素是完全可以的,這一點同普通的指標沒有不同。

綜上,a相當於一個指標常量。(type* const型的)

本質上a[i]操作被編譯器解釋為*(a+i)操作,即[]運算子是通過陣列名指標實現的,因而&a[0]的含義即&(*a),顯然對一個指標先*(解引用),&(引用),等價於什麼都沒做,還是這個指標本身,因而a完全等價於&a[o],--(&a[0])[i]等價於a[i],形式有點詭異,呵呵。而對於&a這個表示式,奇怪的是這個也是陣列的首地址,那麼就是說,這個陣列的首地址中存放了一個指標常量(即陣列名),但是陣列的首地址中不是存放的一個int型的數字嗎?這是怎麼回事呢?難道一個地址能存放兩個東西?

暫時無法解釋,可以這樣認為編譯器發現這種&和陣列名的結合運算時,即返回陣列首地址,只不過,這是,這僅僅是個純粹的地址,它不再具有指標的特性,即編譯器不再將其解釋為指標,使用者不可以通過+1運算來訪問下一個陣列元素。它的+1就是數學上的+1

當陣列變為多維,問題變成怎麼樣了呢?

考慮二維陣列 int b[4][3] = {{1,2,3}, {4,5,6}, {7,8,9}, {10,11,12}}; b&b, b[0], &b[0], &b[0][0]幾個表示式的含義:

首先,c++中陣列元素的存放是以行序為主序,即第一段存放第一行的資料,第二段存放第二行的資料,....,如此。

首先考慮陣列名b,編譯器同樣將陣列名b解釋為一個指標,但是顯然這個指標不是普通的指標。這個b指向陣列所有元素的首地址,這一點是勿庸置疑的,那麼這個指標一步跨越的記憶體是多大呢?在本例中,b一步跨越12個位元組,即b一步跨越陣列中的一行元素,實際上b是一個指標的指標,或者說指向指標的指標,即b所指向的內容是一個指標,(同樣對於b+1b+2)b[i][j]這種訪問方式本質上即:先通過+i,將指標跳躍到第i行,從而獲得了指向第i行首地址的指標b[i],然後通過這個指標,再通過+j,跳躍j步,到達了第j個元素,即找到第i行,第j列的元素。

所以b是指標的指標,b[i]是指標,這兒,b[i]類似於一維中的a

那麼&b呢?&b仍然是陣列的首地址,但是跟一維類似的是,這是個純粹的地址,不再具有指標的特性,它的+1就是數學上的+1,不可以利用+1來訪問下一個元素。同樣的道理對於&b[i],&運算子加上之後,本來是作為指標的b[i]被剝奪的指標的資格,返回一個純粹的地址。

實際上,由於[]本質上是對指標的解引用,那麼我們訪問陣列元素時可以不拘於a[i][j]這種方式,可以這樣:(*(a+i))[j], 或者*(a[i]+j),或者 *(*(a+i)+j),這幾種寫法是等價的。

對於&b[i][j]呢?我們把b[i][j]換一種寫法,寫成*(*(b+i)+j),這樣問題就容易看清楚了,原來的*b[i][j]就等價於&(*(*(b+i)+j)),我們可以把最外層的括號脫掉,就成了*(b+i)+j,b[i]+j,顯然這是一個指標,指向第i行,第j列元素的指標,對該指標的解釋是一次跨越一個int型的資料。

讓我們再變態一點,考慮三維的情形,雖然三維的陣列不多見,還是考慮一下吧,畢竟空間的座標是用三維表示的。

int c[2][3][4] = {{{1,2,3,4},{5,6,7,8},{9,10,11,12}}, {{13,14,15,16},{17,18,19,20},{21,22,23,24}}};

首先,陣列名c,編譯器將c解釋為一個指標,指向陣列的首地址,由於行序是主序,所以,該指標一步跨越12個整型數,共48個位元組,實際上即跨越了一個二維陣列。

對於&c,跟一維二維的情形類似,是一個純粹的地址.

c[i]呢?可以推測,c[i]與二維中的b類似,即指向指標的指標,c[i]一步跨越4個整數,16個位元組。c[i]是指向指標的指標,那麼c便是指向指向指標的指標的指標(暈~)。

c[i]亦等價於*(c+i)

至於c[i][j],這才是真正的int型的指標,即指向真實資料的指標,一步跨越一個int型,4個位元組。跟二維類似,對於&c[i][j],編譯器返回一個地址,雖然跟c[i][j]的值一樣,但是隻是一個純粹的地址,跨越單元為一個位元組。

對於c[i][j][k],不需要廢話,對於&c[i][j][k],這是一個地址嗎?這是一個指標嗎?我們還是要藉助[]的另一種表示方法:c[i][j][k]等價於*(*(*(c+i)+j)+k),那麼&c[i][j][k]就等價於*(*(c+i)+j)+k,即c[i][j]+k,即指向第(ijk)個元素的指標,一步跨越單元為一個int型。

讓我們來看一看,尋找第(ijk)個元素有哪些寫法:

1c[i][j][k]

2*(c[i][j]+k)

3*(*(c[i]+j)+k)

4*(*(*(c+i)+j)+k)

5(*(c+i))[j][k]

6(*(*(c+i)+j))[k]

7*((*(c+i))[j]+k)

8(*(c[i]+j))[k]

可見,共八種寫法,實際上就是一共有三個解引用,選擇用[]還是用*,這樣,總共有8個組合。(那麼二維的就是4種,一維的2種)

7typedef與指標

typedef似乎很簡單,如typedef int integer;然而,這些簡單的typedef語句容易讓人產生一種誤解,typedef就是一種巨集替換,把後面的自定義型別替換成前面的已知型別,事實是這樣的嗎?顯然不是!

考慮這樣的問題:如何定義一個指向整型的指標型別?如何定義一個函式指標型別?

第一個問題很簡單:typedef int* int_pointer;即可,對於第二個問題,似乎就沒有那麼簡單了,首先,看函式指標的定義方法:int (*p)(const&, int); 這個p指向的函式必須返回int,形參必須是const&int。現在要將這種指標型別命名為func_pointer,其定義的方法如下:

typedef int (*func_pointer)(const&, int);

可以這樣來理解:typedef int integer;typedef去掉,那就是個變數的定義,這兒即定義了一個int型的變數integer,考慮這個integer是什麼型別的,那麼這個typedef語句就是將integer定義為這個型別的。將typedef int (*func_pointer)(const&, int);中的typedef去掉,就成了一個函式指標定義,即func_pointer被定義為函式指標型別變數,那麼原來的typedef即將func_pointer定義為函式指標型別。

8、函式,陣列與指標

int (*testCases[10])();

這個表示式是什麼意思?指標,陣列,函式糅合在了一起問題變得複雜起來。它定義了陣列,testCases[10],陣列中的元素是函式指標,函式指標的型別是 int (*)();

怎麼來理解這種定義呢?首先考慮陣列的定義,陣列的定義一般模式是:

型別 陣列名[大小];

考慮這個表示式,似乎是定義了一個數組,但是陣列名[大小]被夾在了中間,那麼型別是什麼呢,發現型別並不是簡單的資料型別,而是一個函式指標型別int (*p)(),這個函式沒有引數,返回int型。從而這個表示式的含義是:定義了一個函式指標型的陣列,大小是10

可以利用typedef來簡化這種定義:

typedef int (*PFV)();

PFV testCases[10];

其實int (*testCases[10])();這兒我們定義了一個函式指標陣列,陣列是主體。

下面考慮這樣的問題:如何定義一個指向陣列的指標?

指向陣列的指標,好像比較新鮮,所謂指向陣列的指標,即指標的一步跨越是一個數組,跟指向整型的指標一步跨越一個整型一個道理。事實上前面已經碰到了指向陣列的指標,如二維陣列名,實際上就是一個指向陣列的指標,它一次跨越一行的資料,實際上即是跨越了一個一維陣列,而三維陣列名呢,也是一個指向陣列的指標,它一次跨越的是低維組成的一個二維陣列。

陣列指標(即指向陣列的指標)的定義:

int (*ptr)[3];  這個表示式定義了一個數組指標ptrptr一次跨越一個由3int型組成的一維陣列。發現其定義的方式與函式指標定義的方式很相似,只是把()換作了[]

更進一步,如果要定義一個指向陣列的指標,而陣列中的元素不是簡單的int型,而是比較複雜的型別,那該如何定義呢?事實上陣列指標這種東西就已經夠稀有的了,一般程式設計絕對不會用到,我們只需要能讀懂一些比較複雜的東西就行了,自己沒有必要構造這麼複雜的型別。

比較複雜的表示式:

      1int (*(*(*p())[])())[];

首先,根據p()判斷p是一個函式,再根據p()前面的*號判斷該函式返回一個指標,下面就看這個指標指向的是什麼類新了,我們可以把*p()替換成一個*pointer,這個pointer就是函式p返回的指標,那麼就成了int (*(*(*pointer)[])())[];再根據(*pointer)[],這說明了指標pointer是指向的一個數組,那麼這個陣列中的元素是什麼型別呢?由於陣列名實際上就是個指標,我們把(*pointer)[](即(*p())[])替換成一個array,這樣就成了 int (*(*array)())[];發現array是一個函式指標,從而陣列中的每個元素是函式指標,而這個函式呢,又返回一個指標型別,把(*array)()func代替,就成了int (*func)[];這說明了func函式返回的是指向陣列的指標,陣列中的元素是int型。

這個表示式夠酷!!!

2p = (int( * (*)[20])[10])q;

這是一個強制型別轉換,q被強制型別轉換成一個這樣的指標型別,這個指標呢直線一個具有20個元素的陣列,這個陣列中的元素也是指標,是指向另外一種陣列,這種陣列是含有10int型資料的一維陣列。

可見,分析複雜的表示式時(所謂複雜,即糅合了指標,陣列,函式三樣,缺少了一樣就不會複雜了),從括號的最裡層做起,最裡層的東西是複雜表示式的根節點,然後一層一層脫,脫的時候,是這樣的,比如裡層是個陣列,那麼就是說這個陣列的元素是什麼呢,那就是外層的東西,如果裡層是個有返回值的函式,那麼就是說這個函式返回什麼值呢?那就是外層的東西,就這樣一層一層地把表示式解析清楚。

關於typedef還有一些要說的:

typedef  int (*PFV)(); 這是定義了一個函式指標,那麼PFV p;就可以定義了一個指向函式的指標。

typedef int (*p[10])(); 這是把p定義為函式指標陣列,那麼 p array;語句就可以定義了一個函式指標陣列,陣列名即為arrayarray這個陣列含10個元素。

typedef int (*parray)[3];這是定義了一個指向整型陣列的指標,那麼 parray ptr;就定義了一個指向陣列的指標。如何對這個ptr賦值或者初始化呢?事實上,是通過二維陣列名來對其進行賦值(初始化)的,因為二維陣列名作為指標來講,就是一個指向陣列的指標,一次跨越一個數組。

typedef int a[3][3]; 這個語句什麼意思呢?這是把a定義為一個3*3的整型陣列型別。當a b = {1}時就完成了一個3×3的整型陣列的定義初始化的工作。

同樣,簡單一點 typedef int a[3];這個語句是把a定義為一個一維陣列型別。

typedef  void func(int); 這個語句定義了一個函式型別。通過這個typedef,我們可以比較清晰地定義出函式指標,func* p;即可。

typedef char* string;

const string str;

這個str是什麼型別的呢?const char * str,即指向常量的指標型別?事實上,答案有些不可思議,str是一個常量指標,而不是指標常量,即const修飾符針對的是指標,而不是char

9、引用與指標

引用類似與指標常量,只可初始化,不可賦值。別名(alias)是引用(reference)的另一種叫法。通過引用可以間接地操操縱物件。

常量引用,即類似與指向常量的常量指標,對常量引用的初始化,有一點特殊,可以用常量,變數,甚至是常數對其進行初始化。

對於用變數初始化常量引用,那麼不能通過這個引用修改這個變數,但是本來的變數名可以。這一點,類似變數地址賦給常量指標。通過常量指標不可以修改變數,但是變數自身的變數名可以。

可以有指標的引用,如 int a = 1; int* p = &a; int* &r = p; 那麼r就成了指標p的引用。如果是const int* p = &a;說明是常量指標,那麼定義引用的時候,就要如此定義,const int* &r = p;這個語句說明r是一個指標的引用,這個指標是個指向常量的指標變數,而並不意味著這個引用是個常量引用。那如果說這個指標不僅僅是常量指標,而且是個指標常量,即const int* const p;那麼定義引用時要注意應該const int* const &r = p;這樣表明該引用是個常量引用。這裡有一個問題當用一個變數的地址初始化引用時如,int a = 22; int* const &pi_ref = &a;需要注意應該定以為常量引用,因為&a不是變數名,而是類似常數。而若const int a = 22;則應該const int* const &pi_ref = &a;即中間的const是用來定義常量引用的,而前面的const反映的是引用指向的物件(指標)是指向的的const物件。

我們有物件名,或者物件的指標,這些都可以操縱物件,為何要引入引用的概念呢?

事實上,引用最常用的是用作函式的形參。要在函式中操縱一個外部物件的時候,利用引用是一個好辦法。

關於引用

首先,引用只可初始化,不可被賦值,因為,被初始化後的引用,就成了被引用的物件的別名,再行賦值,就不是對引用本身的賦值了,而是對所引用的物件的賦值了。

Come from: http://blog.csdn.net/keensword/archive/2005/06/22/400577.aspx