# 細數C/C++的奇怪特性 ![image](https://hackmd.io/_uploads/ryC3SGV6gx.png =50%x) 眾所周知 C/C++ 是一個非常龜毛的語言。 本文特別徵選幾個最為人所忽視的C++小細節,作為茶餘飯後的輕鬆小話題。 ## C中 qsort 的用法 `qsort(矩陣指標, 大小, 元素大小, 比較器);` 不要忘記 不要錯過 e.g. ```c=1 #include <stdlib.h> /* stdlib includes free, malloc, calloc * and most imporatntly, qsort. */ // 特別注意參數型態 int cmp(const void* l,const void* r) { // 也要轉成 const pointer, god damn int a = *(const int*)l, b = *(const int*)r; if (a==b) return 0; else if (a<b) return -1; return 1; } int main() { int arr[] = {1,1,4,5,1,4}; // 一定要自己寫cmp, god damn qsort(arr,sizeof(arr),sizeof(int),cmp); } ``` 首先,要特別注意回傳值,跟 `std::sort` 要寫的 `cmp` 差很多,那個回傳的是 `bool`,這邊要的是 `<0`, `=0` or `>0` 的數值。 然後 ```cpp if (a==b) return 0; else if (a<b) return -1; return 1; ``` 可以替換為 ```cpp if (a==0) return 0; return a<b?-1:1; ``` 如果 you're paranoid about overflow. ## C/C++ IO ### 讀一整行,以及 `getline` 如果你是在 C,讀一整行的方法是使用 `fgets` 他的 `declaration` 是 `char *fgets(char *str, int n, FILE *stream)` ```c char buf[100]; fgets(buf, 100, stdin); ``` 如果你是在 C++,讀一整行的方法是使用 `getline` 他的(其中一個) `declaration` 是 ```cpp std::basic_istream<CharT, Traits>& getline( std::basic_istream<CharT, Traits>& input, std::basic_string<CharT, Traits, Allocator>& str ); ``` ```cpp string s; getline(cin,s); ``` 常見的誤會是因為 C++ 有 `getline`, C 也有 `getline` 就覺得 C 也是用 `getline` 實情是: C++ 的 `getline` 是 `std::string::getline`, 是附屬於 `<iostream>` 之下的,跟 `string` 搭配使用的 `getline` 而 C 的 `getline` 不是內建的函數,有的地方有,有的地方沒有,還會很阿莎力的自動為傳入的 `char*` 分配記憶體。 其實,我們還有第三種 `getline`,就是 `cin.getline` 一樣來自 `<iostream>`,不過功能大同小異。 > 和cin.getline()类似,但是cin.getline()属于istream流,而getline()属于string流,是不一样的两个函数 > from https://www.cnblogs.com/flatfoosie/archive/2010/12/22/1914055.html ![image](https://hackmd.io/_uploads/ByETmGhpxl.png =50%x) ### `printf, scanf` 的用法 兩個都是用來格式化輸入。 重要的格式化符號如下 * for int: `%d %lld %llu %lf` * for char/string `%s %c` 用錯會有亂碼。 ```c int x,y,z; scanf("%d %d %d",&x,&y,&z); // 也可以 int x,y,z; scanf("%d%d%d",&x,&y,&z); ``` 不要再往 `scanf` 加 `\n` 了,會多讀。 讀不夠時會回傳 `EOF` 也就是 `-1` ## C 常用 headers ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <limits.h> #define ll long long #define max(a,b) (((a)<(b))?(b):(a)) #define min(a,b) (((a)>(b))?(b):(a)) #define true 1 #define false 0 #define bool int ``` ## const pointers Refer to : https://cdecl.org/ ```cpp int p; // 這是 `int` const int p; // 這是常數 `int` int const p; // 這也是常數 `int` int* p; // 這是指向 `int` 的指針 const int* p; // 這是指向常數 `int` 的指針 int const* p; // 這是指向 `int` 的常數指針 int* const p; // 這也是指向 `int` 的常數指針 const int* const p; // 這是指向常數 `int` 的常數指針 int* const* p; // 這是指向[指向 `int` 的常數指針]的指針 int* const** const p; // 這是指向[指向[指向 `int` 的常數指針]的指針]的常數指針 ``` ## `tie` / `auto [a,b]` = ... 懞懂的少年學會了 `get<n>()`, 學會 `tie(...)` 有一天又學會 `auto [a,b]` (structural binding) 後,拋棄 tie 變成了麻木而事故的大人 tie 難道就一無是處嗎? 不!!請考慮以下情況。 你要從 `queue<pii> p1` 拿出來一些東西做運算,然後從 `queue<pii> p2` 拿出一些東西也要做運算。 如果想只用兩個變數 `templ`, `tempr` 就不能用 `auto [...]` 比方說不能這樣 ```cpp auto [templ,tempr] = p1.front(); // 用這兩個變數做一些事情 auto [templ,tempr] = p1.front(); ``` 這是因爲 `auto` 是宣告,然後不能在同個 scope 重複宣告 所以只能這樣 ```cpp auto [templ1,tempr1] = p1.front(); // 用這兩個變數做一些事情 auto [templ2,tempr2] = p1.front(); ``` 這非常不經濟實惠,因為舊的東西就用不到,卻還要開新變數。這時,這樣做吧 ```cpp int templ, tempr; tie(templ, tempr) = p1.front(); // do things tie(templ, tempr) = p2.front(); ``` ## 其他細項 1. 實作 linked list 一定要設定 `nullptr` (其實不是 C/C++的問題🥀) 2. 這是不行的 ```cpp set<int> has; has.emplace(123); has.erase(has.rbegin()); // 刪除最尾元素? has.erase(prev(has.begin())); // 請用這個 ``` 3. 這也是不行的 ```cpp set<int> has = {1,2,3,4,5,6}; auto it = has.begin(); it = it+1; // 不行 it++; // 但這個可以 advance(it,1); // 可以, 並且是 inplace modify advance(it,n); // 不越界就可以 TC : O(n) ``` 4. 是`Math.atan2(y, x)`,不是`Math.atan2(x, y)` 5. 3.1415926 的 macro 是`M_PI`,不是`PI`。使用請`#include <cmath>` 6. `multiset` 系列不會隨著重複元素的變多而退化成 $O(n)$ 7. Use $\text{lcm}(a,b) = a(b/\gcd(a,b))$ instead of $(ab)/\gcd(a,b))$ to avoid overflow. (也不是 C/C++的問題) (* 真的有題目會這樣搞人, e.g. https://leetcode.com/problems/find-the-maximum-factor-score-of-array/description/) 8. 在C, `NULL` 不是內建語法。他來自 `<stddef.h>` 而這個 header **通常來說** 被 `<stdio.h>` 所 include. 9. C 沒有函數重載。`main`在 C++ 也不能被重載。 10. `int main()` 不用手寫 `return 0`,他會預設自動回傳。 11. `inline` 在當今已形同虛設,僅剩對編譯器的提示作用,然大多編譯器根本不會鳥你。 使用這個關鍵字的你就像是對一堵牆吶喊。 ![](https://i.kym-cdn.com/entries/icons/original/000/037/864/wallcover.jpg =50%x) 加了也基本不會變快,所以如果總是超時,不要懷疑是不是因爲少加了 `inline`。 12. `struct` 的用法在 C 與 C++ 有許多難以羅列的小差別,務必注意。 比方說,`C++` 不需要使用 `struct C` 作為類別名,其中 `C` is a struct. 13. `deque` 因為實作方法,沒有 `reserve()`。 14. 在 C,存在一些編譯環境 A, B 使得: * 在 A,你不需要 `#include <limits.h>` 去使用 `INT_MAX` * 在 B,你需要 `#include <limits.h>` 去使用 `INT_MAX` 15. `x++` 跟 `++x` Ref: https://mkfsn.blogspot.com/2016/11/cc-prefixpostfix-incrementdecrement.html In *some version of* C, 這是不行的。 ```C int a; &(++a); // can't compile ``` In *some version of* C++, 這是可以的。 ```cpp int a; &(++a); // valid ``` Note: 我用的 C 的編譯指令是 `gcc $fileName -o $dir$fileNameWithoutExt && $dir$fileNameWithoutExt` C++ 是 `clang++ $fileName -std=c++23 -o $fileNameWithoutExt && $dir$fileNameWithoutExt` clang 版本:Apple clang version 17.0.0 (clang-1700.0.13.5) 16. 這是不行的。 ```cpp struct Matrix { int n; Matrix(int n); // ... }; using Mat = Matrix; Mat::Mat(int n) { // ... } ``` 你仍必須使用 `Mat::Matrix(int n) { //... }` `Mat` 是方法的名字,不是 `struct` 的名子,因此在 `::` 的語義下不受 `using` 影響。 17. 不要這樣做 ```cpp struct DS { int large[20000][20]; // ... }; ``` 放在 struct 裡的東西會被安排在 stack space 裡 which is very limited 17. 不要這樣做 ```cpp vector<bool> hi(4); bool& s = hi[3]; ``` vector<bool> 有做一些神秘的空間優化 所以編譯不行。要不就用 ```cpp vector<char> hi(4); bool& s = hi[3]; ```