幾個數組問題

最近在學習C語言。發現源代碼裏有如下幾種定義:

(char *) p[N];

char *p[N];

char (*p)[N];

一開始覺得應該都一樣吧,仔細研究了一下,竟然大不相同,而且還是C的難點之一。

下面附上解釋:

(char *) p[N]; 把p[N]強制轉化成指向char型的指針;

char *p[N]; 一個char指針數組,包含N個指針;

char (*p)[N]; 一個指向char[N]的指針。


char *p和char p[]賦值時的區別

char *s="abc";
char str[]="abccd";

經過反彙編,得到:

char *str="abc";   的彙編代碼

mov         dword ptr [ebp-4],offset string "abc" (0046f034)

 

char str[ ]="abccd"; 的彙編代碼

mov         eax,[string "abccd" (0046f02c)]
mov         dword ptr [ebp-0Ch],eax
mov         cx,word ptr [string "abccd"+4 (0046f030)]
mov         word ptr [ebp-8],cx

 

1、char *s="abc";

     看這個賦值:

     右邊,是"abc",是個字符串常量,存在於內存某處(我的機器上是ds:0x0046f034),程序員不知道,編譯器安排的,也沒必要知道(當然,這個賦值之後,程序員就知道並能控制這個串了)。字符串常量所在內存是隻讀的。

     左邊,字符指針s,賦值時候,把地址ds:0x0046f034的偏移地址("abc"所在),存放到指針變量s(其地址爲 ds:0x0046f034)中。程序員能完全控制的內存只是指針變量所佔據的這四個字節內存,只能改變該指針的指向,至於其指向的內存能不能寫,那就看程序了,本程序是指向的只讀內存,不能寫!

2、char str[]="abccd";

     再看這個賦值:

     右邊,和上面類似,是"abccd",也是個字符串常量,存在於內存某處(是ds:0x0046f02c),程序員不知道,編譯器安排的(這個賦值之後,程序員還是不知道這個常量在哪裏,因爲並沒有用指針指向這塊內存,這和上面不同)。該字符串常量所在內存也是隻讀的。


關於數組形參的真相

 (2011-07-21 17:00:43)
  分類: C語言基礎

寫了幾個函數,想通過數組的方式傳參,搞了半天終於發現,所謂C/C++中的"數組形參"是根本不存在的,在數組傳入函數時,實際上僅僅傳入了指向實參數組的首地址的指針。這也難怪我企圖使用sizeof求形參所佔內存個數的時候,卻得到了一個指針的長度。

void average(int arr[12]);// 實際形參arr是一個int*

int anArray[]={1,2,3};//聲明瞭一個有三個元素的數組

const int anArraySize = sizeof(anArray)/sizeof(anArray[0]); // =3

average(anArray);

好吧,業內專業人士對這種數組到指針的自動轉換的行爲叫做“退化”。即數組退化成指向其首元素地址的指針。同樣,當函數作爲形參被進行傳遞時,也會自動退化。不過,和數組退化時丟失邊界不同,一個退化的函數同樣具有良好的感知能力,可以保持其參數類型和返回類型。

由於數組形參中數組邊界被忽略了,因此通常在聲明時最好將其省略。

void average(int arr[]); //忽略邊界,形參僅僅是一個int*

當然,如果數組邊界的精確數值非常重要,並且希望函數只接受含有特定數量元素的數組,可以考慮使用一個引用形參:

void average(int (&arr)[12]);//引用調用,區別於傳值調用

這時,函數就只能接受大小爲12的整形數組;

average(anArray);//錯誤!anArray是一個int[3];

在C++中可以使用模板來幫助代碼的泛化:

template<int n>

void average(int (&arr)[n]);//讓編譯器來替我們推導出n的值

但是,更爲傳統的做法還是將數組的大小作爲形參明確的傳入函數:

vodi average(int arr[],int size);

當然,我們也可以使用模板:

template<int n>

inline void average(int (&arr)[n])

{

    average_n(arr,n);

}

從以上討論中我們可以得知,使用數組作爲函數形參的最大問題在於:數組的大小必須以形參的方式顯式的編碼,並以單獨的實參傳入,或者在數組內部以一個結束符值作爲指示。另一個困難在於,不管數組是如何聲明的,一個數組通常是通過指向其首元素地址的指針進行操縱。如果那個指針作爲實參傳遞給函數,我們前面聲明引用形參的技巧將無濟於事。

int *anArray2 = new int [anArraySize];

average(anArray2);//錯誤,不能將int *初始化爲int (&)[n]

average_n(anArray, anArraySize);//沒問題;

出於這些原因,常採用某種標準“容器”(通常是vector或者string)來代替對數組的大多數傳統用法,並且經常應該優先考慮使用標準容器。

 

從本質上來說,對維數組形參並不比一維數組來的困難,但他們看上去更有挑戰性。

void process(int arr[10][20]);

和一維數組一樣,形參並不是一個數組,而是一個指向數組首元素地址的指針。不過,多維數組是數組的數組,因此形參是一個指向數組的指針。

 void process(int (*arr)[20];//一個指針,指向一個具有20個int元素的數組;

注意,第二個邊界沒有退化,否則將無法對形參執行指針算數。但如前所述,讓代碼的讀者清楚的知道你期望的實參是一個數組,這通常是一個好主意:

void process(int arr[][20]); //仍然是一個指針,但更清晰。

 對於多維數組形參的有效性處理往往退化成一個低級的編程練習,此時需要程序員取代編譯器執行索引計算。

void process_2d(int *a, int n, int m)

{

    for (int i=0; i<n; ++i)

        for (int j=0; j<m ++j)

            a[i*m + j] = 0;

}

 同樣,我們可以藉助模板

template<int n, int m>

inline void process(int (&arr)[n][m])

{

    process_2d(&arr[0][0], n, m);

}


     左邊,字符數組str,賦值時候,把地址ds:0x0046f02c("abccd"所在)所指內存中的內容,複製到字符數組str開始(其地址爲 ds:0x0013ff74)的內存中,每複製一個字符都會開闢一個字節(char型變量佔1字節)內存來存放這個字符(這也是實現了數組元素個數的動態確定)。從字符數組str開始的這部分存放這些字符的內存是程序員可以完全控制的,可讀寫,因此在這些內存寫當然是沒有問題的!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章