# 細數C/C++的奇怪特性

眾所周知 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

### `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` 在當今已形同虛設,僅剩對編譯器的提示作用,然大多編譯器根本不會鳥你。
使用這個關鍵字的你就像是對一堵牆吶喊。

加了也基本不會變快,所以如果總是超時,不要懷疑是不是因爲少加了 `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];
```