Mes
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
1
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# <div id=animation_title>陣列、指標 與 物件別名 (Reference 參考)</div> ###### tags: `C++` **<a href="https://hackmd.io/CSZ3EUqhSRa8AyDyBMq1Bw" class="redlink">點此回到 C++筆記 目錄 </a>** # 一維陣列 C++內可以宣告一個以索引(index)作為識別的資料結構,稱作陣列,能夠用來儲存資料,宣告陣列的方式為 `資料型態 名稱[長度];` 長度必須是個編譯時期的常數 (後面會提到動態配置,這邊先當要固定大小), 以下是幾個宣告的範例: ```cpp= int number[10]; // 宣告 10 個元素的整數陣列 double score[10]; // 宣告 10 個元素的浮點數陣列 char ascii[10]; // 宣告 10 個元素的字元陣列 ``` 宣告陣列後,陣列內的元素並不會自動初始化,宣告僅僅是分配了一段記憶體空間給他,並以陣列的名字標記他在哪(後面指標會詳細講到), 因此我們需要手動初始化陣列的值,若想在宣告時一次初始陣列全部的元素值,可以如下: ```cpp= int number[10] = {0}; double score[10] = {0.0}; char ascii[10] = {'\0'}; bool flag[10] = {false}; ``` 上面的幾個宣告,整數陣列中的元素都會被初始為 0,浮點數陣列則會被初始為 0.0,字元陣列會被初始為空字元('\0'),而 bool 陣列會被初始為 false。 當然,我們也可以指定不同的初始值給陣列,如: ```cpp= int number[5] = {0, 1, 2, 3, 4}; double score[5] = {87.0, 78.0, 99.5, 69.5, 82.5}; char ascii[5] = {'A', 'B', 'C', 'D', 'E'}; bool flag[5] = {false, true, false, true, false}; ``` 要存取陣列中的元素值時,可以使用下標(Subscript)運算子 [] 加上索引」,索引值由 0 開始,下面這個簡單的程式是個示範: ```cpp= #include <iostream> using namespace std; int main() { constexpr int LEN = 10; int number[LEN] = {0}; for(int i = 0; i < LEN; i++) { cout << number[i] << " "; } cout << endl; for(int i = 0; i < LEN; i++) { number[i] = i; } for(int i = 0; i < LEN; i++) { cout << number[i] << " "; } cout << endl; return 0; } 輸出: 0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 5 6 7 8 9 ``` 陣列在使用時,一定要先得知陣列長度,不可以存取超過陣列長度的記憶體,這會發生無法預期的結果 (因為超出了有定義值的記憶體位址),陣列本身並不知道自己的長度資訊,在上面的範例中,使用了 LEN 來記錄長度,不過,有沒有辦法計算出長度呢?可以使用底下的方式: ```cpp= #include <iostream> using namespace std; int main() { int number[5] = {0, 1, 2, 3, 4}; int length = sizeof(number) / sizeof(number[0]); for(int i = 0; i < length; i++) { cout << number[i] << " "; } cout << endl; return 0; } ``` 陣列索引值由 0 開始不是沒有原因的,陣列名稱儲存了陣列記憶體的首個位置的位址,而索引值表示陣列元素是相對於陣列首個記憶體位址的位移量(offset),位移的量與資料型態長度有關,如果是 int 整數,每次位移時是一個 int 整數的長度,例如在上例中 number[0] 索引值為 0 時,表示位移量為 0,自然就是指第一個元素,而 number[9] 就是指相對於首個元素的位移量為 9。 <br> 若在宣告陣列時指定各個索引處的的值,可以不用宣告陣列元素大小,例如: ```cpp= int number[] = {1, 2, 3}; double weight[] = {0.4, 3.2, 1.0, 4.2}; char ch[] = {'A', 'B'}; ``` 上面宣告中,number[] 的元素個數會是 3,weight[] 的個數會是 4,而 chs[] 的個數會是 2。 <br> 如果使用 const 或 constexpr 來修飾陣列,每個索引位置就成為唯讀。例如: ```cpp= constexpr int number[] = {1, 2, 3}; number[1] = 10; // error: assignment of read-only location 'number[1]' ``` 另外,陣列無法像vector直接複製,如: ```cpp= int arr1[5]; int arr2[5]; ... arr1 = arr2;// 錯誤!不能直接指定陣列給另一個陣列 ``` 如果要複製陣列,只能一個一個元素複製,如: ```cpp= constexpr int LENGTH = 5; int arr1[LENGTH]; int arr2[LENGTH]; ... for(int i = 0; i < LENGTH; i++) { arr1[i] = arr2[i]; } ``` 同樣的,要比較兩個陣列是否相同的話,也是要一個一個元素比較。 <br> # 二維(多維)陣列 一維陣列使用陣列名稱與一個索引值來指定存取陣列元素,二維陣列使用陣列名稱與兩個索引值來指定存取陣列元素,宣告方式與一維陣列類似: ```cpp= int maze[5][10]; ``` 上面這個宣告會配置 5 * 10 = 50 個整數的記憶體空間給陣列來使用,二維陣列使用兩個索引值來指定存取陣列,這兩個索引值都是由 0 開始,下面這個程式簡單的示範二維陣列的存取: ```cpp= #include <iostream> using namespace std; int main() { constexpr int ROWS = 5; constexpr int COLUMNS = 10; int maze[ROWS][COLUMNS]; for(int row = 0; row < ROWS; row++) { for(int i = 0; i < COLUMNS; i++) { maze[row][i] = (row + 1) * (i+ 1); } } for(int row = 0; row < ROWS; row++) { for(int i = 0; i < COLUMNS; i++) { cout << maze[row][i] << "\t"; } cout << endl; } return 0; } 輸出: 1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 ``` 上面這個程式宣告了 5 列(Row)、10 行(Column)的陣列,第一個 [] 是用來指定存取哪一列,第二個 [] 是用來指定存取哪一行。 <br> 和一維陣列相同,可以在宣告二維陣列的同時指定二維陣列的值,例如: ```cpp= int maze[2][3] = { {1, 2, 3}, {4, 5, 6} }; ``` 從上面這個程式來看,可以清楚地看出 maze[2][3] 中 2 與 3 的意義,maze[2] 表示 maze 有兩個元素,各是 {1, 2, 3} 與 {4, 5, 6},也就是說,這兩個元素是一維陣列,而長度是 3。 <br> 其實二維陣列存取時的行與列,只是為了便於理解陣列元素索引,索引值的意義,仍是指相對於陣列第一個元素的位移量。 例如,在一維陣列中的陣列配置與索引意義如下圖所示(若 int 長度為 4 個位元組): <center> <img src="https://codimd.mcl.math.ncu.edu.tw/uploads/upload_5ba2111cb8992bd71a39b8f6ce1b35fd.png"> </center><br> 對 int 陣列來說,每次位移量是 4 個位元組,指定存取 maze[4],相當於指定存取相對於 maze[0] 四個位移量的記憶體空間。 <br> 二維陣列在記憶體中也是線性配置,例如: <center> <img src="https://codimd.mcl.math.ncu.edu.tw/uploads/upload_8e0f9f50d15e8ff1bdd2342caae0983c.png"> </center><br> 在上面的例子中,二維陣列將得到的記憶體分為兩個區塊,宣告陣列 maze[2[4],表示 maze[0][0] 與 maze[1][0] 相對位移量為 4,存取 maze[1][3] 時,表示存取位置相對於 maze[1][0] 位移3個單位。 <br> 若宣告 maze[3][5] 的話,記憶體位置的指定是如何呢?在這個陣列中 5 的意義是 maze[0][0]、maze[1][0] 與 maze[2][0] 間都是 5 個位移量,如下圖所示: <center> <img src="https://codimd.mcl.math.ncu.edu.tw/uploads/upload_2fe6df6f8a2f317efe64db38bd10c871.png"> </center><br> 如果想要印出陣列中的元素,可以利用for迴圈,如下: ```cpp= #include <iostream> using namespace std; int main() { int maze[2][3] = { {1, 2, 3}, {4, 5, 6} }; for(int i = 0; i < 2; i++) { for(int j = 0; j < 3; j++) { cout << maze[i][j] << " "; } cout << endl; } system("pause"); return 0; } 輸出: 1 2 3 4 5 6 ``` <br> # 指標 ## 指標 + **<span class="pink">& 取址運算子</span>** > &n 是n的位置,位置採16進位 ```cpp= int n=10; cout << &n << endl; 輸出:0x61febc(不一定一樣) ``` + <span class="pink"><strong>* 指標</strong></span> > 資料型態* 變數名 ex: ```cpp= int *n ``` > 則n的型態為int* > 此變數用來儲位置 ```cpp= int main() { int n = 10; int *p = &n ; cout << "n 變數的位址:" << &n << endl << "p 儲存的位址:" << p << endl; return 0; } 輸出: n 變數的位址:0x61feb8 p 儲存的位址:0x61feb8(不一定一樣) ``` + <span class="pink"><strong>* 反參照運算子</strong></span> > 你沒看錯,和指標的符號一模一樣! > 可以使用提取運算子來拿指標儲存位置處的物件,ex: ```cpp= int main() { int n = 10; int *p = &n; cout << "指標 p 儲存的位址:" << p << endl << "提取 p 儲存位址處的物件:" << *p << endl; return 0; } 輸出: 指標 p 儲存的位址:0x61feb8(不一定一樣) 提取 p 儲存位址處的物件:10 ``` + 用 const 宣告的變數,必須使用對應的 const 型態指標才可以: ```cpp= const int n = 10; const int *p = &n; ``` + 要留意的是,const int *p 宣告的 p 並不是常數,可以儲存不同的位址。例如: ```cpp= #include <iostream> using namespace std; int main() { const int n = 10; const int m = 20; const int *p = &n; cout << p << endl; p = &m; cout << p << endl; return 0; } 輸出: 0x61feb8 0x61feb4(不一定一樣) ``` + 如果想令指標儲存的值無法變動,必須建立指標常數,先來看看來源變數沒有 const 的情況: ```cpp= int n = 10; int m = 20; int* const p = &n; p = &m; // error: assignment of read-only variable 'p' ``` + 如果 n、m 被 const 修飾,那麼就必須如下建立指標常數: ```cpp= const int n = 10; const int m = 20; const int* const p = &n; p = &m; // error: assignment of read-only variable 'p' ``` 對於傳址進function是如何運作有疑問的人可以看看這個例子: ```cpp= #include <bits/stdc++.h> using namespace std; void sw( int *a, int *b ) { cout << "現在有一個變數在sw內,名字叫a,存的東西是:" << a << endl << "然後有一個變數在sw內,名字叫b,存的東西是:" << b << endl << endl; int *x = a; //創了一個變數x,x存的東西要是一個位址,a現在存的東西是位址,所以可以給他 a = b; //把sw內,a存的位址改成b存的位址 b = x; //把sw內,b存的位址改成剛剛x存的位址 cout << "現在sw內,a裡面存的東西是:" << a << endl << "現在sw內,b裡面存的東西是:" << b << endl << endl; cout << "實際上sw內a的記憶體位置是:" << &a << endl << "實際上sw內b的記憶體位置是:" << &b << endl << endl; } int main() { int a = 10; int b = 20; cout << "現在在main裡面,經過sw前,a的位址是:" << &a << endl << "現在在main裡面,經過sw前,b的位址是:" << &b << endl << endl; sw( &a, &b ); cout << "現在在main裡面,經過sw後,a的位址是:" << &a << endl << "現在在main裡面,經過sw後,b的位址是:" << &b << endl << endl; system( "pause" ); return 0; } 輸出: 現在在main裡面,經過sw前,a的位址是:0x61ff0c 現在在main裡面,經過sw前,b的位址是:0x61ff08 現在有一個變數在sw內,名字叫a,存的東西是:0x61ff0c 然後有一個變數在sw內,名字叫b,存的東西是:0x61ff08 現在sw內,a裡面存的東西是:0x61ff08 現在sw內,b裡面存的東西是:0x61ff0c 實際上sw內a的記憶體位置是:0x61fef0 實際上sw內b的記憶體位置是:0x61fef4 現在在main裡面,經過sw後,a的位址是:0x61ff0c 現在在main裡面,經過sw後,b的位址是:0x61ff08 ``` 可以看見在sw內仍會間接的產生一個變數來複製a與b的位置,但相較於值皆傳值進去,傳址的效率仍會快一些。 ## 指標的運算 指標加法與減法與一般數值的加減不同,在指標運算上加 1 ,表示前進一個資料型態的記憶體長度,例如 int ,長度為 4 個位元組,在 int* 型態的指標上加 1,表示在位址上前進 4 個位元組的長度: ```cpp= #include <iostream> using namespace std; int main() { int *p = 0; cout << "p 位址:" << p << endl << "p + 1:" << p + 1 << endl << "p + 2:" << p + 2 << endl; return 0; } 輸出: p 位址:0 p + 1:0x4 p + 2:0x8 ``` 如果你不曉得某型態的記憶體空間時,可以使用sizeof()函式: ```cpp= cout << sizeof(double); 輸出: 8 ``` ## 指標與陣列 在宣告陣列之後,使用到陣列變數時,會取得首元素的位址,例如在下面的程式中將指出,陣列 arr 與 &arr[0] 的值是相同的: ```cpp= #include <iostream> using namespace std; int main() { int arr[10] = {0}; cout << "arr:\t\t" << arr << endl << "&arr[0]:\t" << &arr[0] << endl; return 0; } 輸出: arr: 0x61fe98 &arr[0]: 0x61fe98(不一定一樣) ``` 陣列索引(index)其實是相對於首元素位址的位移量,下面這個程式以指標運算與陣列索引操作,顯示出相同的對應位址值: ```cpp= #include <iostream> using namespace std; int main() { constexpr int LENGTH = 10; int arr[LENGTH] = {0}; int *p = arr; for(int i = 0; i < LENGTH; i++) { cout << "&arr[" << i << "]: " << &arr[i] << "\tp+" << i << ": " << p + i << endl; } return 0; } //每個元素的位址型態都是 int* 輸出: &arr[0]: 0x61fe8c p+0: 0x61fe8c &arr[1]: 0x61fe90 p+1: 0x61fe90 &arr[2]: 0x61fe94 p+2: 0x61fe94 &arr[3]: 0x61fe98 p+3: 0x61fe98 &arr[4]: 0x61fe9c p+4: 0x61fe9c &arr[5]: 0x61fea0 p+5: 0x61fea0 &arr[6]: 0x61fea4 p+6: 0x61fea4 &arr[7]: 0x61fea8 p+7: 0x61fea8 &arr[8]: 0x61feac p+8: 0x61feac &arr[9]: 0x61feb0 p+9: 0x61feb0(不一定一樣) ``` 在上面這個程式中,將陣列的首元素位址指定給 p,然後對 p 遞增運算,每遞增一個單位,陣列相對應索引的元素之位址都相同。 也可以利用指標運算來取出陣列的元素值,如以下的程式所示: ```cpp= #include <iostream> using namespace std; int main() { constexpr int LENGTH = 5; int arr[LENGTH] = {10, 20, 30, 40, 50}; int *p = arr; // 以指標方式存取 for(int i = 0; i < LENGTH; i++) { cout << "*(p + " << i << "): " << *(p + i) << endl; } cout << endl; // 以指標方式存取資料 for(int i = 0; i < LENGTH; i++) { cout << "*(arr + " << i << "): " << *(arr+i) << endl; } cout << endl; return 0; } 輸出: *(p + 0): 10 *(p + 1): 20 *(p + 2): 30 *(p + 3): 40 *(p + 4): 50 *(arr + 0): 10 *(arr + 1): 20 *(arr + 2): 30 *(arr + 3): 40 *(arr + 4): 50 ``` 前面知道可以用sizeof來計算陣列長度,在認識指標及其運算後,我們現在知道,透過begin與end函式,也可以計算陣列長度: ```cpp= #include <iostream> using namespace std; int main() { constexpr int LENGTH = 5; int arr[LENGTH] = {10, 20, 30, 40, 50}; cout << sizeof(arr)/sizeof(*arr) << endl; // 顯示 5 cout << end(arr) - begin(arr) << endl; // 顯示 5 return 0; } ``` 每個陣列元素的位址型態是 int*,這表示對它進行運算時,是以 int 長度為單位,而 arr 變數的位址處就是陣列資料的開端,因此 arr+1 就會是array裡面第二個元素。 而&arr是一個 **陣列的指標** ,型態會是 `int (*)[5]`,表示對它進行運算時,是以5個int的長度( `int *[Length]` )為單位,因此 &arr+1 會一次移動5個int的大小,也就是到達arr[5] (arr後的一格,因為arr只有5格),如果想宣告相對應的變數,可以如下: ```cpp= int (*p)[LENGTH] = &arr; ``` 則p是一個指標變數,指到arr的位置,p+1會一次移動整個arr的大小,因此,若我們想計算陣列長度,還可以有這樣的寫法: ```cpp= constexpr int LENGTH = 5; int arr[LENGTH] = {10, 20, 30, 40, 50}; int len = *(&arr + 1) - arr; ``` ## 指標的指標 指標的指標,還是指標(廢話XD), 都是拿來儲存記憶體位址的,差別在於指標的型態,可以看看底下的範例: ```CPP= #include <iostream> using namespace std; int main() { int n = 10; int *p1 = &n; int **p2 = &p1; cout << "n 位址:" << p1 << endl << "p1 位址:" << p2 << endl; return 0; } 輸出: n 位址:0x61feb8 p1 位址:0x61feb4 ``` > n 儲存了 10,n 的位址 0x61feb8,指定給 p1 儲存,而 p1 的位址是 0x61feb4,指定給 p2 儲存。 n的型態是int,因此 &n 的型態為`int*` ,所以宣告p1時宣告的型態為int*,而 &p1 的型態則為`int**`, 因此宣告p2時宣告的型態為int**。 指標的指標好用的地方在於運算多維陣列扮演的作用,因為多維陣列是由陣列的陣列所組成的,實際上就是數段一維陣列所構成的,而若對變數取址後遞增1,則會位移一整個陣列空間。 <br> 在宣告二維陣列時,我們需要宣告每段一維陣列的長度為何: ```cpp= int arr[2][3] = {{10, 20, 30}, {40, 50, 60}}; int (*p)[3] = arr; ``` 同樣的,在宣告三維陣列時,我們需要宣告每段二為陣列的長度為何: ```cpp= int arr[2][2][3] = { {{1, 2, 3},{4, 5, 6}} , {{7, 8, 9},{10, 11, 12}} }; int (*p)[2][3] = arr; ``` 在使用時可以直接使用auto比較方便: ```cpp= int arr1[2][3] = {{10, 20, 30}, {40, 50, 60}}; auto p1 = arr1; int arr2[1][2][3] = {{{10, 20, 30}, {40, 50, 60}}}; auto p2 = arr2; ``` 再利用前面談二維陣列時的例子,如果我們要印出一個二維陣列,也可以利用指標的方式來操作,如下: ```cpp= #include <iostream> using namespace std; int main() { int maze[2][3] = { {1, 2, 3}, {4, 5, 6} }; for(int (*it)[3] = begin(maze); it < end(maze); it++) { int *row = *it; for(int i = 0; i < 3; i++) { cout << row[i] << "\t"; } cout << endl; } return 0; } 輸出: 1 2 3 4 5 6 ``` ## new 與 delete 到目前為止,變數建立後會配置記憶體空間,這些空間在函式執行過後就會被清除掉。 因此,如果我們要將函式的執行結果回傳,就不能直接回傳這類被自動配置空間的位址,而要自行配置或刪除記憶體空間,這時候就需要new和delete了。 舉例來說: `int *p = new int;` 會配置一個int型態大小的記憶體空間,而 new 會回傳這個空間的位址,並用p來儲存,這段程式只配置空間但不初始空間的值,如果要在配置空間後指定儲存值,可以這樣宣告: `int *p = new int(100);` ,如此一來,會將此空間中的儲存值設定為100 我們可以用下面這個程式來看看動態配置的使用: ```cpp= #include <iostream> using namespace std; int main() { int *p = new int(100); cout << "空間位址:" << p << endl << "儲存的值:" << *p << endl; *p = 200; cout << "空間位址:" << p << endl << "儲存的值:" << *p << endl; delete p; return 0; } 輸出: 空間位址:0x787a88 儲存的值:100 空間位址:0x787a88 儲存的值:200 ``` 另外,因為此空間是我們配置的,因此程式結束前不會自動刪除,我們需要自行使用delete來將空間刪除 ex: `delete [] p;`。若大量使用new但沒有適當使用delete( `delete [] p` )的話,會將記憶體空間用盡,或是導致記憶體空間碎片化。 若想配置連續個指定型態的空間,可以這樣宣告: `int *p = new int[1000];`,這段程式碼動態配置了1000個int大小的空間,並回傳空間的第一個位址,配置後的空間資料是未知的,**[ ]中指定的長度可以來自運算式,不必是編譯時期就得決定的值**,這個值必須自行儲存下來,因為沒有任何方式可以從p得知到底配置的長度是多少(p只儲存了位址) 如果要全部設定為型態的零值,可以這樣寫: `int *p = new int[3]()` <br> 之前在談陣列時說過,陣列具有指標性質,因此上面的方式,會被用來克服陣列大小必須事先決定的問題,也就是可以用來動態地配置連續空間,並當成陣列來操作,例如底下是個簡單的示範: ```cpp= #include <iostream> using namespace std; int main() { int size = 0; cout << "輸入長度:"; cin >> size; int *arr = new int[size]{0}; cout << "指定元素:" << endl; for(int i = 0; i < size; i++) { cout << "arr[" << i << "] = "; cin >> arr[i]; } cout << "顯示元素值:" << endl; for(int i = 0; i < size; i++) { cout << "arr[" << i << "] = " << arr[i] << endl; } delete [] arr; return 0; } 輸出: 輸入長度:3 指定元素: arr[0] = 10 arr[1] = 20 arr[2] = 30 顯示元素值: arr[0] = 10 arr[1] = 20 arr[2] = 30 ``` <br> 若要動態配置連續空間,並當成二維陣列來操作,就記得二維(或多維)陣列,就是以陣列的陣列來實作,二維陣列就是多段一維陣列,如果你的二維陣列有兩段一維陣列,那就是如下: ```cpp= #include <iostream> using namespace std; int main() { int **arr = new int*[2]; for(int i = 0; i < 2; i++) { arr[i] = new int[3]{0}; } for(int i = 0; i < 2; i++) { for(int j = 0; j < 3; j++) { cout << arr[i][j] << " "; } cout << endl; } for(int i = 0; i < 2; i++) { delete [] arr[i]; } delete [] arr; return 0; } 輸出: 0 0 0 0 0 0 ``` 既然可以動態配置,那每段一維陣列長度當然可以不一樣囉! ```cpp= #include <iostream> using namespace std; int main() { int **arr = new int*[2]; arr[0] = new int[3]{0}; arr[1] = new int[5]{0}; for(int i = 0; i < 3; i++) { cout << arr[0][i] << " "; } cout << endl; for(int i = 0; i < 5; i++) { cout << arr[1][i] << " "; } cout << endl; for(int i = 0; i < 2; i++) { delete [] arr[i]; } delete [] arr; return 0; } 輸出: 0 0 0 0 0 0 0 0 ``` 因為我這邊常常卡住XD 每次看到這個都要再重新想一遍,所以再貼一個範例上來,用來整理思緒: ```cpp= int main() { int **arr = new int *[2](); arr[0] = new int[3](); arr[1] = new int[3](); for ( int i = 0; i < 3; ++i ) { arr[0][i] = i + 1; arr[1][i] = i + 4; } /* arr[2][3] = { {1,2,3}, {4,5,6} } */ cout << "arr = " << arr << endl << "arr+1 = " << arr + 1 << endl << endl << "*arr = " << *arr << endl << "**arr = " << **arr << endl << endl << "*(arr+1) = " << *( arr + 1 ) << endl << "**(arr+1) = " << **( arr + 1 ) << endl << endl << "*arr+1 = " << *arr + 1 << endl << "*(*arr +1 ) = " << *( *arr + 1 ) << endl << endl; for ( int i = 0; i < 2; i++ ) { delete[] arr[i]; } delete[] arr; system( "pause" ); return 0; } 輸出: arr = 0xfd1910 arr+1 = 0xfd1914 *arr = 0xfd1930 **arr = 1 *(arr+1) = 0xfd1958 **(arr+1) = 4 *arr+1 = 0xfd1934 *(*arr +1 ) = 2 ``` 內部記憶體就會長這樣: ![記憶體的圖](https://codimd.mcl.math.ncu.edu.tw/uploads/upload_28c8571b3a090a27dd8b0e3524038dce.png) 要注意,每個小空間是連續的,但大空間並不是連續的,因為她是動態配置的關係。 <br> # 物件別名 (Reference 參考) (待補) ## Reference Reference是物件的別名,你可以把他想成某個變數的綽號,對Reference進行的任何存取,都會對原物件進行操作。 在C++裡,物件不單只是指類別,而是指記憶體裡的一塊資料,換句話說,Reference類似pointer,但reference指到的是記憶體位址內的數值,pointer則是記憶體的位址。 另外,一個函式內不能有相同名稱的兩個Reference。 要定義Reference,是在型態關鍵字後加上&運算子,例如: ```cpp= int n = 10; // 定義變數 int *p = &n; // 定義指標,儲存 n 的位址 int &r = n; // 定義參考,是 n 的別名 ``` <br> 要注意的是,Reference後面必須要有指定的物件,否則無法通過編譯,如: `int &r;` 就會出錯。 對Reference的進行的任何存取,都會對原物件進行操作,如: ```cpp= #include <iostream> using namespace std; int main() { int n = 10; int &r = n; cout << "n:" << n << endl << "r:" << r << endl; r = 20; cout << "n:" << n << endl << "r:" << r << endl; return 0; } 輸出: n:10 r:10 n:20 r:20 ``` 指標也可以使用Reference,例如: ```cpp= int n = 10; int *p = &n; int *&r = p; // 也就是 int* &r = p, int* 是型態, r是指標p的Reference ``` 陣列也可以,但需要指定長度,如: ```cpp= int arr[] = {1, 2}; int (&r)[2] = arr; ``` <br> 既然陣列也可以使用,那麼再一次借用前面二維陣列的例子,我們也可以使用reference來幫助我們印出二維陣列,如下: ```cpp= #include <iostream> using namespace std; int main() { int maze[2][3] = { {1, 2, 3}, {4, 5, 6} }; for(int (*it)[3] = begin(maze); it < end(maze); it++) { int (&row)[3] = *it; // 使用reference for(auto offset = begin(row); offset < end(row); offset++) { int n = *offset; cout << n << "\t"; } cout << endl; } return 0; } ``` 不過這樣寫有點複雜,可以再搭配 for range 的語法與 auto 進行簡化,如下: ```cpp= #include <iostream> using namespace std; int main() { int maze[2][3] = { {1, 2, 3}, {4, 5, 6} }; for(auto &row : maze) { // 使用reference for(auto n : row) { cout << n << "\t"; } cout << endl; } return 0; } ``` <br> ## Rvalue Reference 記憶體中臨時的資料,如常數,無法擁有Reference,如下: ```cpp= int &r = 10; // error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int' ``` 若真的想要讓他擁有Reference,需要加上const,如下: ```cpp= const int &r = 10; ``` 這段在編譯時會被轉換為像是: ```cpp= const int _n = 10; const int &r = _n; ``` 可以看見實際上 r 並不是真的為 10 的Reference,而是 10 被複製給 _n,然後 _n 的Reference為 r 。 如果不加上 const,那麼編譯器可能會以為變更了 r,就是變更了 10 位址處的值,因此就編譯器要求你一定得加上 const,不讓它可以被改動。 那為什麼常量會需要Reference呢? 這通常跟函式呼叫相關,這之後再來討論;類似地,以下會編譯失敗: ```cpp= int a = 10; int b = 20; int &r = a + b; // error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int' ``` 這是因為 a + b 運算出的結果,會是在臨時的記憶體空間中,無法取址;類似地,若想通過編譯,必須加上 const: ```cpp= int a = 10; int b = 20; const int &r = a + b; ``` 但是!! 在 C++ 11 之後,像以上的運算式,可以直接使用Reference了: ```cpp= int a = 10; int b = 20; int &&rr = a + b; ``` 上面這個程式中,int&& 為 Rvalue Reference ,rr 是 a + b 運算結果的空間的Reference,相對於以下的程式來說比較有效率: ```cpp= int a = 10; int b = 20; int rr = a + b; // 將 a + b 的結果複製給 rr ``` 因為不必有將值複製、儲存至 rr 的動作,效率上比較好,特別是當運算式會產生龐大物件時,複製就會是個成本考量。 <br> 相對於 Rvalue Reference,int& 這類Reference就被稱為 Lvalue Reference;只不過,lvalue 或 rvalue 是什麼?剛剛編譯錯誤的訊息中,似乎也出現了 lvalue、rvalue 之類的字眼,這些是什麼? lvalue、rvalue 是 C++ 對運算式(expression)的分類方式,一個粗略的判別方式,是看看 & 可否對運算式取址,若可以的話,運算式是 lvalue,否則是個 rvalue,換句話說就是: >Lvalue: 有名稱的、在記憶體中有實際位置可供程序員加以存取物件。 > >Rvalue: 沒有名稱的暫時物件,離開該敘述即被摧毀的物件, 以前面的舉例來說: 兩個 int 變數 a, b 相加的結果 (a+b) 就是 rvalue,因為這個結果: >1. 他是沒有名稱的暫時物件 >2. 離開這行敘述後這物件就不見了,你再也沒有其他方法可以存取到它 <style> .green { color:#29E5A9; } .brown { color:#990000; } .pink { color:#DD9FBD; } .red { color:#E71B18 ; } .blue { color:#0b5394; } .purple { color:#AC9FDD; } @-webkit-keyframes A { 0% { color:#C10066;} 10% { color: #CC0000;} 20% { color: #E63F00; } 30% { color:#EE7700; } 40% { color: #DDAA00; } 50% { color:#EEEE00;} 60% { color: #99DD00;} 70% { color:#66DD00;} 80% { color: #00DDDD;} 90% { color: #0044BB;} 100% { color: #A500CC;} } #animation_title{ animation: A 3s ease 0s infinite alternate; -webkit-animation: A 3s ease 0s infinite alternate; } </style> <style> a.redlink { color:#DB1859; } a.redlink:link { color:#DB1859; text-decoration:none; } a.redlink:visiteid { color:#DB1859; text-decoration:none; } a.redlink:hover { color:#19CABC; text-decoration:none; } a.redlink:active { color:#000000; text-decoration:underline; background:#FFFFFF; } </style> <style type="text/css"> h1 { font-size:; color:#0b5394; } h2 { font-size:; color:#0b5394; } p { font-size:; color:; } h3 { font-size: ; color:#990000; } h4 { font-size:; color:#990000; } </style>

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully