###### tags: `Coding` # C / C++ 學習筆記(partII) ## 編寫規則(指標之前) --- ### 陣列 #### 定義 * 一群具有相同資料型態元素集合而成 * 於記憶體中會使用一段"連續"的記憶體空間存放 => 同等於array * 陣列的變數定義: * "元素" 資料型態 陣列名稱[元素個數]; 範例: int arr[3]; * 陣列index從0開始 * 陣列元素的存取: * 陣列變數名稱[索引編號] #### 陣列初始化寫法 ``` int counter[6] = {0,0,0,0,0,0}; //初始化六個位置皆為零 int counter2[6] = {0};//初始化時,最少需要指定一個值,沒有被指定到的位置會被自動指定為0(此case 指定到第一個位置) int counter3[] ={0,0,0,0,0,0} //直接以初始化的值設定矩陣大小 ``` #### 輸入0~99數(數個),並以10為單位,繪製bar chart ``` #include <stdio.h> #include <math.h> void bar(int []); int main(){ char check; int array_size,input_number; printf("This programe is used for value 0~99\nplease input your size of input array:"); scanf("%d",&array_size); int count[10] ={0}; for (int i=0;i<array_size;i++){ printf("your %d number:",i+1); scanf("%d",&input_number); count[input_number/10] += 1; input_number = 0; } bar(count); return 0; } void bar(int count[]){ for(int i=0;i<10;i++){ int CountDown = 0; if (i ==0){ printf("The amount of 00~09:"); } else{ printf("The amount of %d~%d:",i*10,(i+1)*10-1); } while (CountDown <count[i]){ printf("*"); CountDown++; } printf("\n"); } } ``` #### 陣列排序 => 演算法 **註解:此章節先跳過,等之後實作演算法時補充** #### 陣列的記憶體配置 &emsp;通常在宣告定義變數時(例如int)時,會藉由宣告的變數型態安排相對應的記憶體空間給該變數,當在宣告陣列時,會依照指定的元素型態,分派相對應大小的記憶體空間。 #### 陣列中的陣列(矩陣) ``` int v[x][y] =>可看成v[0]存有[y]個element... ``` &emsp;在陣列中的陣列(矩陣)當中,記憶體的儲存方式為藉由row儲存,也就是若今天有矩陣為:<br> > v[2][3]<br> 則其在記憶體內的儲存方式為如下:<br> ``` v[0][0] -> v[0][1] -> v[0][2] -> v[1][0] -> v[1][1] -> v[1][2] ``` #### 以二維陣列實踐隨機九宮格 ``` #include <stdio.h> #include <time.h> #include <stdlib.h> int main(){ srand(time(NULL)); int matrix[3][3] = {{1,2,3},{4,5,6},{7,8,9}}; int count,random,row,col,t; for (int i=0;i<3;i++){ for (int j=0;j<3;j++){ random = rand() % 9; row = random / 3; col = random % 3; t = matrix[i][j]; matrix[i][j] = matrix[row][col]; matrix[row][col] = t; } } for (int i=0;i<3;i++){ for (int j=0;j<3;j++){ printf("%d ",matrix[i][j]); } printf("\n"); } return 0; } ``` #### 陣列的複製 * 陣列無法直接用另一個陣列初始化 ex:<br> v[10]=n (x) ##### 使用迴圈複製陣列 ``` #include <stdio.h> #include <stdlib.h> #include <time.h> int main(){ srand(time(0)); int i,n[10],v[10]; for (i = 0;i < 10;i++){ n[i] = rand() % 100; } for (i = 0;i < 10; i++){ v[i] = n[i]; } for (i = 0;i < 10;i++){ printf("n[%d]:%d\n",i,n[i]); print("v[%d]:%d\n",i,v[i]); } return 0; } ``` #### 函式間傳遞陣列 * 在陣列傳遞間,傳送到函式的陣列並不會為複製者,而是使用原本陣列(若是整數或是浮點數則是複製一份於新的函式使用。) * 函式的回傳值不可為陣列,取代而知當陣列作為引數傳入,即可直接改變陣列(需要注意此點)。 * C語言導入指標運算來做陣列的處理。 * 作為引入時,可以不給予大小(在作為傳入的時候是否有寫陣列大小無差異)。 ##### 原理 &emsp;於函式間傳遞陣列時,複製的不是陣列的全部資訊,而是複製陣列的起始位置,也就是傳入起始的(address),之後藉由搭配每個資料型態的大小,去計算每個元素的起始位置,得到該位置的元素,因此於宣告時,不管函式宣告的陣列大小為何都不影響,因為單純傳入的是起始位置,其餘都是藉由計算得到,同時於函數中更改陣列,會更改到原陣列原因也是於此,改變這個動作是直接操作address,而非複製值。 #### sizeof * 資料型別占用記憶體的大小為「實作定義」,隨編譯器與設定上不同而有所差異。 * 使用sizeof運算子可以求出某個值或型別所占用的記憶體大小空間(於printf內使用%zu呈現結果) ##### 使用sizeof求陣列長度 **註解:此用法只能在沒有經過函式傳遞的情況下,在計算矩陣的位元數時,可能不如想像** ``` int main(){ int v[3] = {1,2,3}; printf("Size of int: %zu\n",sizeof(int)); printf("Size of v[0]: %zu\n",sizeof(v[0])); printf("Size of v: %zu\n",sizeof(v)); printf("Length of v: %zu\n",sizeof(v)/sizeof(v[0])); // 用總共大小除以單個,可以得知陣列長度 return 0; } ``` #### 存取陣列元素的原理 &emsp;每次執行時,陣列的開始記憶體位置會不同,但單次執行到刪去陣列前,陣列的起始記憶體位置會相同,且由於陣列的連續性,可以用開始位置搭配sizeof()計算每個陣列元素的記憶體位置。 ``` v[i]的記憶體起始位置(address) = 第一個元素的起始位置(address of first element)+i * sizeof(元素型態) ``` --- ### 全域變數 #### 特性 1. 在沒有給定初始值的時候,初始值為0。 2. 流程:初始化全域變數 -> 呼叫main() (在呼叫main前,變會初始化全域變數) #### 缺點 &emsp;全域變數的使用會使得函數間的關係不明確(因為沒有特定給哪個變數使用)。 #### 與靜態區域變數差異 * 靜態的區域變數只會有一份,且只會初始化一次,寫法如下: ``` #include <stdio.h> int count(void){ static int k = 0; //使用static指定變數為靜態變數 k++; return k; } int main(){ for (int i = 1; i <= 5; i++){ printf(" %d\n",count()); } return 0; } ``` #### 靜態全域變數 ``` static unsigned int x = 1; //此為靜態全域變數,只有在此檔案內才可使用 int main(){ return 0; } ``` ----- ### 整數溢位 #### limits.h ``` #include <stdio.h> #include <limits.h> int main(){ printf("%d\n", INT_MAX); printf("%d\n", INT_MIN); return 0; } ``` **使用INT_MAX & INT_MIN 查看範圍內的最大與最小值(for 整數)** **Unsigned 型態在溢位後會算輸入值對(UINT_MAX +1 )取餘數** --- ### 偽亂數生成 &emsp;使用線性同餘法(Linear congruential generator) * X~n+1~ = (X~n~xa+c) % m * 初始值X~0~則稱為亂數種 * 可以利用unsigned int 溢位時會求餘數的概念編寫 ``` #include <stdio.h> int main(){ unsigned int next = 1; for (int i =1;i<=5;i++){ next = next * 1103515245+12345; //此行為求餘數 int rand = (unsigned int)(next/65536) % 32768; //取兩次餘數(此方式只會取到高位,以使得隨機性更高) printf("%d\n",rand); } return 0; } ``` --- ### 字串簡介 #### 字串是字元的序列 * 字元型別(char)可儲存「一個」字元,而通常需要處理的文字是「一串」字元。 * 字串沒有特別的資料型別,(char [])。 * 透過<string.h>,提供字元陣列的函式實現對字串的操作。 * char str[] = {'H','e','l','l','o'}; * 字串是以'\0'表示結尾的字元陣列 * char str[] = {'H','e','l','l','o','\0'} * 在讀入字串前就要先決定好存放字串的緩衝大小(字元大小-1因為有\0) ### const修飾字 在const賦值之後,除了初始化的階段之外,其值無法再被賦予 * 於C語言內可看成唯讀(read-only)的屬性 --- ## 編寫規則(指標) ### 於無指標的情況下 * 在無指標的情況下,使用外部函式傳入變數時如下:<br> ``` void addone(int n){ n = n+1; } int main(){ int a = 3; addone(a);//計算方式為複製a的值進去addone,而非將a傳入,因此不會改變a的值 print("%d",a);//此時a會印出3 } ``` * 無法直接複製陣列(同理無法複製字串) * 無法動態變更陣列長度 ### 指標基本簡介 * 指標為一種**資料型別**用以儲存**記憶體位置** !!很重要是資料型別 * 可解決問題: * 呼叫函式時,直接更改引數值 * 複製陣列 * 複製字串 * 動態更改陣列長度 * 變數宣告語法: `資料型別 *變數名稱; //表示變數內儲存的是資料型別的「記憶體位置」` * 範例: ``` int count = 9; int *countAddr = &count; ``` * 以上述範例而言,可整理成下表: | 表示式 | 資料型別 | 值 | | ----- | ------ | -- | |count|int|9| |&count|`int*`|數字9之儲存位置(為int)| |countAddr|`int*`|數字9之儲存位置(為int)| * 注意,雖然指標變數儲存的是整數,但是當中儲存的資料型別與int不同,而指標變數間不能自由轉型(無隱性轉型),如下: ``` int count = 9; int a ; int *b; // type 1 compare a = count //(o) int == int b = count // (x) (int* ) != int // type 2 compare a = &count//(x) int != (int*) b = &count //(o) (int*) == (int*) ``` ### 指標的間接運算 #### 取址運算子 (`&`) 可使用取址運算子取得變數開頭的記憶體位置。 ``` int count = 9; int *countAddr = &count; ``` #### 間接運算子(`*`) 可使用間接運算子,取得開頭位於指定地址的變數 ``` int count = 9; int *countAddr = &count; result = *countAddr; ``` | 表示式 | 資料型別 | 值 | | ----- | ------ | -- | |count|int|9| |&count|`int*`|數字9之儲存位置(為int)| |countAddr|`int*`|數字9之儲存位置(為int)| |`*countAddr`|int|9| |result|int|9| #### 指標運算(基礎) ``` int count = 9; int *countAddr = &count; *countAddr = 10; => 等同於將count改為10 => 使用指標直接改變該address的位置 ``` #### 指標與函式呼叫 函式呼叫時,複製記憶體位置(同樣是複製,但是由值改為記憶體位置) ``` #include <stdio.h> void pass_pointer(int *n){//這邊的星號為告知資料型態 *n += 1; //這邊的星號為間接運算子 } int main(){ int a = 3; pass_pointer(&a);//函式所需要的變數為指標變數,因此傳入address printf("%d",a); return 0; } ``` &emsp;以上述的方式編寫函式時,由於傳入的值為指標變數(複製address),因此在函式內做變動時更改的是address內的值,由此可以直接變更到a,而不同於之前的方式,變更到的是複製的值。 ##### 簡易兩變數排序(由定義兩function,使用pointer) ``` #include <stdio.h> void swap(int *a,int *b), sort(int *a,int *b); int main(){ int a,b; printf("please input your first and second number:"); scanf(" %d%d",&a,&b); sort(&a,&b); printf("your smaller number is %d,the bigger number is %d",a,b); return 0; } void sort(int *a,int*b){ if (*a > *b){ swap(a,b);//注意這邊的傳入值,不能只傳入&a,&b,因為這兩個值存的是記憶體位置,因此需要先用間接運算子,touch到原本的值再傳送該值的記憶體(swap(&*a,&*b)),or 直接傳入a,b(因為已經是記憶體位置) }; } void swap(int *a,int *b){ int tmp = *a; *a = *b; *b = tmp; } ``` #### 何時該傳數值本身或是數值位置 * 基本原則: * 可以傳值就傳值 => 複製值比較安全,確保函式間乾淨,且用起來較方便 * 例外規則: * 作為引數的變數在呼叫函式時會變動 * 無法直接複製值(陣列、字串) * 複製成本過高(較複雜的結構) #### 指標對整數的加減運算 ``` int v[5]; // 可透過將陣列元素的位置加減一個整數,來求得其他元素的位置 &v[0] + 1 == &v[1];//從v[0]往前移動一個陣列元素後的位置(為C語言定義) &v[1] + 1 == &v[2];//從v[1]往前移動一個陣列元素後的位置 &v[2] - 1 == &v[1];//從v[2]回推移一個陣列元素後的位置 &v[0] + &v[1] // 編譯失敗(x) &v[2] - &v[1] == 1 // 從v[2] 位置到 v[1]位置距離1個元素 &v[3] - &v[0] == 3 // 從v[3] 位置到 v[0]位置距離1個元素 ``` #### 陣列與指標間的關係 !!非常特別 ``` int v[5]; //宣告一個有五個元素的陣列 int *n; //宣告定義一個int指標 n = &v[0]; // !!!陣列型別可以隱性轉型成該陣列第一個元素的記憶體位置(通常陣列無法直接複製) n = v // n = &v[0] ``` 透過指標運算存取陣列元素 ``` int v[5]; int *n = v; n == &v[0]; *n == v[0]; // *n = 0等價於v[0] = 0 n+1 == &v[1]; *(n+1) == v[1]; // *(n+1) == 0 等價於 v[1] = 0 n+2 == &v[2]; *(n+2) == v[2]; // *(n+2) == 0 等價於 V[2] = 0 ``` #### 循序存取陣列元素 ``` #include <stdio.h> int main(){ int v[5] = {1,2,3,4,5}; int *n = v; int i; for (i = 0;i<5;i++) { printf("%d\n",*(n+i)); } return 0; } ``` ``` #include <stdio.h> int main(){ int v[5] = {1,2,3,4,5}; int *n; for (n = v;n != &v[5],n++){ //在此有一個很特別的點,雖然v[5]不存在,但是當在C語言之中寫存在陣列的下一個不存在元素時,其地址會被寫於該陣列尾端+1(下一個陣列開始) printf("%d\n",*n); } return 0; } ``` #### 指標與下標運算子(`[]`) &emsp;在C語言之中,下標運算子具有下列特性: ``` v[0] = 0; // a[b] => *(a+b),v[0] => *(v+0),0[v] => *(0+v) ``` 由上述規律可得下列: ``` int v[5]; int *n = v; //這邊會記錄v[0]的記憶體位置 n[0] == *n; //n[0] => *(n+0) = > *(&v[0]+0) n[1] == *(n+1); //n[1] => *(n+1) = > *(&v[1]+0) ``` #### 使用指標在函式中傳遞陣列 ``` int maxv(int[]); int main(){ int a[3] = {3,9,7}; printf("Max: %d\n",maxv(&a[0])); return 0; } int maxv(int *v){ int max = *(v+0),i; for (i = 1;i<3;i++){ if (*(v+i) > max){ max = *(v+i); } } return max; } ``` #### 統整:指標與陣列關係 * 指標儲存某陣列元素位置時的特殊性 * 透過加減整數,可以算出陣列其他元素的記憶體位置 * 加N等於向後移動N個元素後的記憶體位置 * 減N等於向前(往回)移動N個元素後的記憶體位置 * `a[b]` 運算等同於`*(a+b)`,反之亦同 * 在陣列中從a開始往後移動b所在的陣列元素 * 當指標儲存某陣列的第一個元素記憶體後,使用起來與陣列幾乎相同 * 陣列可以隱性轉型成該陣列第一個元素的記憶體位置 #### 指標與遞增遞減運算子 ``` int main(){ int v[5]; int i; int *p ; for ( p = v; p != &v[5];i++){ *p = 0; } return 0; } ``` or 寫成: ``` int main(){ int v[5]; int *p = v; while (p!= V+5){ *p++=0; //*(p++) = 0 => ++放於後,會獲得++前的值 } return 0; } ``` #### 指標與字串 * 字串以雙引號包裝時,可直接隱性轉成指標(不須先轉乘陣列) * 複合字面常數 => 藉由強制轉型將變數轉型 printf((char[]){'t','e','s','t','\0'}); #### 字面常數特殊性 ``` char strA[] = "test"; // 會開設記憶體空間(五個)儲存現有字串 char *strB = "test"; // 字串字面常數可隱性轉型成字元指標,於此種宣告方式,strB會儲存記憶體位置,而字串會被儲存於唯讀記憶體空間 strA[0] = 'T' // (o) strB[0] = 'T' // (x) 未定義行為,因為儲存於唯讀空間 strA = "Test";//(x) 編譯失敗 strB = "Test"; // (o) ``` #### 字串字面常數與const char* 於使用字面常數儲存資料時,為了安全,會使用const字面常數儲存,在printf內傳遞資料時,傳遞的也是const資料,避免呼叫printf時產生未定義行為(害怕誤改內容),通常能放雙引號(字串字面)位置時,大多都會使用const char。 #### 指標與const Type`*`可以轉成const Type`*` ``` char strA[] = "test"; char *strB = "test"; const char *strC = "test"; strA[0] = 'T'; //(o) strB[0] = 'T'; //未定義行為 strC[0] = 'T'; //(x)編譯失敗 strA = strB; //(x) (char []) = (char * ) strA = strC; //(x) (char []) = (const char *) strB = strA; //(o) (char *) = (char []) strB = strC; //(x) (char * ) = (const char * ) strC = strA; //(o) (const char *) = (char []) strC = strB; //(o) (const char * ) = (char *) ``` #### 陣列的指標 ``` int (*q)[3] = &v; //q儲存具有三個整數的陣列v的位置 //存取 (*q)[2] =5 ``` 陣列傳遞差別 ``` void print_int(int *n){ int i; for (i=0; i<3; i++){ printf(" %d",n[i]); } printf("\n"); } void print_addres(int (*q)[3]){ int i; for (i=0;i<3,i++){ printf("%d",(*q)[i]); } printf("\n"); } int main(){ int v[3] = {1,2,3}; print_int(v); //此方式傳遞的是v陣列開頭第一個元素的位置 print_address(&v); // 此方式傳遞的是V陣列的位置 return 0; } ``` 使用sizeof的情況下,如果傳入的是陣列的指標,則可以得到陣列大小:<br> ``` void print(int (*p)[3]{ //(但此情況傳入的陣列大小其實是固定的,不能傳入變動值) int i; for (i = 0;i<sizeof(*q)/sizeof((*q)[0]); i++){ printf("%d",(*q)[i]) } printf("\n"); } ``` #### 指標間轉型的限制 &emsp;絕大部分的情況下,指向不同型別的指標間是不能隱性轉型的。 ##### 合法的隱性轉型 1. 同類型別內的互轉 1. float = double 2. int = char 2. 整數與浮點數間互轉 1. int = double (無條件捨去) 2. double = int 3. 陣列可以隱性轉型成指向第一個元素的指標 1. int * = int [3] 4. Type * 可以隱性轉型成 const Type * 1. const int * = int * 5. void * 與其他型別的指標"可能"可以互轉 ##### 指標與整數間的轉型 **電腦如何儲存指標?** * 指標是一個儲存記憶體位置的資料型別 * 電腦中記憶體位置想像成一段連續空間,以位元組為單位,替每個位元組給定特定編號或表示法(每個電腦的表示法並沒有規定要相同(C語言的case下)) &emsp;void 可視為一個泛用型的記憶體空間,因此可自由轉型,在指標與整數型別中,無法確定記憶體位置所需的位元數,因此可能會發生整數的資料型別位元組不夠的情況。而是否可直接將整數轉型為指標,則需要依照強制轉型的實作定義,未必可成功。<br> ##### 指標間的強制轉型 **記憶體對齊** &emsp;在記憶體的對齊上為實作定義,可能為對齊到偶數、或是以固定公差為對齊基準,可有效增加記憶體的讀取效率。以此類推,若是將char對齊為1、int對齊為4,則char的記憶體位置未必能儲存int,反之int的記憶體位置必定能儲存char,因此在轉型時,需要注意到對齊的大小,才能確保技藝體能互轉。 ##### 改變陣列大小 * 不可行 -> 無法以現有的記憶體空間做擴充(因為陣列需要連續擺放,擴充的記憶體位置可能已經有儲存資料) * 可行 -> 創造新陣列 ->使用指標實現 ##### 動態記憶體(包含在<stdlib.h>內) * 使用malloc函式動態配置記憶體 * void* malloc(size_t size); * size為非負整數型態(size_t),表示要配置的記憶體空間大小(可由sizeof算出) * 回傳值void* 表示可隱性轉型成其他資料型態的指標 * 範例: int* larger = malloc(sizeof(int) * (length+1));//創建一個大小為(length+1)個int大小的空間,larger指向的malloc空間不會被自動釋放 * 使用free函式釋放動態配置的記憶體 * void free(void* ptr); * 使用realloc函式複製記憶體內容 * void* realloc(void* ptr,size_t size); * ptr 是要重新配置的記憶體空間開頭位置 * size是重新配置後的記憶體空間大小 * 回傳值為重新配置後的記憶體空間開頭位置 * 在空間足夠的情況下,有可能使用擴充的方式執行,可能會較有效率。 ##### 在函式中傳遞二維陣列 &emsp;編寫邏輯:<br>使用p儲存A陣列的內部陣列開頭位置,並將此值傳入至新的函式中,以此傳入內部陣列的開頭位置,而後面呼叫回全時,則會先進入p,也就是A陣列的相對應元素開頭位置,以此讀取內部值。 ``` #include <stdio.h> #include <stdlib.h> int print_two_dimensional(int **p,int height,int wide){ for (int i = 0;i<height;i++){ for (int j = 0; j<wide;j++){ printf("%d",p[i][j]); } printf("\n"); } }; int main(){ int A[2][3] = {{1,2,3},{4,5,6}}; int *p[2] = {A[0],A[1]}; //printf("%d",*p[0]); print_two_dimensional(p,2,3); return 0; } ``` ###### 儲存多個字串 1. 使用二維陣列儲存多個字串 ``` char strA[3][4] = {"How","are","you"}; // 三個字卻使用四格空間,是因為字串結尾需要'\0'作為結束點。 ``` 2. 只用指標陣列儲存多個字串 ``` const char *strB[3] = {"How","are","you"};//strB會儲存的是後面三個字串的開頭位置,為不可變動 ``` 兩者差異:<br> ``` strA[2][0] = 'Y'; // (o) strB[2][0] = 'Y'; // (x) ->因為資料型態為const,為不可變動型態 strB[2] = 'What'; // (o) ->因為是更改整個陣列 strA[2] = 'What'; //(x) - >陣列不可直接被複製 ``` 3. 藉由儲存開始位置 ``` char *strC[3] = {strA[0],strA[1],strA[2]}; strC[2][0] = 'Y'; // (o)相等於修改strA char strD[5] = "What" // (o) ->讓字串由const char轉為char strB[0] = strC;//(o) strB[0][0] = 'w'; //(o) ``` ##### 輸入多個字串 **ver 1 ->在輸入大於等於三個字串時會出現亂碼(還沒找到原因)** ``` #include <stdio.h> #include <stdlib.h> #include <string.h> /* 讓使用者輸入多字串,印出使用者輸入的字串,當使用者輸入END表示結束。 */ int main(){ int size = 0; //紀錄共多少字元 char *save = NULL; //設定save用來儲存多個字串 (後續會用realloc使其變浮動) char input[50]; //設定input字串大小 char **str = NULL; //雙重指標,用來儲存每個字串開頭的位置(後續會用realloc使其變浮動) int len = 0; //紀錄總共儲存多少字串 while (1){ printf("please input your word:"); scanf("%s",input); if (strcmp(input,"end") == 0) break; int size_loop = size; size += strlen(input)+1; save = realloc(save,sizeof(char)*(size)); str = realloc(str,sizeof(char)*(len +1)); str[len] = &save[size_loop]; strcpy(str[len],input); len++; } printf("-------\n"); int i; for (i =0;i<len;i++){ printf("%s ",str[i]); } printf("\n(%d,%d)\n",len,size); return 0; } ``` **ver 2 可正常運作** ``` #include <stdio.h> #include <stdlib.h> #include <string.h> /* 讓使用者輸入多字串,印出使用者輸入的字串,當使用者輸入END表示結束。 */ int main(){ int size = 0; char *save = NULL; char input[50]; char **str = NULL; int len = 0; while (1){ printf("please input your word:"); scanf("%s",input); if (strcmp(input,"end") == 0) break; size += strlen(input) + 1; str = realloc(str,sizeof(char)*(len +1)); str[len] = calloc(strlen(input) + 1, sizeof(char)); strcpy(str[len],input); len++; } printf("-------\n"); int i; for (i =0;i<len;i++){ printf("%s ",str[i]); } printf("\n(%d,%d)\n",len,size); return 0; } ``` ---- <a href ="https://hackmd.io/61b3sW1zR2q-rJboeJr0OA?both">C/C++ 學習筆記(PartI)</a>