---
# System prepended metadata

title: 細數C/C++的奇怪特性

---

# 細數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`

* 在 Ｂ，你需要 `#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];
```

    