當前位置:網站首頁>C語言———指針進階4

C語言———指針進階4

2022-01-27 23:37:00 脫韁的野驢、

目錄

9、指針和數組筆試題解析:

10、指針筆試題:


9、指針和數組筆試題解析:

sizeof(數組名)—— 如果sizeof裏面單獨放了一個數組名,那麼該數組名錶示整個數組的,計算的是整個數組所占空間的大小,單比特是字節。

&數組名 —— 如果取地址符號後面單獨放了一個數組名,該數組名錶示整個數組的,取出的是整個數組的地址

除此之外,所有的數組名,都是數組首元素的地址

一、

//一維數組

1.int a[ ] = {1,2,3,4};
2.printf("%d\n",sizeof(a));
3.printf("%d\n",sizeof(a+0));
4.printf("%d\n",sizeof(*a));
5.printf("%d\n",sizeof(a+1));
6.printf("%d\n",sizeof(a[1]));
7.printf("%d\n",sizeof(&a));
8.printf("%d\n",sizeof(*&a));
9.printf("%d\n",sizeof(&a+1));
10.printf("%d\n",sizeof(&a[0]));
11.printf("%d\n",sizeof(&a[0]+1));

2.

sizeof(a)中的a是數組名,單獨放在了sizeof內部,他就代錶整個數組,計算的是整個數組所占空間的大小,一個int整型占4byte,整個數組裏面有4個int整型,所以,整

個數組所占的空間大小就是4*4=16byte;

3.

sizeof(a+0),,其中,a是數組名,但不是單獨放在sizeof內部的,因為sizeof中放的是a+0而不是單獨的a,a這裏也沒有&符號,,所以a代錶的是數組首元素的地

址,,所以a+0也相當於是數組首元素的地址,現在就是計算地址所占內存空間的大小,地址又是指針,指針就是地址,地址或指針所占空間的大小只和平臺有關,32

比特平臺即X86下,大小就是4byte,64比特平臺即X64下大小就是8byte,要注意的是,此時沒有解引用,所以a+0不是指的數組首元素的內容,而是數組首元素的地址。

4.

sizeof(*a),此處不是把數組名單獨放在了sizeof內部,他是一個*a,又沒有&數組名,,所以,這裏的a就是數組首元素的地址,所以*a即對數組首元素地址進行解引

用,拿到的就是數組的首元素,而數組首元素的類型又是int整型,所以所占內存空間的大小就是4byte。

5.

sizeof(a+1),,其中,a是數組名,但不是單獨放在sizeof內部的,因為sizeof中放的是a+1而不是單獨的a,a這裏也沒有&符號,,所以a代錶的是數組首元素的地

址,,數組首元素的類型又是int整型,所以a+1就會跳過一個int整型,即4byte,所以,a+1指的就是數組中第二個元素的地址,現在sizeof就是對地址進行計算所占內存

空間的大小,地址又是指針,指針就是地址,地址或指針變量所占空間的大小只和平臺有關,32比特平臺即X86下,大小就是4byte,64比特平臺即X64下大小就是8byte,我

們一般常用的是都是X86平臺即32比特平臺,所以結果4byte。

6.

sizeof(a[1]),a與[1]結合,這就是通過下標進行的訪問,所以a[1]指的就是數組第二個元素,,,計算的是第二個元素所占空間的大小,而不是計算一個地址的大

小,,第二個元素是一個int整型,所以4byte;

7.

sizeof(&a),雖然不是單獨的把數組名放在sizeof中,但是54444444胡讓你爸在&符號後面單獨放了一個數組名,即:&a,,a是數組名,在單獨的一個數組名前面加

個&,所以,這裏的a就是代錶整個數組,取出的是整個數組的地址,即使是整個數組的地址,她仍是一個地址,,sizeof(&a)就是在計算一個地址的大小,計算地址

或指針變量的大小結果就是4或8byte,所以結果就是4byte;

8.

sizeof(*&a),,*的優先級高於&,但是兩者中間沒有數據,所以即使*的優先級高,也要先進行&取地址,然後整體再去解引用,現在sizeof中並不是單獨的數組名,但

是,&後是單獨的數組名,此時數組名代錶的是整個數組,所以,&a取出的是整個數組的地址,該整個數組地址的類型是:int(*)[4],然後再去對整個數組的地址進行

解引用,拿到的就是整個數組,所以,*&a代錶的就是整個數組,,sizeof(*&a)計算的就是整個數組所占內存空間的大小,已知,該數組有4個元素,每個元素都是int

整型,所以大小就是:4*4=16byte,其次一種方法就是:因為&和*可以相互抵消,,所以,sizeof(*&a)=== sizeof(a),就和上面的上面的分析是一樣的,要知道的

是可以對指針變量進行解引用,也可以直接對地址進行解引用,如果是一個整型int類型的地址的話,,解引用就可以找到一個int整型,,一個字符類型的地址解引用就

可以找到一個字符,一個數組類型的地址,解引用就可以找到一個數組。

9.

sizeof(&a+1),這裏的&a取出來的是整個數組的地址,該地址的類型是int(*)[4],整個數組的地址加1,就會找到跳過這整個數組然後找整個數組後面的數組的地

址,它仍是一個地址,就是計算一個地址的大小,,所以結果是4或8byte。

10.

sizeof(&a[0]),這裏要考慮優先級,,因為[ ]的優先級高於&,,所以,a先和[0]結合,,指的就是下標為0的元素的內容,訪問的就是數組首元素,然後再取地址,取

出的就是數組首元素的地址,再對一個地址計算所占內存空間的大小,那麼結果就是4或者8byte;

11.

sizeof(&a[0]+1),前面的&a[0],這就是第一個元素的地址,首元素的類型是int整型,首元素的地址+1,就會跳過一個int整型,跳過4byte,指向第二個元素的地址,

然後計算地址的大小就是4或者8byte;

二、

我們所說的指針變量或地址的大小只和平臺是什麼類型有關,32比特平臺,指針變量大小就是4byte,64比特平臺,指針變量大小就是8byte,指針變量的大小只和平臺類型

有關,和指針變量的類型是無關的,,不論你是什麼樣的指針類型,int*也好,char*也好,都沒關系,結果都是4或8byte,只和平臺有關,地址的大小也是一樣,地址的

大小也是只和平臺有關,和什麼樣類型的地址沒有關系,,整型int的地址還是char字符型的地址,都沒關系,只和平臺有關,,因為指針變量就是地址,兩者是一個東西;

char arr[] = {'a','b','c','d','e','f'};

printf("%d\n", sizeof(&arr+2));

這裏的&arr取出來的是整個數組的地址,&arr+1,就是直接跳過這一個數組,就是跳過6byte,如果是&arr+2,就會跳過新的這一個數組,再跳過一個同樣長度的數組,

就會跳過6+6=12個字節;

sizeof只關心要求的內容所占內存空間的大小,對於字符數組來說,不關心字符數組裏面是否存在字符\0 ,,,sizeof只關注占用內存空間的大小,單比特是字節,sizeof

不關注類型,什麼類型都可以計算,strlen關注的是字符串中字符\0的比特置,計算的是字符串中字符\0前面的字符個數,strlen只針對於字符數組和字符串,只能用來求字

符串或字符數組的長度,在此,sizeof是一個從操作符,而strlen是一個庫函數,

三、

char arr[] = {'a','b','c','d','e','f'};
1.printf("%d\n", strlen(arr));

2.printf("%d\n", strlen(arr+0));

3.printf("%d\n", strlen(*arr));

4.printf("%d\n", strlen(arr[1]));

5.printf("%d\n", strlen(&arr));

6.printf("%d\n", strlen(&arr+1));

7.printf("%d\n", strlen(&arr[0]+1));

1.

因為這裏沒有sizeof和&,,所以這裏的數組名arr就代錶數組首元素的地址,把這個地址傳給strlen函數,那麼函數就會從這個地址對應的那個元素,就是字符a開始沿著

元素往後找,a,b,c,d,e,f,現在把整個數組裏面的元素都找完了,還是沒有發現字符\0,所以會出去該數組,在內存空間裏面去找字符\0,但是在內存空間中,不

確定字符\0的確切比特置,所以說,會返回一個隨機值,由於內存空間在每次執行代碼的時候都會被隨機分配,所以每次返回的隨機值都可能不同, 在本題中,返回的數

值是>=6的;

2.

因為這裏沒有sizeof和&,,所以這裏的數組名arr就代錶數組首元素的地址,則arr+0也是數組首元素的地址,就和上述情况是一樣的,返回一個大於等於6的隨機值。

3.

因為我們strlen函數是用來求字符串長度的,,我們用my_strlen函數來模擬實現strlen函數功能的時候,我們設定的函數my_strlen的返回類型是int,因為最後要知道字符

串長度為多少,,該數是一個int整型,,而我們函數的參數部分由於傳過來的是一個地址,,所以要用指針來接收,,而我們又是對字符串求長度,所以該地址是一個

字符的地址,就要用一個字符指針來接收,,,即,char*str來接收的,做好用const來修飾一下,即:const char*str,,因為我們確認一下從那個元素開始數,但我們

不需要改變這個元素的內容,,所以用const放在*左邊,,那樣const就會固定str指向的那個元素的內容不會發生改變,但是不管用不用const修飾,,最終,都是一個

char類型的指針變量,,所以,對於strlen函數來說,,參數部分必須要傳過來的是一個地址才可以,strlen需要的是一個地址,從該地址向後找字符,直到找到字符\0,

再統計字符的個數,strlen函數會進行其參數類型的檢查。

我們這裏的arr是數組首元素的地址,然後*arr,解引用找到的就是字符a,,把字符a傳給strlen函數,strlen函數默認接收到的是一個地址,地址又是一串數字,所以,

他會認為傳過來的是一個數字,因為字符a的ASCII碼是97,,系統就會默認,把97看做一個地址,就是從起始地址為97的地方開始找字符\0,,對於strlen函數來說,,

參數部分必須為一個地址,,如果不是地址,就會出現錯誤,不管傳過去的參數是什麼東西,,一律會被strlen函數默認為該東西是一個地址;但是以97作為起始地址的

時候,該地址是非法的,該地址不是分配給我們的,所以系統就會報錯,會形成內存訪問沖突,系統就會報錯為:在讀取比特置0x00000061時出現錯誤,,這是16進制數

字,,轉為二進制就是00000000000000000000000001100001,對應的數字正好是我們的字符a的ASCII碼97,就是把我們的97當做了一個地址;

4.

arr[1]代錶的就是字符數組第二個元素'b',,把字符b傳過去和把字符a傳過去得到的結果是一樣的。

5.

&arr取出來的是整個數組的地址,整個數組的地址和數組首元素的地址只是類型不同,但是所指的比特置,即他們的值是相同的,該數組地址的類型應該是一個數組指針

類型,即: char(*)[6],這就是該整個數組的地址的類型,把整個數組的地址傳給strlen函數,在strlen函數內部裏,即strlen函數形參部分用的是const char*str,所

以,即使你是一個數組指針類型的char(*)[6],傳過來,我strlen函數內部就會直接把他看成const char*的類型,,就比如强制類型轉換一樣,,現在把整個數組的地

址的類型轉換為const char*的類型,,就是直接把那個整個數組的地址看成了數組首元素的地址了,,因為我們知道,,數組首元素的地址和整個數組的地址的值是一

樣的,,二者指的是同一個地址,只是指向的這個地址兩者的類型是不一樣的,我現在把整個元素的地址換個角度看他,,雖然地址沒有發生變化,因為是同一個地

址,但是換個角度看,他就是成了數組首元素的地址了,現在接收到了一個數組首元素的地址,所以結果還是一個隨機值。

6.

&arr是整個數組的地址,&arr+1,就會直接跳過這整個數組,找到下一個數組的地址,,就是直接跳過一整個數組,直接來到這個數組最後一個元素,即字符f的後面,

指針指向字符f的後面,,然後把這個地址傳給strlen函數,在strlen函數內部就會直接把該地址看成 const char*的類型,就會直接看成字符f後面那個元素的地址了,,就

不再是一個數組指針類型的地址了,同樣也是,發生這種轉變,所指的那個地址比特置沒發生改變,只是地址的類型發生了改變,現在strlen函數從該比特置往後繼續找字符

\0,,由於不知道字符\0的比特置,所以還是一個隨機值,但是,這個隨機值和前三個隨機值是不一樣的,因為前面三個隨機值已經把abcdef這6個字符長度算了進去,,後

面這個隨機值一定比前三個隨機值小6,如果我們把前三個隨機值得出來的數看成一個隨機值的話,,那麼該隨機值就可以理解成:隨機值(前三個隨機值得到的

數)-6,但是只看這一個結果的話,應該是一個大於等於0的隨機值。

7.

&的優先級小於[ ],,所以arr[0]先結合,就代錶訪問下標為0的那個元素,就是字符a,取出字符a的地址,然後他是一個數組首元素的地址,加1就可以得到數組第二個

元素的地址,就是字符b的地址,,,我們現在得到b的地址,傳給strlen函數,就是告訴函數,要從這個地址對應的那個元素,即字符b開始往後數,直到找到字符\0,

所以還是一個隨機值,但是該隨機值應該比前三個隨機值數值小1,比第四個隨機值的數值大5,單獨看這一個結果的話應該是一個大於等於5的隨機值。要知道,如果執

行一次代碼就會把上述幾個值都打印出來的話,,那麼執行一次代碼,內存空間就會隨機產生一些數據,在這一次中,內存空間中產生的數據是不會發生改變的,所

以,上述5個隨機值之間會有一定的聯系,,但是內存空間每次分配數據的時候,本質上是會隨機分配的,每次執行代碼就會分配不一樣的數據,但是在同一臺電腦上可

能執行多次代碼的時候,內存空間分配的數據是一樣的,,在不同的電腦上,內存空間數據的分配可能是不同的。

四、

char arr[6] = {'a','b','c','d','e','f'};

這種類型的初始化就是,數組裏面能够看到的數據,就是所有的數據,沒有隱藏的字符 \0;

char arr[7] = "abcdef";                                     a b c d e f \0 , \0算一個字符

如果是使用常量字符串去初始化字符數組的話,在字符串的結尾處會隱藏著一個字符\0,作為字符串結束的標志。

char arr[ ] = "abcdef";
//此時就是使用常量字符串去初始化字符數組的話,在字符串的結尾處會隱藏著一個字符\0,作為字符串結束的標志。

1.printf("%d\n", sizeof(arr));

2.printf("%d\n", sizeof(arr+0));

3.printf("%d\n", sizeof(*arr));

4.printf("%d\n", sizeof(arr[1]));

5.printf("%d\n", sizeof(&arr));

6.printf("%d\n", sizeof(&arr+1));

7.printf("%d\n", sizeof(&arr[0]+1));

8.printf("%d\n", strlen(arr));

9.printf("%d\n", strlen(arr+0));

10.printf("%d\n", strlen(*arr));

11.printf("%d\n", strlen(arr[1]));

12.printf("%d\n", strlen(&arr));

13.printf("%d\n", strlen(&arr+1));

14.printf("%d\n", strlen(&arr[0]+1));

1.

此處沒有&,但是把數組名單獨放在了sizeof中,所以,此時的數組名代錶的是整個數組,計算的是整個數組所占內存空間的大小,由於字符串末尾還有一個隱藏的字符

\0,所以,該字符數組中一個有7個元素,每個元素都是char類型,占1byte,所以一共占:7*1=7byte。

2.

此時得數組名代錶數組首元素的地址,arr+0還是數組首元素的地址,是地址或指針變量所占內存空間的大小就是4或8byte。

3.

此時沒有&,也沒有單獨放在sizeof中,所以數組名arr就是數組首元素的地址,然後解引用就拿到了數組首元素,即字符'a',又因字符a的類型是char,所以占內存空間

的大小就是1byte,還可以把*arr看做*(arr+0),再看做是arr[0],所以指的就是數組首元素。

4.

arr[1]就是數組第二個元素,計算方法和上題是一樣的。

5.

此時沒有把數組名單獨放在sizeof中,但是&後面是單獨的數組名arr,所以,取出的就是整個數組的地址,仍是一個地址,計算地址或指針變量的大小就是4/8byte。

6.

此時,沒有把數組名單獨放在sizeof中,但是&後面是單獨的數組名arr,所以,取出的就是整個數組的地址,&arr+1就會跳過一個數組,指向下一個數組,即下一個數組

的地址,仍是一個地址,所以結果還是4/8byte。

7.

此時,[ ]的優先級高於&,,所以,arr和[0]先結合,指的就是數組首元素,然後整體進行取地址,&arr[0],得到的就是數組首元素的地址,數組首元素的類型又是char類

型,+1就會跳過一個char類型,所以就是數組第二個元素的地址,仍是一個地址,結果還是4/8byte。

8.

此時,沒有sizeof也沒有&,,所以arr就是數組首元素的地址,是一個地址傳給strlen是可以的,從該地址往後找字符\0,並統計字符\0前面得字符的數量,不包括字符\0

,所以結果是6。

9.

此時,沒有sizeof也沒有&,,所以arr就是數組首元素的地址,所以arr+0還是數組首元素的地址,和上題是一樣的答案。

10.

此時,沒有sizeof也沒有&,,所以arr就是數組首元素的地址,*arr找到的就是數組首元素,即字符a,把字符a傳給strlen函數是不允許的,因為不是一個地址,形成非法

訪問內存,結果是error。

11.

arr[1]指的就是數組第二個元素,類型是char類型,不是一個地址,所以和上題一樣會形成非法訪問內存,結果是error。

12.

此時,沒有sizeof,但是在&後放了數組名,所以該數組名代錶整個數組,取出的是整個數組的地址,,該地址的類型是char(*)[7],把該地址傳給strlen函數,類型就

會被强制類型轉為const char*,然後從該地址往後找字符\0,,結果還是6byte。

13.

此時,沒有sizeof,但是在&後放了數組名,所以該數組名代錶整個數組,取出的是整個數組的地址,,該地址的類型是char(*)[7],,&arr+1就會跳過一整個數組,代

錶的就是下一個數組的地址,從該比特置往後找字符\0,但是,已經出了字符數組,所以繼續在內存空間中找字符\0,則會返回一個大於等於0的隨機值。

14.

由於[ ]的優先級高於&,,所以arr[0]結合代錶著是數組首元素,然後整體取地址,得到的就是數組首元素的地址,&arr[0]+1得到的就是數組第二個元素的地址,從該比特

置往後找字符\0,所以答案就是5;

五、

char *p = "abcdef";

1.printf("%d\n", sizeof(p));

2.printf("%d\n", sizeof(p+1));

3.printf("%d\n", sizeof(*p));

4.printf("%d\n", sizeof(p[0]));

5.printf("%d\n", sizeof(&p));

6.printf("%d\n", sizeof(&p+1));

7.printf("%d\n", sizeof(&p[0]+1));

8.printf("%d\n", strlen(p));

9.printf("%d\n", strlen(p+1));

10.printf("%d\n", strlen(*p));

11.printf("%d\n", strlen(p[0]));

12.printf("%d\n", strlen(&p));

13.printf("%d\n", strlen(&p+1));

14.printf("%d\n", strlen(&p[0]+1));

如果把一個常量字符串放在一個指針變量中去,,那麼,就會把這個字符串首元素的地址放在這個指針變量中去,,同時還要知道,該常量字符串的內容不能改動,但

是如果把一個常量字符串放在一個字符數組裏面的時候,它的字符串內容是可以進行改動的,這兩種要區分開。

1.

字符指針變量p裏面存放的就是常量字符串首元素,即字符a的地址,因為字符指針變量p是一哥指針變量,現在sizeof(p)就是來求一個指針變量的所占內存空間的大

小,結果就是4/8byte。

2.

字符指針變量p的類型是char*,,所以p+1就會跳過一個char類型,又因,字符指針變量p裏面存放的是字符串首元素的地址,即字符a的地址,所以,p+1代錶的就是字

符串第二個元素的地址,即字符b的地址,仍是一個地址,所以結果就是4/8byte。

3.

字符指針變量p裏面存放的是字符串首元素的地址,即,字符a的地址,然後對p解引用就可以拿到字符串的首元素,即拿到了字符a,又因為字符a的類型是char類型,,

所以計算出來的結果就是1byte。

4.

p[0]=*(p+0),字符指針變量p裏面存放的是字符串首元素的地址,p+0還是字符串首元素的地址,那麼*(p+0)拿到的就是字符串的首元素,其類型是char,再計算大小

就是1byte。

5.

字符指針變量p也是一個變量,所以也在內存裏面開辟空間,所以字符指針變量p也有地址,所以&p也是地址,對地址進行sizeof計算出來的就是4/8byte,因為字符指針

變量p的類型是char*,,所以,&p的類型就是char**,,是一個二級指針。

6.

因為,由上題可知,&p也是一個地址,該地址的類型就是char**,所以,&p+1就會跳過一個char*的類型,代錶的就是下一個char*類型的元素的地址, 仍是一個地址,

所以,在使用sizeof時就計算出的結果就是4/8byte,和類型無關。

7.

由於[ ]的優先級高於&,所以,p先和[0]結合,等價於*(p+0),,p又是字符串首元素的地址,p+0還是字符串首元素的地址,再對他整體解引用得到的就是字符串的首

元素,即到了字符a,然後對p[0]整體進行解引用,得到的就是字符串首元素的地址,又因為字符串首元素的類型是char類型, 所以,&p[0]+1就會跳過一個char類型,

得到了字符串第二個元素的地址,即字符b的地址,仍是一個地址,所以計算出來的結果還是4/8byte。

8.

字符指針變量p裏存放的就是字符串首元素的地址,把該地址傳給strlen函數,就從該地址往後找字符\0,,所以結果是6;

9.

字符指針變量p裏存放的就是字符串首元素的地址,字符指針變量p的類型是char*,,所以p+1就會跳過一個char類型,又因字符指針變量p裏存放的就是字符串首元素的

地址,所以p+1就是字符串中第二個元素,即字符b的地址,然後從該地址往後找字符\0,得到的結果就是5;

10.

因為p裏面存放的是字符串首元素的地址,所以*p拿到的就是字符串的首元素,即拿到了字符a,字符a的類型是char類型,是一個變量,不是一個地址, 把他傳給strlen

函數的話就會造成非法訪問內存,所以會報錯,

11.

此時的p[0]===*(p+0)===*p,所以和上題答案是一樣的,

12.

由上面分析可知,&p是字符指針變量p的地址,該變量p仍會在內存中開辟空間,且該變量p的大小是4/8字節,這是由字符串首元素的地址的大小决定的,所以該變量p

的大小就是4/8byte,而字符指針變量p裏面存放的又是字符串首元素的地址,由於字符串首元素的地址又是系統給隨機分配的,我們並不知道字符串首元素的地址編號

裏面是否存在字符\0,,並且出去該變量p之外的內存空間中的數據也是不確定的,所以,該結果就會返回一個隨機值,

13.

由上題可知,&p是字符指針變量p的地址,它的類型是char**,所以,&p+1就會跳過一個char*類型,指向字符指針變量p的後一個char*類型的變量的地址,然後把該地

址傳給strlen函數,由於他的地址類型是char**,傳給strlen函數之後就會强制類型轉換為const char*類型,然後從該處往後找字符\0,,但是相當於是在字符指針變量p

的所占內存空間的後面往後找字符\0,仍在整個內存空間塊裏面尋找字符\0,,所以計算出來仍是一個隨機值,並且該隨機值和上題中的隨機值還沒有關系,因為是不知

道字符指針變量p中的4個或者8個字節的內容是否存在字符\0。

14.

由於[ ]的優先級高於&,,所以p先和[0]集合,,等價於*(p+0)===*p,,然後整體再去解引用,發現,*和&抵消,就剩下了p,而字符指針變量p裏面存放的又是字符

串首元素的地址,即字符a的地址,且p的類型是char*,,所以,&p[0]+1===p+1,,代錶了字符串中第二個元素的地址,即字符b的地址,然後把改地址傳給strlen函

數,從該比特置往後找字符\0,出來的結果就是5;

六、

int a[3][4] = {0};

1.printf("%d\n",sizeof(a));

2.printf("%d\n",sizeof(a[0][0]));

3.printf("%d\n",sizeof(a[0]));

4.printf("%d\n",sizeof(a[0]+1));

5.printf("%d\n",sizeof(*(a[0]+1)));

6.printf("%d\n",sizeof(a+1));

7.printf("%d\n",sizeof(*(a+1)));

8.printf("%d\n",sizeof(&a[0]+1));

9.printf("%d\n",sizeof(*(&a[0]+1)));

10.printf("%d\n",sizeof(*a));

11.printf("%d\n",sizeof(a[3]));

1、

a是數組名,單獨放在sizeof中,所以,代錶的就是整個數組,計算的就是整個數組所占內存空間的大小, 所以則有:3*4*4=48byte。

2、

a是數組名,arr[0][0]就是通過下標訪問二維數組中第一行第一列的元素,該元素是int整型, ,所以占4byte。

3、

對於該二維數組,要想訪問第一行的話,應該是a[ 0 ][ j ],其中j=0,1,2,3,要想訪問第二行的話,就是:a[ 1 ][ j ],其中j=0,1,2,3,同理第三行就是:a[ 2 ][ j ],其中

j=0,1,2,3,所以,a[0]就相當於是第一行的數組名,,a[1]就相當於是第二行的數組名,,a[2]就相當於是第三行的數組名,,所以現在的a[0]代錶的就是第一行的數組

名,單獨放在sizeof中,代錶的就是整個數組,即,整個第一行,計算的就是整個數組的大小,即整個二維數組第一行所占內存空間的大小,,則有:4*4=16字節。

4、

a[0]代錶的是二維數組第一行的數組名,沒有單獨放在sizeof中,且沒有&,,所以就是數組首元素的地址,即,二維數組中,第一行元素的首元素的地址,該變量的類

型是int整型,,所以a[0]+1就會跳過一個int整型,指向二維數組中第一行元素中的第二個元素的地址,是地址,計算大小就是4/8byte。

5、

上題可知,a[0]+1指的就是二維數組中第一行中第二個元素的地址,然後整體解引用就拿到了二維數組中第一行第二個元素,該元素的類型是int整型,所以計算大小就

是4byte。

6、

因為,a是數組名,且沒有單獨放在sizeof中,並且也沒有&,,所以a這裏就代錶了二維數組首元素的地址,,而二維數組首元素又是二維數組中的第一行,所以, a就

代錶了二維數組中第一行的地址,它的類型是int(*)[4],所以,a+1就會跳過4個int整型,即跳過第一行指向了二維數組中第二行的地址,然後,仍是一個地址,計算

出來的大小就4/8byte。

7、

由上題可知,a+1就是二維數組中第二行的地址,那麼對該整體解引用,拿到的就是該二維數組中的整個第二行,現在計算二維數組中整個第二行的大小, 就是

4*4=16byte,或者也可以理解成:因為,a+1就是二維數組中第二行的地址,那麼對該整體解引用,拿到的就是該二維數組中的整個第二行,也就等價於二維數組中第二

行的數組名,即等價於a[1],,即:*(a+1)=== a[1],,然後sizeof(a[1]),其中,a[1]是二維數組第二行的數組名,單獨放在了sizeof中,代錶整個數組,計算的是

整個數組的大小,即,計算的是整個二維數組中第二行的大小,則還是4*4=16byte、

8、

由於[ ]的優先級高於&,,所以,a和[0]先進行結合,得到的是a[0]代錶的是二維數組中第一行的數組名,,沒有單獨放在sizeof中,但是單獨被取地址了,,所以&a[0]中

的a[0]代錶的就是整個數組,即,二維數組中的整個第一行,它的類型是一個整型數組指針,即int(*)[4],&a[0]+1就會跳過4個int整型,即跳過第一行,得到的就是二

維數組中整個第二行的地址,是地址,計算出來的大小就是4/8byte。

9、

由上題可知,&a[0]+1指的就是二維數組中整個第二行的地址,對該地址解引用就可以拿到整個第二行,計算整個第二行的大小就是4*4=16byte,或者可以理解成:

&a[0]+1指的就是二維數組中整個第二行的地址,對該地址解引用就可以拿到整個第二行,就相當於是二維數組整個第二行的數組名,即:*(&a[0]+1)=a[1],,就是

sizeof(a[1]),,a[1]是第二行的數組名,單獨放在sizeof中,代錶的就是整個第二行,計算出來的就是二維數組中整個第二行的大小,則有:4*4=16byte。

10、

a是二維數組的數組名,沒有單獨放在sizeof中,也沒有單獨被取地址,所以,a在這裏就是二維數組首元素的地址,即二維數組整個第一行的地址,然後解引用就可以拿

到整個第一行,計算整個第一行的大小就是4*4=16byte,,或者是,a在這裏就是二維數組首元素的地址,即二維數組整個第一行的地址,然後解引用就可以拿到整個第

一行,就相當於是拿到了二維數組第一行的數組名,即:*a===a[0],即,sizeof(a[0]),a[0]是二維數組第一行的數組名,單獨放在sizeof中,代錶整個第一行,計算的是

整個第一行的大小,就是4*4=16byte,即:*a===*(a+0)===a[0];

11、

a[3]看起來像是越界訪問了,,但是,沒有關系,屬性分為值屬性和類型屬性,而在sizeof內部,不會進行值屬性的運算,即不會真正計算,只進行類型屬性的運算,

即,sizeof只根據他的類型就可以得出結果,不需要知道他的值到底是多少, 比如:int a=10,,就可以寫成:sizeof(a)和sizeof(int),,前者看起來是需要值才可

以得出來結果的,其實不然,sizeof這個操作符是根據a來推出a得類型,然後去計算該類型所占內存空間的大小的,即,你即使把值給我傳了過來,我也沒有進行真正的

運算,只是根據這個值來推導出它的類型,即推導出後者再得出結果的,所以,不會真的計算的意思就是不會真的取訪問a這個變量中的數據到底是多少,就比如這裏的

sizefo(a[3]),sizeof該操作符內部不會真正的去訪問a[3],所以就不存在越界訪問這個問題,只不過是根據a[3]來推導出他的類型,然後最終得出結果,又因為我們知

道,a[3]是二維數組第四行的數組名,單獨放在sizeof中,代錶的就是該二維數組中第4行,即代錶的是整個數組,,所以,第四行,即a[3]的類型就是int [4],,然後再

根據該類型推導出最後的結果,

即sizeof(a[3])===sizeof(int [4]),,裏面是一個整個一維數組,,該數組有4個元素,且每個元素類型都是int整型的數組,,所以,根究該類型就可以知道,最後的結

果應該是:4*4=16byte。

比如:

s=a+6,,這裏是s說的算,,你這裏的a是4,即int整型類型,,6也是int整型類型, ,所以兩者可以進行加法運算,得出來的結果是10,也是int整型,,現在要把這個

int整型放在s裏面,,而s是短整型,占2byte,,,這裏是s說的算,,所以,我的10是占4byte,放不進去short類型裏面,就會進行截斷,截斷成2byte,然後再存在s裏

面,對於sizeof(s=a+6)來說,,sizeof()這個操作符看的就是s=a+6這個錶達式的最終值,就是我們這裏的s,,因為s是short短整型,所以計算短整型的大小就是

計算:sizeof(short),,因為short占2byte,所以計算出來的結果就是2byte,,但是我們這裏的sizeof操作符,他是通過最後的結果,即s的類型來計算出s這個類型所

占空間的大小,,sizeof函數內部並沒有進行真正的計算,,即s=a+6,這個錶達式根本就沒有進行計算,,,所以說,s的值,根本就沒有發生變化,s的值還是5,,

現在以 %d 的形式打印s,而s占2byte,所以要進行,整型提昇,對於短整型的s,值為5來說,它的二進制序列,即內存中的二進制序列,即補碼,short占2byte,

16bit,,,因為5是正數,且原碼為:0000000000000101,因為5是正數,,所以補碼也是0000000000000101,,因為short是有符號的short,所以,有符號比特,不

够%d,所以要補到32bit,高比特補符號比特,即補0,得到:00000000000000000000000000000101,,這就是以%d,有符號,所以最高比特為符號比特,,0代錶正數,現

在的00000000000000000000000000000101是補碼,,又因為正數,原碼也是00000000000000000000000000000101,,打印出來的結果還是5;

我們平時見得類型都是有符號比特的類型,比如char,int,short等等,,這都是有符號比特的類型,而對於

unsigned char,unsigned int,unsigned short,這都是無符號比特的類型,而無符號比特的類型在整型提昇的時候用的都是高比特補0;

同理,,如果在sizeof()操作符裏面放置一個函數,,,我們知道,,函數調用也是錶達式,在sizeof內部不會去執行這個錶達式的,所以該函數不會被調用的,但是

函數的返回值的類型可以被我們這裏的sizeof計算出來,比如我把函數的返回值,設為5,放在了sizeof內部,因為5是int整型,,所以計算出來的結果應該是4byte;

10、指針筆試題:

一、

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0; }
//程序的結果是什麼?

首先,a是數組名,是一個一維數組的數組名,&a,此處的a代錶整個數組,取出來的是整個數組的地址,整個數組的地址,應該放在一個數組指針類型的變量裏面去,

該變量的類型就是:int(*)[5],,所以說,&a的類型就是一個數組指針類型,,現在,&a+1,就會跳過整個數組,直接到下一個數組,,即直接到原數組最後一個元

素的後面,下一個數組的起始比特置,,但是它仍然還是一個數組的地址,即第二個數組的地址,,對於數組地址來說,,它的類型就是數組指針類型,,現在要把一個

數組的地址,即數組指針類型的地址放在一個int*類型的變量ptr裏面,肯定是不行的,類型不匹配,所以要强制類型轉換成int*類型,這時候只是進行了類型的轉換,,

本來的那個地址的值是沒有發生變化的,,所以還是指向該數組最後一個元素的後面,下一個數組的起始比特置,,現在,ptr裏面存放的地址就是該數組最後一個元素的

後面,下一個數組的起始比特置,但是是int*類型,,現在ptr-1,就會往前跳過一個int整型,即跳過4byte,就是找到了數組中整型元素5的地址,然後解引用,就找打了5

這個數字,,同時,前面的a,沒有&,也沒單獨放在sizeof裏面,,所以a是數組首元素的地址,且數組首元素的類型是int類型,a+1就跳過一個int整型,找到元素2的地

址,然後解引用就找到了元素2,,,所以打印出來是2,5、

對於一個指針變量來說,,比如,int(*p)[5],int*ptr,,還是char*arr,,對於指針變量來說,,不管你是什麼類型的指針變量,只要去掉指針變量的名字,即去掉,

p,ptr,arr,剩下的內容就是指針變量的類型,,所以,int(*)[5],就是數組指針變量p的類型,int*,就是整形指針變量ptr的類型,,char*,就是字符指針變量arr的

類型;

二、

指針類型决定了指針的運算,,此處的運算指的是加减整數的運算,

如果在代碼中輸入一個數字,僅僅輸入一個數字的話,就會被系統默認為是一個int整型數字,占4byte,不會默認成其他類型。

因為變量p的類型是結構體指針類型,即:變量p的類型是:struct Test*,,現在又告訴了變量p的值為:0x100000,這就相當於是在代碼中輸入了一個數字,並被系統

默認為是int整型,由於是int整型數字,則占4byte,此時的十六進制以及其他進制僅是整型數字int的錶達形式而已,不管那種形式,都是占4byte,對於十六進制錶示形

式來說,4byte就要用8個十六進制數字來錶示,因為一個十六進制的數字占4比特,所以此處給的0x100000,如果補全的話,應該是0x00100000,而此時的變量p是一個

結構體指針變量,所以該結構體指針變量裏面存放的內容應該是地址,但是,僅僅告訴了變量p的值為0x00100000,中的0x00100000,他就會被系統認為是一個十六進

制數字,該數字並不能直接作為一個地址來看,,,如果想把某個數字看做地址的話,就要先進行强制類型轉換,把該數字轉換成指針變量p的類型,即把十六進制數字

轉換成struct Test*類型,才能被當做是結構體指針變量p的地址,所以,題中告訴的,假設p的值為0x00100000等價於p=(struct Tset*)(0x00100000);

struct Test,這是一個結構體類型,,如果在結構體類型後面加個*p,所以,對於p來說,他就是一個結構體指針變量,類型是結構體類型;

一個int整型的大小是4byte,一個char字符型的大小是1byte,double浮點型的大小是8byte,,但是我們不知道一個結構體所占內存空間的的大小是多少,還沒有學習,

現在,題目告訴了一個結構體所占內存空間的大小是20byte,這20byte是指,,結構體要存,int Num,char*pcName.....short sBa[4],,把這些存進去總共占20byte,

如果整型int類型的數字沒有特殊錶明,就默認是10進制的數字,平常輸入的整型賦值為10,20,等,這都是10進制的數字,,現在給了個0x1,我們平時看到的數字都是

整型int,占4byte,寫成16進制應該也是占4byte,我們知道,0x1就是一個16進制的數字,,0x代錶十六進制,1代錶一個16進制的數字,十進制,二進制,八進制,十

六進制僅是整型數字的錶示形式,整型數字占4byte,補齊應該是 0x 00 00 00 01,,這才是16進制整型數字1應該的寫法,這是16進制的數字,我們首先把它轉換為10

進制的數字,首先轉為二進制得:00000000000000000000000000000001,,這是內存中的二進制序列,就是補碼,最高比特符號比特0,正數,,所以,原碼也是這個,

結果十進制的值就是1,,我們知道,對於十進制和十六進制,1-9都是一樣的,,十進制的10-15,在16進制中是,a,b,c,d,e,f,,所以,0x1就是代錶十進制中

的1,我們平常寫的數字都默認是十進制,所以 p+0x1,就可以看成p+1;

我們知道這裏的p就是一個結構體指針變量,,對於指針變量的加减運算,取决於指針變量的類型,,

因為p是結構體指針變量,它的類型就是:struct Test*類型,,p+1,就會直接跳過一個結構體,直接指向下一個結構體的起始比特置,因為整型int占4byte,所以他會直接

訪問4byte,char字符占1byte,所以他會直接訪問1byte,現在我們知道,該結構體占20個byte,所以,p+1,就會直接跳過一個結構體,就會跳過訪問20byte,我們又

知道,一個字節分配一個地址,那這裏跳過20byte,就會跳過20個地址,就相當於在原來p的地址上加上20個字節,即20個地址,這裏的20是十進制,我們地址是以二

進制存儲,十六進制展示的,,所以說,十進制20要轉為二進制,即:00000000000000000000000000010100,這是二進制的10,,轉為16進制,每4bit組一個小組,

補全得:0000 00000 00000 00000 0000 0000 0001 0100,,所以轉為16進制就是14,這不是十四,而是一四,,加到0x00100000後得:

0x00100000

0x00000014

0x00100014;

所以第一個結果就是:0x00100014;

指針變量p本來的類型是結構體指針類型,現在把結構體變量p强制類型轉換為 unsigned long類型,就是一個無符號的長整型的類型,此時變量p不再是一個結構體指針

變量了,只是一個無符號長整型變量了,此時就把無符號長整型變量p中的值當做是一個數字了,比如:int* p=&a,,因為整形指針變量p的類型是int*,所以,p+1就會跳

過1個int整型,跳過4byte,,但是,如果是 unsigned long p=10 ,,p+1的話,得到的結果就是11,,unsigned long 即為長整型,是一個整型,此時p中的值還是

0x00100000,但是該十六進制數字僅僅被認為是一個十六進制數字,它的類型就是unsigned long,就不再是struct Tset*類型的了,,0x00100000代錶的是一個十六進

制無符號長整型數字,現在要在該數值上加上0x01,對於十進制的錶示形式就是單純的加上1即可,對於十六進制錶達形式就是單純的加上0x00000001即可,即加上:

0x00 00 00 01,,,得到的就是:0x00100001、

下面:

最初,p是一個結構體指針變量,它的類型是struct Test* 類型,,如果該指針變量p+1,就會跳過一個結構體類型, ,,因為該結構體是20byte,所以就會訪問20byte,

現在,强制類型轉換為:unsigned int*,即無符號的整形指針,,所以現在的p仍是一個指針變量,只不過是一個無符號的整型指針變量,它的類型是unsigned int*,,

無符號整形指針變量+0x1,即相當於加上十進制的1,,相當於:p+1,p是無符號的整型指針變量,p+1就會跳過一個int整型變量, 就會訪問4byte,就相當於在p的地

址上加上4byte,現在的4是十進制的4,轉化為十六進制,還是4,即十六進制的4,即:0x00 00 00 04,,所以,最後結果為:0x00100004;

整形指針變量+1就會跳過一個int整型, 跳過4byte,字符指針變量+1就會跳過一個char字符類型,跳過1byte,對於int整數加1,就是加1,比如,50+1,就是51,不在

考慮跳過幾個字節,對於無符號的長整型 unsigned long類型的整數加1,也是+1,和int整型加1是一樣的,無論是對於有符號的int,還是無符號 unsignen int,都是int,

占4byte,都是占4字節,,和有無符號沒有關系;

%p是用來打印地址的,以地址的形式進行打印,按照十六進制的錶示形式進行打印,要打印地址的話就要打印全部的十六進制數字,所以即使高比特不起作用的0也不會

被省略,如果使用%#p打印的話,就不會把0x去掉,也不會把高比特的0,即無作用的0扔掉。

%x就是用來打印十六進制的,和%p打印地址不同,他會把高比特不起作用的0會被省略掉,以最簡的形式展現,兩者打印出來的都是十六進制的錶示形式,並且都會把0x

省略掉。如果是%#x打印的話,,就不會把0x去掉,,但是高比特的0,,,無用的0是會被扔掉的;

地址的本質是數值,二進制,八進制,十進制,十六進制等,都是數值的錶示形式,所以地址可以使用這些錶示形式來錶示,地址原本是使用二進制進行錶示的,但

是,已知地址需要使用%p進行打印,而%p是按照十六進制的錶示形式進行打印的,所以當使用%p對地址進行打印的時候,出來的結果是以十六進制的形式來錶示的,

但不代錶地址只能使用十六進制來錶示,而是使用其他進制一樣可以進行錶示。

如果一個十六進制的數字加上一個十進制的數字,最後要求得到一個十六進制的數,有兩種思路:

1.把這個十六進制的數字轉為十進制,,與另一個十進制相加,求出來的和再轉為十六進制;

2.把這個十進制的數,直接轉為十六進制,然後與我們已知的十六進制的數直接相加就可以了;

三、

int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int *ptr1 = (int *)(&a + 1);
    int *ptr2 = (int *)((int)a + 1);
    printf( "%x,%x", ptr1[-1], *ptr2);
    return 0; }

對於ptr1所在的一行代碼來說,a是數組名,沒有單獨放在sizeof中,但是,單獨被取地址,所以,a在這裏代錶的是整個數組,取出的是整個數組的地址,該地址的類型

是int(*)[4],,然後&a+1就會跳過一整個數組,指向整型int類型數據4的後面,此時&a+1的類型還是int(*)[4]的類型,現在整體被强制類型轉化為int *類型,存放在

整型指針變量ptr1裏面,又因,ptr1[-1] === *(ptr1+(-1)) === *(ptr1-1),,因為ptr1的類型是int* ,所以,ptr1-1就會往前跳過一個int整型,此時指向了整型數

據,4的地址,然後整體再被解引用就拿到了整型數據4,但是,該4是十進制錶示形式,而%x打印的是十六進制,並且能够把高比特不起作用的0省略掉,所以要先把十進

制數字4轉為十六進制的數字4,即得到了:0x00 00 00 04 ,現在使用%x進行打印,就會把高比特的不起作用的0省略掉而且把0x省略掉,所以最終打印在屏幕上的就是4;        

對於ptr2所在的一行代碼來說,a是數組名,沒有sizeoif和&,所以a代錶的就是數組首元素的地址,又因為該地址的類型是int* 類型,,並且强制類型轉換的優先級高於

+,,所以,先對a進行强制類型轉換為int類型,而已知,對於地址或指針變量的大小來說,一般都是4/8byte,但是我們使用的電腦一般都默認地址或指針變量的大小是

4byte,並且地址都是使用%p進行打印的,%p又是按照十六進制的形式進行打印的,且不會把高比特不起作用的0省略掉,所以,再此就假設數組首元素的地址為:0x00

00 00 10,,在强制類型轉換之前,該地址的類型是int*,,但是强制類型轉換之後,就變成了int類型,int是整型,對於整型的0x 00 00 00 10來說,他就是一個十六進

制數字,所以(int)a就是一個十六進制數字,現在(int)a+1,就相當於是給十六進制數字加上一個十進制數字1,而十進制數字1轉為十六進制的數字1就是:0x 00

00 00 01,,所以(int)a+1 =0x 00 00 00 11,,然後0x00 00 00 11就是一個十六進制數字,它的類型還是int類型,現在整體被强制類型轉換為int*類型,,所以,該

十六進制數字就會被認為是一個地址,該地址的類型是int*類型, 把該地址存放在整型指針變量ptr2中,現在觀察,ptr2中存放的地址相比於數組首元素的地址就相當於

是往後偏移了一個字節,因為一個字節代錶一個地址,所以說,地址+1就代錶往後偏移了一個字節,假設是小端存儲,所以,數組在內存中的存儲順序應該是: 01 00

00 00 02 00 00 00 03 00 00 00 04 00 00 00 ,,其中,a是數組首元素的首地址,即指向了字節01 的前面,現在(int)a+1 作為地址就是往後偏移了一個字節,即指向

了字節01 後面的一個字節00,,又因為ptr2的類型是int*類型,,所以,解引用就會訪問4個字節,訪問出來的結果就是:00 00 00 02,又因是小端存儲,,訪問到的只

是在內存中的,讀取出來就是 0x 02 00 00 00,,現在以%x打印,他會省略掉高比特不起作用的0,並且把0x省略掉,所以打印在屏幕上的結果應該是:2 00 00 00,,所

以最終的兩個結果是: 4 、2000000 。

四、

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int *p;
    p = a[0];
    printf( "%d", p[0]);
 return 0; 
}

c語言中逗號運算符和逗號錶達式

C語言提供一種特殊的運算符——逗號運算符,用它將兩個錶達式連接起來。

如:  3+5,6+8稱為逗號錶達式,又稱為“順序求值運算符”。逗號錶達式的一般形式為:  

     錶達式1,錶達式2

逗號錶達式的求解過程是:先求解錶達式1,再求解錶達式2。整個逗號錶達式的值是錶達式2的值。

+的優先級高於逗號 所以,+先進行運算,3+5和6+8先進行運算,然後得到的兩個值又是逗號錶達式

例如,上面的逗號錶達式“3+5,6+8”的值為14。

又如,逗號錶達式  a=3*5,a*4 對此錶達式的求解,讀者可能會有兩種不同的理解:

一種認為“3*5,a*4” 是一個逗號錶達式,先求出此逗號錶達式的值, 如果a的原值為3,則逗號錶達式的值為12,將12賦給a, 因此最後a的值為12。

另一種認為:“a=3*5”是一個賦值錶達式”,“a*4”是另一個錶達式,二者用逗號相連,構成一個逗號錶達式。這兩者哪一個對呢?

對於3*5來說,賦值運算符的優先級別高於逗號運算符, 因此應先求解a=3*5(也就是把“a=3*5”作為一個錶達式)。經計算和賦值後得到a的值為15,然後求解a*4,得60。

整個逗號錶達式的值為60。 

一個逗號錶達式又可以與另一個錶達式組成一個新的逗號錶達式,如(a=3*5,a*4),a+5

()的優先級高於逗號,先進行()內的操作,把()看成一個整體,所以這就是一個逗號錶達式,然後,()裏面的內容,又是一個逗號錶達式,我們知道=和*的優

先級高於+,所以,括號裏面是一個逗號錶達式,兩個錶達式分別為a=3*5和a*4,

先計算出a的值等於15,再進行a*4的運算得60,此時,逗號錶達式得出來的結果是60,但是我們後面用到的是a+5即用到a的值,,,,我們的60是逗號錶達式的值,但

並沒有賦值給我a,所以,a的值仍是前面的15,,(但a值未變,仍為15),再進行a+5得20,即整個錶達式的值為20。  

逗號錶達式的一般形式可以擴展為:

     錶達式1,錶達式2,錶達式3……錶達式n —— 它的值為錶達式n的值。  

逗號運算符是所有運算符中級別最低的。

因此,下面兩個錶達式的作用是不同的:  

① x=(a=3,6*3)  ② x=a=3,6*a  

第①個是一個賦值錶達式,將一個逗號錶達式的值賦給x,x的值等於18。

()的優先級高於=,把()內看成一個整體,然後,括號內又是一個逗號錶達式,且兩個錶達式分別為:a=3和6*3,,因為=和*的優先級都高於逗號;

第②個是逗號錶達式,它包括一個賦值錶達式和一個算術錶達式,x的值為3。 

對於a兩邊都是=,不用管,對於3和6,=和*優先級都高於*,,所以,是逗號錶達式,且兩個錶達式分別為:x=a=3和6*a 

其實,逗號錶達式無非是把若幹個錶達式“串聯”起來。在許多情况下,使用逗號錶達式的目的只是想分別得到各個錶達式的值,而並非一定需要得到和使用整個逗號錶達

式的值,逗號錶達式最常用於循環語句(for語句)中. 

請注意並不是任何地方出現的逗號都是作為逗號運算符。例如函數參數也是用逗號來間隔的。

如 printf("%d,%d,%d",a,b,c);  

上一行中的a,b,c並不是一個逗號錶達式,它是printf函數的3個參數,參數間用逗號間隔。

如果改寫為printf( " %d,%d,%d " ,(a,b,c),b,c);則“(a,b,c)”是一個逗號錶達式,它的值等於c的值。括弧內的逗號不是參數間的分隔符而是逗號運算符。括

弧中的內容是一個整體,作為printf函數的一個參數,前面的 '' '' 裏面的內容的逗號不算逗號錶達式;

逗號錶達式的存在不一定必須有();

C語言錶達能力强,其中一個重要方面就在於它的錶達式類型豐富,運算符功能强,因而c使用靈活,適應性强

首先,我們要看到,二維數組初始化裏面是(),而不是{ },所以對(0,1),(2,3),(4,5)來說,這三個括號外面的兩個逗號是這個二維數組分隔元素的分隔符,

而不是逗號錶達式,,我們又發現,(0,1)和(2,3)和(4,5)這三個整體作為二維數組的一個元素,但他們不是用{ }來括起來的,,如果用{ }括起來,說明這一個整

體就是我們二維數組的一個元素,這樣正好對應我們的行數和列數,,但是現在,不是用的{ },,而是括號,就不能再把他們三個整體看做二維數組的元素,所以,就

是三個逗號錶達式,()代錶是一個整體,,所以,(0,1)===1,,,(2,3)===3,,(4,5)===5,,所以:

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

又因為我們是三行兩列,,所以,不够的地方的元素用0來補充,,

1 3

5 0

0 0

這才是我們二維數組的面目,,現在,我們又知道,,a[0]是二維數組第一行的數組名,,既沒有放在sizeof裏面,也沒有&,,所以,數組名就相當於數組首元素的地

址,所以,p裏面放的就是二維數組第一行首元素的地址,即1的地址,1是一個int整型,即一個整型的地址,而指針變量p的類型正好也是一個整形指針類型,,所也正

好可以放進去,,我們的p[0]===*(p+0),,所以,p裏面放的是二維數組第一行首元素的地址,,而且指針變量p是一個int*類型的,,所以,p+1就會直接跳過一個整

型,訪問4byte,但是現在是p+0,所以p+0還是指向1的地址,,然後解引用並以%d打印,,就可以打印出來數字1;

五、

int main()
{
    int a[5][5];
    int(*p)[4];
    p = a;
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
 }

a是一個二維數組,5行5列,,在p=a這裏,沒有&,也沒有sizeof,,並且,a是數組名,,a代錶二維數組首元素的地址,又因為a是一個二維數組,,所以,代錶二維

數組第一行的地址,,它的類型應該是,,int(*)[5],,但是,,int(*p)[4],,說明, p是一個數組指針變量,他指向一個數組,該數組有4個元素,每個元素的類

型都是int整型,,現在p=a,,把a賦值給p,,,a的類型就是int(*)[5],而p的類型是int(*)[4],,類型不一樣,在這裏就相當於是强制類型轉換,由於是不同類型

的,並且沒有進行强制類型轉換,可能出現警告,但是可以正常運行,,如果想要不被警告,,就加上强制類型轉換就可以了;

即:p=a 改寫成 p =(int(*)[4]) a ,,, 現在,a是指二維數組第一行的地址,把a放在數組指針p裏面,,所以,p指向的地方就是二維數組第一行的地址,,但是類型是

int(*)[4],,即,數組指針類型,,如果p+1,就會直接跳過一個數組,該數組有4個元素,每個元素都是int整型,,p的類型是int(*)[4],,p+1就會直接跳過一個數

組,,所以直接跳過4個整型,對於,&p[4][2]來看,[ ]的優先級高於&,先看p[4]===*(p+4),,把p[4]看做一個整體,,就可以得到: *(*(p+4)+2),所以,

p[4][2] === *(*(p+4)+2),數組指針變量p指向二維數組第一行的地址,類型是int(*)[4],,所以,p+1就會跳過一整個數組,即,跳過4個int整型,16個字節,

p+4就會跳過4個整個數組,即跳過16個int整型,64個字節,因為數組指針變量p的類型是int(*)[4],所以,p+4的類型還是這個,所以,*(p+4)就拿到了一整個數

組,也相當於拿到了這個數組的數組名,*(p+4)在這裏相當於是解引用後拿到的這個整個數組的數組名,在這裏沒有sizeof和&,所以就相當於是該整個數組的數組首

元素的地址,該整個數組首元素的類型又是int整型, 所以,*(p+2)+2,就會跳過兩個int整型,8byte,然後再解引用就拿到了對應的那個元素,即,二維數組中第四

行第四列的這個元素,然後&p[4][2]就拿到了二維數組第四行第四列元素的地址,,又因,&a[4][2]中,[ ]的優先級高於&,所以就先進行a[4][2],就是二維數組通過下標

進行訪問,即,第五行第三列的元素,再取出他的地址,現在有:&p[4][2]-&a[4][2],是低地址减去高地址,如果再以%d進行打印,出來的就是兩個指針之間的元素個數

的相反數,結果就是 -4,,如果&p[4][2]-&a[4][2]直接以%p進行打印的話,我們知道,如果以%d打印出來就是 -4 ,所以根據 -4 寫出在內存中的二進制序列,即補碼,

因為 -4 的原碼是:100000000000000000000000000000100,反碼就是:1111111111111111111111111111111011,補碼就是:11111111111111111111111111111100,,

現在要用%p來打印他的地址,就相當於是把補碼的二進制序列以十六進制的形式進行打印,補碼寫成十六進制即為:0xFFFFFFFC ,,直接打印補碼,不考慮它的原

碼,%p打印會省略掉0x和高比特不起作用的0,,所以,最後的結果就是:FFFFFFFFC,,

指針和指針相减的前提是,兩個指針指向同一個空間;

六、

int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int *ptr1 = (int *)(&aa + 1);
    int *ptr2 = (int *)(*(aa + 1));
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    return 0;
}

aa是一個二維數組的數組名,,,&aa,aa就代錶整個二維數組,,取出來的就是整個二維數組的地址,,&aa它的類型就是一個數組指針類型,,即,它的類型就是

int(*)[2][5],類型,,他指向一個2行5列的二維數組,數組裏面每個元素的類型都是int整型,,現在,&aa+1,就會直接跳過一個二維數組,即直接跳過2行5列,跳

過10個元素,指針指向了元素10後面的那個地址,,現在强制類型轉換為int*,只是類型發生了改變,,該地址,即指向的那個地方的比特置時沒有發生改變的,,,再把

這個地址放在整型指針變量ptr1裏面,,下面的ptr1-1,,因為ptr1的類型是int*類型,,ptr1-1就會往前跳過一個整型,即跳過4byte,找到了元素10的地址,,然後解引

用就可以得到元素10,打印出來,

第二個裏面的aa沒有&,也沒單獨放在sizeof裏面,,所以,這裏的aa是數組名,而且代錶數組首元素的地址,,所以是二維數組第一行的地址,,第一行可以看成是一

個 一維數組,aa的類型就是int(*)[5],,現在aa+1,就會直接跳過這個一維數組,指向第二行的地址,aa+1的類型也是

int(*)[5],,即指向元素6的地址,,這個地址是整個第二行的地址,然後解引用就可以得到整個第二行,相當於拿到了整個第二行的數組名,,整個第二行可以用數

組名aa[1]來錶示,

就比如:int arr [5]={1,2,3,4,5};,,,我們就可以用arr這個數組名來錶示這個數組,數組裏面有5個元素,每個元素類型都是int整型,,因為對於一個數組來說,,去

掉數組名,剩下的就是這個數組的類型,,然後,我們上面,解引用就可得到整個第二行,,就可以用一個數組名來錶示這整個第二行,即用aa[1]來錶示整個第二行;

或者,,因為,*(aa+1)===aa[1],,也可以得出來aa[1];

所以,這裏的(*(aa+1))===aa[1],,而aa[1],就相當於是第二行的數組名,,沒有&,也沒有sizeof,,所以代錶數組首元素的地址, 即第二行首元素6的地

址,,本來就是int*類型,,所以這裏的(int*),沒起到作用,,就相當於把第二行數組首元素6的地址放在了ptr2裏面,,又因

ptr2的類型是int*,所以,,ptr2-1就會跳過一個整型,跳過4byte,找到元素5的地址,解引用打印出來5;

 要注意,,第一個意思就是,,把&aa+1這個整體進行强制類型轉換,,

第二行中,强制類型轉換的優先級低於&,所以先進行&的操作,又因,强制類型轉換的優先級高於+,所以,要先對&aa進行强制類型轉換啊之後,整體再去+1。

七、

#include <stdio.h>
int main()
{
 char *a[] = {"work","at","alibaba"};
 char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0; 
}

因為[ ]的優先級高於*,,所以,a和[ ]先結合成一個數組,該數組裏面每個元素的類型都是char*,,所以是一個字符指針數組,數組裏面每個元素都是字符指針類

型,,現在,初始化裏面是三個字符串,,我們之前寫過,,char *p=''abcdef'',,這是一個常量字符串放到字符指針變量中,,內容不可以發生改變,把一個常量字符

串放在一個字符指針裏面,其實就是把字符串首元素,即字符a的地址放在字符指針變量p裏面,,我們這裏的數組也是一樣的,,只不過是三種這樣的情况;

對於這個字符指針數組來說,裏面放置的就是work中w,at和alibaba中的a,這三者的地址,

a是數組名,沒有&,也沒有sizeof,,這裏的數組名a就是代錶,數組首元素的地址,就是這個字符指針數組首元素的地址,就是第一個char*類型的地址,,也就是字符

w的地址的地址,,就是一個一級指針的地址,,把一個一級指針的地址放在一個二級指針變量中,所以把第一個char*類型的地址放在一個字符二級指針變量pa中,它

的類型就是char**,,因為現在是,char**pa=a;,,這裏的pa是一個字符二級指針變量,,我們知道,,如果是int*pa=arr,,pa+1就會跳過一個整型,就跳過

4byte,,其中,這裏的*代錶,pa是一個指針變量,,pa+1就會跳過一個int整型,,所以,因為這裏的char**pa=a;,,後面的*代錶我這裏的pa是一個指針變量,,

pa+1或者pa++,就會跳過一個char*,,而我們的這個數組,每個元素的類型都是char*類型的,,剛開始,,a是數組首元素的地址,,所以就指向第一個char*類型的

元素的地址,,現在pa++,就會跳過一個char*類型,所以,就會指向我們數組第二個元素的地址,所以,,現在的pa指向的就是數組第二個元素的地址,,現在*pa,

就是解引用,就可以找到數組第二個元素,而數組第二個元素裏面存放的就是字符串''at''裏面的首元素的地址,就是字符a的地址,,現在我們就找到了一個字符串首元

素的地址,,現在如果以%s打印就可以直接把這個字符串全部打印出來,,因為對於字符串來說,他有專門打印字符串的%s,只需要找到該字符串首元素的地址,然後

以%s進行打印就可以打印出整個字符串的內容,,這是與整型和字符數組不一樣的,這兩者無法通過一次直接把所有的元素打印出來;

八、

int main()
{
 char *c[] = {"ENTER","NEW","POINT","FIRST"};
 char**cp[] = {c+3,c+2,c+1,c};
 char***cpp = cp;
 printf("%s\n", **++cpp);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 return 0; 
}

已知c這裏沒有&,也沒有sizeof,,而且c是數組名,所以,c就代錶數組首元素的地址,,,我們又知道,,c是放在char** cp[ ],這個數組裏面的,,所以說,c的類型

就是char**類型,,現在c+1,,char**後面的*說明,是一個指針,所以,c+1就會跳過一個char*,,

而cp又是數組名,,沒有&,沒有sizeof,,所以這個數組名就代錶數組首元素的地址,,即cp這個數組首元素的地址放在char三級指針變量cpp中,,指向的就是數組

cp首元素的地址,,前三句代碼就可以把圖畫出來;

對於第一個printf,++的優先級高於*,,先進行++cpp,前置++,先++後使用,現在,cpp的類型是char***,,最後一個*代錶cpp是一個指針變量,,++cpp就會跳過一

個char**類型的元素,,剛開始,cpp指向c+3,,現在++cpp,就會跳過一個char**類型,指向c+2,,,所以,++cpp後,指向的就是c+2的地址,,現在*++cpp,對

++cpp解引用,就拿到了c+2的內容,即就拿到了c+2這個元素,,而我們c+2的內容裏面存放的就是''POINT''這個字符串的首元素即,字符P的地址的地址,即存放的就

是c[2]的地址,,,,現在,再對整體解引用,就拿到了c[2]的內容,就可以拿到字符p的地址,,或者看圖,,我們第一次解引用拿到了c+2這個元素,即c+2這個元素

的內容,,這裏面存放的又是第三個char*的地址,,所以,一次解引用我們就拿到了,第三個char*的地址,然後第二次解引用,就拿到了,第三個char*的內容,,第

三個char*的內容裏面存放的又是字符p的地址,,所以,兩次解引用就拿到了字符p的地址,

對於%s,打印字符串來說,如果一個字符串,''abcdef'',如果拿到了字符a的地址,以%s打印,,就會把整個字符串打印出來,,如果拿到了字符c的地址,以%s打

印,就會打印出來''cdef'',,所以對於,以%s打印,字符串來說,,,,拿到了誰的地址,就從那個字符開始打印,一直往後打印,,直到遇到\0,,,把\0前面,該地

址後面的,,所有的字符都打印出來;

所以,我們這裏拿到了字符p的地址,,現在以%s打印,,就會把POINT這個字符串全部打印出來,

對於第二個printf,,++的優先級高於*,且++的優先級高於+,所以先算++cpp,,,我們cpp最初是指向c+3的地址,,即數組cp首元素的地址,在第一個printf中,,

已經有了++cpp,,所以,cpp自增了1,,現在cpp指向了c+2的地址,,然後,到第二個printf中,又有++cpp,,所以,,現在的cpp指向了c+1的地址,,*優先級高

於+,,然後,解引用,就拿到了c+1這個元素,就拿到了c+1這個元素的內容,,,,現在,又因為--的優先級高於+,,所以,要先對*++cpp這個整體先進行--,,因

為,*++cpp就相當於是c+1這個內容,而c+1的類型,,即,*++cpp的類型就是char**類型,,現在要對*++cpp這個整體進行--,char**類型後面的*代錶是一個指針變

量,,就會跳過一個char*類型,,本來c+1指向了第二個char*,,現在,,往前跳過一個char*類型,,所以,c+1就指向了第一個char*的地址,又因*的優先級高於

+,, 然後再解引用,就拿到了第一個char*的內容,,第一個char*的內容裏面是字符E的地址,,即,*--*++cpp就相當於是字符E的地址,,而字符E的地址類型又是

char*類型,,所以,*--*++cpp+3就會往後跳過3個char類型的元素,,所以,就找打了ER這個E的地址,,所以再以%s打印就會打印出來ER;

對於第三個printf,,[ ]的優先級高於*,,所以cpp[-2]先進行結合,,cpp[-2]===*(cpp-2),,因為cpp在第二個printf中已經指向了c+1的地址,而且,cpp的類型是

char***,現在,,cpp-2,,就是往前跳過2個char**類型,,所以,現在cpp-2指向了c+3的地址,,然後解引用,就拿到了c+3這個元素,然後,*的優先級高於+,

*cpp[-2]===*(*(cpp-2)),所以,現在,已經拿到了c+3這個元素,,c+3這個元素裏面是第四個char*的地址,然後再解引用就拿到了第四個char*的內容,就是拿到

了F的地址,,現在,整體再+3,,就是,F的地址再加3,,又因F的地址類型是char*,所以+3就會往後跳過3個char類型,就找到了,S的地址,然後已知S的地址,

再以%s打印,,所以就把ST打印出來了,我們這裏的,cpp-2,只是用了cpp的值,,讓cpp-2得到一個其他的值,並沒有把得到的這個值再次賦值給cpp,,所以cpp本

身並沒有發生變化,,所以,第三個printf結束後,cpp仍指向c+1的地址,,並不會改變,,,我們的++cpp,,是先++,後使用,,即cpp自增1,這是可以使得cpp發

生改變的,,然後得到的這個值再拿去被使用,,++cpp和cpp++,就相當於是cpp=cpp+1,即cpp += 1,,和前面的cpp-2是不一樣的,要注意區分;

對於第四個printf,,cpp[-1][-1]+1,,=== *(*(cpp-1)-1)+1,,從裏往外看,,已知,cpp在第二個printf中已經指向了c+1的地址,,再第三個printf中,只是用到

了cpp的值,讓cpp-1得到一個新的值,,但是這個新的值並沒有在此賦給cpp,,所以,cpp的值沒發生改變,,所以,cpp還是指向c+1的地址,,,cpp的類型是

char***,所以,cpp-1就會往前跳過一個char**類型,,所以,cpp-1就指向了c+2的地址,,然後解引用,就拿到了c+2這個元素,c+2這個元素的內容就是第三個char*

的地址,,然後c+2這個元素的類型是char**,,所以,*(cpp-1)就相當於是c+2,,*(cpp-1)-1就會往前跳過一個char*,,

所以現在,*(cpp-1)-1即指向了第二個char*的地址,,然後解引用,就拿到了第二個char*的內容,,即字符N的地址,,而字符N地址的類型又是char*,,,所以,

現在整體再+1,,而字符N的地址類型又是char*,,所以就會往後跳過一個char類型,,找到了E的地址,,然後以%s打印出來,EW;

這裏的,c是一級字符指針數組,cp是二級字符指針數組,,cpp是一個三級字符指針;

到此為止,指針進階算是結束了,,如果對您有幫助,記得點贊收藏~~

版權聲明
本文為[脫韁的野驢、]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/01/202201272336596792.html

隨機推薦