# C++ Primer 第五版簡體中文版閱讀筆記
### C/C++ 基礎先備知識
1. C/C++ 為了執行效率,並不會自動檢查陣列索引值是否超過陣列邊界(不管在定義或是參數傳遞各方面都是這樣,連 warning 都不噴), 我們要自己寫程式來確保不會越界。一旦越界,將導致無法預期的後果。
2. A[B] = *((A)+(B)),這是在 C 語言中的標準定義,所以我今天先 A+=2 之後再 A[-2]也是可以的,可以取到前兩個位置的值,但是如果在標準函式庫「 做出來的 [ ] 」中放負數你會死的很難看。
3. for(auto c : s) 這是 C++ 特有的寫法,意思是把每個 s 中的元素 copy 到 c 執行一次,如果要修改到 s 本身的話必須要用 &c 傳 reference 才行
4. C 和 C++ 中都有規範 ={0} 時視作 static 處理,故自動初始化為 0
5. a->b 的意思是 (*a).b
6. 相同大小的 unsigned 加 signed 會自動把 signed 轉成 unsigned ,不同大小的話就看裝不裝的下來決定是不是 unsigned
7. int array[10] 的意思其實就是 int (*array)[10],array 的變量本質上都是 pointer
### (1/7)心得 @2018/07/15
C++ 比起 C 把對程式整體的操作更加的抽象化,泛型、運算子重載、迭代器都是把程式抽象到「邏輯」的領域去,像是對於一個迭代器的操作我可以透過運算子重載,做到跟指標距離運算結合的數值運算領域,透過泛型我可以建立一個針對不同資料型態統一界面的邏輯操作,而邏輯的統一又更進一步讓界面跟接口可以在物件導向的設計上被共用。
此外 STL 支援的不同資料結構讓設計師不用啥都重頭開始刻,真是太棒惹,只是「別人寫的函式」就會有「不如你預期的地雷」,要特別注意。
### namespace 的美麗與哀愁
using namespace std;
呼叫函式會自動到 std 找,就不用寫 std::cout 可以直接寫 cout
But! 到這裡我想到一個問題
在 python 中我們會特別寫
using namespace std "as stdl";
然後用 stdl.cin 來呼叫,就是為了避免如果我 using 到的兩個套件都有 cin 這個函式,然後讓編譯器找不到是誰。
在 C++ 裡面......~~並沒有人在意這個~~是大家發現這個問題時為時已晚,所以會相撞沒錯,假設如下程式碼
int max = 0;
max(5,1);
對他就撞車了, max 同時為 stdl 裡面的函式又被重命名為變數,所以通常會建議直接打 std::max 或是只在某個 scope 內宣告 using namespace std::max 就好,**並且切記別在標頭檔的 globle 裡面用**,會害死人的,[這裡有一些很有趣的討論](https://www.zhihu.com/question/26911239),[這裡有一些比較正經的討論](https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice)。
> 似乎可以透過寫一些 macro 的方式做到類似 python 的方法? 這可以問問看 Allen。
> 剛剛想想阿不就用 typedef 就行了ㄏㄏ
:::info
這在 C++ Primer 第六版有被明確的拿出來討論,但第五版並沒有。
:::
### cout 的 << 到底在位移什麼
<< 本質上是 cout 這個物件的 function,透過運算子重載來把 << 這個運算子重定義成一個專門處理輸出的 function ,這個 function 的回傳值是一個 string。
所以 `std::cout<< "Apple" << "pen";`
等價於`(std::cout<< "Apple") << "pen";`
然後 `<<endl` 的意思不只包含 `\n` 還包含 fflush 的概念,會清空 buffer
然後這個是左結合律喔!
### 默認初始化
之前看過 memory layout 的介紹說就有提到:
uninitialized data segment:
通常又被稱為 bss segment (block started by symbol, bss),包含未明確初始的global 和 static 變數,當執行程式時會將其這區段的記憶體初始為零。
所以 global 和 static 是會自動初始化的,但區域變數就不會了,若是 class 內又未定義默認值,就會出現未定義的情況。
假設如下
```=c
std::string global_str; //空字串
int glocal_int; //0
int main(){
int local_int; //未定義
std::string local_str; //空字串
}
```
### scope 的覆蓋
用 :: 可以在 scope 內取全域變數
### reference 的使用
用 :: 可以在 scope 內取全域變數
int a = 2
int &r = a <-這樣就綁定了,而且初始化後不得再更改
此外 &r 沒有實體,故不得再 referrence 一次
### std::string 的使用
這裡的字串是真的字串(至少封裝起來了),不是單純的字元串,所以在存儲時到底會不會有 /0 呢!?
查了網路上好幾種不同的答案有人說有,有人說沒有,用我現在電腦的 gcc 測試看看
> 測試之後發現這是個好大的坑阿,C++ primer 根本沒有說清楚這個東西的操作阿 [name=任品謙]
```=c
#include <string>
#include <iostream>
int main(){
std::string test("x",3);
int i=0;
test[1]='\0';
test[2]='a';
while(test[i]!='\0'){
std::cout<<i<<std::endl;
++i;
}
std::cout<<test<<std::endl;
std::cout<<sizeof(test)<<std::endl;
std::cout<<test.length()<<std::endl;
}
```
std::string 本身是一個 32 byte 的物件,裡面還包含了字串長度的定義,所以如果今天你 cout 一個 string 的時候他是固定輸出「定義長度的字元」,並且因為物件內已經包含了長度的定義所以不用以 \0 來做結尾。
But! 如果今天我定義的時候這樣寫 `std::string test("x\0a");` 則在初始化的時候他只會抓到 x ,長度為1,`std::string test("x\0a,5);` 這樣的話就會抓到 'x','\0','a',並且因為登記的長度是3,輸出時會輸出 xa(\0存在但是輸出時會被跳過)
此外因為 string 是一個 32 byte 的物件,所以就算你只宣告`std::string test("xa,2);`卻還是可以修改 test[2] 這種越位的記憶體位置,**而且編譯器有時候不會報錯,執行時也不一定會,這點要非常非常注意。**
這點要非常注意,然後越位的修改是無效的,就算 resize 的時候他依然會刷新該位置,所以以下先上車後補票的玩法無效。
std::string test("abc",n);
test[n]='c';
test.resize(n+1);
### auto 的使用
這是一個很潮的東西,編譯器會自動幫你判別變數型態,但與弱型別不同的是你一定要在初始化時賦值,編譯器材有幫你判別型態的依據,在 C++11 時還只能用在變數上,在 C++14 之後連 function 都可以用了,其實換個說法就是把 gcc extension 的 typeof 規範化了。
```=c
auto a = 1; //自動判別為 int
auto b ; //不合法,無從判斷
等價於
typeof(1) a = 1; //自動判別為 int
```
### 運算子結合順序的問題
在 C++ 支援運算子重載之後,function call 也可以寫成算式的寫法,但是 function call 本質上是不一定支援「結合律」的!
e.g.運算子重載 + 為一個 function call
a+b+c 可以看成 ((a+b)+c) 也就是 `c(b(a))`
但如果我換成
c+b+a 可以看成 ((c+b)+a) 也就是 `a(b(c))`
然而 `c(b(a)) != a(b(c))` 故 `a+b+c != c+b+a`
這個在寫的時候要注意
e.g.
```=c
std::string test("abc");
std::string test2=test+"0"+"a"; //合法 (test+"0")+"a"
std::string test2="0"+"a"+test; //不合法 前方兩個字元串是無法相加的
std::string test2="0"+("a"+test);//合法 後方的字串+字元串=新字串 新字串再+字元串=結果字串
```
這個問題挺好玩的,有種邏輯測驗的感覺,可以拿回去考學弟?
### 邏輯運算執行順序及使用小技巧
邏輯運算的執行順序如下
&& 檢查左方 T-> 檢查右方 T/F-> 返回
------------------F-> 返回
|| 檢查左方 F-> 檢查右方 T/F-> 返回
----------------T-> 返回
利用這個順序可以避免一些問題
if(index!=s.size && iswewant(s[index]))
這樣會先執行左式檢查 index 是否合法再執行右式去存取 s[index],避免存取到無效的記憶體空間。
所有邏輯運算符都是從左到右,然後回傳「Boolean」結果,所以下面這個算式會有問題
3 < x < 5
他其實是這樣看的: (3 < x) < 5
這樣變成把 Boolean < 5 ,然後編譯器會「很好心」的自動幫你把布林轉成 int ,結果這個算式就恆成立了,GG
### vector 的操作
vector 的初始化有兩種,尤其是用 () 和 {} 的初始化是不同的
vector<int> v1(10) // 10 個默認初始化的 int
vector<int> v2{10} // 1 個列表初始化為 10 的 int
且因為操作外層為 vector 對象,所以賦值初始化是不行的
### 迭代器的使用
首先要知道的是,在 C++ 中有很多東西是被「做出來」的,像是 cout << 的 「<<」就是被做出來的,雖然看起來像運算但本質上其實 function call ,迭代器也是一種被「做出來模擬指標行為」的資料型態,所以會有跟指標不同的情況。
以下列出幾個缺點
1. 設計起來麻煩
這是最基礎的問題了,一個指標不用做就可指向任何東西,幹麻多個迭代器?但有時候為了維持界面的共用性或是抽象化邏輯操作,還是得做。
2. 迭代器有失效的可能性
由於迭代器跟容器行為是有嚴密相關性的,在執行某些操作時迭代器是有可能失效的
如下列
```=c
iter = v.begin()
//有機率爆炸的 code
vec.insert(iter);
vec.erase(iter);
//比較安全的 code
iter=vec.insert(iter);
iter=vec.erase(iter);
```
前一段是有可能爆炸的,在迭代器的使用上如果遇到要分配空間的行為(尤其是增加元素的行為),容器有可能直接換另一段更大的空間來放東西,這時之前的迭代器指向的地方就是無效的數據了,這個情況在 for 遍歷的尤其常見,所以切記不能寫出以下代碼:
```=c
//有高機率爆炸的 code(或是編譯器根本不會讓你過)
for(iter = v.begin();iter != v.end;iter++){
v.erase(iter);
}
```
寫出這種炸彈 code 的話我大概會被主管打死QAQ
C++ Primer 給的準則是:請相信 insert 或是 erase 後所有的迭代器都會失效。
此外迭代器設計時要支援 ++ 或是 -- 已經是慣例,所以盡量使用 ++ 或是 -- ,因為相對的迭代器不一定會支援算術運算。
還有如果只是要加一,請盡量用 ++iter 而不是 iter++,因為編譯器對於相對複雜的 iterator 其實比較難作優化,會造成額外的負擔。
### C 風格的字串
C 風格的字串最典型的就是由 空字符結束 0x00,這有好處也有壞處,以下舉一個明顯的例子
```=c
char ca[] = {'C','+','+'}
cout << strlen(ca)<<endl;
```
這是無法預期輸出的,程式有可能沿著 ca 一路跑直到撞到空字串才回報,還有另外一個最近才踩到的雷
```=c
char ca[] = {0x1A,0x00,0x2A}
cout << strlen(ca)<<endl; // = 2
```
撞到 0x00 就會停,管你後面有沒有東西呵呵。
C++ primer 給的說法是:盡量用 std::string 和迭代器,避免使用 pointer 和 array
### 左值與右值
這個發哥考過
lvalue 指 等號的左值
rvalue 指 等號的右值
lvalue 是一個物件的
rvalue 是一個物件在某個時間單位的結果
++c 是lvalue
c++ 是rvalue
### 調用順序
C++ 未明確規範運算左右的調用順序
e.g. A+B
先執行 A 還是 B 全看編譯器,這是為了給編譯器留下優化的空間,也因為這個設計,我們在寫程式時必須注意未定義行為的產生。
像下面這個我一開始以為很漂亮的寫法就是未定義的行為。
arr[i] = (arr[i] + arr[j]) - (arr[j] = arr[i]);
假設 arr[i] = 5 , arr[j] = 4
左邊先執行就會有以下行為
arr[i] 5+4 - (j=5) = 4
右邊先執行就會有以下行為
arr[i] 5+5 - (j=5) = 5
因為右邊先執行了所以左邊的 j 也變成 5,這樣會導引到不同的結果。
### 一元運算在 % 和 / 的定義
-m/n 和 m/-m 都等於 -(m/n)
所以負負得正 -m/-n = --(m/n)
但是在 % 的情況下只看首位
-m % n = -(m%n)
m % -n = m%n
-m % -n = -(m%n)
不搞什麼負負得正的喔!
這是 C++11 的新標準。
### 賦值運算的回傳值
對,賦值運算是有回傳值的,他會回傳他自己,並且滿足右結合律。
所以這種寫法是合法的。
int ival,jval;
ival = jval = 0; // ival = (jval = 0)
所以 ival 會被賦予右邊運算回傳之 jval 的值,然後我想了想還可以加上這樣的玩法
int a;
int *b;
b = &( a = 5);
感覺就很潮齁XDD
然後賦值運算的優先度不高,所以建議加上括號避免錯誤。
此外還有這樣的玩法
if(i = j)
/* 跟下面的寫法一樣意思
i=j;
if (i)
不過這樣寫有可能人家會搞錯就是,建議別用= =
### 三元運算子的使用
三元運算子是可以嵌來嵌去的,像是
a? i? i:j :b;
但是嵌個兩三層之後真的沒幾個人知道你想幹麻,所以當條件多時請就乖乖的寫 if 吧。
還有邏輯運算子的優先權偏低所以如果前面有判斷一定要加括號,如以下例子
cout << a>b? a:b <<endl;
由於 > 的優先權比 << 低,所以編譯器會看成
cout << a ;
cout >b ? a:b< endl ;
然後就爆炸了。
### 位元運算其實是一種未定義行為!?
好啦我標題寫的聳動一點而已,應該說對 sign bit 的位移其實是一種未定義行為,如果今天這個編譯器特別鬧硬要左移就塞 0 那其實也不違反規範,所以 C++ primer 特別強調位移「強烈建議」使用在無符號類型上。
### 逗號運算子
其實逗號也是一個運算子耶= =
他會由左往右運算,然後回傳最右邊那個運算的結果本身,所以下面寫法合法
cout <<( ++a,++b ? a : b)<<endl;
//等價於
++a;
++b;
cout << (b ?a:b) <<endl;
然後我還試著這樣寫
cout <<( ++a,b = a = 10 ? a : b)<<endl;
// 噴出了 556
我本來預期是 10 的捏?應該是被優先權 gank 了,分析一下
//被解析成了
++a;
cout << (10 ? a : b) <<endl;
b=a=(10 ? a : b);
//也就是
cout <<( ++a),b = a = (10 ? a : b)<<endl;
// 看起來真他媽鬼扯。
反正就是這樣,很好玩齁呵呵。
### 不同類型轉換的功能及其限制
型態轉換分六種類型
隱式轉換
(type)expression
dynamic_cast<type>(expression)
static_cast<type>(expression)
const_cast<type>(expression)
reinterpret_cast<type>(expression)
隱式轉換就是我們平常常用的 int 轉 float 或是 int 轉 boolean之類的操作,不用特別註明但是 warning 會叫就是了,然後也內建了一些 promotion 之類的規則,理論上隱式轉換一次只能轉換一個類型。
dynamic_cast 只能轉換為派生類,也就是有相關繼承關係的類別。
static_cast 除了不能轉 const 為非 cons,還有轉爆了要自己負責,編譯器不負責檢查這部份型別轉換之後長度有沒有問題。
| 轉換前類型 | 轉換後類型 | 是否准許 |
| -------- | -------- | -------- |
| 非指標 | 非指標 | 准許 |
| 非指標 | 非指標(長度不同) | 准許 |
| void* | 任意指標 | 准許 |
| 任意指標 | void* | 准許 |
| 任意非void指標 | 任意非void指標 | 不准許|
| 非指標 |指標 |不准許|
| 指標 |非指標 |不准許|
| const |非 const |不准許|
| 非 const | const |准許|
指標間只准許 void 指標的轉換,但是直接互轉是不准許的。
> 基本上 static_cast 的功能就是把隱式轉換的 warning 關掉而已。
> ~~雖然理論上不能轉 const,不過 g++ 並不屌這件事情,我相信比較嚴謹的 VC++ 應該會管, g++ 瘋狂的無視規範也不是第一天了= =。~~
> 非 const to const 本來就符合隱式轉換的標準,上面那句是錯的。
const_cast 就是用來把 const 拔掉或加上去的,常用在 overload 的時候,我可以把父類的 const value/method 改寫成非 const ,相對的它也只能用來處理這個功能,不能同時拔 const 又轉換成其他類型,有需求的話請分開來寫。
reinterpret_cast 通常是用來轉指標的,只要長度相同想非指標轉指標或是反之也不是問題,但是兩個非指標的就不行,那種情況請用 static_cast
| 轉換前類型 | 轉換後類型 | 是否准許 |
| -------- | -------- | -------- |
| 非指標 | 非指標 | 不准許 |
| 非指標 | 非指標(長度不同) | 不准許 |
| void* | 任意指標 | 准許 |
| 任意指標 | void* | 准許 |
| 任意非void指標 | 任意非void指標 | 准許|
| 非指標 |指標(非浮點數) |准許|
| 非指標 |指標(為浮點數) |不准許|
| 指標 |非指標(非浮點數) |准許|
| 指標 |非指標(為浮點數) |不准許|
| const |非 const |不准許|
| 非 const | const |不准許|
(type)expression
這是 C style 的轉換,就是 static_cast 加上 reinterpret_cast 的加強版,就真的是愛怎麼轉就怎麼轉,在 C++ 中不建議這樣的寫法。
| 轉換前類型 | 轉換後類型 | 是否准許 |
| -------- | -------- | -------- |
| 非指標 | 非指標 | 准許 |
| 非指標 | 非指標(長度不同) | 准許 |
| void* | 任意指標 | 准許 |
| 任意指標 | void* | 准許 |
| 任意非void指標 | 任意非void指標 | 准許|
| const |非 const |准許|
| 非 const | const |准許|
| 非指標 |指標(非浮點數) |准許|
| 非指標 |指標(為浮點數) |不准許|
| 指標 |非指標(非浮點數) |准許|
| 指標 |非指標(為浮點數) |不准許|
除了在撞到浮點數會失敗以外,其他愛怎樣轉就怎樣轉。
> g++ 大概是覺得浮點數跟指標互轉太搞笑了才擋掉吧科科,我猜部份編譯器大概連轉浮點數都不會擋掉。
C++ Primer 的建議使用程度如下
dynamic_cast = const_cast > static_cast > reinterpret_cast > (type)expression
以下列舉一個 const_cast 的使用例子
```=c++
string &strchrr(string &origin,string &target){
auto &r = strchrr(const_cast<const string&>(origin),
const_cast<const string&>(target)); // 其實非 const to const 不用加
return const_cast<string&>(r);
}
```
就可以直接用 const 的版本重載出非 const 的版本,真4太蚌惹
### sizeof 運算
sizeof 的回傳值在編譯時期就已決定,所以其實是一個常數,可以用來放在 array 沒有問題,也可以對無效的指針取引用,反正他並不會真的去訪問該位置,只會回傳他註冊的範圍。
### switch 的注意事項
記得加 break 阿,還有 case 後面其實只能放常數,沒想過齁。
C++ primer 建議就算用不上也寫 default,然後有無 break 的情況建議別混用,要混用也要註解清楚,避免後人搞不懂是寫錯還是故意的
### continue 的玩法
這個玩法好帥所以紀錄一下
string buf;
while( cin >> buf && !buf.empty()){
if(buf[0] != '_')
continue;
// 讀取到底線開始的字串 開始處理字串
}
想想好像我已經在 processor 寫了很多這種寫法了
### goto 的用法
想都別想,別用這東西好吧,這東西不但破壞程式結構,還讓編譯器的優化有可能出問題。
### exception 的處理
其實有定義好的異常類可以用,像上次的溢位就可以直接處理不用搞東搞西,特別記一下,之後就不用重新造輪子。
stdexcept 定義的異常
exception 常見問題
runtime_error 只有在運行時才能檢測的問題
range_error 常見的存取錯誤,但是在運行時才發生
overflow_error 運行時發生,計算上溢
underflow_error 運行時發生,計算下溢
logic_error 程式邏輯錯誤(這什麼鬼?)
domin_error 邏輯錯誤 參數對應
invaild_argument 邏輯錯誤 無效參數
length_error 邏輯錯誤 創建一個超出該類別最大長度的對象
out_of_range 邏輯錯誤 使用一個超出有效範圍的值
### () 其實也是運算符
() 是調用運算符,然後在實體化()內部的參數時,並沒有既定的順序,所以請避免未定義行為的發生。
### reference 定義的地雷
用 refernce 可以避免 copy 資料的時間成本,如果是不會修改到的資料請切記加 const ,像是以下 findchar 函數
string::size_type find_char(string &s,char c)
這樣會沒辦法丟不可修改的常量進去,像是這樣的呼叫就會爆掉
findchar("Hello World",'o');
這樣編譯就會爆炸,因為 findchar 只能接受普通引用而非常量引用,反而是如果把型別參數定義成常量引用的話,普通引用是可以自動轉型成常量引用的。
> 重點就是普通引用可以自動轉常量引用,常量引用不能升級為普通引用。
### 請愛用括號明確程式的行為
今天我想要定義一個指向 array[10] 的 ptr
int *aptr[10]; //一個有 10 個 ptr 的 array
int (*aptr)[10]; //一個 ptr 指向有 10個元素的 array
行為是不同的,今天你要定義(一個指針)指向 array[10] 就明確的把前面的定義括好,既不會錯也增加可讀性,尤其是在複雜的定義中,並沒有人想把整個優先表背下來,所以就乖乖的先括號好讓大家知道撰寫者的意思會更好,就像上面的例子
cout <<( ++a,b = a = 10 ? a : b)<<endl;
cout <<(( ++a),(b = a = (10 ? a : b)))<<endl;
這兩個雖然是等價的但下面這個的可讀性完勝上方的敘述。
### 返回 reference 的玩法
返回 reference 的話本質上其實是一個左值,所以是可以放在等號左邊的,
如
char &get_val(string &str)
return &str[2];
string stri = "bbbb";
get_val(stri) = 'A';
### 用容器可以返回多個參數
vector<string> func (){
return {"hello","world"};
}
### 尾置返回類型
不是我要說,這寫法他媽的看起來根本不像是 C
auto func(int i) -> int(*)[10]
// 返回一個指向 int[10] 的指針
20180724 - P171 第五章開始
p201 第六章開始
### 指定返回的範圍
這東西的功能大概就是請編譯器幫忙檢查有沒有找錯地方吧,可讀性也相當不錯就是了,可以多用。
int odd[] = {1,3,5,7,9};
int even[] = {0,2,4,6,8};
decltype(odd) *arrptr (int i){
return (i%2) ? odd:even;
}
看起來似乎只是用現有的東西聲明他的返回型態而已,並沒有說返回的一定要是一開始聲明的那個東西。
### 重載和 const
變數頂層或指針底層的 const 是沒辦法被分辨的,舉例如下
int test(const int);
int test(int);
int test(int*);
int test(int* const);
上方兩兩等價,但是下方不等價
int test(int&);
int test(const int&);
int test(int*)
int test(const int*)
雖然非 const 都可以自動轉換成 const ,但是編譯器會優先選擇非 const 的版本,再考慮 const 的版本。
然後不同的作用域內無法重載,下會蓋上,不是重載而是直接覆蓋。
### 默認參數的注意事項
默認參數只會自動往後補,前面不能跳過
像以下這樣
int test(int a = 0, int b = 0);
test(0)//合法
test(,0);//不合法,不能往前補
### constexpr
將一個物件指定為常量表達式,感覺很有趣但是暫時想不到能幹麻,大概就替換掉 define, constexpr 會指定讓編譯器檢查該值是否能在編譯時期就計算出來,如果不能就丟個 error 給你這樣。
### 函數指針的宣告方式
這東西有夠複雜,搞了我一整天,特別紀錄一下。養成習慣,由內往外解讀,有規律會比較看得懂。
typedef string *(test[3]);
//等價於
typedef string (*(test[3]));
typedef test 為 一個大小為三的陣列->這個陣列是裡面放的是指針->指針是指向字串
typedef string (*last)[3];
//等價於
typedef string ((*last)[3]);
typedef last 為 一個指針->指針指向一個大小為三的陣列->這個陣列是字串的陣列
string (&learn())[3]{
return s3;
}
定義 learn 是一個無參數的 function -> 這個 function 回傳的是引用 -> 是一個大小為三陣列的引用 -> 是一個大小為三字串陣列的引用 = 定義 learn 是一個無參數的 function 回傳值是一個大小為三字串陣列的引用
vector< int((*)(int,int))> num ;
定義一個 vector -> 內容物是一個指針 -> 是一個帶兩個 int 參數的函式指針 -> 是一個帶兩個 int 參數的並回傳 int 的函式指針 = 定義一個 vector 是一個帶兩個 int 參數的並回傳 int 的函式指針 名稱叫做 num ;
媽的真的有夠複雜,難怪敬群說這是頭腦體操= =
### this 的使用注意
理論上 this 是一個 const 指針,所以別妄想從 this 去改東西。
如果今天我要 return 一個 obj 的引用,宣告的部份這樣寫
obj& test(){
return *this;
}
因為 this 本身是一個指標,直接回傳 this 不對,是**回傳 this 指向的東西的引用**才是正確的程式行為
### 用建構子初始化注意事項
如何初始化 const ? 如果希望初始化 const 為一個不定值的話必須這樣寫
class learn{
learn(int i);
int a;
const int b;
int &c;
};
learn::learn(int i):a(i),b(i),c(i){
}
//這樣寫才對 這是在初始化時指定,下面的寫法屬於賦值是錯的
learn::learn(int i){
a = i;
b = i;
c = i;
}
然後初始化的順序按照定義時排的順序,而非建構子指定的順序,千萬別亂排。
此外在使用多個參數的建構子時,不得使用隱式轉換,使用單個建構子的時候隱式轉換最多一次,可以加入 explicit 來讓建構子不得使用隱式轉換。
e.g. vector 就是 explicit 的(容器大概都要這樣設)
### stream 操作注意事項
stream 是一個物件,裡面有一些用來判別的像是 failbit,bad bit,good bit 這些東西,可以透過直接判斷 if(stream) 來檢查,透過 clear 來更改,如果重複 open 之類的也會導致這些 bit 被設置,檢查時返回 false,其他綁定和緩存相關的注意事項寫在這 https://hackmd.io/_u8hbFLjRpmCY_z0l2JYqQ
然後 fstream 對應 fopen 的操作權限如下
in = r
out = w
app = a
ate = point to end
trunc = w
binary = b
app only for ostream, ate for both of I/O, app will point to EOF every time.ate only point to EOF when first.
out default with trunc, primer say the only way let out not to delete message is use with in or app,but if I use with ate?Primer didn't mention that.
And I try to searching that, see here
https://stackoverflow.com/questions/12929378/difference-between-iosapp-and-iosate
ate still trunc,so it is useless at ostream. app have some mine like it will move pointer to EOF when every time buffer been flush.And if you use it without endl or tie,Nobody know when the buffer been flush. app's main features is to prevent race and make sure the new out putwill place at end.
istringstream 的特性是 string 在換行時返回 EOF ,相對起 cin 在文件結束時才返回 EOF,
可以用來處理以下類似資料
假設資料為
danny 09123123 074564546 0912231
andy 56151
sharon
```=c++
while(getline(cin,line)){
istringstream recode(line);
recode >> datainfo.name;
string temp;
while(recode >> temp){
datainfo.number.push_back(temp);
}
people.push_back(datainfo);
}
```
內部的 getline string 撞到換行就會自動把 while 中止,就不用對 cin 做額外判斷。
然後 stream 本身是一種串流的意思,所以好像也可以把 stringstrem 用來當一個、 string 的 queue 來操作。
:::info
今天踩到地雷:使用ofstream写入0x0a时,该函数实际写入0x0d 0x0a两个字节? 去你媽的我寫一個你給我寫兩個,傻了嗎。 據說用 ios::binary 可以解決這個問題,幹但還是很蠢阿。
:::
### 容器
Primer 提出了一個很勁爆的觀點, Never use 內置數組 and pointer.Use STL Container and iterator
> iterator 好難用[name=Allen]
容器支持的額外操作
vector <int> a = {6,6,7};
a = vector<int> b(8);
a = {4,5,6};
上面的寫法都合法,只有 array 不支援,因為怕直接撞出範圍,同時 stl 也幫你寫好的 swap 可以直接用,只要兩邊的類型相同即可。
> 而且該 swap 操作是由內部數據結構操作來完成的,所以會在常數時間內就可以完成。(除了 array 和 string)
> 此外因為不會實際交換位置,所以相關 pointer 和 iterator 還是會指向之前的數字
另外一點就是可以直接讀取其他容器的 iterater 來當作範圍
a.assign(b.begin(),e.end());
這種寫法也合法,但要記得經過這種操作後迭代器那些的都會失效。
deque 有一個特殊性質,插入到首尾之外會導致指針引用迭代器那些全爆,但是如果只插首尾的話會讓迭代器失效,指針跟引用都不會
> C++ primer 建議編程時保證每次改變容器的操作之後都會正確的"重新定位"迭代器,可以善用容器操作的回傳值來達成這點。
> end() 返回的 iterator 非常容易失效,請在每次修改容器後重新調用,反正他的速度非常快多叫幾次不會怎樣。
### 越界問題
C 和 C++ 都有編譯器不檢查存取範圍的原則,但也提供了 at 來確保執行範圍的功能,但如果加上了在存取時的效能會大幅降低,端看使用者決定lo
### adapter
就是可以把 container 轉成特定的資料結構
queue 可以用 vector 以外的東西創
priority_queue 可以用 list 以外的東西創
通常 array 跟 forward_list 以外的都可以創再外加上上面的限制,創完之後原本 container 的資料操作會失效,僅能使用更改後資料結構提供的 push , pop 之類的方式操作,目前除了安全以外沒有想到別的去使用這個資料結構的理由。
### reference
引用右值 OK
引用左值 OK
引用左值的引用 OK
引用右值的引用 不 OK
C++ 只會幫忙保留一層
引用右值時他會幫你保留右值,引用右值的引用時,他會幫你保留該引用
但右值本身會被放掉,然後就爆炸了
引用左值的引用也會有相同問題。
### unordered_map 的坑
map['a']
其實隱含了插入的動作
這個動作會初始化 map['a'] 成 0
所以 map.count('a') 會 = 1
就算沒有 = 去賦值會也觸發該行為