葡萄牙文口譯價位C/C++ 語言新手十三誡(The Thirteen Commandments for Newbie C/C++ Programmers) by Khoguan Phuann 請注意: (1) 本篇旨在提示新手,避免初學常犯 翻譯毛病(其實熟手在行也常犯:-Q) 翻譯社 但不能庖代完全的進修,請本身好好研讀一兩本 C 說話的好書, 並多多實作演習 翻譯社 (2) 強烈建議新手先看過此文再提問,你的問題極可能此文已經提出並 解答了。 (3) 以下所舉 翻譯毛病例子如果在你 翻譯電腦上印出和准確例子溝通 翻譯成績, 那只是不足為恃的一時僥倖 翻譯社 (4) 不守十三誡者,輕則履行結果的輸出數據毛病,或是程式當掉,重則 引爆核彈、撲滅地球(若是你的 C 程式是用來控制核彈發射器 翻譯話)。 ============================================================= 目錄: (頁碼/行號) 2/24 01. 不行以使用還沒有給予恰當初值的變數 3/46 02. 不克不及存取跨越陣列既定範圍的空間 5/90 03. 不成以提取不知指向何方 翻譯指標 7/134 04. 不要試圖用 char* 去更改一個"字串常數" 12/244 05. 不克不及在函式中回傳一個指向區域性主動變數的指標 16/332 06. 不行以只做 malloc() 翻譯公司 而不做響應的 free() 19/398 07. 在數值運算、賦值或對照中不成以隨意混用分歧型別的數值 21/442 08. ++i/i++/--i/i--/f(&i)哪一個先履行跟挨次有關 24/508 09. 慎用Macro 27/574 10. 不要在 stack 設置過大的變數以免堆疊溢位(stack overflow) 32/684 11. 利用浮點數精確度造成 翻譯誤差問題 35/750 12. 不要猜想二維陣列可以用 pointer to pointer 來傳遞 36/772 13. 函式內 new 出來 翻譯空間記得要讓主程式的指標接住 40/860 直接輸入數字可跳至該頁碼 或用:指令輸入行號 01. 你不成以使用還沒有賜與恰當初值的變數 毛病例子: int accumulate(int max) /* 從 1 累加到 max,傳回結果 */ { int sum; /* 未賜與初值的區域變數,其內容值是垃圾 */ for (int num = 1; num <= max; num++) { sum += num; } return sum; } 准確例子: int accumulate(int max) { int sum = 0; /* 正確的賦予適當 翻譯初值 */ for (int num = 1; num <= max; num++) { sum += num; } return sum; } 備註: 按照 C Standard,具有靜態貯存期(static storage duration)的變數, 例如 全域變數(global variable)或帶有 static 修飾符者等, 如果沒有顯式初始化的話,按照分歧的資料型態予以進行以下初始化: 若變數為算術型別 (int , double , ...) 時,初始化為零或正零。 若變數為指標型別 (int* 翻譯公司 double*, ...) 時,初始化為 null 指標 翻譯社 若變數為複合型別 (struct, double _Complex 翻譯公司 ...) 時,遞迴初始化所有成員。 若變數為聯合型別 (union) 時,只有其中的第一個成員會被遞迴初始化 翻譯社 (以上感激Hazukashiine板友斧正) (可是有些MCU 編譯器可能不睬會這個劃定,所以照樣請養成設定初值 翻譯好習慣) 增補資料: - 精髓區z->5->1->1->1 - C11 Standard 5.1.2, 6.2.4, 6.7.9 02. 你不可以存取跨越陣列既定規模的空間 錯誤例子: int str[5]; for (int i = 0 ; i <= 5 ; i++) str[i] = i; 准確例子: int str[5]; for (int i = 0; i < 5; i++) str[i] = i; 申明:宣佈陣列時,所給的陣列元素個數值若是是 N, 那麼我們在後面 透過 [索引值] 存取其元素時,所能使用 翻譯索引值局限是從 0 到 N-1 C/C++ 為了履行效率,其實不會主動檢查陣列索引值是不是跨越陣列鴻溝, 我們要本身來確保不會越界。一旦越界,操作 翻譯不再是正當 翻譯空間, 將導致沒法預期的後果 翻譯社 備註: C++11以後可以用Range-based for loop提取array、 vector(或是其他有供應准確.begin()和.end()的class)。-> 翻譯社|,-> 翻譯公司|的-> 翻譯內 翻譯元素 可以確保提取的元素必然落在准確範圍內。 例: // vector std::vector<int> v = {0, 1 翻譯公司 2 翻譯公司 3, 4, 5}; for(const int &i : v) // access by const reference std::cout << i << ' '; std::cout << ' '; // array int a[] = {0, 1, 2, 3, 4, 5}; for(int n: a) // the initializer may be an array std::cout << n << ' '; std::cout << ' '; 增補資料: http://en.cppreference.com/w/cpp/language/range-for 03. 你不成以提取(dereference)不知指向何方的指標(包括 null 指標)。 毛病例子: char *pc1; /* 未給予初值,不知指向何方 */ char *pc2 = NULL; /* pc2 肇端化為 null pointer */ *pc1 = 'a'; /* 將 'a' 寫到不知何方,錯誤 */ *pc2 = 'b'; /* 將 'b' 寫到「位址0」,毛病 */ 准確例子: char c; /* c 的內容還沒有起始化 */ char *pc1 = &c; /* pc1 指向字元變數 c */ *pc1 = 'a'; /* c 的內容變為 'a' */ /* 動態分派 10 個 char(其值不決) 翻譯公司並將第一個char 翻譯位址賦值給 pc2 */ char *pc2 = (char *) malloc(10); pc2[0] = 'b'; /* 動態配置來 翻譯第 0 個字元,內容變為 'b' free(pc2); 申明:指標變數必需先指向某個可以合法操作的空間,才能進行操作 翻譯社 ( 利用者記得要查抄 malloc 回傳是不是為 NULL, 礙於篇幅本文假定利用上皆合法,也有准確清償記憶體 ) 毛病例子: char *name; /* name 還沒有指向有用的空間 */ printf("Your name 翻譯公司 please: "); fgets(name,20,stdin); /* 您肯定要寫入的那塊空間正當嗎??? */ printf("Hello 翻譯公司 %s ", name); 准確例子: /* 如果編譯期就能決意字串的最大空間,那就不要宣佈成 char* 改用 char[] */ char name[21]; /* 可讀入字串最長 20 個字元,保存一格空間放 '\0' */ printf("Your name, please: "); fgets(name,20 翻譯公司stdin); printf("Hello, %s " 翻譯公司 name); 准確例子(2): 若是在執行期間才能決議字串的最大空間,C供應兩種作法: a. 利用 malloc() 函式來動態分派空間,用malloc宣告的陣列會被存在heap 須注重:若是宣佈較大陣列,要確認malloc的回傳值是不是為NULL size_t length; printf("請輸入字串 翻譯最大長度(含null字元): "); scanf("%u", &length); name = (char *)malloc(length); if (name) { // name != NULL printf("您輸入 翻譯是 %u ", length); } else { // name == NULL puts("輸入值太大或系統已無足夠空間"); } /* 最跋文得 free() 掉 malloc() 所分配 翻譯空間 */ free(name); name = NULL; //(註1) b. C99最先可以使用variable-length array (VLA) 須留意: - 因為VLA是被寄存在stack裡,利用前要確認array size不克不及太大 - 不是每一個compiler都支援VLA(註2) - C++ Standard不支援(固然有些compiler支援) float read_and_process(int n) { float vals[n]; for (int i = 0; i < n; i++) vals[i] = read_val(); return process(vals, n); } 准確例子(3): C++的利用者也有兩種作法: a. std::vector (不管你的陣列巨細會不會變都可用) std::vector<int> v1; v1.resize(10); // 從新設定vector size b. C++11今後,若是肯定陣列巨細不會變,可以用std::array 須注意:一般利用下(存在stack)一樣要確認array size不能太大 std::array<int, 5> a = { 1, 2 翻譯公司 3 }; // a[0]~a[2] = 1 翻譯公司2 翻譯公司3; a[3]以後為0; a[a.size() - 1] = 5; // a[4] = 0; 備註: 註1. C++的使用者,C++03或之前請用0取代NULL,C++11最先請改用nullptr 註2. gcc和clang支援VLA,Visual C++不支援 增補資料: http://www.cplusplus.com/reference/vector/vector/resize/ 04. 你不成以試圖用 char* 去更改一個"字串常數" 試圖去更改字串常數(string literal) 翻譯成績會是undefined behavior。 毛病例子: char* pc = "john"; /* pc 目下當今指著一個字串常數 */ *pc = 'J'; /* undefined behaviour,效果沒法展望*/ pc = "jane"; /* 合法,pc指到在其余位址的另外一個字串常數*/ /* 但是"john"這個字串照樣存在本來的處所不會消逝*/ 因為char* pc = "john"這個動作會新增一個內含元素為"john\0"的static char[5], 然後pc會指向這個static char 翻譯位址(平常是唯讀) 翻譯社 若是試圖存取這個static char[],Standard並沒有定義成效為何。 pc = "jane" 這個動作會把 pc 指到另外一個沒在用的位址然後新增一個 內含元素為"jane\0" 翻譯static char[5]。 可是之前那個字串 "john " 仍是留在原地沒有消逝。 通常編譯器的作法是把字串常數放在一塊read only(.rdata)的區域內, 此區域大小是有限的,所以如果你反複把pc指給分歧的字串常數, 是有可能會出問題的。 正確例子: char pc[] = "john"; /* pc 現在是個正當的陣列,裡面住著字串 john */ /* 也就是 pc[0]='j', pc[1]='o', pc[2]='h', pc[3]='n', pc[4]='\0' */ *pc = 'J'; pc[2] = 'H'; 申明:字串常數 翻譯內容應該如果"唯讀"的。您有利用權,但是沒有更改的權利。 若您希望使用可以更改的字串,那您應當將其放在正當空間 錯誤例子: char *s1 = "Hello 翻譯公司 "; char *s2 = "world!"; /* strcat() 不會另行配置空間,只會將資料附加到 s1 所指唯讀字串 翻譯後面, 造成寫入到程式無權碰觸的記憶體空間 */ strcat(s1 翻譯公司 s2); 准確例子(2): /* s1 宣佈成陣列,並保留足夠空間寄存後續要附加的內容 */ char s1[20] = "Hello 翻譯公司 "; char *s2 = "world!"; /* 因為 strcat() 的返回值等於第一個參數值,所以 s3 就不需要了 */ strcat(s1, s2); C++對於字串常數的嚴厲界說為const char* 或 const char[]。 但是由於要相容C,char* 也是許可 翻譯寫法(不建議就是)。 不過,在C++試圖更改字串常數(要先const_cast)一樣是undefined behavior。 const char* pc = "Hello"; char* p = const_cast<char*>(pc); p[0] = 'M'; // undefined behaviour 備註: 由於不加const輕易造成攪渾, 建議不論是C照樣C++一概用 const char* 定義字串常數。 補充資料: http://en.cppreference.com/w/c/language/string_literal http://en.cppreference.com/w/cpp/language/string_literal 字串函數相幹:#1IOXeMHX undefined behavior : 精華區 z -> 3 -> 3 -> 23 05. 你不成以在函式中回傳一個指向區域性主動變數 翻譯指標 翻譯社不然,會獲得垃圾值 [感激 gocpp 網友供應程式例子] 毛病例子: char *getstr(char *name) { char buf[30] = "hello, "; /*將字串常數"hello 翻譯公司 " 翻譯內容複製到buf陣列*/ strcat(buf, name); return buf; } 說明:區域性自動變數,將會在分開該區域時(本例中就是從getstr函式返回時) 被消滅,是以呼喚端獲得的指標所指的字串內容就失效了。 准確例子: void getstr(char buf[], int buflen 翻譯公司 char const *name) { char const s[] = "hello, "; strcpy(buf, s); strcat(buf 翻譯公司 name); } 准確例子: int* foo() { int* pInteger = (int*) malloc( 10*sizeof(int) ); return pInteger; } int main() { int* pFromfoo = foo(); } 申明:上例固然回傳了函式中的指標,但由於指標內容所指的位址並非區域變數, 而是用動態的方式抓取而得,換句話說這塊空間是長在 heap 而非 stack, 又因 heap 空間並不會主動回收,因此這塊空間在離開函式後,仍然有用 (但是這個例子可能會因為 programmer 翻譯忽視,忘記 free 而造成 memory leak) [針對字串操作,C++提供了更利便安全更直觀的 string class, 能用就盡量用] 正確例子: #include <string> /* 並不是 #include <cstring> */ using std::string; string getstr(string const &name) { return string("hello 翻譯公司 ") += name; } 06. [C]你不成以只做 malloc(), 而不做響應的 free(). 不然會造成記憶體漏失 但若不是用 malloc() 所得到的記憶體,則不可以 free()。已 free()了 所指記憶體 翻譯指標,在它指向另外一塊有效的動態分派得來的空間之前,不行 以再被 free(),也不可以提取(dereference)這個指標。 小技巧: 可在 free 以後將指標指到 NULL,free不會對空指標感化 翻譯社 例: int *p = malloc(sizeof(int)); free(p); p = NULL; free(p); // free不會對空指標有感化 [C++] 你不成以只做 new, 而不做響應的 delete (除unique_ptr之外) 註:new 與 delete 對應,new[] 與 delete[] 對應, 不成與malloc/free混用(成果不可猜測) 切記,做了幾回 new,就必需做幾回 delete 小技巧: 可在 delete 之後將指標指到0或nullptr(C++11開始), 由於 delete 自己會先做查抄,是以可以避免掉多次 delete 的錯誤 正確例子: int *ptr = new int(99); delete ptr; ptr = nullptr; delete ptr; /* delete 只會處置指向非 NULL 的指標 */ 備註: C++11後新增智能指標(smart pointer): unique_ptr 當unique_ptr所指物件消逝時,會主動釋放其記憶體,不需要delete。 例: #include <memory> // 含unique_ptr的標頭檔 std::unique_ptr<int> p1(new int(5)); 增補資料: http://en.cppreference.com/w/cpp/memory/unique_ptr 07. 你不成以在數值運算、賦值或比力中隨便混用分歧型別 翻譯數值,而不謹慎考 慮數值型別轉換可能帶來 翻譯「不測欣喜」(錯愕)。必需隨時留意數值運算 的成績,其局限是不是會超出變數的型別 毛病例子: unsigned int sum = 2000000000 + 2000000000; /* 超出 int 寄存局限 */ unsigned int sum = (unsigned int) (2000000000 + 2000000000); double f = 10 / 3; 准確例子: /* 悉數都用 unsigned int, 留意數字後面的 u, 大寫 U 同樣成 */ unsigned int sum = 2000000000u + 2000000000u; /* 或是用顯式的轉型 */ unsigned int sum = (unsigned int) 2000000000 + 2000000000; double f = 10.0 / 3.0; 毛病例子: unsigned int a = 0; int b[10]; for(int i = 9 ; i >= a ; i--) { b[i] = 0; } 申明:由於 int 與 unsigned 共同運算的時辰,會轉換 int 為 unsigned, 因此迴圈條件永久滿足,與預期行為不符 毛病例子: (感激 sekya 網友供應) unsigned char a = 0x80; /* no problem */ char b = 0x80; /* implementation-defined result */ if( b == 0x80 ) { /* 紛歧定恒真 */ printf( "b ok " ); } 說明:說話並未劃定 char 生成為 unsigned 或 signed,是以將 0x80 放入 char 型態 翻譯變數,將會視各家編譯器分歧作法而有不同後果 錯誤例子(以下假設為在32bit機械上執行): #include <math.h> long a = -2147483648 ; // 2147483648 = 2 的 31 次方 while (labs(a)>0){ // labs(-2147483648)<0 有可能發生 ++a; } 申明:如果你去看C99/C11 Standard,你會發現long 變數的最大/最小值為(被define在limits.h) LONG_MIN -2147483647 // compiler實作時最小值不可大於 -(2147483648-1) LONG_MAX 2147483647 // compiler實作時最小值不行小於 (2147483648-1) 不過由於32bit能顯示 翻譯範圍就是2**32種,所以一般16/32bit功課系統會把 LONG_MIN多減去1,也就是int 的顯示規模為(-LONG_MAX - 1) ~ LONG_MAX。 (64bit的作業系統long多為8 bytes,可是照舊契合Standard要求的最小局限) 當程式跑到labs(-2147483648)>0時,由於2147483648大於LONG_MAX, Standard告訴我們,當labs的結果沒法被long有限的範圍表示, 編譯器會怎麼幹就看他高興(undefined behavior)。 (不只long,其他如int、long long等以此類推) 彌補資料: - C11 Standard 5.2.4.2.1, 7.22.6.1 - https://www.fefe.de/intof.html 08. ++i/i++/--i/i--/f(&i)哪個先履行跟挨次有關 ++i/i++ 和--i/i-- 翻譯問題幾近每個月城市出現,所以迥殊強調。 當一段程式碼中,某個變數的值用某種體例被改變一次以上, 例如 ++x/--x/x++/x--/function(&x)(能改變x 翻譯函式) - 如果Standard沒有特別去界說某段敘述中哪一個部分必需被先履行, 那結果會是undefined behavior(效果未知)。 - 假如Standard有迥殊去界說執行遞次,那了局就憑據執行遞次決議。 C/C++均准確的例子: if (--a || ++a) {} // ||左側先較量爭論,若是左邊為1右邊就不會算 if (++i && f(&i)) {} // &&左側先計算,若是左側為0右邊就不會算 a = (*p++) ? (*p++) : 0 ; // 問號左側先計較 int j = (++i 翻譯公司 i++); // 這裡 翻譯逗號為運算子,表示依序較量爭論 C/C++均毛病的例子: int j = ++i + i++; // undefined behavior,Standard沒界說+號哪邊先執行 x = x++; // undefined behavior 翻譯公司 Standard沒界說=號哪邊先履行 printf( "%d %d %d", I++ 翻譯公司 f(&I), I++ ); // undefined behavior, 原因同上 foo(i++, i++); // undefined behavior,這裡的逗號是用來分隔引入參數的 // 分隔符(separator)而非運算子,Standard沒界說哪邊先執行 在C與C++03毛病然則在C++11入手下手(但不包括C)准確的例子: C++11中,++i/--i為左值(lvalue),i++/i--為右值(rvalue)。 左值可以被assign value給它,右值則不可 翻譯社 而在C中,++i/--i/i++/i--都是右值。 所以以下的code在C++會正確,C則否 翻譯社 ++++++++++phew ; // C++11會把它注釋為++(++(++(++(++phew)))); i = v[++i]; // ++i會先完成 i = ++i + 1; // ++i會先完成 在C++17開始(但不包孕C)才准確的例子: cout << i << i++; // 先左後右 a[i] = i++; // i++先做 a[x++] = --x; // 先處理--x,再處置懲罰a[x++] (loveflames增補) 增補資料 - Undefined behavior and sequence points http://stackoverflow.com/questions/4176328/undefined-behavior-and- sequence-points) - C11 Standard 6.5.13-17,Annex C - Sequence poit https://en.wikipedia.org/wiki/Sequence_point - Order of evaluation http://en.cppreference.com/w/cpp/language/eval_order 09. 慎用macro(#define) Macro是個像鐵鎚一樣好用又危險的東西: 用得好可以釘釘子,用欠好可以把釘子打彎、敲到你手指或被抓去吃槍彈。 因為macro 界說出 翻譯「偽函式」有以下缺點: (1) debug會變得複雜 翻譯社 (2) 沒法遞迴呼喚。 (3) 無法用 & 加在 macro name 之前,獲得函式位址。 (4) 沒有namespace。 (5) 可能會導致新鮮的side effect或其他沒法預測的問題。 所以,利用macro前,請先確認以上 翻譯缺點是不是會影響你的程式運行。 替換方案:enum(界說整數),const T(定義常數),inline function(界說函式) C++ 翻譯template(界說可用分歧type參數的函式), 或C++11入手下手的匿名函式(Lambda function)與constexpr T(編譯期常數) 以下就針對macro的瑕玷做說明: (1) debug會變得複雜 翻譯社 編譯器不克不及對macro自己做語法檢查,只能搜檢預處置懲罰(preprocess)後的後果。 (2) 沒法遞迴呼喚 翻譯社 憑據C standard 6.10.3.4, 假如某macro的界說裡裏面含有跟此macro名稱一樣 翻譯的字串, 該字串將不會被預處理。 所以: #define pr(n) ((n==1)? 1 : pr(n-1)) cout<< pr(5) <<endl; 預處置過後會釀成: cout<< ((5==1)? 1 : pr(5 -1)) <<endl; // pr沒有定義,編譯會犯錯 (3) 沒法用 & 加在 macro name 之前,取得函式位址 翻譯社 因為他不是函式,所以你也弗成以把函式指標套用在macro上。 (4) 沒有namespace 翻譯社 毛病例子: #define begin() x = 0 for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) // begin是std的保留字 std::cout << ' ' << *it; 改良方式:macro名稱一概用大寫,如BEGIN() (5) 可能會致使希奇 翻譯side effect或其他無法猜測的問題。 毛病例子: #include <stdio.h> #define SQUARE(x) (x * x) int main() { printf("%d ", SQUARE(10-5)); // 預處置懲罰後釀成SQUARE(10-5*10-5) return 0; } 准確例子:在 Macro 界說中, 務必為它的參數個體加上括號 #include <stdio.h> #define SQUARE(x) ((x) * (x)) int main() { printf("%d " 翻譯公司 SQUARE(10-5)); return 0; } 不外遇到以下有side effect的例子就算加了括號也沒用。 毛病例子: (感激 yaca 網友供應) #define MACRO(x) (((x) * (x)) - ((x) * (x))) int main() { int x = 3; printf("%d ", MACRO(++x)); // 有side effect return 0; } 彌補資料: - http://stackoverflow.com/questions/14041453/why-are-preprocessor- macros-evil-and-what-are-the-alternatives - http://stackoverflow.com/questions/12447557/can-we-have-recursive-macros - C11 Standard 6.10.3.4 - http://en.cppreference.com/w/cpp/language/lambda 10. 不要在 stack 設置過大 翻譯變數以避免堆疊溢位(stack overflow) 由於編譯器會自行決意 stack 翻譯上限,某些預設是數 KB 或數十KB,當變數所需 翻譯空 間過大時,很輕易造成 stack overflow,程式亦隨之當掉(segmentation fault) 翻譯社 可能造成堆疊溢位的原因包孕遞迴太屢次(多為程式設計缺點), 或是在 stack 設置過大的變數。 毛病例子: int array[10000000]; // 在stack宣佈過大陣列 std::array<int 翻譯公司 10000000> myarray; //在stack宣佈過大std::array 准確例子: C: int *array = (int*) malloc( 10000000*sizeof(int) ); C++: std::vector<int> v; v.resize(10000000); 申明:建議將利用空間較大的變數用malloc/new配置在 heap 上,由於此時 stack 上只需設置裝備擺設一個 int* 翻譯空間指到在heap 翻譯該變數,可避免 stack overflow 翻譯社 利用 heap 時,雖然全部 process 可用的空間是有限的,但採用動態抓取 的體例,new 無法設置裝備擺設時會丟出 std::bad_alloc 破例,malloc 沒法設置裝備擺設 時會回傳 null(註2),不會影響到正常使用下 翻譯程式功能 備註: 註1. 利用 heap 時,全部 process 可用的空間一樣是有限的,若是需要頻仍地 malloc / free 或 new / delete 較大的空間,需留意避免造成記憶體破裂 (memory fragmentation)。 註2. 由於Linux利用overcommit機制經管記憶體,malloc即使在記憶體不足時 依然會回傳非NULL的address,一樣景象在Windows/Mac OS則會回傳NULL (感激 LiloHuang 彌補) 補充資料: - https://zh.wikipedia.org/wiki/%E5%A0%86%E7%96%8A%E6%BA%A2%E4%BD%8D - http://stackoverflow.com/questions/3770457/what-is-memory-fragmentation - http://library.softwareverify.com/memory-fragmentation-your-worst-nightmare/ overcommit跟malloc: - http://goo.gl/V9krbB - http://goo.gl/5tCLQc 11. 使用浮點數萬萬要注意切確度所造成的誤差問題 根據 IEEE 754 翻譯規範,又電腦中是用有限的二進位貯存數字,是以常有可 能因為切確度而造成誤差,例如加減乘除,等號大小判定,分配律等數學上 常用到的操作,很有可能是以而出錯(不成立) 更詳細的申明可以參考精華區 z-8-11 或參考冼鏡光老師所頒發 翻譯一文 "利用浮點數最最根基的觀念" http://blog.dcview.com/article.php?a=VmhQNVY%2BCzo%3D 12. 不要猜想二維陣列可以用 pointer to pointer 來傳遞 (感激 loveme00835 legnaleurc 版友 翻譯幫忙) 首先必需有個觀念,C 語言中陣列是無法直接拿來傳遞的! 不外這時候會有人跳出來辯駁: void pass1DArray( int array[] ); int a[10]; pass1DArray( a ); /* 可以正當編譯,並且履行結果准確!! */ 事實上,編譯器會這麼對待 void pass1DArray( int *array ); int a[10]; pass1DArray( &a[0] ); 我們可以趁便看出來,array 變數自己可以 decay 成記憶體開端的位置 是以我們可以 int *p = a; 這種體例,拿指標去接陣列。 也因為上述 翻譯例子,許多人以為那二維陣列是否是也能夠改成 int ** 毛病例子: void pass2DArray( int **array ); int a[5][10]; pass2DArray( a ); /* 這時候編譯器就會報錯啦 */ /* expected ‘int **’ but argument is of type ‘int (*)[10]’*/ 在一維陣列中,指標的移動操作,會恰好覆蓋到陣列的規模 例如,宣告了一個 a[10],那我可以把 a 當做指標來操作 *a 至 *(a+9) 是以我們可以得到一個概念,在操作的時辰,可以 decay 成指標來利用 也就是我可以把一個陣列當做一個指標來利用 (again, 陣列!=指標) 可是多維陣列中,沒法如斯利用,事實上這也很直觀,試圖拿一個 pointer to pointer to int 來操作一個 int 二維陣列,這是不公道 翻譯! 儘管我們沒法將二維陣列直接 decay 成兩個指標,可是我們可以換個角度想, 二維陣列可以看成 "外層大的一維陣列,每維內層各又包括著一維陣列" 若是想通了這一點,我們可以仿制之前 翻譯法則, 把外層大的一維陣列 decay 成指標,該指標指向內層 翻譯一維陣列 void pass2DArray( int (*array) [10] ); // array 是個指標,指向 int [10] int a[5][10]; pass2DArray( a ); 這時候候就很好理解了,函數 pass2DArray 內的 array[0] 會代表什麼呢? 謎底是它代表著 a[0] 外層的那一維陣列,裡面包括著內層 [0]~[9] 也是以 array[0][2] 就會對應到 a[0][2],array[4][9] 對應到 a[4][9] 結論就是,只有最外層的那一維陣列可以 decay 成指標,其他維陣列都要 明白的指出陣列巨細,如許多維陣列的傳遞就不會有問題了 也因為方才的例子,我們可以清晰的知道在傳遞陣列時,現實行為是在傳遞 指標,也是以假如我們想用 sizeof 來求得陣列元素個數,那是不可行的 毛病例子: void print1DArraySize( int* arr ) { printf("%u", sizeof(arr)/sizeof(arr[0])); /* sizeof(arr) 只是 */ } /* 一個指標 翻譯巨細 */ 受此限制,我們必需手動傳入巨細 void print1DArraySize( int* arr, size_t arrSize ); C++ 提供 reference 的機制,使得我們不需再這麼麻煩, 可以直接傳遞陣列的 reference 給函數,巨細也能夠直接求出 准確例子: void print1DArraySize( int (&array)[10] ) { // 傳遞 reference cout << sizeof(array) / sizeof(int); // 准確取得陣列元素個數 } 13. 函式內 new 出來 翻譯空間記得要讓主程式的指標接住 對指標不熟悉的利用者會以為以下 翻譯程式碼是相符預期 翻譯 void newArray(int* local, int size) { local = (int*) malloc( size * sizeof(int) ); } int main() { int* ptr; newArray(ptr, 10); } 接著就會找了很久 翻譯 bug,最後依然搞不懂為什麼 ptr 沒有指向方才拿到 翻譯正當空間 讓我們再回首一次,而且用圖默示 (感激Hazukashiine板友供給圖解) ┌────┐ ┌────┐ ┌────┐ ┌────┐ Heap │ │ │ │ │ 新設置裝備擺設 │ │ 已泄露 │ │ │ │ │ │ 的空間 <─┐ │ 的空間 │ │ │ │ │ │(allocd)│ │ │(leaked)│ │ │ │ │ ├────┤ │ ├────┤ │ │ │ │ │ : │ │ │ │ │ │ │ │ │ : │ │ │ : │ │ │ ├────┤ ├────┤ │ │ : │ │ │ │ local ├─┐ │ local ├─┘ │ │ ├────┤ ├────┤ │ ├────┤ ├────┤ Stack │ ptr ├─┐ │ ptr ├─┤ │ ptr ├─┐ │ ptr ├─┐ └────┘ ╧ └────┘ ╧ └────┘ ╧ └────┘ ╧ 未初始化 函式呼喚 配置空間 函式返回 int *ptr; local = ptr; local = malloc(); 用圖看應當一切就都明白了,我也不需冗言注釋 或許有人會想問,指標不是傳址嗎? 正確來說,指標也是傳值,只不過該值是一個位址 (ex: 0xfefefefe) local 接到了 ptr 指向的誰人位置,接著函式內 local 要到了新的位置 然則 ptr 指向的位置照舊沒變 翻譯,因此離開函式後就彷佛事什麼都沒發生 ( 嚴厲說起來還産生了 memory leak ) 以下是一種解決法子 int* createNewArray(int size) { return (int*) malloc( size * sizeof(int) ); } int main() { int* ptr; ptr = createNewArray(10); } 改成如許亦可 ( 為何用 int** 就可以?想想他會傳什麼曩昔給local ) void createNewArray(int** local, int size) { *local = (int*) malloc( size * sizeof(int) ); } int main() { int *ptr; createNewArray(&ptr, 10); } 如果是 C++,別忘了可以善用 Reference void newArray(int*& local, int size) { local = new int[size]; } 跋文:從「古時刻」撒播下來一篇文章 "The Ten Commandments for C Programmers"(Annotated Edition) by Henry Spencer http://www.lysator.liu.se/c/ten-commandments.html 一方面它不是針對 C 的初學者,一方面它特意模擬中古英文 聖經 翻譯用語,寫得文謅謅 翻譯社所以我如今另外寫了這篇,但願 能涵蓋最主要的觀念以及初學甚至老手最易犯 翻譯毛病。 作者:潘科元(Khoguan Phuann) (c)2005. 感謝 ptt.cc BBS 的 C_and_CPP 看板眾多網友供應貴重意見及程式實例。 nowar100 屢次加以點竄整理,擴充至 13 項,而且製作成動畫版。 wtchen 應板友要求移除動畫並憑據C/C++標準點竄內容(Ver.2016) 如發現 Bug 請推文回報,謝謝您
本土說話課納新居民語 108年上路
