--- tags: Coding --- # C/C++筆記 ## 編譯與執行 c用gcc c++用g\+\+ 第一行編譯 第二行執行 **在mac上** ![image](https://hackmd.io/_uploads/SJX1GhUyR.png) ```cpp= gcc -o aaa.c test //aaa.c要編譯的檔案 test指定的名子 -o代表重新命名 ./test //直接執行 ./test input.txt // ./當前資料夾 input.txt餵給argc ./test<input.txt // ./當前資料夾 input.txt內的內容當成輸入資料 ``` ### 法一 argc 傳遞檔案名稱作為參數 ```cpp= ./test input.txt ``` 引入terminal中的字 argc是argc的長度,argc[]是存字串的陣列 argv[0]是程式的路徑,argv[1]是輸入的第一個字,argv[2]是第二個,依此類推 字與字中間用空白隔開 如果沒輸入東西argc會是1,裡面是執行檔的路徑 **terminal** ![截圖 2024-03-31 下午1.42.22](https://hackmd.io/_uploads/B1XWAoIJA.png) **test.c內容** ```cpp= int main(int argc, char *argv[]) { for (int i = 0; i < argc; i++) { printf("%s\n", argv[i]); } } ``` ### 法二(<)重定向 ```cpp= ./test < input.txt ``` input.txt裡面的內容會被當成輸入 ```cpp= #include <stdio.h> int main() { char buffer[100]; while (scanf("%s", buffer) != EOF) { // 從標準輸入讀取 printf("Input: %s\n", buffer); } return 0; } ``` 輸出(假設 input.txt 內容為 hello world): ```md Input: hello Input: world ``` ### 編譯兩個檔案 ```cpp= gcc -c lex.yy.c -o lex.yy.o gcc -c y.tab.c -o y.tab.o ``` gcc:GNU 編譯器,這裡用來編譯 C 程式碼。 -c:告訴編譯器只執行編譯過程,不進行連結。這樣會生成目標檔案(.o檔案)。 lex.yy.c:由 Lex(或 Flex)生成的 C 程式碼檔案,負責詞法分析。 -o lex.yy.o:指定輸出的目標檔案名稱為 lex.yy.o。 這條指令的作用是將詞法分析器的 C 程式碼(lex.yy.c)編譯為對應的目標檔案(lex.yy.o)。 這些目標檔案是編譯後的中間產物,下一步會將這些檔案與其他必要的檔案(如標準函式庫)進行連結,生成最終的可執行檔案。 把lex.yy.o與y.tab.o合成一個檔案 ```cpp= gcc lex.yy.o y.tab.o -o my_program ``` 這會將兩個目標檔案連結起來,生成名為 my_program 的可執行檔案。 ## I/O列印讀取 ### cin的加速 ```cpp= ios_base::sync_with_stdio(false); cin.tie(0); ``` **sync_with_stdio(true); 的作用** 在 C++ 中,sync_with_stdio(true); 是 C++ 預設的行為,它的作用是: * 保持 C++ 的 I/O (cin/cout) 和 C 的 I/O (scanf/printf) 之間的同步,確保它們的輸出順序是正確的。 * 但會降低 I/O 效率,因為每次執行 cin 或 cout 時,系統會先強制同步 C 標準 I/O (stdio) 的 buffer,這樣能確保混用 cin 和 printf 時不會發生輸出錯誤或順序錯亂。 =>可以安全混用 cin / cout 和 printf / scanf。 如果呼叫 sync_with_stdio(false);,那麼 cin 和 printf 可能會交錯輸出,導致結果不可預測,因為它們不再同步。 **cin.tie(true)** cin 和 cout 是綁定的 在 C++ 預設情況下,cin 和 cout 是綁定 (cin.tie(&cout);) 的,這表示: 每次執行 cin 讀取輸入時,cout 會先自動 flush (刷新緩衝區),確保之前的輸出內容已經顯示到螢幕上。 =>確保輸出順序正確,但會影響效能 | | `ios::sync_with_stdio(false);` | `cin.tie(nullptr);` | |-----------|--------------------------------|----------------------| | **主要作用** | `cin/cout` 與 `stdio.h` (`scanf/printf`) 的同步 | `cin` 和 `cout` 之間的關聯 | | **預設行為** | `true`(同步開啟,確保與 C I/O 一致) | `cout` 綁定 `cin`,`cin` 讀取時會刷新 `cout` | | **影響 `cout` 輸出時機?** | 否,`cin` 仍然會刷新 `cout` | 是,`cin` 讀取時不會再自動 flush `cout` | | **影響 `cin` 效能?** | 是,加速 `cin` 讀取 | 否,只影響 `cout` 刷新時機 | 設定|是否可以混用 cin / cout 和 printf / scanf| 速度 |-|-|-| sync_with_stdio(true);(預設)| ✅ 可以,輸出順序正確| 慢 sync_with_stdio(false); |❌ 不能,可能會輸出錯亂 |快 cin.tie(&cout); (預設) |✅ |會自動 flush 保證輸出順序正確,但 I/O 效能較低 cin.tie(nullptr); |❌| 不會自動 flush 提高效能,但可能導致輸出順序錯亂 ex: cin 和 cout 的緩衝區是獨立的,但在預設情況下,cin 會在讀取時自動 flush cout,這讓它們的行為看起來像是共用緩衝區。 ```cpp= #include <iostream> using namespace std; int main() { cin.tie(0); // 解除 cin 和 cout 的綁定 string s; cin >> s; cout.flush(); // 強制輸出緩衝區內容 return 0; } // 輸入:ab cd // 輸出:(無) ``` ### cin.get getchar getline cin.geline 整理 | 函式名稱 | 讀取類型 | 是否包含空格 | 是否包含 `\n` | 適用場景 | |-----------------|---------------|------------|------------|--------| | `getchar()` | 單一字元 (`int`) | ✅ | ✅ | C 語言標準函式,適合單字元讀取 | | `cin.get()` | 單一字元 (`char`) | ✅ | ✅ | C++ 版本,與 `getchar()` 類似,但能搭配 `cin` | | `getline(cin, str)` | 整行輸入 (`std::string`) | ✅ | ❌ | 適合讀取整行 `std::string` | | `cin.getline(buffer, size)` | 整行輸入 (`char[]`) | ✅ | ❌ | 適合讀取整行 `char[]` | ### cout and print用法 print不能印c++的東西例vector、pair cin cout printf scanf混用可能會有印出順序不同的問題 ```cpp= cout<<setprecision(3)<<11.111//11.1 從最大位開始看往後數幾位 cout<<setprecision(3)<<111.11//111 cout<<setprecision(3)<<1111.11//1.11e+03 整數超過3位就會變成科學符號 cout<<fixed<<setprecision(3)<<11.11111//11.11 fixed只看小數點後面幾位fixed與setprecision的順序沒差 cout<<fixed<<setprecision(3)<<11.1//11.100 cout<<setw(7)<<setfill('p');//setw決定寬度位數 setfill決定填入什麼,預設是空白,每次輸出都要打一次 //注意會被endl洗掉 cout << left << setw(7) << setfill('0') << "abc" << endl;//補在右邊abc0000 //注意會被endl洗掉 cout.unsetf( ios::fixed );//關掉fixed cout << defaultfloat << 11.11111 << endl;// 關閉 setprecision 的影響,恢復到預設的輸出格式,輸出 11.11111 cout<<defaulfloat;//關掉fixed printf("%5d",10);//空空空10 少了補0,多了不動 printf("%-5d",10);//10空空空 補在右邊 printf("%05d",10);//00010 printf("%6.3f",1.1111);//空1.111 小數點後面是最多幾位,整數是總共幾位 printf("%6.3s","11111");//空空空111 截斷(長度3)後補零 printf("%s",string.c_str());//用printf印c++的string 要加c_str() ``` ### 整數與字元分開讀 ```cpp= int n; string s; cin>>n; //當輸入是123asd時 只會讀入123(整數) 輸出123 cin>>s; //讀入asd 輸出asd ``` ### getline cin用法 getline被包在<string\> cin.getline則不用因為是讀陣列 cin後面要接cin.ignore()或cin.get()。因為cin讀取完迴車後會多保留一個迴車在緩衝區。所以我們就要用ignore函數來把緩衝區多餘的東西刪掉,好讓getline可以讀取新的東西。但getline讀完後不會留動西在緩衝區,所以cin.get()可以讀到下一行。 ```cpp= #include <iostream> #include <string> int main() { string n; string str; char s[20]; cin>>n;//讀完會在緩衝區留下迴車或是空白,會讓getline()讀到所以用cin.ignore()可以讀掉 getline(cin,str);//讀取直到迴車。如果第一個入就是迴車,就會讀迴車且不放東西在字串裡(迴車不放入),最後一個字元是'\0'。 getline(cin,str,'a');//會讀空白、迴車直到a,且a會被讀掉不會放進字串,所以前面有迴車也會被輸出。 cin.getline(s,5);//只能用字元陣列。其實只有讀4個,會讀空白,因為迴車算一個。如果遇到迴車會不管是否已經讀5個字元。 for(char *i=s;*i!='\0';i++){ cout<<*i; }//迴圈印法(可以知道長度)或是用cout<<s; cin.getline(s,5,'a');//同上,迴車換成a。 getline(cin,str,'a'); cin.get();//可以讀掉不輸入,單純讀一個字 cout.flush();//ensure output is written cin.peek(); //peek character return 0; } ``` #### 讀一整行直到遇到EOF ```cpp= while(getline(cin,s)){ if(s.empty())break;//遇到換行就會是空值 然後就結束 arr.push_back(s); maxN=max(maxN,(int)s.size()); } ``` #### 比較 ```cpp= string b; cin>>b; cout<<(char)cin.peek();//輸入完第一行,peek讀到第一行最後的換行 ``` ```cpp= string b; getline(cin,b); cout<<(char)cin.peek();//輸入完第一行,peek讀到第二行 ``` #### 換行\\n 輸入方式 |實際存入文件的內容 |被程式讀取時的表現 |-|-|-| 手動按 Enter|真正的換行符(ASCII 10)| 會導致 getline() 或 cin.getline() 換行 鍵入 \n(兩個字元) |字面上的 \n(反斜槓 \ 和 n)| 被讀取為普通的 \n 字串 ```cpp= ifstream file("test.txt"); string line; while (getline(file, line)) { cout << "讀取: " << line << endl; } // 文件:Hello\nWorld // 輸出:Hello\nWorld ``` 但如果你在程式中寫 "\\n",它會被解釋為 單一字元 \\n(ASCII 10,換行符)。 在string或是char\[\]中都一樣 ```cpp= string str = "Hello\nWorld"; cout << "字串長度: " << str.length() << endl; // 計算長度 for (char c : str) { cout << "[" << c << "] ASCII: " << int(c) << endl; } // 輸出: // 字串長度: 11 // [H] ASCII: 72 // [e] ASCII: 101 // [l] ASCII: 108 // [l] ASCII: 108 // [o] ASCII: 111 // [\n] ASCII: 10 // 這裡是單一的換行符 // [W] ASCII: 87 // [o] ASCII: 111 // [r] ASCII: 114 // [l] ASCII: 108 // [d] ASCII: 100 string str1 = "Hello\nWorld"; // \n 代表換行符 string str2 = "Hello\\nWorld"; // 手動輸入 \n(兩個字元) cout << "str1 長度: " << str1.length() << endl; cout << "str2 長度: " << str2.length() << endl; // 輸出: // str1 長度: 11 // str2 長度: 12 ``` ### EOF https://blog.csdn.net/henulwj/article/details/39338727 EOF其實就是-1 像這樣 ```cpp= #define EOF (-1) ``` 不用define就有這樣的效果 ```cpp= printf("%d", EOF);//-1 ``` **讀到EOF就結束** ```cpp= while(cin>>n){ //code } while(getline(cin,s)){ //code } while(fgets(arr, sizeof(arr), stdin)!=NULL) ``` ### 讀入檔案 在input中複製上題目的範例就可以直接讀 ```cpp= #include<fstream> ifstream cin; cin.open("input.txt"); ``` ### fgets字串讀取 **scanf方法** ```cpp= scanf("%[^\n]", str);//直到換行才讀進去 ``` **fgets** 會放入\n跟\0 ```cpp= char arr[100]; fgets(arr, sizeof(arr), stdin);//abc 放入a b c \n \0 int i = 0; while (arr[i] != '\0') i++; cout << i<< endl;//4 cout << strlen(arr);//4 換行也會被掃進去 ``` **超出問題** ```cpp= char arr[3]; fgets(arr, sizeof(arr), stdin);//輸入1234 printf("%s ", arr);//空間只有3 所以裡面是1 2 \0 但\0不會被印出來 ``` **不要用gets** std::gets was deprecated in C\++11 and removed from C\++14 c\++11有,但是c++14又被刪掉了 ```cpp= char arr[100]; gets(arr); ``` 與string cin ```cpp= string s ; cin>>s;//abc 放入a b c \0 i = 0; while (s[i] != '\0') i++; cout << i;//3 ``` **puts** 印出字串後順便印換行 ```cpp= puts("abc"); puts("abc"); //abc //abc ``` **getchar putchar** getchar一次只讀一個char。 putchar一次輸一個char,不會順便換行。 ```cpp= char c = getchar();//a putchar(c); putchar(c); //aa ``` **sprintf** 輸出到陣列,並返回字串的長度 ```cpp= char buffer [50]; int n, a=5, b=3; n=sprintf (buffer, "%d plus %d is %d", a, b, a+b); printf ("[%s] is a string %d chars long\n",buffer,n); //[5 plus 3 is 8] is a string 13 chars long ``` **sscanf** 把陣列的東西讀入變數 ```cpp= char sentence[] = "Rudolph is 12 years old"; char str[20]; string s="Rudolph is 12 years old"; int i; sscanf(sentence, "%s %*s %d", str, &i);//%*s 單純讀取不賦值,與單獨cin.get()可以讀掉相同。 sscanf(s.c_str(), "%s %*s %d", str, &i);//c++的string讀取時要用c_str() printf("%s -> %d\n", str, i); ``` ### fstream 可用 file.is_open() 來確認是否成功開啟檔案: ```cpp= std::ifstream file("test.txt"); if (file.is_open()) { std::cout << "檔案開啟成功!" << std::endl; } ``` 模式 |說明 -|- std::ios::in| 讀取模式(ifstream 預設模式)。 std::ios::out| 寫入模式(ofstream 預設模式,會清空檔案內容)。 std::ios::app| 追加模式(寫入時不清空原內容)。 std::ios::trunc| 清空檔案後寫入(ofstream 預設行為)。 std::ios::binary| 以二進位模式開啟檔案。 ```cpp= #include <iostream> #include <fstream> #include <string> int main() { std::fstream file("data.txt", std::ios::in | std::ios::out | std::ios::app); if (!file) { std::cerr << "無法開啟檔案!" << std::endl; return 1; } file << "新的一行數據" << std::endl; // 寫入新內容 file.seekg(0, std::ios::beg); // 移動讀取指標到檔案開頭 std::string line; while (std::getline(file, line)) { std::cout << line << std::endl; // 讀取檔案內容 } file.close(); // 關閉檔案 return 0; } ``` #### 讀取csv檔 ```cpp= #include <fstream> #include <iostream> #include <sstream> #define int long long using namespace std; signed main() { string line; fstream file("data.csv"); if (!file) { cerr << "無法開啟檔案" << endl; } file.clear(); file.seekg(0); while (getline(file, line)) { if (line.empty()) continue; if (!line.empty() && line.back() == '\r') { line.pop_back(); } // 這個if非常重要 因為這個\r 害我debug超級久 /*或是這個也可以 line.erase(0, line.find_first_not_of(" \t\r\n")); // 去掉前導空白 line.erase(line.find_last_not_of(" \t\r\n") + 1); // 去掉尾部空白 */ stringstream s(line); string tmp; vector<int> arr; while (getline(s, tmp, ',')) { arr.push_back(stoi(tmp)); } for(auto i:arr)cout<<i; } } ``` #### 檔案指標控制 (seekg & seekp) 當讀寫檔案時,可能需要移動檔案指標來調整讀取或寫入位置: 方法 |用途 -|- seekg(offset, direction)| 設定讀取位置(ifstream / fstream)。 seekp(offset, direction)| 設定寫入位置(ofstream / fstream)。 tellg()| 取得當前讀取位置。 tellp() |取得當前寫入位置。 ```cpp= file.seekg(0, std::ios::end); // 移動到檔案結尾 std::streampos size = file.tellg(); // 取得檔案大小 file.seekg(0, std::ios::beg); // 回到開頭 ``` #### 二進位檔案讀寫 (binary) 若要處理二進位檔案(如圖片、音訊、序列化資料),需使用 std::ios::binary。 ```cpp= #include <iostream> #include <fstream> int main() { std::ifstream file("binary.dat", std::ios::binary); if (!file) { std::cerr << "無法開啟檔案!" << std::endl; return 1; } int data; file.read(reinterpret_cast<char*>(&data), sizeof(data)); std::cout << "讀取的數據:" << data << std::endl; file.close(); return 0; } ``` #### 檔案錯誤處理 (eof, fail, bad) 可使用 eof()、fail()、bad() 檢查檔案狀態: * file.eof():是否到達檔案結尾。 * file.fail():是否發生讀取或寫入錯誤(如開啟不存在的檔案)。 * file.bad():是否發生更嚴重的錯誤(如硬碟損壞)。 ```cpp= if (file.fail()) { std::cerr << "讀取錯誤!" << std::endl; } ``` ## 資料流stream與緩衝區 **重在理解流的一个概念** **输入流(Input Stream)** 是用于从外部源(例如文件、键盘输入等)读取数据到程序中的流。 **输出流(Output Stream)** 是用于将程序中的数据发送到外部源(例如文件、屏幕输出等)的流。 **箭頭的方向是寫入寫出檔案的方向** 会引发缓冲区的刷新: 1. 缓冲区满时; 2. 执行flush语句; 3. 执行endl语句; 4. 关闭文件。 ### 輸入緩衝 像是cin cin.get peek getline cin.getline 在緩衝區沒東西的時候都會等待 但只要緩衝區有東西那他們就會一直讀緩衝區直到沒有緩衝區清空 當按下回車時那一行包括換行也會進入緩衝區 ```cpp= abcd efghi ↵ // 在緩衝區長這樣 // abcd efghi \n ``` | | | | | -------- | -------- | ----| | cin | 會清空第一個字元前所有的空白與換行 | 保留字元後的空白或換行 | | cin.getline、getline | 不管什麼字元都會讀 | 但會丟棄\\n | |cin.get、peek|不管什麼字元都會讀|| ```cpp= char c, d; string s; cin >> s; c = cin.get(); d = cin.peek(); cout <<'['<< c <<']'<<'[' << d <<']'<< endl; // 輸入: // a↵ // b // 輸出: // [ // ][b] ``` ```cpp= char c=cin.get(); cout<<'['<<c<<']'<<endl; c=cin.get(); cout<<'['<<c<<']'<<endl; // 輸入: // a↵ // 輸出: // [a] // [ // ] ``` ```cpp= string s; cin>>; // 輸入: // [空白]↵ //結果會等待 因為[空白]↵進入緩衝區之後被cin清掉 ### stringstream ```cpp= //基本用法----------------------- ss << "Hello, " << "world! " << 2024; // 像 cout 一樣寫入字串 string result = ss.str(); // 取得字串內容 cout << "stringstream 內容: " << result << :endl; // 輸出: // stringstream 內容: Hello, world! 2024 //解析數字----------------------- string input = "123 456 789"; stringstream ss(input); int num1, num2, num3; ss >> num1 >> num2 >> num3; // 從 ss 讀取數字,類似於 std::cin cout << "解析出的數字: " << num1 << ", " << num2 << ", " << num3 << endl; // 輸出: // 解析出的數字: 123, 456, 789 //清空 stringstream----------------------- stringstream ss; ss << "12345"; cout << "第一次內容: " << ss.str() << endl; ss.str(""); // 清空字串 ss.clear(); // 清空錯誤標誌(如 EOF) ss << "67890"; cout << "第二次內容: " << ss.str() << endl; // 輸出: // 第一次內容: 12345 // 第二次內容: 67890 //分割字串----------------------- string input = "apple banana cherry"; stringstream ss(input); string word; while (ss >> word) { cout << "讀取: " << word << endl; } // 輸出: // 讀取: apple // 讀取: banana // 讀取: cherry //getline分割----------------------- string input = "apple,banana,cherry"; stringstream ss(input); string word; while (getline(ss, word, ',')) { // 以逗號為分隔符 cout << "讀取: " << word << endl; } // 輸出: // 讀取: apple // 讀取: banana // 讀取: cherry //數字轉字串----------------------- int num = 42; stringstream ss; ss << num; // 數字寫入 ss string str = ss.str(); // 轉成字串 cout << "字串: " << str << endl; // 輸出: // 字串: 42 //字串轉數字----------------------- string str = "1234"; stringstream ss(str); int num; ss >> num; // 字串轉成數字 cout << "數字: " << num << endl; // 輸出: // 數字: 1234 ``` **stringstream可以做到istringstream與ostringstream的事** 只是為了可讀性 ![IMG_0522](https://hackmd.io/_uploads/HyiAzV4iyl.jpg) 1.istringstream(只讀) 適用於僅解析字串,不需要寫入的情境(例如從 std::cin 或檔案中讀取內容後解析)。 只能用 >> 讀取,不能 << 寫入。 ```cpp= string input = "123 456 789"; istringstream iss(input); // 只讀入 int num1, num2, num3; iss >> num1 >> num2 >> num3; // 解析數字 cout << "讀取數字: " << num1 << ", " << num2 << ", " << num3 << endl; ``` 2.ostringstream(只寫) 適用於只需要寫入但不讀取的場景(例如格式化輸出)。 只能用 << 寫入,不能 >> 讀取。 ```cpp= ostringstream oss; // 只寫入 oss << "C++ " << 20 << "23"; // 格式化字串 string output = oss.str(); // 取得字串 cout << "輸出結果: " << output << endl; ``` ### 緩衝區 scanf與printf也有緩衝區 但與c++的緩衝區機制不同 所以不能混用 還有緩衝區是有大小的4~8kb都有 超出就會輸出 **1. 標準輸入輸出(cin, cout, cerr)** **cout(標準輸出)** 預設有緩衝區 會先將輸出存入緩衝區,直到: - 遇到 std::endl(會刷新緩衝區 flush()) - 緩衝區滿了 - 程式結束時,自動刷新緩衝區 ```cpp= cout << "Hello, "; // 不會立刻輸出 cout << "world!"; // 仍然不會立刻輸出 cout << endl; // 遇到 std::endl,強制刷新緩衝區,輸出到螢幕 ``` **cin(標準輸入)** 預設有緩衝區 使用者輸入時,實際上是先輸入到緩衝區,然後 cin 再從緩衝區讀取數據。 cin 會在 按 Enter 之後才開始讀取輸入內容。 ```cpp= int num; cout << "輸入一個數字: "; cin >> num; // 直到使用者按 Enter,才會將數據從緩衝區讀取 cout << "你輸入的數字是: " << num << endl; ``` **cerr(標準錯誤輸出)** 而且會把緩衝區的先輸出 沒有緩衝區(即 cerr 是 不緩衝 的) 輸出錯誤訊息時,不會被緩衝,會立刻輸出 適合即時錯誤訊息,例如程式錯誤訊息、日誌 ```cpp= cerr << "這是錯誤訊息" << endl; //cerr 立即輸出,不受 std::cout 的緩衝影響! ``` **clog(日誌輸出)** 有緩衝區 用來輸出日誌,類似 cerr,但因為有緩衝,所以效能較好 ```cpp= clog << "這是日誌訊息" << endl; // 會進入緩衝區,適時輸出 ``` **2. 檔案 I/O (fstream)** **ifstream(讀取檔案)** 有緩衝區 讀取時,會一次讀取多個字元到緩衝區,提高效率。 ```cpp= ifstream file("data.txt"); // 開啟檔案 string content; while (file >> content) { // 逐字讀取 cout << content << endl; } file.close(); ``` **ofstream(寫入檔案)** 有緩衝區 ofstream 會先將資料寫入緩衝區,等到緩衝區滿了或 flush() 才會真正寫入檔案。 ```cpp= ofstream file("output.txt"); file << "Hello, file!" << endl; // 可能還在緩衝區,尚未寫入檔案 file.flush(); // 立即寫入檔案 file.close(); ``` **3. 控制緩衝區** **cout.flush() 立即輸出** endl 自動刷新緩衝區 cout.flush() 手動刷新緩衝區 ```cpp= cout << "開始... "; cout.flush(); // 立即輸出,不等到換行 this_thread::sleep_for(chrono::seconds(2)); // 等 2 秒 cout << "結束!" << endl; //輸出: // 開始... (2 秒後) 結束! ``` **ios::sync_with_stdio(false);** 關閉 C++ I/O 和 C I/O (printf/scanf) 之間的同步 提高 cin/cout 效率(約 5~10 倍) ```cpp= ios::sync_with_stdio(false); // 關閉同步,提高效能 cin.tie(nullptr); // 解除 cin 和 cout 的關聯,提高效能 ``` **4. getchar、getline、cin.get() 是否有緩衝區?** **getchar()** 有緩衝區,按下 Enter 才會開始讀取(類似 scanf)。 可以一次輸入多個字元,然後 getchar() 會 逐個從緩衝區讀取。 ```cpp= char c; printf("請輸入一個字元: "); c = getchar(); // 會等到按 Enter 才從緩衝區讀取 printf("你輸入的是: %c\n", c); ``` **getline(cin, s)** 有緩衝區,讀取一整行直到 \n,但不保留 \n。 不會遺留換行符,適合讀取完整字串。 ```cpp= string input; cout << "請輸入一行文字: "; getline(cin, input); cout << "你輸入的是: " << input << endl; ``` **cin.get()** 有緩衝區,但可以用來讀取單個字元或整行。 cin.get() 保留換行符 \n,而 getline() 會丟棄它。 ```cpp= char c; cout << "請輸入一個字元: "; c = cin.get(); // 讀取一個字元(包括空白或換行) cout << "你輸入的是: " << c << endl; ``` 操作 |讀取方式 |是否讀取 \\n| 緩衝區影響 |-|-|-|-| 按 Enter 鍵 |會產生 \n,並進入 輸入緩衝區|| cin |讀取變數(忽略空白)|❌ 不讀 \\n(留在緩衝區)| getline() 可能讀取到 \\n(錯誤行為) getline(cin, s)| 讀取整行| ✅ 讀 \\n 並丟棄 |受 cin >> 遺留的 \\n 影響 cin.getline(buf, size) |讀取整行 |✅ 讀 \n 並丟棄 |受 cin >> 遺留的 \\n 影響 cin.get\(c\) |讀取單個字元 |✅ 讀 \\n |可能讀取 \\n,影響後續輸入 cin.ignore() |忽略單個字元 |✅ 讀 \\n(可忽略掉) |用來清理 cin 遺留的 \\n **總結** 類別/函式 |緩衝區 |立即輸出/讀取 |-|-|-| cout |✅| ❌(預設緩衝,遇 std::endl 或 flush() 才輸出) cin |✅| ❌(按 Enter 後才讀取) cerr |❌| ✅(會把緩衝區的先輸出,無緩衝區,立即輸出) clog|✅ |❌(適合日誌,使用緩衝區) ifstream|✅ |❌(預設使用緩衝區讀取) ofstream |✅|❌(預設使用緩衝區寫入) getchar() |✅ |❌ 逐字讀取,從緩衝區讀取一個字元 getline(cin, s) |✅|❌ 讀取整行,直到 \n 為止 cin.get() |✅ |❌ 讀取單個字元或整行(視參數而定) ### 各自的緩衝區與大小 下面程式用來測試 以我的筆電來說是4096byte 超過4096再輸出一個進去的話,就會先把前面4096個字輸出 ```cpp= for(int i = 0; i < 4096; i++) { cout<<"x"; } sleep(3); cout<<"d"; sleep(3); cout<<"z"; ``` 每個都有各自的緩衝區 ![image](https://hackmd.io/_uploads/ryTMMAHjkx.png) * 全缓冲:在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。 * 行缓冲:在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。 * 不带缓冲:也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。 ![image](https://hackmd.io/_uploads/ryx5z0Sjyl.png) 但這些緩衝區還是可以自定義更改大小的 並且就算是cin與cout的緩衝區也不是共用的 而是cin.tie()有強制讓cin的時候要先把cout的緩衝區先輸出,才會看起來是共用的 ### 緩衝區的互相影響 不是所有的 I/O 流都會刷新其他的緩衝區,但某些情況下,特定的 I/O 操作會影響其他流的緩衝區。這主要取決於 C++ 標準庫的同步機制 (std::ios::sync_with_stdio(true);) 以及 I/O 流的緩衝行為。 ![image](https://hackmd.io/_uploads/BylBmRHskl.png) ## STL ### 優化 ```cpp= #pragma GCC optimize(3) ``` **作用:** 讓stl容器變快,但編譯時間變久。但拿編譯時間換執行時間很划算。 ### vector end()是最後一個位置加一=v[最後一位]+1 不等於v[最後一位] begin()是最初的位置=V[0],但它們都是iterator(跟指標有點像但不完全相同)。vector的記憶體是連續的,就算用insert也還是會連續,因此insert很耗時。 ```cpp= vec.push_back(); vec.pop_back(); cout<<&(*a.begin())<<endl;//0xac9eb0 cout<<&a[0]<<endl;//0xac9eb0 cout<<&(*a.end())<<endl;//0xac9eb8 cout<<&a[1]<<endl;//0xac9eb4 cout<<&a[1]+1<<endl;//0xac9eb8 沒有vector<int> a[10]//不能用陣列的宣告方式 vector<int> vec(2);//代表vec[0]~vec[1]都是0 vector<int> vec(5, 10); // 初始化一個包含 5 個元素的 vector,每個元素值為 10 vector<int> vec={1,2,3}; vec.assign({1, 2, 3, 4}); //如果裡面原本有東西像是{5,23,5,1,7,9,3,6,} 會直接覆蓋掉 //size的值會被重新分配 但capacity沒有變 //清空的三種方法 vec.assign({}); vec.erase(vec.begin(),vec.end()); vec.clear(); //複製三種方法 vector<int> vec1 = {1, 2, 3, 4, 5}; vector<int> vec2 = vec1; // 直接賦值 vector<int> vec2(vec1); // 使用拷貝建構子 vec2.assign(vec1.begin(), vec1.end());// 使用 assign 來複製 vec.push_back(1);//0 0 1 //二微陣列宣告 //法一 vector<vector<int>> v; for(int i=0;i<size;i++){ vector<int> a; a.push_back(i); v.push_back(a); } //法二 vector<vector<int>> v(size); for(int i=0;i<size;i++){ v[i].resize(size); }//裡面都會是零 //法三 vector<vector<int>> vec(size,vector<int>(size))//跟法二一樣都是0 //法四 vector<vector<int>> vec(size); /* vector<vector>vec; vec.resize(size); 另一種不用在宣告時決定長度的方式 */ for(int i=9;i>=0;i--){ for(int j=0;j<=i;j++){ vec[i].push_back(j); } }//可以從後面塞東西長度可改動 //可以這樣印 for(auto i:array){ for(auto j:i)cout<<j<<" "; cout<<endl; } ``` 存放位置問題: ```cpp= ector<vector<int>> vec(2, vector<int>(2)); cout << &vec[0][1] - &vec[0][0] << endl;//1 /* &vec[0][1] - &vec[0][0] 時,這個表達式的含義是計算這兩個指標所指向的元素之間相隔的「int」數量。 由於 vec[0][1] 與 vec[0][0] 相差一個 int,而 int 的大小是 4 個位元組,因此結果為 1,這代表它們之間相隔一個 int。 */ cout << &vec[1][0] - &vec[0][0] << endl;//不可預期 //不是最底下那維的話 記憶體不會像是陣列一樣連續 ``` 如果沒有先宣告大小就用的話會出事 除非用push ```cpp= vector<int> arr; 這樣是不法的arr[0]=1; arr.push_back(1); ``` ### stack first in - last out 不能用begin()、[]、for(auto i:d) ```cpp= stack<int> s; s.push();//從尾端放入 s.pop();//移除尾端 s.top();//尾端的值 s.empty();//空的話傳1 for(auto i:s)//不能,也不能用s[i] //沒有begin(),end() while(!s.empty()){ cout<<s.top(); s.pop(); }//遍歷的方式 ``` ### queue 不能用begin()、[]、for(auto i:d) #### queue ```cpp= queue<int> q; q.push();//從尾端放入 s.pop();//移除頭 s.front();//頭的值 s.back();//尾端的值 s.empty();//空的話傳1 while(!s.empty()){ cout<<s.front(); s.pop(); }//遍歷的方式 ``` #### priority_queue ```cpp= prioirty_queue<int> q;//預設最大堆升續1 2 3 4 priority_queue<int,vector<int>,greater<int>> q//最小堆 priority_queue<int,vector<int>,less<int>> q//最大堆 priority_queue<int,vector<int>,cmp> q //因為優先判定為!cmp,所以「由大排到小」需「反向」定義實現「最小值優先」。反之亦然。 s.push(); s.pop();//移除尾端 s.top();//尾端的值 s.empty();//空的話傳1 ``` ### set 會自動排序的stl(升續) 時間複雜度都是O(log(n)) 1. set中 s.insert()返還pair<iterator,bool>如果set中沒有重複的數字,s.second(bool)=ture。不管返回true或是false,s.first會是插入的值。 2. multiset是可以重複放的set 3. 但multiset中s.insert()返還iterator ```cpp= set<int> s{4,1,2}//會自動排列成1 2 4 s.insert(val);//不能重複插入同個值 s.erase(val);//新增與刪除只能用這兩個 s.find(val);//找到返回值的位置(iterator),否則返回end() s.count(val);//找到返回1否則返回0 s.clear();//清空 s.empty();//是否空 auto it=s.insert(val); if(it.second){ cout<<*it.first; } //印出方式 不能用s[i] 1. for(auto &i:s)cout<<i; 2. for(set<int>iterator i=s.begin(),i!=s.end();i++)cout<<*i; ``` ### unordered_set ```cpp= #include <iostream> #include <unordered_set> using namespace std; int main() { unordered_set<int> uset = {1, 2, 3, 3}; // 重複元素不會儲存 uset.insert(4); // 插入元素 uset.erase(2); // 刪除元素 if (uset.find(3) != uset.end()) { // 查找元素 cout << "3 存在\n"; } for (int num : uset) { // 遍歷元素(順序不固定) cout << num << " "; } } ``` ### map 插入:記m["str"]=100這個方法就好了 查找:find返回iterator。count返回1或0,但multimap的count會返回相同元素的數量 使用:像是陣列一樣使用就好了 m["str"] ```cpp= map<string,int>m; m["str"]=100; m.insert({"str",100}); m.insert(make_pair("str",100)); m.insert(pair<string,int>("str",100)); //如果對應的值一樣就不會成功,像是m["str"]=1,由於前面已經m["str"]=100所以不會插成功。 m.count("str");//看看有沒有重複,有回傳1,沒有回傳0 m.erase("str");//刪除key與對印的值 cout<<m["s"]; //雖然前面沒有宣告過,key也沒有值,但會預設0,同時也宣告了,相當於m["s"]=0 auto it=m.find("str");//沒有的話返還值為end() cout<<it->first;//str cout<<it->second;//100 auto it=m.insert({"str",100}); cout<<it.first->first;//str cout<<it.first->second;//100 cout<<it.second;//true auto it=m.insert(m.begin(),{"str",100});//給位置代表從哪裡開始蒐。為了可以比較快找到位置,沒找到還是會按照順序排。 cout<<it->first//str cout<<it->second;//100 cout<<it.second;//沒有it.second,這行會發生錯誤。 for(auto i:m){ cout<<i.first<<" "<<i.second<<enld;//first是key second是value } ``` ### unorder_map 平均複雜度O(1) 最糟複雜度O(n) ```cpp= unordered_map<string, int> m; // 插入元素的幾種方法 m["str"] = 100; m.insert({"str", 100}); m.insert(make_pair("str", 100)); m.insert(pair<string, int>("str", 100)); // 插入值相同的情況 // 如果鍵已經存在,並且對應的值相同,插入將不會成功 // 如:m["str"] = 1,由於前面已經有 m["str"] = 100,因此不會覆蓋 // 但若用 m["str"] = 1,會直接覆蓋原本的值變成 1 // 使用 count() 函式檢查鍵是否存在 cout << m.count("str"); // 存在返回 1,不存在返回 0 // 刪除元素 m.erase("str"); // 刪除鍵 "str" 及其對應的值 // 如果鍵不存在,會預設值為 0 並插入到 `unordered_map` cout << m["s"]; // 如果 "s" 不存在,會返回 0,並且相當於 m["s"] = 0 // 使用 find() 查找元素 auto it = m.find("str"); // 找不到時,返回 m.end() if (it != m.end()) { cout << it->first; // str cout << it->second; // 100 } // 使用 insert() 插入元素,返回 pair<iterator, bool> auto result = m.insert({"str", 100}); // 已存在時不會插入 cout << result.first->first; // str cout << result.first->second; // 100 cout << result.second; // true,如果插入成功返回 true,否則返回 false // 使用 insert() 並提供提示位置 auto it2 = m.insert(m.begin(), {"str", 100}); // 提供起始位置作為提示 cout << it2->first; // str cout << it2->second; // 100 // unordered_map 中沒有 second,it2 直接是 iterator,所以無法使用 it2.second ``` #### multimap 一個標籤可以對應到不同的值,所以不會有新增失敗的問題。 新增時只能用insert,不能用中括號例如m["str"]=100。 要印出一個標籤所有的值方法特別。 ```cpp= multimap<int,char,greater<int>> mm;//由小到大 m.insert({"a",100}); m.insert({"a",200}); auto it=m.c({"a",300}); cout<<it->first;//a cout<<it->second;//100 cout<<m.count("a");//3 a的次數 auto it=m.find("a"); cout<<it->first;//a cout<<it->second;//100 typedef multimap<string,int>::iterator mmit;//type跟#define一樣 pair<mmit,mmit>result=m.equal_range("a"); for(mmit i=result.first;i!=result.second;i++){ cout<<i->second<<endl; } //100 200 300 ``` ### deque 可以用[] ```cpp= deque<int> d; d.push_back(2);//後面放入 d.push_front(3);//前面放入 d.pop_back();//後面拿掉 d.pop_front();//前面拿掉 ``` ### list 可以用begin()、auto,但不能用[] ```cpp= list<int> l; l.push_back(2);//後面放入 l.push_front(3);//前面放入 l.pop_back();//後面拿掉 l.pop_front();//前面拿掉 auto it1=l.begin(); auto it2=l.end(); it1++;//只能用it++不能用it=it+1 l.erase(it1);//刪除it的位置 l.erase(it1,it2);//刪除[it1,it2)的範圍 l.insert(it1,10); ``` ## iterator ### distance 算兩個iterator的距離(不能用減的),順序小的要放前面,大的放後面。 ```cpp= vector<int>vec={1,2,3,4,5}; cout<<distance(vec.begin(),vec.end());// 5 ``` ### advance 對一個iterator移動(不能用加的)。 ```cpp= vector<int>vec={1,2,3,4,5}; auto it=vec.begin(); advance(it,2); cout<<(*it);//3 ``` ## string | **分類** | **函數** | **功能** | **範例** | |----------|----------|----------|----------| | **基本操作** | `size()` / `length()` | 取得字串長度 | `s.size()` → `5` | | | `empty()` | 判斷字串是否為空 | `s.empty()` → `false` | | | `clear()` | 清空字串 | `s.clear()` | | | `operator[]` | 存取字元(索引) | `s[1]` → `'e'` | | | `at(pos)` | 存取字元(帶邊界檢查) | `s.at(1)` | | | `front()` | 取得字串的第一個字元 | `s.front()` | | | `back()` | 取得字串的最後一個字元 | `s.back()` | | **變更字串內容** | `append(str)` | 追加字串 | `s.append("World")` | | | `operator+=` | 追加字串 | `s += "World"` | | | `insert(pos, str)` | 插入字串 | `s.insert(2, "ABC")` | | | `replace(pos, len, str)` | 取代部分字串 | `s.replace(1, 2, "XY")` | | | `erase(pos, len)` | 刪除部分字串 | `s.erase(1, 3)` | | | `push_back(ch)` | 追加單一字元 | `s.push_back('X')` | | | `pop_back()` | 刪除最後一個字元 | `s.pop_back()` | | **字串搜尋** | `find(str)` | 從左側搜尋子字串 | `s.find("lo")` → `3` | | | `rfind(str)` | 從右側搜尋子字串 | `s.rfind("lo")` | | | `find_first_of(str)` | 找到第一個匹配字元 | `s.find_first_of("aeiou")` | | | `find_last_of(str)` | 找到最後一個匹配字元 | `s.find_last_of("aeiou")` | | | `find_first_not_of(str)` | 找到第一個**不**匹配的字元 | `s.find_first_not_of("Hello")` | | | `find_last_not_of(str)` | 找到最後一個**不**匹配的字元 | `s.find_last_not_of("Hello")` | | **子字串處理** | `substr(pos, len)` | 擷取部分字串 | `s.substr(1, 3)` → `"ell"` | | **字串比較** | `compare(str)` | 比較字串(回傳 `0` 表示相等) | `s.compare("ABC")` | | **大小寫轉換** | `std::transform(s.begin(), s.end(), s.begin(), ::toupper)` | 轉換為大寫 | `"hello"` → `"HELLO"` | | | `std::transform(s.begin(), s.end(), s.begin(), ::tolower)` | 轉換為小寫 | `"HELLO"` → `"hello"` | | **數值轉換** | `std::to_string(num)` | 整數/浮點數轉字串 | `std::to_string(123)` → `"123"` | | | `std::stoi(str)` | 字串轉 `int` | `std::stoi("123")` → `123` | | | `std::stod(str)` | 字串轉 `double` | `std::stod("123.45")` → `123.45` | ```cpp= getline(cin,string); string str1; string str2; str1.empty(); //檢查str1是否為空,是回傳1,否則回傳0 str1.size(); //回傳str1的長度 str1.find("abc",3); //從str1[3]開始找第一個"abc",回傳索引號,找不到則回傳-1 str1.rfind("abc",3); //找最後的"abc",回傳索引號,找不到則回傳-1 str1.insert(3,"def"); //在str[3]插入"def" str1.assign(str2); //複製str2到str1 == str1=str2 str1.assign(str2,3,5); //從str2的第3個字元取出5個字元指定給str1 str1.assign(str2,3); //從str2的第3個字元指定整串字串到str1 str1.assign(10,'c'); //複製10個c到str1 //append用法跟assign大同小異 str1.append(str2,3,5); //從str2的第3個字元取出5個字元加在str1後面 str1.append("hello"); //直接給字串 ub str1.push_back('j');//string的push只能用''字元 要push字串要用apeend str1=str2.substr(3,5); //從str2的第3個位置取5個字元放到str1 string a="ac",b="b"; max(a,b);//字串比大小由左到右比ascii code的大小誰先比較大就贏了 //不是比長度,但前面都一樣就會輸出長的 ``` ### 通常不包含結尾字符(\\0) cin>>string 或是 字串轉成string 都不會包含\\0 ```cpp= char arr[] = "hello"; // C 字符陣列,最後有隱含的 '\0' string str = arr; // 將字符陣列轉換成 std::string cout << "C 字符陣列: " << arr << endl; cout << "std::string: " << str << endl; cout << "std::string 的長度: " << str.length() << endl; // 字串長度 cout << str[5]; => 會是錯的 因為字串轉過來不會把最後的\0放進去 ``` 除非強制給他 ```cpp= string str = "abc\0def"; // 這個字串實際上包含 7 個字符 cout << "String length: " << str.length() << endl; // 輸出字串長度 cout << "String content: " << str << endl; // 直接輸出字串內容 // 遍歷字串的每個字符,顯示其 ASCII 值 for (size_t i = 0; i < str.length(); ++i) { cout << "Character at index " << i << ": " << (int)str[i] << endl; } String length: 7 String content: abc Character at index 0: 97 // 'a' Character at index 1: 98 // 'b' Character at index 2: 99 // 'c' Character at index 3: 0 // '\0' Character at index 4: 100 // 'd' Character at index 5: 101 // 'e' Character at index 6: 102 // 'f' ``` ### bitset二進制 ```cpp= int a=14; bitset<6> c(a);//6 is lenghth cout<<c<<endl;//001110 c<<=1;//乘以2 cout<<c<<endl;//011100 cout<<c[2];//1 ``` ### char陣列轉成string ```cpp= char charArray[] = "Hello, World!"; // 法一 使用 string 的建構函式將 char 陣列轉為 string string str(charArray); //法二 string str; str.assign(charArray); //法三 //char 陣列不包含終止符 \0 char charArray[] = {'H', 'e', 'l', 'l', 'o', '!', '!', '!'}; // 將 char 陣列指定長度轉為 string std::string str(charArray, sizeof(charArray)); //但如果char陣列 有結尾字符\0 sizeof()也會算進去 char a[]="b"; string arr(a,sizeof(a)); cout<<arr.size();//2 cout<<arr;//b cout還是一樣會印到\0 //法四 char charArray[] = "Hello, World!"; std::string str; str.append(charArray); ``` ### 數字轉成string #### to_string ```cpp= int num = 123; string str = to_string(num); ``` #### sprintf ```cpp= int num = 123; char buffer[50]; sprintf(buffer, "%d", num); std::string str(buffer); ``` ### stringstream c++專用 類似於c的ssprintf、sscanf ```cpp= stringstream ss; int age = 30; string name = "Alice"; ss << "Name: " << name << ", Age: " << age; // 獲取寫入的字符串 string output = ss.str(); cout << output << endl; // 輸出: Name: Alice, Age: 30 // 讀取數據從字符串 string input = "100 200"; stringstream ssInput(input); int x, y; ssInput >> x >> y; // x = 100, y = 200 cout << "x: " << x << ", y: " << y << endl; // 輸出: x: 100, y: 200 ``` 清空內容:如果你想重用一個 stringstream 對象,可以使用 str("") 清空內容 ```cpp= ss.str(""); // 清空 ss.clear(); // 重置標誌 ``` 錯誤檢查:在讀取數據後,可以檢查流的狀態 ```cpp= if (ss.fail()) { std::cout << "讀取失敗" << std::endl; } ``` ## char與字元與字元陣列與含式 ### 字元 **\0:** 空字元 ascii碼 0 (與空白space不是同一個東西) **\n:** 換行 ascii碼 10 **\t:** tab ascii碼 9 **' ':** space ascii 碼 32 ```cpp= char arr[] = " ,\t\r\n";// \t \r \n都算一個字元 所以長度是5 ``` **ascii碼** ![ASCII-Table-wide.svg](https://hackmd.io/_uploads/H1tw0jo7p.jpg) **char在指定前為隨機的 而不是\0(空字符)** ```cpp= char c; if(c=='\0')printf("a") else printf("b") //b ``` ### char陣列 包含在<string.h>裡面 ```cpp= char a[4]={"asdf"}//會報錯,因為最後一格要留給\0 char a[4]={"asd"}//才對 char s[] = {"asdf"}; printf("%s", s);//印出全部asdf,s是位址也是s[0]的位址 printf("%s", &s[1]);//印出sdf,會取s[1]的位址,然後印出直到遇到\n ``` **strtok** **strlen** ```cpp= char s=[100]; printf("%d",strlen(s));//100 ``` **strtod** 分裂第一個遇到的浮點數與後面的字串 ```cpp= char str[40] = "20.4test 31.3"; char *ptr; double ret; ret = strtod(str, &ptr); printf("%lf\n",ret); printf("%s",ptr); //20.400000 //test 31.3w char str[40] = "www20.4test 31.3w";//改成這樣的輸出 //0.000000 //www20.4test 31.3w ``` **strtol** 其他進位轉換成十進位 ```cpp= char szNumbers[] = "2001 60c0c0 -1101110100110100100000 0x6fffff"; char* pEnd; long int li1, li2, li3, li4; li1 = strtol(szNumbers, &pEnd, 10); li2 = strtol(pEnd, &pEnd, 16); li3 = strtol(pEnd, &pEnd, 2); li4 = strtol(pEnd, NULL, 0); printf("The decimal equivalents are: %ld, %ld, %ld and %ld.\n", li1, li2, li3, li4); //The decimal equivalents are: 2001, 6340800, -3624224 and 7340031. ``` **strchr** str中找到第一個有出現字元C的地址 ```cpp= char str[] = "fcba73"; printf("%c", *(strchr(str, 'c')));//c ``` **strcspn** str找到第一個與s2重複的字元的位置 ```cpp= char str[] = "fcba73"; char keys[] = "1234567890"; printf("position %d.\n", strcspn(str, keys);); //position 4. ``` **字串處理** ```cpp= char s1[100]="abcde",s2[100]="fghi"; strcpy(s1,s2);//fghi 覆蓋複製 strncpy(s1,s2,n);//fghde 覆蓋前n個的複製 strcat(s1,s2);//abcdefghi 加到最後面 strncat(s1,s2,n);//abcdefgh 前n個加到最後面 strcmp(s1,s2);//-5 一個一個比較ascii code的大小前減後(a-f=-5),都一樣就輸出0。 strncmp(s1,s2,n);//取前n個比較 char s1[100] = "a", s2[100] = "ab"; strcmp(s1, s2); //-98,如果前幾個都的ㄧ樣,則超出去沒有的字母會用0去計算 ``` **字元處理** ```cpp= //包含在ctype.h char c; isalpha(c)//是否a~z A~Z是的話回傳1 isdigit(c)//0~9 isxdigit(c)//0~9 a~z A~Z islower(c)//a~z isupper(c)//A~Z toupper(c)//變大寫 tolower(c)//變小寫 ``` ## pair ```cpp= #define pii pair<int,int> pii a; a={1,2}; a=make_pair(1,2); a=pii(1,2); vector<pair<int,int>>//是錯的 vector<pair<int,int> >//才是對的,因為>>會被當成是cin>>的>> //binary_search不能用{}要用make_pair(1,2)或是pii(1,2) ``` ## 各種定義 ### 定義 其實int char都是用有號以及沒號的byte去定義 除了float不一樣。 uintX-t就是通过typedef定义的,利用预编译和typedef可提高效率也方便代码移植。总结如下: ```cpp= typedef unsigned char uint8_t; //无符号8位数 typedef signed char int8_t; //有符号8位数 typedef unsigned int uint16_t; //无符号16位数 typedef signed int int16_t; //有符号16位数 typedef unsigned long uint32_t; //无符号32位数 typedef signed long int32_t; //有符号32位数 typedef float float32; //单精度浮点数 typedef double float64; //双精度浮点数 ``` 一般来说整形对应的*_t类型为: uint8_t为1字节 uint16_t为2字节 uint32_t为4字节 uint64_t为8字节 不难看出,通过头文件X.h定义了uint8_t,其实编译器实际上是把它作为"char"来处理的,在对字符型的变量进行操作 #### c99的定義 ```cpp= //typedef 舊名子 新名子 // Represents true-or-false values typedef _Bool bool; //bool其實就是uint1_t(雖然沒有這個表示法)只有一bit的變量 enum { false, true }; //也就是說打false會完全等於0 打true會完全等於1 ``` ```cpp= // Explicitly-sized versions of integer types typedef __signed char int8_t; typedef unsigned char uint8_t; typedef short int16_t; typedef unsigned short uint16_t; typedef int int32_t; typedef unsigned int uint32_t; typedef long long int64_t; typedef unsigned long long uint64_t; // Pointers and addresses are 32 bits long. // We use pointer types to represent virtual addresses, // uintptr_t to represent the numerical values of virtual addresses, // and physaddr_t to represent physical addresses. typedef int32_t intptr_t; typedef uint32_t uintptr_t; typedef uint32_t physaddr_t; ``` 格式化输出:(都會是數字) unit64_t %llu unit32_t %u unit16_t %hu 注意: 必须小心 uint8_t 类型变量的输出,例如如下代码,会输出什么呢? ```cpp= uint8_t fieldID = 67; cerr<< "field=" << fieldID <<endl; ``` **结果发现是:field=C 而 不是我们所想的 field=67** 这是由于 typedef unsigned char uint8_t; uint8_t 实际是一个 char, cerr << 会输出 ASCII 码是 67 的字符,而不是 67 这个数字. 因此,输出 uint8_t 类型的变量实际输出的是其对应的字符, 而不是真实数字. 若要输出 67,则可以这样: ```cpp= cerr<< "field=" << (uint16_t) fieldID <<endl; ``` **结果是:field=67** ### 定義代表的數值 數值是印出來真的就是數字 其中定義的方式可能用define或是enum #### 1. C/C++ 標準庫中的數值定義 | 特性 | NULL(通常是 `0`) | `nullptr` | |------------|------------------|----------| | **本質** | 整數(`int` 或 `0`) | `std::nullptr_t` | | **可用於指標** | ✅(但易混淆) | ✅ | | **可用於整數** | ✅(因為是 `0`) | ❌ | | **過載解析** | 可能造成歧義 | 清楚選擇指標版本 | | **建議使用** | ❌(C++11 以上建議用 `nullptr`) | ✅ | | 宏名稱 | 數值 | 描述 | |--------|------|------| | `NULL` | `0` 或 `(void*)0`(C),`nullptr`(C++11+) | 空指標 | | `EOF` | `-1` | End of File(文件結束) | | `EXIT_SUCCESS` | `0` | `exit(0)`,表示程式正常結束 | | `EXIT_FAILURE` | `1` | `exit(1)`,表示程式異常結束 | | `RAND_MAX` | `32767`(可能依系統不同) | `rand()` 產生的最大隨機數 | | `FLT_MAX` | `3.402823e+38` | `float` 型別的最大值 | | `DBL_MAX` | `1.797693e+308` | `double` 型別的最大值 | **備註** 1. `NULL` 在 C++11 之後應該使用 `nullptr` 來取代,避免與整數 `0` 混淆。 2. 許多數學常數在 C++ 標準庫 `<cmath>` 中已經可以用 `constexpr` 替代,如 `std::numbers::pi`(C++20)。 3. `__cplusplus` 的數值代表 C++ 標準版本,例如: - `199711L` → C++98 - `201103L` → C++11 - `201402L` → C++14 - `201703L` → C++17 - `202002L` → C++20 #### 2. 數學相關 (`<math.h>` / `<cmath>`) | 宏名稱 | 數值 | 描述 | |--------|------|------| | `M_PI` | `3.141592653589793` | 圓周率 π | | `M_E` | `2.718281828459045` | 自然對數底數 e | | `M_LOG2E` | `1.4426950408889634` | log₂(e) | | `M_LOG10E` | `0.4342944819032518` | log₁₀(e) | | `M_LN2` | `0.6931471805599453` | ln(2) | | `M_LN10` | `2.302585092994046` | ln(10) | | `M_SQRT2` | `1.4142135623730951` | √2 | | `M_SQRT1_2` | `0.7071067811865476` | 1/√2 | #### 3. 文件 I/O (`<stdio.h>` / `<cstdio>`) | 宏名稱 | 數值 | 描述 | |--------|------|------| | `SEEK_SET` | `0` | `fseek()`,從檔案開頭計算偏移量 | | `SEEK_CUR` | `1` | `fseek()`,從目前位置計算偏移量 | | `SEEK_END` | `2` | `fseek()`,從檔案結尾計算偏移量 | #### 4. 錯誤碼 (`<errno.h>`) | 宏名稱 | 數值(依系統不同) | 描述 | |--------|------------------|------| | `EPERM` | `1` | 操作不允許 | | `ENOENT` | `2` | 沒有此文件或目錄 | | `ESRCH` | `3` | 沒有此程序 | | `EINTR` | `4` | 被中斷的系統調用 | | `EIO` | `5` | I/O 錯誤 | | `ENOMEM` | `12` | 記憶體不足 | | `EACCES` | `13` | 權限不足 | | `EEXIST` | `17` | 檔案已存在 | #### 5. 限制 (`<limits.h>` / `<climits>`) | 宏名稱 | 數值 | 描述 | |--------|------|------| | `CHAR_BIT` | `8` | `char` 型別的位數 | | `SCHAR_MIN` | `-128` | `signed char` 最小值 | | `SCHAR_MAX` | `127` | `signed char` 最大值 | | `UCHAR_MAX` | `255` | `unsigned char` 最大值 | | `INT_MIN` | `-2147483648` | `int` 最小值 | | `INT_MAX` | `2147483647` | `int` 最大值 | | `UINT_MAX` | `4294967295` | `unsigned int` 最大值 | | `LONG_MIN` | `-9223372036854775808` | `long` 最小值(64 位系統) | | `LONG_MAX` | `9223372036854775807` | `long` 最大值(64 位系統) | | `ULONG_MAX` | `18446744073709551615` | `unsigned long` 最大值(64 位系統) | #### 6. C++ 特定的宏 | 宏名稱 | 數值 | 描述 | |--------|------|------| | `__cplusplus` | `199711L`, `201103L`, `201402L` 等 | C++ 標準版本(C++98、C++11、C++14 等) | | `__STDC__` | `1` | 是否符合 ANSI C 標準 | | `__FILE__` | - | 當前文件名稱 | | `__LINE__` | - | 當前行號 | | `__DATE__` | - | 編譯日期 | | `__TIME__` | - | 編譯時間 | ## \#ifdef, \#ifndef, \#if 定義 ### 1. `#ifdef`(如果已定義) `#ifdef` 用來檢查某個 **宏是否已經被定義**,如果該宏存在,則執行後續的程式碼。 ```cpp #define DEBUG_MODE // 定義 DEBUG_MODE int main() { #ifdef DEBUG_MODE //DEBUG_MODE被定義過的話執行 cout << "Debug 模式開啟!" << endl; #endif //表示ifdef的結束 cout << "程式執行中..." << endl; return 0; } // 輸出: // Debug 模式開啟! // 程式執行中... ``` ### 2. #ifndef(如果未定義) \#ifndef 用來檢查某個 宏是否沒有被定義,如果該宏 尚未定義,則執行後續的程式碼。 ```cpp= #ifndef MAX_VALUE // 如果 MAX_VALUE 沒有被定義 #define MAX_VALUE 100 #endif int main() { cout << "MAX_VALUE = " << MAX_VALUE << endl; return 0; } // 輸出: // MAX_VALUE = 100 ``` ### 3. #if(條件判斷) \#if 可用來根據 數值或表達式的結果 來決定是否編譯某段程式碼。 ```cpp= #include <iostream> #define VERSION 2 // 定義版本號 using namespace std; int main() { #if VERSION == 1 cout << "版本 1:基本功能" << endl; #elif VERSION == 2 //else if cout << "版本 2:進階功能" << endl; #else cout << "未知版本" << endl; #endif return 0; } // 輸出: // 版本 2:進階功能 ``` * #if VERSION == 1,如果 VERSION 是 1,則執行對應程式碼。 * #elif VERSION == 2,如果 VERSION 是 2,則執行進階功能。 * #else,如果沒有匹配的條件,則執行 未知版本。 ### 4. 常見用法:防止重複包含頭文件 在 C/C++ 中,防止頭文件被重複包含的常見做法是使用 #ifndef: 這樣可以避免頭文件被多次包含,導致編譯錯誤。 ```cpp= #ifndef MY_HEADER_H #define MY_HEADER_H void myFunction(); #endif // MY_HEADER_H ``` ## 模板template 允許我們用一個通用的函式來適用於不同類型的變數,而不需要重複編寫相同邏輯的函式。 ### 函式模板 ```cpp= template <typename T> T add(T a, T b) { return a + b; } int main() { cout << "整數相加: " << add(3, 5) << endl; cout << "浮點數相加: " << add(2.5, 3.7) << endl; return 0; } // 輸出: // 整數相加: 8 // 浮點數相加: 6.2 ``` * T 是 泛型參數(Type Parameter),代表任何數據類型。 * add(3, 5) 會自動推導 T 為 int。 * add(2.5, 3.7) 會自動推導 T 為 double。 ### 類別模板 ```cpp= template <typename T> class Box { private: T value; public: Box(T val) : value(val) {} void show() { cout << "Box value: " << value << endl; } }; int main() { Box<int> intBox(10); Box<double> doubleBox(5.75); Box<string> strBox("Hello Template"); intBox.show(); doubleBox.show(); strBox.show(); return 0; } // 輸出: // Box value: 10 // Box value: 5.75 // Box value: Hello Template ``` ### 非型別模板參數 除了類型參數 typename T,模板還可以接受 非型別參數(整數、指標、引用等)。 通常是陣列之類的才需要接受參數(引數) ```cpp= template <typename T, int SIZE> class Array { private: T arr[SIZE]; public: void set(int index, T value) { if (index >= 0 && index < SIZE) arr[index] = value; } T get(int index) { return arr[index]; } void printSize() { cout << "Array size: " << SIZE << endl; } }; int main() { Array<int, 5> intArray; intArray.set(0, 42); cout << "Element at index 0: " << intArray.get(0) << endl; intArray.printSize(); return 0; } // 輸出: // Element at index 0: 42 // Array size: 5 ``` #### 函式泛型化 ```cpp= template <class ForwardIterator, class T>//通常class跟tepename可以互相替代 ``` 函式模板 或 類別模板 的開頭,表示: * ForwardIterator:代表某種「迭代器(Iterator)」類型。 * T:代表某種「資料類型」。 **(1) ForwardIterator:泛型「迭代器類型」** - ForwardIterator 讓函式可以接受 任何可遍歷的容器類型,不只是 int*(指標),還可以是: * vector\<int\>::iterator * list\<double\>::iterator * set\<string>::iterator * 甚至 指標(int*) **(2) T:泛型「資料類型」** * T 讓函式可以適用於 任何資料型別,不只 int,還可以是: * double * string * char * 甚至是使用者自訂的型別 **沒有模板** 這個函式只能處理 int 陣列,如果要找 double 或 string,就得再寫一次類似的函式。 ```cpp= #include <iostream> using namespace std; int* findInt(int* first, int* last, int value) { while (first != last) { if (*first == value) return first; // 找到,回傳指標 ++first; } return nullptr; // 沒找到 } int main() { int arr[] = {1, 2, 3, 4, 5}; int* result = findInt(arr, arr + 5, 3); if (result) cout << "找到數字:" << *result << endl; else cout << "找不到數字" << endl; return 0; } ``` **有模板** 現在我們想讓這個 find 函式支援 任何型別的容器(如 vector、list)和任何資料型態(int、double、string) ```cpp= #include <iostream> #include <vector> using namespace std; // 泛型 find 函式 template <class ForwardIterator, class T> ForwardIterator myFind(ForwardIterator first, ForwardIterator last, const T& value) { while (first != last) { if (*first == value) return first; // 找到目標值,回傳迭代器 ++first; } return last; // 沒找到則回傳 last } int main() { vector<int> vec = {1, 2, 3, 4, 5}; auto it = myFind(vec.begin(), vec.end(), 3); if (it != vec.end()) cout << "找到數字:" << *it << endl; else cout << "找不到數字" << endl; return 0; } ``` **其中** ```cpp= auto it = myFind(vec.begin(), vec.end(), 3); ``` 編譯器會: * 推導 ForwardIterator 為 vector\<int>::iterator * 推導 T 為 int * 自動產生對應的函式 結果: ```cpp= vector<int>::iterator myFind(vector<int>::iterator first, vector<int>::iterator last, const int& value) ``` const int\& value 是完整複製變數並且在函式中不能改變的意思 ## int、long、long long、double大小 1byte = 8bits sizeof(int a) => 4byte sizeof(int a[10]) => 40byte sizeof(char c) => 1byte sizeof(char c[10]) => 10 byte | 類型 | 最大值 | |:---------:|:------------------:| | int (4byte)| 2^31^ - 1 (2.1*10^9^) | | int* (8byte)| | | long | 2^31^ - 1 | | long long (8byte)| 2^63^ - 1 (9.2*10^18^)| | size_t (=unsigned long long)(8byte) | 2^64^ -1(1.8*10^19^) | float| 2^128^ (3.403x10^38^)| | double| 2^1024^ (1.798x10^308^)| | INT_MAX| int 最大值| | INT_MIN| int 最小值| | LLONG_MAX| long long 最大值| | LLONG_MIN| long long 最小值| | FLT_MAX| float 最大值| ## 看型別 ```cpp= typeid(k).name(); ``` ## 函數預設值 ```cpp= int function_name(int param1, int param2 = default_value); ``` EX: ```cpp= int tailrecsum(int x, int runningtotal = 0) { if (x == 0) return runningtotal; return tailrecsum(x - 1, runningtotal + x); } int main() { cout << tailrecsum(5); // ✅ 自動使用 runningtotal = 0 cout << tailrecsum(5, 10); // ✅ runningtotal = 10 //避免編譯錯誤(若 runningtotal 無預設值,呼叫 tailrecsum(5); 會報錯) } ``` 要注意預設值要從右設到左,從最後設到前面 ```cpp= int func(int a, int b = 10); // ✅ 正確 int func(int a = 10, int b); // ❌ 錯誤:非最後一個參數不能有預設值 ``` ## switch 可以用if來替代,但可讀性有差 ```cpp= int day = 6; switch (day) { case 6: cout << "Today is Saturday"; break; case 7: cout << "Today is Sunday"; break; default: cout << "Looking forward to the Weekend"; } ``` 可以多個case做同一件事,下面的case0與case1做相同的事 ```cpp= for(int a=0;a<3;a++){ switch(a){ case 0: case 1: cout<<"01"; break; case 2: cout<<"02"; break; } } //010102 ``` ## binary search 時間複雜度log(n) ```cpp= binary_search(a.begin(),a.end(),val);//回傳是否找到值 lower_bound(a.begin(),a.end(),val);//找到大於等於的值的位置 auto it=lower_bound(a.begin(),a.end(),make_pair(a,b)) auto it=lower_bound(a.begin(),a.end(),pair<int,int>(a,b)) //pair的正式寫法要用make_pair()或pair<int,int>()而不是{a,b}(會錯)。 template < ForwardIterator, class T> bool binary_search (ForwardIterator first, ForwardIterator last, const T& val) { first = std::lower_bound(first,last,val); return (first!=last && !(val<*first)); }//背後原理 //搜尋struct struct Node{ int x,y,z; }; bool cmp(const Node &a,const Node &b){ if(a.x == b.x && a.y == b.y)return a.z<b.z; else if(a.x == b.x)return a.y<b.y; else return a.x<b.x; }//每一層都要排到序才行 bool cmp2(const Node &a,const Node &b){ return a.x<b.x; } bool cmp3(const Node &a,const Node &b){ if(a.x == b.x)return a.y<b.y; else return a.x<b.x; } signed main(){ vector<Node> a={{1,2,3},{3,4,5},{5,6,7}}; Node ans={1,2,3}; sort(a.begin(),a.end(),cmp); binary_search(a.begin(),a.begin()+3,ans,cmp);//ans的地方不能放{1,2,3} //用struct一定要用cmp才行 Node b;//假如只想搜尋一個元素的方法 b.x=1; cout<<binary_search(a.begin(),a.end(),b,cmp2); Node b;//假如只想搜尋兩個元素的方法 b.x=1; b.y=2; cout<<binary_search(a.begin(),a.end(),b,cmp3); } ``` **pair版** ```cpp= #define pii pair<int,int> #define ff first #define ss second bool cmp4(pii a,pii b){ if(a.ff==b.ff)return a.ss<b.ss; else return a.ff<b.ff; } signed main(){ vector<pii> a={{1,4},{1,3},{5,6}}; pii ans={1,4}; sort(a.begin(),a.begin()+3); cout<<binary_search(a.begin(),a.begin()+3,pii(1,2));//ans的地方不能放{1,2},可以不用cmp。 } ``` ## 陣列最大長度問題 陣列,是在棧(stack)中申請的一段連續的空間。棧的預設大小為2M或1M,開的比較小。 全域性變數,全域性陣列,靜態陣列(static)則是開在全域性區(靜態區)(static)。大小為2G,所以能夠開的很大。 而malloc、new出的空間,則是開在堆(heap)的一段不連續的空間。理論上則是硬碟大小。 ```cpp= //測試環境ram==8GB vscode int a[22000][22000];//用相乘22000*22000計算最多到500000000 int a[490000000];//差不多這麼大 int mian(){ int array[500000];//差不多這麼大 char a[4*518028]; int a[2000][200];//用相乘2000*200計算最多500000 //vector、new放外面裡面一樣大 vector<int>a(5000000000); vector<vector<int>> a(200000,vector<int>(20000)); //二維也是相乘最多5GB,但編譯時間無敵久 //new方法 二維new最多開5GB int *tem =new int [1000000001]; int **a=new int*[500000]; for(int i=0;i<500000;i++){ a[i]=new int[10000]{0}; } } ``` ## 數學 ```cpp= pow(a,b);//a的b次方 sqrt(a);//==pow(a,(float)1/2) round(a);//4捨5入 4.5 -> 5 floor(a);//無條件捨去 4.5 -> 4 ceil(a);//無條件進位 4.2 -> 5 a = 12.45 modf(a,&b);//a == 0.45,b == 12; abs(a)//絕對值 ``` ## 指標 *去那個位置中的值 &取得它位置 ```cpp= int a //宣告變數 int *a //宣告指標 ``` ### const修飾 ```cpp= int *a;//指向的值與指向的地址都可以改 const int *a;//指向的值不能改 指向的地址可以改 int * const a;//指向的值可以改 指向位置不能改(不能寫成int const *a) const int * const a;//指向位置與值都不能改 ``` ### 函式指標 ```cpp= int main(){ void hello(); void(*func)()=hello;//(*func)()一定要這樣寫,原本hello要寫成&hello,但hello本來就會自動轉型成&hello,所以可以簡化 func();//func()隱性轉型成&func() (***func)();//(***func)()隱性轉型成(***&func)(),*跟&抵銷變成(**func)() (&func)();//編譯錯誤自動轉型後變成(&&func)()沒辦法抵銷 } void hello(){ cout<<"hello"; } ``` 有傳參數的話括號裡面要寫東西 ```cpp= void hello(int,int); void(*func)(int,int)=hello;//(*func) ``` ### 指標傳函式 sort(a.begin(),a.end(),cmp)中cmp的原理 ```cpp= int main(){ void hello(int(*)(int,int)); int add(int ,int); hello(add); return 0; } void hello(int (*op)(int,int)){ cout<<op(1,2); } int add(int a,int b){ return a+b; } ``` ### 陣列用其他指標搭配[ ] ```cpp= char c[]="ab"; char *d=c; cout<<d[1]; ``` ## 函式完整複製變數 ```cpp= void change(int &a){ a=2; } int main(){ int h=1; cout<<h<<" ";// 1 change(h); cout<<h;// 2 } ``` ## 遍歷 ```cpp= for(auto i:a){ cout<<i<<endl; i = i*2;//不會改到值 }//auto不能改成vector<int>::iterator for(vector<int>::iterator i=a.begin();i!=a.end();i++){ cout<<(*i); i = i*2;//會改到值 } ``` ## algotithm ### heap ```cpp= int myints[] = {10,20,30,5,15}; vector<int> v(myints,myints+5); make_heap (v.begin(),v.end());//製作,要先執行。把數字最大排在第一,其他亂排 pop_heap (v.begin(),v.end()); //把數字最大排在最後,其他亂排 v.push_back(99); push_heap (v.begin(),v.end());//(重排)把數字最大排在第一,其他亂排 std::sort_heap (v.begin(),v.end());//用sort就好 ``` ### reverse 反轉 ```cpp= myvector//1 2 3 4 5 reverse(myvector.begin(),myvector.end());//5 4 3 2 1 ``` ## conditional(ternary)operator三元運算子 ```cpp= ((i==a) ? 10 : 20);//if i==a return 10,else return 20. ``` ## 位元運算子 \&優先權大於\| \&\&\與|\|也是 ```cpp= // >> << 0001 << 1 = 0010//右加零 左去掉 1000 >> 1 = 0100//左加零 右去掉 // & AND 要兩個都是1 0 & 0 = 0 0 & 1 = 0 1 & 0 = 0 1 & 1 = 1 // | OR 其中一個是1 0 | 0 = 0 0 | 1 = 1 1 | 0 = 1 1 | 1 = 1 // ^ XOR 兩個不一樣 0 ^ 0 = 0 0 ^ 1 = 1 1 ^ 0 = 1 1 ^ 1 = 0 // ~ not 補位(相反) ~ 00000000000000000000000000000011 -> 3 ---------------------------------- 11111111111111111111111111111100 -> -4 ``` 運算例子 ```cpp= //16進位表示法 但底層都是用二進制 //去掉最後四位 取8~4位後 最左邊加上 1111 0x10 | (N>>4) & 0xF /* * ex: * n= 000100101100 * (N>>4) 00010010 去四位 * & 0xF 00001111 取四位 * = 00000010 * (0x10 |) 11110000 最左邊加1111 * = 11110010 */ ``` ### 特殊技巧 ```cpp= int a=15;//odd 奇數 a<<=1;//a乘以2 a>>=1;//a除以2 (慎用,-9 變 -5 但 9 變 4) a=(a|1);//if a is even ,plus 1. if a is odd,no add. a=(a&1);//equal a%2 void swap(int &a ,int &b){ a^=b; b^=a; a^=b; } ``` ## 三元運算子 條件式 ? 條件式為true時執行的陳述句 : 條件式為false時執行的陳述句 ```cpp= int ret, a = 10, b = 11; bool flag = true; if (flag) ret = a; else ret = b; ``` 相當於 ```cpp= ret = flag ? a : b; ``` ## distance,resize,unique ```cpp= //時間複雜度是 O(n) int myints[] = {10,20,20,20,30,30,20,20,10}; // 10 20 20 20 30 30 20 20 10 it = unique (myvector.begin(), myvector.end()); // 10 20 30 20 10 ? ? ? ? ^ 傳回的位置 v.resize(大小) distance(v.begin(),v.end()) ->[a,b) ``` ## insert ```cpp= vector<int> a{2,3,4}; a.insert(a.begin(),0)//0 2 3 4 a.insert(a.begin()+1,1);//0 1 2 3 4 ``` ## 離散化 當要數字代表的陣列太大時使用,重點是取得相對位置 ```cpp= vector<int> a{2,1,10}; sort(a.begin(),a.end());//要先排列因為lower_bound要用排列過後的陣列 a.erase(unique(a.begin(),a.end()),a.end());//刪除多餘的元素,unique刪除連續重複的元素並傳回新陣列end的位置。 lower_bound(a.begin(),a.end(),x)-a.begin()+1;//查詢X所在的位置。沒有則返回第一個大於X的位置。 upper_bound(a.begin(),a.end(),x)-a.begin()+1;//離散化沒用到(補充),返回第一個大於X的位置。 ``` ## sort 包含在\<algorithem> sort只會交換陣列裡面的值,位置不變 複雜度n*log(n) ```cpp= #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> a{2,1}; int k[2] = {2,1}; cout<<&a[0]<<endl; cout<<a[0]<<endl; sort(a.begin(),a.end()); cout<<a[0]<<endl; cout<<&a[0]<<endl; /*0x1076eb0 2 1 0x1076eb0 */ cout<<&k[0]<<endl; cout<<k[0]<<endl; sort(begin(k),k+2); cout<<k[0]<<endl; cout<<&k[0]<<endl; /* 0x7fffdcda5918 2 1 0x7fffdcda5918 */ } ``` ```cpp= bool cmp1(const vector<int> a, const vector<int> b){ return a[1] > b[1]; }//一微陣列用法 bool sortbysec(pair<int,int> a,pair<int,int> b){ return (a.second < b.second);//升序1,3,4,6 }//pair用法 沒有指定時就是排first的順序。 sort(a.begin(),a.end(),cmp) ``` ### 多條件排法 #### 法一 ```cpp= struct Node{ int x,y,z; }; bool cmp(const Node &a,const Node &b){ if(a.x == b.x && a.y == b.y)return a.z<b.z; else if(a.x == b.x)return a.y<b.y; else return a.x<b.x; }//每一層都要排到序才行 signed main(){ vector<Node> a={{1,2,3},{3,4,5},{5,6,7},{3,7,1},{2,0,518}}; Node ans={1,2,3}; sort(a.begin(),a.end(),cmp); for(auto i:a)cout<<i.x<<" "<<i.y<<" "<<i.z<<endl; } ``` #### 法二 ```cpp= #include <bits/stdc++.h> using namespace std; struct Node{ int x,y,z; }; bool cmp(const Node &a,const Node &b){ if(a.x > b.x)return false; else if(a.x == b.x && a.y>b.y)return false; else if(a.x == b.x && a.y==b.y && a.z>b.z)return false; else return true; }//false會交換 signed main(){ vector<Node> a={{1,2,3},{3,4,5},{5,6,7},{3,7,1},{2,0,518}}; Node ans={1,2,3}; sort(a.begin(),a.end(),cmp); for(auto i:a)cout<<i.x<<" "<<i.y<<" "<<i.z<<endl; } ``` #### 法三(最推) ```cpp= int cmp(vector<int> a, vector<int> b) { if (a[0] != b[0]) return a[0] < b[0]; else if (a[1] != b[1]) return a[1] < b[1]; else return a[2] < b[2]; } signed main() { vector<vector<int>> arr = {{3, 2, 4}, {2, 1, 2}, {5, 7, 2}, {3, 1, 4}}; sort(arr.begin(), arr.end(), cmp); } ``` ## 陣列 ```cpp= int arr[3] = {7};//會是7 0 0 int arr[z][y][x] = {};//全部初始為0,{}裡面不用放東西 ``` ### 陣列不能亂用函式傳指標 **下面會出錯** ```cpp= char* a(){ char seg[7]="000000"; return seg; } int main(int argc, char *argv[]) { printf("%s",a());//印不出東西來 } ``` ### 陣列用其他指標搭配\[ \] ```cpp= char c[]="ab"; char *d=c; cout<<d[1]; ``` #### 但二維會出錯 **錯誤語法** ```cpp= int a[2][2]={{1,2},{3,4}}; int* b =a; printf("%d\n",b[1][0]); ``` **正確方法** ```cpp= int a[2][2] = {{1, 2}, {3, 4}}; int (*b)[2] = a; // b 是一個指向包含 2 個 int 元素的陣列的指標 // a 的型態是 int (*)[2],即「指向包含 2 個 int 元素的陣列的指標」 printf("%d\n", b[1][0]); // 輸出 3 ``` **三維的話** 宣告了一個指標 b,其型態是 int (*b)[3][4]。這表示 b 是一個指向「包含 3 行,每行 4 個元素的二維陣列」的指標。由於 a 的型態退化為 int (*)[3][4],所以 b 可以正確地指向 a。 ```cpp= int a[2][3][4] = { { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }, { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} } }; // 使用指標來指向三維陣列 int (*b)[3][4] = a; // 打印 b 指向的三維陣列中的一些元素 printf("a[0][1][2] = %d\n", b[0][1][2]); // 輸出 7 ``` ### 陣列在函式中 陣列是一串連續的記憶體,但在函示中只會傳位置與值,長度另外傳。所以在函式中只佔8byte(兩個int的長度)。簡單來說,在宣告函式中就只是複製陣列的位置與長度,因此可以改到本體。 ```cpp= void printA(int b[]){//int b[]也可改成int *b /*完整複製 除非b[]改成(&b)[100]但是長度要一樣,且for(auto)也要改成這個才能運行。 sizeof(b)/sizeof(b[0]);就會是100*/ int a = sizeof(b)/sizeof(b[0]);//長度不是100而是2 cout << sizeof(b) << " "<< a << endl; for(int i=0;i<100;i++){ cout<<b[i]<<" "; } b[0] = 1;//會改到原本的 } int main(){ int b[100] = {0}; cout << b <<endl;//注意這是位置0xced21ff520 int a = sizeof(b)/sizeof(int);//算長度的方式 cout << a << endl;//100 printA(b);//只有傳位置與長度大小所以佔4*2個byte } ``` 但是vector就要傳位置(跟其他元素一樣) ```cpp= int printV(vector<int> *a){ vector<int> k = *a;//只是複製一份 for(int i=0;i<k.size();i++){ cout<< k[i]<<" "; }//1 2 3 k[1] = 111;//沒改到1 2 3 (*a)[1] = 111;//有改到1 111 3 } int main(){ vector<int> v{1,2,3}; printV(&v); cout << v[1]; } ``` ### 多維陣列 ![](https://i.imgur.com/WCeEDdC.jpg) c的多維陣列沒辦法像vector寫死大小傳入到函式中。因為c的多維陣列是連續的,所以要用算的。 ```cpp= /* 錯誤示範 void print(int a[][][] 或 a*** 或 a* ){//除非寫死大小int a[4][3][2] for(int i=0;i<l;i++){ for(int j=0;j<r;j++){ for(int k=0;k<c;k++){ cout<a[i][j][k]; } cout<<endl; } cout<<endl; } }*/ void print(int *a){ for(int i=0;i<l;i++){ for(int j=0;j<r;j++){ for(int k=0;k<c;k++){ cout<<*(a+i*r*c+j*c+k); } cout<<endl; } cout<<endl; } } int arr[l][r][c]={}; print((int*)arr);//(int*)arr=(&arr)[0][0][0]=int * //arr=&arr[0]=int *[][] ``` ### 特殊寫法 #### 二維 ```cpp= void func(int arr[][5]){//這裡是關鍵,5一定要放在這裡,而且不用寫int * for(int i=0;i<2;i++){ for(int j=0;j<5;j++){ cout<<*(*(arr+i)+j); } } arr[0][0]=11;//一樣會改到 } int arr[2][5]={{1,2,3,4,5},{6,7,8,9,10}}; func(arr); cout<<endl<<arr[0][0];//11 ``` #### 三維 依此類推也是這樣 ![](https://i.imgur.com/hkQWoEO.jpg) ```cpp= void func(int arr[][2][5]){ for(int i=0;i<2;i++){ for(int j=0;j<2;j++){ for(int k=0;k<3;k++){ cout<<*(*(*(arr+i)+j)+k)<<" "; } cout<<endl; } } arr[0][0][0]=11; } int arr[2][2][5]={ {{1,2,3},{6,7,8}}, {{9,10,11},{12,13,14}} }; func(arr); cout<<endl<<arr[0][0][0]; ``` ## class 用法 ### 變數 ```cpp= #include <iostream> using namespace std; class Car { // The class public: // Access specifier int a; int b; };//注意要打分號 int main() { Car x[4];// Print values x[1].a=1; x[1].b=2; cout<<x[1].a<<endl; swap(x[0],x[1])//也可以整坨交換 return 0; } ``` ### 含式 ```cpp= void print(){ cout<<"good"; } class myclass { public: void print(){ cout<<"bad"; print();//印出bad 然後一直遞回下去不會印上面的good } int hello(int a) { return a; } }; signed main() { myclass obj; cout << obj.hello(1); obj.print(); return 0; } ``` ## struct ### 使用方法 ```cpp= struct Point{ int x, y; }; int main(){ Point p,x=1,k; cout<<p.x;//1 p={1,2};// c++11以上可以這樣寫 p=(struct Point){1,2};// c++11之下要用標準寫法 k={1,2}; if(k.x==p.x && k.y==p.y)//合法 if(k==p)//兩坨直接比是不合法的 } ``` ### 配合typedef c++可以不用typedef就直接用Point宣告東西 ```cpp= typedef struct Point{ int n; }Point;//把struct Point命名成Point ``` ```cpp= typedef struct Point{ struct Point link; }Point;//如果要用串列鏈結指向自己就不能先用重新命名的來宣告 ``` ### 記憶體算 https://hackmd.io/TZ2D4_O1Rbm-o3c7cjoObw ## bool 印成true ```cpp= cout.setf(ios_base::boolalpha);布林值會印成true false 而不是0 或 1 ``` ## partition (分組) ```cpp= vector<int>v{1,3,2,4,5}; partition(v.begin(),v.end(),[](int i){ return i%2==0; });//2 4 1 3 5不會照順序 partition(v.begin(),v.end(),[](int i){ return i>=3; });//5 3 4 2 1 partition_point(v.begin(),v.end(),[](int i){ return i%2==0;});//傳送第一組的end()->1的位置 is_partitioned(v.begin(),v.end(),[](int i){ return i%2==0;});//判斷有沒有分過組 ``` ## switch 用法 有點像是if else的用法,但是比較有條理一點。 ```cpp= int day = 4; switch(day) { case 1: cout << "Monday"; break; case 2: cout << "Tuesday"; break; default: cout <<"No match";//當沒有配對到時執行 } ``` ## max_element min_element 返回一個iterator,區間內最大的值。 ```cpp= vector<int> vec={2,3,1,5};//也可以用陣列 int max=*max_element(vec.begin(),vec.end());// 5 //注意這裡是返回iterator,要加*才是值 ``` ## enum 有點像是字指定給的數字 https://www.programiz.com/c-programming/c-enumeration ```cpp= enum week {Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday}; enum week today; today = Wednesday; printf("Day %d",today+1);//3 ``` ## static variable 跟全域變數不樣一樣,可以活永久的時間,但只活在自己的scope內。 ```cpp= void fuc(int i){ static int x=0; x++; if(i==4)cout<<x;//5 return; } int main(){ for(int i=0;i<5;i++){ fuc(i); } } ``` ## 看C++編譯器版本 ```cpp= cout << __cplusplus << endl; ``` 看數字對應到的版本 ``` g++ main.cpp 199711 g++ -std=c++11 main.cpp 201103 g++ -std=c++14 main.cpp 201402 g++ -std=c++17 main.cpp 201500 ``` ## unique與erase ```cpp= //每個區塊有重複的元素會被丟到後面,然後回傳刪完的尾端。 //接著用erase刪掉 int myints[] = {10,20,20,20,30,30,20,20,10}; // 10 20 20 20 30 30 20 20 10 vector<int> myvector (myints,myints+9); vector<int>::iterator it; it = std::unique (myvector.begin(), myvector.end()); // 10 20 30 20 10 ? ? ? ? // ^ erase(it,myvector.end()); ``` ## 記憶體分配 ![image](https://hackmd.io/_uploads/SyTDUWYJJl.png) malloc不會指定值,但calloc與realloc都會指定值為0。realloc可以用在擴充陣列大小。 ![image](https://hackmd.io/_uploads/ryr8Y-FyJe.png) **malloc、free** ```cpp= int *arr = (int *)malloc(5 * sizeof(int)); // 分配空間給 5 個整數 if (arr == NULL) { printf("記憶體分配失敗\n"); return 1; } free(arr); // 釋放記憶體 ``` **calloc** ```cpp= int size=10; int *arr = (int *)calloc(5, sizeof(int)); // 分配並初始化 5 個整數 printf("%d",arr[1]);//0 ``` **realloc** 可以變大變小矩陣 但使用 realloc 縮小記憶體時,後面的數據會被釋放並無法再訪問。 ```cpp= int size=10; int *arr = NULL; // relloc之前的指標不能沒有指定東西,就算是NULL也好 arr = (int *)realloc(arr, 10 * sizeof(int)); printf("%d",arr[1]); ``` **memset** 複雜度O(n) 可用來重置陣列 1.int用memset()函数初始化时,只能初始化为0或者-1,否则值將為随机数。 2.char数组利用memset()函数初始化时不受限制,可初始化为任意字符。 ```cpp= arr[2][2]; memset(arr,0,sizeof(arr));//arr[0][0]=0 arr[1][1]=0 memset(arr,-1,sizeof(arr)); memset(arr,'=',sizeof(arr)); memset(arr,2,sizeof(arr));//不合法的 ``` **new、delet** ```cpp= int* p = new int; // 分配單個變量 delete p; // 釋放單個變量 int* arr = new int[5]; // 分配陣列 delete[] arr; // 釋放陣列 ``` new也可以用來構建class物件 ```cpp= #include <iostream> using namespace std; class MyClass { public: MyClass() { // 建構函數 cout << "物件被初始化!" << endl; } }; int main() { MyClass* obj = new MyClass; // 使用 new 分配記憶體並調用建構函數 delete obj; // 使用 delete 釋放記憶體 return 0; } ``` ## if特殊用法 可以先存入然後再比較 ```cpp= int c; if((c=2)==1){ printf("test1");//沒印出 } if((c=2)==2){ printf("test2");//有印出 } ``` ## 檔案操作 ### 讀取寫入 https://openhome.cc/Gossip/CGossip/FileIO.html ### exit vs EXIT_FAILURE 參數EXIT_FAILURE(是1),EXIT_SUCCESS(是0) https://blog.csdn.net/deniece1/article/details/102880681 ## int 與 unsiged https://blog.csdn.net/leaf_in_the_moon/article/details/121866091 在比较int和unsigned时,会先把int隐式类型转换为unsigned int int型转换为unsigned int型其实就是把int型的31位和表示符号的最高位,一起看作是unsigned int型的32位一并读取,例如: int型1:0000 0000 0000 0000 0000 0000 0000 0001,按unsigned的32位读法仍然为1 int型-1:1111 1111 1111 1111 1111 1111 1111 1111,按unsigned的32位读法为2^32-1 = 4294967295 正数转换为unsigned时,数值与原来相等;负数转换为unsigned时,数值会发生变化。 ```cpp= #include<iostream> using namespace std; int main() { if (-1 < (unsigned int)1) cout << "-1小于(unsigned int)1为真" << endl; else cout << "-1小于(unsigned int)1为假" << endl; } //程序运行打印输出为: //-1小于(unsigned int)1为假 ``` 打印输出结果为: 1 4294967295 ## 二進位 八進位 十六進位 https://blog.csdn.net/leaf_in_the_moon/article/details/121866091 可以輸入不同進位的表示法 程式會自動轉換 ```cpp= #include <stdio.h> int main(void) { int a = 0b10100011; //二进制数字 前綴0b int b = 0244; //八进制数字 前綴0 int c = 0XA5; //十六进制数字 前綴0x printf("八进制输出:\n"); //以八进制形似输出 printf("a=%o, b=%#o, c=%#o\n", a, b, c); //中间加上#,可以输出前缀 printf("十进制输出:\n"); //以十进制形式输出 printf("a=%d, b=%#d, c=%#d\n", a, b, c); //十进制没有前缀,加上没啥用 printf("十六进制输出:\n"); //以十六进制形式输出 printf("a=%x, b=%#x, c=%#X\n", a, b, c); //X大写,则输出的前缀和字母都大写 return 0; } ``` ## 二進位轉十進位 ~~~cpp= string a; for(int i=0;i<strlen(a);i++){ sum=(sum>>2)+a[i]-'0'; } ~~~ ![image](https://hackmd.io/_uploads/Hy-kSI2rC.png) ## struct 算記憶體 1byte = 8bit 4byte = 32bit int = 4byte double = 8byte **規則一:** 每放入一個東西要先檢查前面的記憶體大小是不是要放入的倍數 ```cpp= struct ex { int a;//4byte double b;//放入a之後變4byte,但是4不是8的倍數,所以最後記憶體會變成16byte } printf("%lu", sizeof(ex));//16byte ``` **規則二:** 最後要檢查是不是最大記憶體的大小 ```cpp= struct side { int c;//4byte double s;//4不是8的倍數 4要補成8 8+8=16 int d;//16是4的倍數 不用補 16+4=20 最後20不是8的倍數要補成24 }; printf("%lu", sizeof(ex));//是24byte不是20 ``` 但在指定bit時不適用規則一 然後規則二看的是byte不是指定的bit ```cpp= struct ex { unsigned int a : 28;//29bit unsigned int b : 3;//29不是3的倍數,但不用看規則一,所以28+3=31bit,但31bit不是一個int(4byte)的倍數,所以補成4byte };//4 ``` 電腦預設最大對齊數是8,但其實可以用pragma pack改。改完後最大對齊就不能超過。就是說,如果就算有double的話對齊要是8,但pragma設成2的話最多就只能對齊就只能是2。 ```cpp= #pragma pack(2)//預設2 #pragma pack(4)// 4 #pragma pack(8)// 8 ``` **預設** ```cpp= struct ex { char a; // 0+1 int b; // 4+4 }; //原本是8 ``` **改2** ```cpp= #pragma pack(2) struct ex { char a; // 0+1 int b; // 2+4 }; //變成6 ``` **不具名指定** ```cpp= struct t1 { unsigned int a : 1; unsigned int :0; // 等於unsigned int c:31; unsigned int b : 1; }; // 8 ``` **unsigned int a:32 == int a** ```cpp= struct ss { unsigned int a : 29; //29 0+29(bit) unsigned int b : 31; //31 29+31(bit) unsigned int d : 32; //4byte 8+4 32的話跟int一樣,就沒有指定bit忽略規則一的功能。 unsigned int e : 1; //1bit 12byte+1bit }; //16 ``` **其他例子** ```cpp= struct ex { char a;// 0+1 unsigned int b : 1; // 1+1 因為指定bit不用看規則一 // 1+1 最後才因為int對齊,這裡雖然指定int b的大小,但是最後對齊還是看int的4byte。如果原本就有double在裡面,最後對齊就對齊8byte };//4 ``` ```cpp= struct ex { unsigned int b : 1; // 0+1 int a; // 4+4 };//8 ``` ```cpp= struct ss { char d; // 1 0+1 char a[1]; // 1 1+1 unsigned int b : 8; // 1 2+1 unsigned int : 0; // 1 3+1 unsigned int f : 8; // 1 4+1 double s; // 8+8 int l; // 4 16+4 };//24 ``` ```cpp= #pragma pack(2) struct ss { char d; // 1 0+1 char a[1]; // 1 1+1 unsigned int b : 8; // 1 2+1 unsigned int : 0; // 1 3+1 unsigned int f : 8; // 1 4+1 double s; // 8 6+8 int l; // 4 14+4 };//18 ``` ```cpp= struct test { char a[21];//21 0+21 char b[16];//16 21+16 unsigned int c;//4 40+4 union { char h[13]; char d[40]; };//union 會取最大的40 44+40 struct { char e[40]; char f[3]; char g[6]; };//struct算完後 49 49+84 };//算出133後對齊int完136 ``` ```cpp= #pragma pack(2) struct test { char a[21]; //21 0+21 char b[16]; //16 21+16 unsigned int c; //4 38+4 union { char h[13]; char d[40]; };//40 42+40 struct { char e[40]; char f[3]; char g[6]; };//49 82+49=131 };//132 ``` ```cpp= struct t1 { char c;//1 0+1 struct test { int a[10]; };//40 4+40 char d[3];/3 44+3 };//48 ``` ## 找元素位置lowerbound find 沒辦法排序用find **時間複雜度n** 有排序用lower_bound **時間複雜度logn** 只是要看存不存在就用binary_search **時間複雜度logn** ```cpp= std::vector<int> vec = {1, 3, 5, 7, 9}; int target = 5; auto it = std::find(vec.begin(), vec.end(), target); if (it != vec.end()) { std::cout << "找到了 " << target << ",位於索引 " << (it - vec.begin()) << std::endl; } else { std::cout << target << " 不存在於向量中。" << std::endl; } ``` lower_bound回傳第一個大於或等於要找的元素的位置 找不到會回傳end upper_bound回傳第一個大於要找的元素的位置 找不到會回傳end ```cpp= std::vector<int> vec = {1, 3, 5, 7, 9}; int target = 5; auto lower = std::lower_bound(vec.begin(), vec.end(), target); auto upper = std::upper_bound(vec.begin(), vec.end(), target); if (lower != vec.end() && *lower == target) { std::cout << target << " 找到了,位於索引 " << (lower - vec.begin()) << std::endl; } else { std::cout << target << " 不存在於向量中。" << std::endl; } std::cout << "不小於 " << target << " 的第一個元素位置為索引 " << (lower - vec.begin()) << std::endl; std::cout << "大於 " << target << " 的第一個元素位置為索引 " << (upper - vec.begin()) << std::endl; /* 5 找到了,位於索引 2 不小於 5 的第一個元素位置為索引 2 大於 5 的第一個元素位置為索引 3 */ ``` equal_range如果找不到也是回傳大於的第一個或是end ```cpp= std::vector<int> vec = {1, 3, 5, 5, 5, 7, 9}; int target = 5; auto range = std::equal_range(vec.begin(), vec.end(), target); std::cout << target << " 的範圍從索引 " << (range.first - vec.begin()) << " 到索引 " << (range.second - vec.begin()) << std::endl; //5 的範圍從索引 2 到索引 5 ``` ## define int long long的風險 使用 #define int long long int 會有以下風險: * 影響標準庫:會干擾標準庫中使用 int 的地方,導致不預期的行為。 * 影響第三方庫:會影響第三方庫或舊代碼,可能導致不兼容。 * 內存和性能問題:long long int 佔用更多內存,影響性能。 * 調試困難:代碼難以讀懂和維護,錯誤訊息混淆。 * 全局影響:宏的作用域是全局的,難以控制局部應用。 建議使用typedef或是using ```cpp= typedef long long int ll; // 或使用 C++11 的 using using ll = long long int; ``` ## return 與 exit ### 不同點 **return** 用於 main() 函數的結束,表示程式執行成功。 語義上屬於從 main() 函數返回結果給呼叫者(作業系統)。 ```cpp= int main() { printf("Hello, World!\n"); return 0; // 返回 0 給作業系統 } ``` **exit** 用於任何地方直接結束程式(不局限於 main() 函數)。 是標準庫函數,立即結束程式執行,不會返回到 main()。 * exit(0):成功。 * exit(1):一般性錯誤。 * exit(2):命令行參數錯誤。 * exit(3):文件未找到。 * exit(4):權限問題。 * exit(>4):自定義的錯誤情況 ```cpp= void cleanup() { printf("Cleaning up...\n"); exit(0); // 在其他函數中結束程式 } int main() { cleanup(); return 0; // 永遠不會執行到這裡 } ``` 父進程檢查退出碼 ```cpp= #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> int main() { pid_t pid = fork(); if (pid == 0) { // 子進程模擬不同的退出情況 exit(3); // 假設子進程因文件未找到而退出 } else { int status; wait(&status); // 父進程等待子進程結束 if (WIFEXITED(status)) { int exit_code = WEXITSTATUS(status); if (exit_code == 0) { printf("Child process exited successfully\n"); } else if (exit_code == 3) { printf("Child process: File not found error\n"); } else { printf("Child process exited with code %d\n", exit_code); } } } return 0; } 或是用 Shell腳本處理退出碼 ``` ```bash= #!/bin/bash ./program arg.txt exit_code=$? if [ $exit_code -eq 0 ]; then echo "Program executed successfully" elif [ $exit_code -eq 2 ]; then echo "Error: Command line argument missing" elif [ $exit_code -eq 3 ]; then echo "Error: File not found" else echo "Program failed with exit code $exit_code" fi ``` ### 什麼時候使用 return 0 或 exit(0)? **使用 return 0:** 正常完成程式並退出。 用於 main() 函數的結尾。 **使用 exit(0):** 在程式執行過程中需要立即退出,例如: 某個條件滿足後提前結束程式。 發生不可恢復的錯誤(可以搭配非 0 的退出碼,如 exit(1) 表示錯誤)。 不需要返回到 main() 繼續執行程式。 ## 物件class #### 兩種建構子 ```cpp= class Car { public: string brand; int year; //法一:傳統建構子 //易讀,但有額外的賦值開銷(物件建立時,成員變數會先被初始化為垃圾值,然後再賦值)。 //無法初始化 const 或 reference 成員變數(因為它們不能被重新賦值)。 Car(string b, int y) { brand = b; year = y; } // 法二:初始化列表建構子 //更有效率(避免額外的賦值開銷)且可以初始化 const 或 reference 成員變數。 //可讀性稍差 Car(string b, int y) : brand(b), year(y) {} void display() { cout << brand << " " << year << endl; } }; int main() { Car car1("Toyota", 2020); car1.display(); // Toyota 2020 return 0; } ``` **當需要在建構函式內執行額外邏輯(如 if 判斷):使用傳統方法** ```cpp= class Student { public: string name; int age; Student(string n, int a) { if (a < 0) age = 0; else age = a; name = n; } }; ``` **當有 const 或 &(參考)成員變數:必須使用初始化列表** ```cpp= class Example { private: const int a; int &ref; public: Example(int x, int &y) : a(x), ref(y) {} // 必須使用初始化列表 }; ``` ### 主要架構 ```cpp= // 定義類別 class Car { private: string password; // 只有類別內能存取 public: // 屬性(成員變數) string brand; int year; // 建構函式 如果函式內要用與物件同樣名子的變數,那物件的變數要加this Car(string brand, int year) { this->brand = brand; this->year = year; } // 解構函式 物件被銷毀時自動執行 ~Car() { cout << brand << " 被銷毀" << endl; } // 設定 private 成員 void setPassword(string pwd) { password = pwd; } // 方法(函式) void displayInfo() { cout << "Brand: " << brand << ", Year: " << year << endl; } }; int main() { // 創建物件 //法一 Car car1; car1.brand = "Toyota"; car1.year = 2020; //法二 Car car1("Honda", 2022); car1.setPassword("1234") car1.displayInfo(); // 輸出:Brand: Toyota, Year: 2020 return 0;// 物件 car1 離開作用域時,自動執行解構函式 } ``` ### 物件陣列 ```cpp= class Car { public: string brand; int year; void display() { cout << brand << " " << year << endl; } }; int main() { Car cars[2] = { {"Toyota", 2018}, {"Ford", 2022} }; for (int i = 0; i < 2; i++) { cars[i].display(); } return 0; } ``` #### 隱含的拷貝建構函式(允許使用 {} 進行初始化) ```cpp= Car(const Car& other) {}; // 編譯器自動生成 ``` 允許 Car 物件透過 {} 初始化,例如: ```cpp= Car cars[2] = { {"Toyota", 2018}, {"Ford", 2022} }; ``` * {"Toyota", 2018} 會對應到 brand = "Toyota"; year = 2018; * {"Ford", 2022} 會對應到 brand = "Ford"; year = 2022; C++ 允許用「聚合初始化(Aggregate Initialization)」,這種初始化方式適用於純資料類別(POD,Plain Old Data),其條件是: 1. 沒有 private 或 protected 成員變數 2. 沒有自定義的建構函式 3. 沒有虛擬函式 4. 沒有繼承 **如果提供「顯式建構函式」,聚合初始化會失效** 假設我們改寫 Car 類別,加上明確的建構函式: ```cpp= class Car { public: string brand; int year; // 明確建構函式 Car(string b, int y) : brand(b), year(y) {} void display() { cout << brand << " " << year << endl; } }; int main() { Car cars[2] = { {"Toyota", 2018}, {"Ford", 2022} }; // ❌ 編譯錯誤 return 0; } ``` ❌ 這會導致錯誤! 因為 C++ 不允許聚合初始化用 {} 來呼叫顯式建構函式,需要改成: ```cpp= Car cars[2] = { Car("Toyota", 2018), Car("Ford", 2022) }; 或是 Car cars[] = { Car("Toyota", 2018), Car("Ford", 2022) }; ```