objective-c - 关于C语言指针的问题
黄舟
黄舟 2017-04-21 11:17:34
0
3
427

C语言指针问题 下面代码为什么第二个数字是5呢?

int a[5] = {1,2,3,4,5};
int *p = (int *)(&a+1);
NSLog(@"%d,%d", *(a+1), *(p-1));
黄舟
黄舟

人生最曼妙的风景,竟是内心的淡定与从容!

全部回覆(3)
阿神

&a得出來的是指向數組的指針,所以&a+1其實是以數組的長度為單位來移動的。如果你只是想要得到陣列的第二個元素的話,那麼就用*(&a[0]+1),因為&a[0]的資料型別是int*。畫個圖先:

假設有以下數組

int a[5] = {1,2,3,4,5};
int *x = &a+1;

雖然上面的程式可以編譯通過,但是,編譯器會出警告,因為x和a的型別不符,要想把警告去掉,有兩種方法

1、透過強制型別轉換

int *x = (int *)(&a+1); 
print("%d\n",*(x-1));

2、為它尋找合適的類型 &a的類型是int (*)[5]

int (*x)[5] = &a+1;
printf("%d\n",**(x-1));

這裡就牽涉到如何寫出適當的資料類型,這在賦值和參數傳遞中很重要!

所以,首先我得總結一下a,&a和&a[0]這三個資料的資料型別

  1. a是數組名,是指向數組第一個元素的指標,毫無疑問,在這裡,數組第一個元素的資料型別是int所以a的資料型別是就是int*

  2. &a是對一個一維數組取位址,得出來的是指向數組的指標(在這裡是pointer to array of int), 也就是int(*)[5]

  3. &a[0]就很簡單,首先a[0]得到的是一個整形數int,然後對它取位址,所以它的資料型別就是int*

知道了資料類型,那麼對指標運算看起來就清晰多了!

先看看透過強制型別轉換的那部分程式碼,它會輸出什麼數字呢?

答案是5! 透過剛才對資料類型的總結可以知道,&a的資料型別是int (*)[5],所以&a+1其實是已經移動了5*sizeof(int)個位元組了 現在指標是指到了陣列最後一個元素的後一個元素(圖1),也就是說,已經越界了!但因為x的資料型別其實是int * 所以對於x-1,其實是向左移動了1*sizeof(int)個位元組,也就是指向了最後一個元素,所以*(x-1)得出來的 值就是陣列的最後一個元素:5

好了,現在再看第二部分,它又會輸出什麼數字呢?先不說答案,剛才也說了,在這裡 x的資料型別是int (*)[5],是一個指向含有5個int元素的一維數組的指針,對它進行加減運算的話就會以 sizeof(int)*5個位元組為單位移動,所以x-1其實是向左移動了sizeof(int)*5個位元組,在我的機器上是移動了 20個字節,也就是回到了數組的第一個元素,所以得到的答案就是:1

以上是一維數組的,下面我想說說二維數組的情況,有以下一段程式碼:

int a[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
/*
?? = a; // int (*b)[3] = a;
?? = a[0]; // int *c = a[0];
?? = a[0][0] // int d = a[0][0];
?? = &a; // int (*e)[3][3] = &a;
?? &a[0]; // int (*f)[3] = &a[0]
?? &a[0][0]; // int *g = &a[0][0]
*/

還是先看看a,a[0],&a,&a[0],&a[0][0]這幾者的資料型態:

注意上面加粗了的文字,數組名是指向數組第一個元素的指標!

  1. a的資料型別是? ? int *?不是,這句加粗了的文字的核心就是第一個元素這五個字, a的第一個元素不就是a[0][0]嗎?嚴格來說,不是,a的第一個元素其實是a[0],那麼a[0]的資料型別是什麼呢? a[0] 是包含三個int元素的數組,所以a[0]的型別就和int t[3]中t的型別一樣,是int*,既然第一個元素 的資料型別是 int*,那把這個二維數組看成一維數組的話,其實它就是一個含有三個int*元素的 數組,也就是指標數組,所以a的資料型別就是int (*)[3]

  2. 再來看看&a,在一維數組的時候說了,對一個數組名取地址得出來的是指向數組的指針,所以&a的資料型態 就是int (*)[3][3]

  3. &a[0]這個看起來有點蛋疼,但是在上上段文字中也說了,a[0]是一個包含三個int元素的數組,和int t[3] 中的t的資料型別一樣,是int*,自然,&a[0]的資料型別就跟&t的資料型別一樣,也就是int (*)[3]

到這裡,二維數組中關於資料類型就寫得差不多了,既然知道了資料類型,那麼運算起來就可以準確知道指標會移動到哪裡!

看看下面這段程式碼:

int a[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
int (*x)[3] = a;
int *k = a[0];
int (*y)[3][3] = &a;
int (*q)[3] = &a[0];
int *z = &a[0][0];

printf("%d\n",(*(x+1))[0]);
printf("%d\n",*(k+1));
printf("%d\n",(*(*(y+1)))[0]);
printf("%d\n",(*(q+1))[0]);
printf("%d\n",*(z+1));

int *p = (int *)(y+1);
printf("%d\n",*(p-1));
  • x的資料型別是int (*)[3],所以,x+1其實是移動了3*sizeof(int)個位元組,如圖所示:

  • k的資料型別是int *,所以,k+1其實是移動了sizeof(int)個位元組。

  • y的資料型別是int (*)[3][3],所以y+1其實是移動了3*3*sizeof(int)個字節,也就是到了數組最後一個 元素的後面的一個元素。如圖所示:

  • q的資料型別是int (*)[3],所以,q+1其實也是移動了3*sizeof(int)個位元組。

  • z的資料型別是int *,所以z+1其實是移動了sizeof(int)個位元組。

所以對陣列指標進行加減運算,最重要的是知道它的步長,而步長又是由資料類型決定的!

Ty80

一樓答主寫的很好,做點擴充

指標的值是一個陣列的位址
int ( * pa )[5];
pa++;pa往後移動20個位元組(5*4)

對比,陣列中的元素是指標(int*)
int *pa[5];
pa++;pa往後移動4個位元組

小葫芦

不好意思翻老帖。無意間看到這個,因為以前做嵌入式編程,數組、指針是經常使用的,包括對指針變量直接賦值一個內存地址(這個在做正常的應用程序一般不會用到),以前也沒有發現&arr和arr有什麼差別。今天特意查了一下,原來這個要視編譯器而定。有的編譯器把數組和指標是分別對待的,對數組名取地址的意義是針對整個數組內存,有的編譯器則把數組認為是指針的一個特定形式,這種情況指針跳一個就是一個數組元素的內存,而不存在整個數組內存這一說。

熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板