[筆記]陣列與指標|C++

我以為我傳的是陣列,但居然傳的是指標?為什麼我無法回傳陣列?只能回傳指標?所以陣列和指標是什麼關係?指標運算怎麼算?釋放動態記憶體時,指標變數會不會也被釋放?

還是很困惑嗎?文章裡有答案喔~😎

ㄧ、陣列 (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. 傳陣列參數

雖然陣列名稱是一個指標變數,但還是有點不一樣:

  1. void func(int b[]){cout << sizeof(b) << endl;} // sizeof(int)*1,b是一個指標 --(1)
  2. 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. 比較不會出錯
  2. 編譯器可以對此優化

__1__ int * __2__ ptr; // 在讀C++的型態時,由後往前讀!!

  1. ____, ____:pointer to int,指標與物件皆可更改。
  2. const, ____:pointer to const int,指標可改,物件不可改,常用於傳遞參數。
  3. ____, const:const pointer to int,指標不可改,物件可改,例如陣列、參考(reference)。
  4. const, const:const pointer to const int,指標與物件皆不可更改。

__1__ int __2__ * __3__ ptr; // __1__ == __2__:兩者位置皆相同

(三)動態記憶體

  1. int *p1 = new int(2); delete p1; p1 = nullptr; // 配置一個整數2給p1
  2. int* p2 = new int[5]; delete[] p2; p2 = nullptr; // 配置一個5個元素的空陣列給p2

剛new出來的東西是沒有名字的,new出來的東西位址要存放在指標中,用這個指標去存取這個新東西。new除了可以配置動態記憶體外,也可以做初始化的動作。

如果new後沒有delete,可能會造成記憶題遺失,所以使用new,就一定要使用delete!然後delete是釋放指標所指向的物件,而非指標本身被釋放!因此被釋放的空指標仍可以重複再指向新的物件。建議釋放記憶體後,要給指標接上nullptr,避免重複釋放記憶體,重複釋放記憶體會造成錯誤。但如果多多使用STL,就不會造成記憶題遺失,因為它會自動幫你配置予釋放記憶體。如:C++ <vector>。

三、相關文章

留言