我以為我傳的是陣列,但居然傳的是指標?為什麼我無法回傳陣列?只能回傳指標?所以陣列和指標是什麼關係?指標運算怎麼算?釋放動態記憶體時,指標變數會不會也被釋放?
還是很困惑嗎?文章裡有答案喔~😎
ㄧ、陣列 (Array)
原始碼是一個陣列,但編譯後就變成指標,所以陣列和指標到底有什麼關係?
(一)指標與陣列的關係
- int a[N]
- a[i] == *(a+i) // Get value
- &a[i] == &*(a+i) == a+i // Get address
- The name of an array is a constant pointer pointing to the first element of the array.
- a[0] == *(a+0) == *a // Get value
- &a[0] == &*(a+0) == a // Get address
a其實是一個指向第一元素的常數指標,i為index,N是 one past the end,最後一個元素後一個位置。
- typeof(a) == int[N]
- sizeof(a) == N*sizeof(int)
如果想要弄一個index為1開始的陣列,該如何實作?
int a[10]; int *p = a -1; // p[1] == a[0]
建議陣列的大小一開始先定義在一個常數變數上,未來要更動陣列大小時,就會比較方便!!
更多指標與陣列的關係例子,請參考期中考第2和5題。
(二)傳陣列
1. 傳陣列參數
雖然陣列名稱是一個指標變數,但還是有點不一樣:
- void func(int b[]){cout << sizeof(b) << endl;} // sizeof(int)*1,b是一個指標 --(1)
- int a[10]; cout << sizeof(a) << endl; // sizeof(int)*10,a是一個陣列 -- (2)
例子(1)可以發現,傳陣列給func()中,但引數b的大小是sizeof(int)*1,所以傳陣列給函式時,我們傳的是指標。例子(2)可以發現,直接檢查陣列a的大小,會顯示sizeof(int)*10,因此如果再呼叫func(a);,會顯示sizeof(int)*1。
從例子(1)可知,傳陣列給函數,會以指標的方式傳給函數,因為這樣會比較有效率。在函數存取時,可以使用陣列的[]存取,也可以使用指標的*存取。
void function (int a[]); == void function (int *a); // int a[] == int *a for passing arguments
2. 回傳陣列
那如果要回傳陣列出來呢?此時就不能直接回傳陣列,而是要回傳指標,因為我們傳給函式的陣列,其實是一個指標,而非陣列,因此回傳時也要回傳指標出來。
- int [] someFunction(int a[]); // 非法,不能回傳陣列
- int* someFunction(int a[]); // 合法,可以回傳指向陣列的指標,但不要回傳區域變數
另一種回傳陣列的方法是,將陣列包裹在struct / class中。
3. 陣列讀取
陣列的讀取有分by row或是by column,而為什麼C++函式參數列中,陣列的第一維可以不用給數字?
因為不需要第一維的數字,C++知道第二維長度就可以自動判斷出第一維長度。例如一個3x4的a陣列,傳陣列參數時,只要寫a[][4]即可。C++知道這是一個12大小,第二維為4的陣列,自然第一維就是3。
int a[2][5]; a[0][5] == a[1][0];?
complie error,不一定總是會成立,因為可能有些設計是by row或是by column
(三)陣列的指標運算
在陣列中,byte和element要有所區分,因為byte == sizeof(element),byte會隨著陣列裝的東西有關,但index不會。byte和element的區分,在指標運算中很重要。如果只是單一指標和常數的四則運算,這是記憶體位置的運算,因此和byte有關;而如果是指向同陣列的兩指標做加減,這是兩指標距離的運算,因此和element有關。
更多陣列的指標運算的例子,請參考期中考第3和4題
二、指標 (Pointer)
(一)指標變數
指標為存放記憶體位址的變數型態!指標是位址,位址是整數,但指標不是整數!
- int i = 10; void *p = &i; // 正確,pointer to void可以接受任何型態
- int *q = p; // 錯誤,pointer to int無法直接接受pointer to void
- int *q = (int*)p; //正確,必須使用casting將之轉成pointer to int
- int*p1, *p2, v1, v2; //盡量不要這樣宣告,容易混淆,p1和p2為int*,而v1, v2為int
- p2 = p1; // p2和p1所存放的值(位址)一樣,也就是p1和p2指向同一個位址
- *p1=*p2; //p2指向的物件值,給予p1指向的物件
(二)指標與const
如果參數不必修改,建議多使用const。
- 比較不會出錯
- 編譯器可以對此優化
__1__ int * __2__ ptr; // 在讀C++的型態時,由後往前讀!!
- ____, ____:pointer to int,指標與物件皆可更改。
- const, ____:pointer to const int,指標可改,物件不可改,常用於傳遞參數。
- ____, const:const pointer to int,指標不可改,物件可改,例如陣列、參考(reference)。
- const, const:const pointer to const int,指標與物件皆不可更改。
__1__ int __2__ * __3__ ptr; // __1__ == __2__:兩者位置皆相同
(三)動態記憶體
- int *p1 = new int(2); delete p1; p1 = nullptr; // 配置一個整數2給p1
- int* p2 = new int[5]; delete[] p2; p2 = nullptr; // 配置一個5個元素的空陣列給p2
剛new出來的東西是沒有名字的,new出來的東西位址要存放在指標中,用這個指標去存取這個新東西。new除了可以配置動態記憶體外,也可以做初始化的動作。
如果new後沒有delete,可能會造成記憶題遺失,所以使用new,就一定要使用delete!然後delete是釋放指標所指向的物件,而非指標本身被釋放!因此被釋放的空指標仍可以重複再指向新的物件。建議釋放記憶體後,要給指標接上nullptr,避免重複釋放記憶體,重複釋放記憶體會造成錯誤。但如果多多使用STL,就不會造成記憶題遺失,因為它會自動幫你配置予釋放記憶體。如:C++ <vector>。
留言
張貼留言