1. c語言指針問題
C語言的大多語法都是簡單易懂的,這常常會給初學者一種「別人都說C語言難,我看也不過如此」的感覺。但是這種感覺常常會停止在初學者學到指針時,這是一些讀者跟我說的。
鑒於很多C語言初學者都覺得指針非常難,
小明定義了一個C語言函數 int f(int * ),為什麼 f(&5) 不能正常工作呢?
如果希望傳遞 5 給函數 f(),在C99中,可以使用下面這種方法:
f( (int[]){5} );
上面這行C語言代碼相當於定義了一個數組,並且數組只有一個元素 5,函數 f() 接收到的參數是該數組,只不過這一過程中的數組名沒有「顯示」。
拋開C99的這個特性,C語言調用 f(&5) 就不能這么寫了,而是需要藉助變數:
int five = 5;
f(&five);
在C語言中,接受某個值指針的函數是有可能通過該指針修改該值的(即使程序員無此打算,C語言還是會一直這么認為),因此只有變數才能勝任。在C語言中,常數是只讀的,只能作為右值,& 運算符是不能處理常數 5 的,f(&five) 會引發編譯錯誤。
f(&five) 會引發編譯錯誤
表達式 * p++ 增加了 p 指向的數值,還是指針 p 本身?
C語言中的 ++ 和 — 運算符的優先順序高於 * 運算符,所以 *p++ 其實就相當於 *(p++)。顯然,++ 運算符增加的是指針 p 本身的值,不過在指針 p 自增之前,*p++會先返回 p 指向的值。如果希望 ++ 運算符增加 p 指向的值,應該使用括弧運算符:(*p)++。
小明想使用指針操作數組里的數值,下面這段C語言代碼有什麼問題?
小明預計程序會輸出 3,但是程序卻輸出了「垃圾值」,他的C語言代碼如下,請看:
int array[5], i, *ip;
for(i = 0; i < 5; i++)
array[i] = i;
ip = array;
printf("%d\n", *(ip + 3 * sizeof(int)));
2. c語言問題
A
a[3]是5
a單獨用實際上表示的是數組第一個元素的地址,所以a+a[3]就是a+5就是數組第六個元素的地址,再在前邊加上*取值符號,所以*(a+a[3])就是4,最後的答案就是a[4],即0,選A
3. 如何用指針和數組實現C++的四則運演算法則
可選中1個或多個下面的關鍵詞,搜索相關資料。也可直接點「搜索資料」搜索整個問題。
4. C語言中指針引用二維數組元素的問題
下面是本人文庫中的文章,關於二維數組與指針的講解。
1、兩條基本准則:
a、首先要明白,指針運算符的作用,我用一言以概之,你在哪裡使用都不會錯。指針運算符*的作用是求出*後面所指地址里的值。因此只要*後面的變數表示的是一個地址就可以使用*運算符,來求出這個地址中的值,你不用管這個地址的表示形式是怎樣的,只要是地址就可以使用*來求出地址中的值。
b、[ ]這個運算符的的運演算法則是,把左側的地址加上[ ]內的偏移量然後再求指針運算,注意有[ ]運算符的地方就有個隱含的指針,比如x[2]表示的就是將指針x偏移2個單位量後再求指針運算。也就說x[2]與*(x+2)是相等的。
2、對二維數組的講解:
a、對一維數組地址的詳細講解:
比如一維數組a[5],眾所周知,一維數組的數組名a表示的是第一個元素a[0]的地址,也就是說數組名與&a[0]是等價的,因此數組名a的地址,並不是這個一維數組的數組的地址。那麼&a,表示的又是什麼呢?因為,a是一個一維數組,所以&a表示的就是這個一維數組的地址,也就是說&a中的地址是一個包含有4個元素的一維數組的地址。就好比int i中的&i,才表示的是這個變數i的地址一樣。
b、對二維數組地址的講解:
比如二維數組b[3][4],我們首先從一維開始分析,眾所周知b[0],b[1]分別表示的是二維數組中第一行第一個元素和第二行第二個元素的地址,也就是說b[0]是與&b[0][0]等價的,b[1]是與&b[1][0]等介的。因此數組名就與&b[0]等介的,也就是說數組名表示的是二維數組中第一行所包含的一維數組的地址,說簡單一點,就是說二維數組名是二維數組中第一行的行地址。因此二維數組名b所包含的地址中包含有二維數組中第二維中元素的個數的一維數組,也就是b的地址中包含一個含有4個元素的一維數組的地址(也就是所謂的數組的數組了)。
c、對二維數組中地址的相加運算的講解:
同樣以b[3][4]為例來講解,在上面講到b[0]表示的是&b[0][0],因此對b[0]進行相加運算,比如b[0]+1,那麼就將使地址偏移一個單位,也就是地址被偏移到了&b[0][1]處,也就是b[0]+1表示的是b[0][1]的地址。上面也講到數組名b,表示的是一個一維數組的地址,因此對數組名進行偏移,比如b+1,則將使指針偏移一個一維數組的長度,也就是b+1,將是&b[1]的地址,因此b+1的地址,表示的是二維數組中第二行所包含的一維數組的地址,簡單點就是第二行的行地址。
d、對二維數組的指針運算:
*b
在上面講解過,因為b表示的是二維數組中第一行所包含的一維數組的地址,因此*b=*(b+0)=*(&b[0])=b[0],所以*b表示的是二維數組中第一行第一個元素的地址,即*b=&b[0][0],用語言來描術*(b+0)就是,把數組名的地址偏移0個單位,然後再求這個地址所包含的值。在二維數組中,這個值就是指的b[0],因此這個值是與b[0][0]的地址相等的,結果就是*(b+0)與b是相等的,這在多維數組中是個怪現象,至少本人無法理解這是為什麼。因為對同一個地址,進行指針運算得到了不同的結果,比如*b和*b[0],因為b和b[0]都是相同的地址,但對他們進行指針運算卻得到了不同的結果,*b得到了&b[0][0]的地址,而*b[0]得到了b[0][0]的值,這是對同一個地址進行指針運算卻得到了不同的值,對於這個問題,無法理解。
*(b+1)和*(b+0)
對於*(b+1)也和*(b+0)是同樣的道理,*(b+1)=b[1]。我們再來看*b[1],因為*b[1]=*(b[1]+0)=*(&b[1][0])=b[1][0],可以看出,這就是二維數組中第二行第一個元素的地址。
*(*(b+1)+1)
因為*(*(b+1)+1)=*(*(&b[1])+1)=*(b[1]+1)=*(&b[1][0]+1)=*(&b[1][1])=b[1][1],語言描術就是,b+1使地址偏移到了二維數組中第二行所包含的一維數組的地址(或第二行的行地址),然後再對這個行地址求指針(或求值)運算,因此就得到第二行第一個元素的地址,然後再對這個地址偏移一個單位,就得到第二行第二個元素的地址,再對這個地址進行指針運算,就得到了這個元素的值,即b[1][1],其他的內容可以以止類推。
e、對二維數組的指針和[ ]的混合運算
在下面的指針和[ ]的混合計算中,要記住兩點關鍵法則,記住了這兩點在哪裡計算都不會出錯
a、對於像b[1]這樣的地址,最好應表示為&b[1][0]再進行偏移計算,比如對於b[1]+1,這不是直接在對b[1]加1,也就是b[1]+1不等於b[2],因為b[1]表示的是第二行行1個元素的地址,對其加1,應該表示的是第二行第二個元素的地址,也就是&b[1][1],而b[2]則表示的是第二行第一個元素的地址,因此錯誤,所以在計算時應把b[1]轉換為&b[1][0]之後,才能直接進行地址的偏移,也就是說b[1]+1=&b[1][0]+1=&b[1][1],這樣才能得到正確的結果,並且不會出錯。
b、對於有小括弧的地方,一定不要省略小括弧。比如(&b[1])[1]與&b[1][1]將表示的是不同的結果,第二個是顯然的,對於第一個(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],可以看到,表示的是第3行第1個元素的地址,因此這兩個的結果是顯然不一樣的。因此對於(b+1)[1]這樣的運算,不能省略小括弧,即(b+1)[1]=(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],如果省略了小括弧,則是(b+1)[1]=&b[1][1],這將是不易發現的錯誤。因此這是兩個完完全全不同的符案。
c、總結,指針和[ ]混合運算2點關鍵,
第1:應把是地址的[ ]運算,轉換為地址的形式,比如b[1]應轉換為&b[1][0]。因為只有這樣才能進行直接的地址相加運算,即&b[1][0]+1=&b[1][1],而b[1]+1不等於b[2]。
第2:有小括弧的地方不能省略小括弧,如(b+1)[1]=(&b[1])[1]=*((&b[1])+1)=*(&b[1]+1)=*(&b[2])=b[2],也&b[1][1]是完全不同的。
(*(b+1))[1] ,(*b)[1]
最簡單的理解方法為*(b+1)和語句b[1]等價,即(*(b+1))[1]和語句b[1][1]是相同的,也就是二組數組第2行第2個元素的值b[1][1],理解方法2逐條解釋,如上面的解釋*(b+1)表示的是二維數組b的第二行第一個元素的地址,也就是b[1],然後再與後面的[1]進行運算,就得到b[1][1]。或者(*(b+1))[1]=*((*(b+1))+1)=*(*(b+1)+1)這個語句上面解釋過。同理(*b)[1]=b[0][1]
*(b+1)[1]:
計算方法1把[ ]分解為指針來計算:因為[ ]運算符高於指針,因此應先計算[ ]再計算指針,因為[1]表示的是把左側的地址偏移1個單位,再求指針,因此(b+1)[1]=*((b+1)+1),最後再計算一次指針運算,也就是*(b+1)[1]=**((b+1)+1)=**(b+2)=*(*(b+2))=*b[2]=b[2][0],可以看到,最後表示的是b中第3行第一個元素的值。
計算方法2把指針化為[ ]來計算:*(b+1)[1]=*(&b[1])[1]=*(*(&b[1]+1))=**(&b[2])=*b[2]=b[2][0],注意*((&b[1])[1])表達式中應把(&b[1])括起來,若不括起來,則[ ]運算符的優先順序高於&運算符,因此(&b[1])[1]與&b[1][1]是不一樣的,後一個表示的是第二行第二個元素的地址,而頭一個(&b[1])[1]則表示的是,對b的第二行的行地址偏移1個單位後再求指針的結果,也就是*(&b[1]+1)=*(&b[2])=b[2],所以性質是不一樣的。
(*b+1)[2]
計算方法1化簡[ ]運算符:(*b+1)[2]=*((*b+1)+2)=*(*b+3)=*(&b[0][0]+3)=*(&b[0][3])=b[0][3],這里要注意*b表示的是b[0]=&b[0][0],在計算時最好不要代入b[0]來計算,而應把b[0]轉換為&b[0][0]後再計算,因為b[0]+3,很容易被錯誤的計算為b[3],而實際上b[0]指向的是第一行第一個元素的地址,對其偏移3個單位應該是指向第一行第4個元素的地址,即&b[0][3],而b[3],則指向了第3行第1個元素的地址,這是不同的。
計算方法2化簡*運算符:(*b+1)[2]=(b[0]+1)[2]=(&b[0][0]+1)[2]=(&b[0][1])[2]=*(&b[0][1]+2)=*(&b[0][3])=b[0][3],注意,在計算過程中小括弧最好不要省略,省略了容易出錯,因為[ ]運算符的優先給更高,如果省略了,某些地方將無法計算。比如(&b[0][0]+1)[2]=(&b[0][1])[2],如果省略掉括弧,則成為&b[0][1][2],這對於二維數組來講,是無法計算的。
(*(b+1))[5]
計算方法:(*(b+1))[5]=(*(&b[1]))[5]=(b[1])[5]=*(b[1]+5)=*(&b[1][0]+5)=*(&b[1][5])=b[1][5],結果等於第二行第6個元素的值。
f、注意,在二維或者多維數組中有個怪現象,比如對於多維數組a[n][m][i][j],那麼這些地址是相同的,即數組名a, a[0], a[0][0], a[0][0][0], &a[0][0][0][0],都是相同的地址。而且對數組名依次求指針運算將得到,比如*a=a[0],*a[0]=a[0][0], *a[0][0]=a[0][0][0], *a[0][0][0]=a[0][0][0][0],可以看到,只有對最後這個地址求指針運算才真正得到了數組中的值,因此對數組名求指針運算,要得到第一個元素的值,應該****a,也就是對4維數組需要求4次指針運算。同樣可以看到,對數組名進行的前三次指針運算的值都是相同的,即*a, **a, ***a和a的值都是&a[0][0][0][0]的值,這就是這個怪問題,按理說對地址求指針應該得到一個值,但對多維數組求指針,卻得到的是同一個地址,只是這些地址所包含的內容不一樣。
3、數組指針與二維數組講解:
下面我們將以y[4]={1,2,3,4}這個一維數組為例來層層講解,指針和數組的關系。
1、數組指針:
定義形式為:int (*p)[4];表示定義了一個指向多維數組的指針,即指針p指向的是一個數組,這個數組有4個元素,對這個指針p的賦值必須是有4個int元素的數組的地址,即只要包含有四個元素的數組的地址都能賦給指針p,不管這個數組的行數是多少,但列數必須為4。即int y[4],x[22][4];都可以賦給指針p。賦值方式為p=&y或p=x,對於&y和二維數組數組名前面已講過,&y中的地址是一個包含有4個元素的一維數組的地址,二維數組名x也是一個含有4個元素的一維數組的地址,因此可以這樣賦值。而這樣賦值將是錯誤的p=y,或者p=x[0]; 因為y和x[0]的地址只包含一個元素,他們不包含一個數組,因此出錯。
2.注意()必須有,如果沒有的話則,int *p[4];則是定義了一個指針數組,表示每個數組元素都是一個指針,即p[2]=&i;指向一個int型的地址,而*p[2]則表示p[2]所指向的元素的值。
3.初始化數組指針p:
a、當把int y[4]賦給指針p時p=y將是錯誤的,正確的方式為p=&y因為這時編譯器會檢查賦給指針p的元素是否是含有四個元素的數組,如果是就能正確的賦值,但語句p=y中的y代表的是數組y[4]第一行第一列的元素的地址也就是&y[0]的地址,因此y指向的地址只有一個元素,而指針p要求的是有4個元素的數組的地址,因此語句p=y將出錯。而&y也表示的是一個地址,因為數組名y表示的是&y[0]的地址,因此&y=&(&y[0])。可以把&y理解為是數組y[4]的第一行的行地址,即&y包含了數組y[4]的第一行的所有元素,在這里&y包含有4個元素,因則p=&y才是正確的賦值方法,在這里要注意,數組的某行的行地址是第本行的第一個元素的地址是相同的。
b、把x[22][4]賦給指針p有幾種方法,方法一:p=x;我們這樣來理解該條語句,首先x[0]表示的是二維數組x[22][4]的第1行第一列元素的地址,這個地址包含一個元素,這是顯而易見的,而數組名x也表示一個地址,但這個地址包含的是一個數組(即數組的數組),這個數組是包含4個元素的數組,這4個元素就是數組x第一行的4個元素,也就是說x表示的是數組x[22][4]的第1行的行地址,即數組名x就包含了數組x[22][4]第1行的4個元素,因此這種賦值方式是正確的。這時指針p就相當於是數組名一樣,比如p[2][1]訪問的就是數組x的第3行的第2個元素。
c、方法二:p=x+1或者p=&x[1];注意必須要有地址運算符&,同理語句&x[1]表示的是數組x[22][4]第2行的行地址,因為x[1]表示的是數組x[22][4]第的第2行第1列的元素的地址,因此&x[1]表示的就是數組x的第2行的行地址,因為&x[1]這個地址包含了一個數組,這個數組的起始地址是從x[1]這個地址開始的,這,即數組x[22][4]中x[1]這一行的4個元素。在這一行中包含了4個元素。而x+1本身就是指的x[1]的地址。這時指針p的起始地址是&x[1],所以p[0][1]不再是訪問的x的第一行第二個元素,而是訪問的x的第二行第二個元素。
d、注意,再次提示,數組的某行的行地址是與本行的第一個元素的地址是相同的。
5. int (*p)()表示
指針的指針
其實是 *(*p)
意思是該指針指向 int *p
數組
以指針為元素的數組
是函數指針
這個函數指針指向的函數的返回類型是一個函數指針構成的數組
參考:
復雜指針解析
因為C語言所有復雜的指針聲明,都是由各種聲明嵌套構成的。如何解讀復雜指針聲明呢?右左法則是一個既著名又常用的方法。不過,右左法則其實並不是C標准裡面的內容,它是從C標準的聲明規定中歸納出來的方法。C標準的聲明規則,是用來解決如何創建聲明的,而右左法則是用來解決如何辯識一個聲明的,兩者可以說是相反的。右左法則的英文原文是這樣說的:
The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you
encounter parentheses, the direction should be reversed. Once everything in the parentheses has been
parsed, jump out of it. Continue till the whole declaration has been parsed.
這段英文的翻譯如下:
右左法則:首先從最裡面的圓括弧看起,然後往右看,再往左看。每當遇到圓括弧時,就應該掉轉閱讀方向。一旦解析完圓括弧裡面所有的東西,就跳出圓括弧。重復這個過程直到整個聲明解析完畢。
筆者要對這個法則進行一個小小的修正,應該是從未定義的標識符開始閱讀,而不是從括弧讀起,之所以是未定義的標識符,是因為一個聲明裡面可能有多個標識符,但未定義的標識符只會有一個。
現在通過一些例子來討論右左法則的應用,先從最簡單的開始,逐步加深:
int (*func)(int *p);
首先找到那個未定義的標識符,就是func,它的外面有一對圓括弧,而且左邊是一個*號,這說明func是一個指針,然後跳出這個圓括弧,先看右邊,也是一個圓括弧,這說明(*func)是一個函數,而func是一個指向這類函數的指針,就是一個函數指針,這類函數具有int*類型的形參,返回值類型是 int。
int (*func)(int *p, int (*f)(int*));
func被一對括弧包含,且左邊有一個*號,說明func是一個指針,跳出括弧,右邊也有個括弧,那麼func是一個指向函數的指針,這類函數具有int *和int (*)(int*)這樣的形參,返回值為int類型。再來看一看func的形參int (*f)(int*),類似前面的解釋,f也是一個函數指針,指向的函數具有int*類型的形參,返回值為int。
int (*func[5])(int *p);
func右邊是一個[]運算符,說明func是一個具有5個元素的數組,func的左邊有一個*,說明func的元素是指針,要注意這里的*不是修飾 func的,而是修飾func[5]的,原因是[]運算符優先順序比*高,func先跟[]結合,因此*修飾的是func[5]。跳出這個括弧,看右邊,也是一對圓括弧,說明func數組的元素是函數類型的指針,它所指向的函數具有int*類型的形參,返回值類型為int。
int (*(*func)[5])(int *p);
func被一個圓括弧包含,左邊又有一個*,那麼func是一個指針,跳出括弧,右邊是一個[]運算符號,說明func是一個指向數組的指針,現在往左看,左邊有一個*號,說明這個數組的元素是指針,再跳出括弧,右邊又有一個括弧,說明這個數組的元素是指向函數的指針。總結一下,就是:func是一個指向數組的指針,這個數組的元素是函數指針,這些指針指向具有int*形參,返回值為int類型的函數。
int (*(*func)(int *p))[5];
func是一個函數指針,這類函數具有int*類型的形參,返回值是指向數組的指針,所指向的數組的元素是具有5個int元素的數組。
要注意有些復雜指針聲明是非法的,例如:
int func(void) [5];
func是一個返回值為具有5個int元素的數組的函數。但C語言的函數返回值不能為數組,這是因為如果允許函數返回值為數組,那麼接收這個數組的內容的東西,也必須是一個數組,但C語言的數組名是一個右值,它不能作為左值來接收另一個數組,因此函數返回值不能為數組。
int func[5](void);
func是一個具有5個元素的數組,這個數組的元素都是函數。這也是非法的,因為數組的元素除了類型必須一樣外,每個元素所佔用的內存空間也必須相同,顯然函數是無法達到這個要求的,即使函數的類型一樣,但函數所佔用的空間通常是不相同的。
作為練習,下面列幾個復雜指針聲明給讀者自己來解析。
int (*(*func)[5][6])[7][8];
int (*(*(*func)(int *))[5])(int *);
int (*(*func[7][8][9])(int*))[5];
實際當中,需要聲明一個復雜指針時,如果把整個聲明寫成上面所示的形式,對程序可讀性是一大損害。應該用typedef來對聲明逐層分解,增強可讀性,例如對於聲明:
int (*(*func)(int *p))[5];
可以這樣分解:
typedef int (*PARA)[5];
typedef PARA (*func)(int *);
這樣就容易看得多了。
答案,同時給出用typedef的分解方法:
int (*(*func)[5][6])[7][8];
func是一個指向數組的指針,這類數組的元素是一個具有5X6個int元素的二維數組,而這個二維數組的元素又是一個二維數組。
typedef int (*PARA)[7][8];
typedef PARA (*func)[5][6];
int (*(*(*func)(int *))[5])(int *);
func是一個函數指針,這類函數的返回值是一個指向數組的指針,所指向數組的元素也是函數指針,指向的函數具有int*形參,返回值為int。
typedef int (*PARA1)(int*);
typedef PARA1 (*PARA2)[5];
typedef PARA2 (*func)(int*);
int (*(*func[7][8][9])(int*))[5];
func是一個數組,這個數組的元素是函數指針,這類函數具有int*的形參,返回值是指向數組的指針,所指向的數組的元素是具有5個int元素的數組。
typedef int (*PARA1)[5];
typedef PARA1 (*PARA2)(int*);
typedef PARA2 func[7][8][9];
6. C語言行指針
錯解了。p沒有指向a[0][0],因為使用*p不能輸出a[0][0]的值,*p才是指向a[0][0]的,因為**p就是a[0][0]的值,而且*p輸出的是&a[0][0]的地址。p是指向的a[0]也就是說p=&a[0]=a+0;的,也就是指向的二維數組的第一行。
p++ = &a[1]=a+1; 也就是指向二維數組中的第二行。
總之,要記住的是a[1]才是指向的二維數組中第2行第1個元素的地址。
而&a[1]表示的就是二維數組中第二行的行地址。
同樣,因為a=a+0,表示的是二維數組中第一行的行地址。因此a+1就是表示的是二維數組中第二行的行地址。
還要記住:[ ]這個運算符的的運演算法則是,把左側的地址加上[ ]內的偏移量然後再求指針運算,注意有[ ]運算符的地方就有個隱含的指針,比如x[2]表示的就是將指針x偏移2個單位量後再求指針運算。也就說x[2]與*(x+2)是相等的。