8.IO庫
IO類
三個(gè)頭文件:
iostream 定義了用于讀寫流的基本類型
fstream 定義了讀寫命名文件的類型
sstream 定義了讀寫內(nèi)存 string 對象的類型
頭文件 | 類型 |
---|---|
iostream | istream, ostream, iostream |
fstream | ifsream, ofstream, fstream // 讀寫文件 |
sstream | istringstream, ostringstream, stringstream |
可以添加一個(gè) w 在前面,表示對應(yīng)的寬字符版本闽晦。
>> 可以從不同的來源讀取數(shù)據(jù)扳碍,是通過繼承實(shí)現(xiàn)的。
O沈取笋敞!IO對象無拷貝或者賦值
IO操作可能發(fā)生錯誤,一些函數(shù)和標(biāo)志可以幫助訪問和操縱流的條件狀態(tài):(其中strm是一種IO類型)
:-:|:-:
strm::iostate | iostate是一種機(jī)器相關(guān)的類型荠瘪,提供了表達(dá)條件狀態(tài)的完整功能
strm::badbit | 指出流已經(jīng)崩潰
strm::failbit | 指出一個(gè)IO操作失敗了
strm::eofbit | 指出流到達(dá)了文件結(jié)束
strm::goodbit | 指出流未處于錯誤狀態(tài)夯巷,此值保證為0
s.eof() | 若流 s 的 eofbit 置位,則返回 true
s.fail() | 若 s 的 badbit 或 failbit 置位哀墓,則返回 true
s.bad() | badbit 置位趁餐,則返回 true
s.good() | 流處于有效狀態(tài)則返回 true
s.clear() | 將流 s 中所有條件狀態(tài)復(fù)位
endl 完成換行并刷新緩沖區(qū)的工作
flush 刷新緩沖區(qū),但不輸出任何額外的字符
ends 向緩沖區(qū)插入一個(gè)空字符麸祷,然后刷新緩沖區(qū)
如果想在每次輸出操作后都刷新緩沖區(qū)澎怒,可以使用 unitbuf 操作符:
cout << unitbuf; // 所有輸出后都會立即刷新緩沖區(qū)
// 任何輸出都立即刷新,無緩沖
cout << nounitbuf; // 回到正常的緩沖方式
!喷面! 如果程序崩潰星瘾,輸出緩沖區(qū)是不會被刷新的
文件輸入輸出
fstream 特有的操作:
:-:|:-:
fstrm; | 創(chuàng)建一個(gè)未綁定的文件流
fstrm(s); | 創(chuàng)建一個(gè) fstream ,并打開名為 s 的文件
fstrm(s, mode); | 按指定 mode 打開文件
fstrm.opem(s) | 打開名為 s 的文件惧辈,并與 fstrm 綁定琳状,返回 void
fstrm.close() | 關(guān)閉文件,返回 void
fstrm.is_open() | 返回 bool 值盒齿,是否成功打開且尚未關(guān)閉
打開的文件名既可以是 string 對象念逞,也可以是 C 風(fēng)格的字符數(shù)組翎承。
ofstream out
... // 打開文件
if(out) // 檢查open是否成功
文件模式:
- in 以讀方式打開
- out 以寫方式打開符匾,會清空當(dāng)前文件
- app 每次寫操作前均定位到文件末尾
- ate 打開文件后立即定位到文件末尾
- trunc 截?cái)辔募?/li>
- binary 以二進(jìn)制方式進(jìn)行 IO
_犊А甸各!保留 ofstream 打開的文件中已有的數(shù)據(jù)的唯一方式是,顯式指定 app 或 in 模式焰坪。
每次調(diào)用 open 時(shí)都會確定文件模式某饰,例如:
ofstream out;
out.open("sadqw"); // 模式隱含設(shè)置為輸出和截?cái)?out.close();
out.open("repw", ofstream::app); // 模式為輸出和追加
out.close();
string 流
:-:|:-:
sstream strm | strm 是一個(gè)未綁定的 stringstream 對象
sstream strm(s) | strm 是一個(gè)保存了 string s 的一個(gè)拷貝的 sstream 對象
strm.str() | 返回 strm 所保存的 string 的拷貝
strm.str(s) | 將 string s 拷貝到 strm 中露乏,返回 void
讀取:
while(getline(cin, line)){
PersonInfo info;
istringstream record(line);
record >> info.name;
while(record >> word)
info.phones.push_back(word);
}
輸出:
ostringstream formatted, badNums;
...
badNums << " " << nums; // 將 nums 的字符串形式存入 badNums
formatted << " " << nums;
if(badNums.str().empty())
os << "empty!!" << endl;
else
cerr << "some errors:" << badNums.str() << endl;
9.順序容器
順序容器中,元素的順序不依賴于元素的值驹止,而是與元素加入容器時(shí)的位置相對應(yīng)臊恋。
順序容器類型:
- vector 可變大小數(shù)組抖仅。支持快速隨機(jī)訪問撤卢。在尾部之外的位置插入或刪除元素可能很慢。
- deque 雙端隊(duì)列智听。支持快速隨機(jī)訪問到推。在頭尾位置插入/刪除很快莉测。
- list 雙向鏈表悔雹。只支持雙向順序訪問。在 list 中任何位置插入/刪除都很快。
- forward_list 單向鏈表驯鳖。只支持單向順序訪問浅辙。在任何位置插入/刪除都很快记舆。
- array 固定大小數(shù)組泽腮。支持快速隨機(jī)訪問诊赊。不能添加或刪除元素。
- string 與vector相似的容器鲸郊,但專門用于保存字符严望。隨機(jī)訪問快像吻,在尾部插入/刪除速度很快拨匆。
挑選容器的基本原則:
除非有很好的理由骨饿,否則都用 vector
如果程序有很多小的元素宏赘,且空間的額外開銷很重要察署,則不要使用 list 或 forward_list
如果程序要求在容器中插入或刪除元素贴汪,應(yīng)使用 list 或 forward_list
如果程序要求在頭尾位置插入或刪除元素,但不會在中間位置進(jìn)行插入刪除阳懂,則用 deque
-
如果只有在讀取輸入時(shí)才需要在容器中間位置插入元素希太,隨后需要隨機(jī)訪問元素,則
- 首先確定是否一定要在中間添加元素堕澄,如果可以排序避免蛙紫,vector很好
- 如果一定要在中間位置插入元素坑傅,考慮在輸入階段使用 list蒜茴,輸入完成后拷貝到一個(gè) vector 中
概覽
容器庫上的操作有三個(gè)層次:
- 所有容器類型都提供的
- 僅針對順序容器粉私、關(guān)聯(lián)容器或無序容器的
- 只使用于一小部分容器的
容器操作:
:-:|:-:
類型別名|
iterator | 迭代器類型
const_iterator | 只讀诺核,不可修改的迭代器類型
size_type | 無符號整數(shù)類型,足夠保存最大可能的容器大小
difference_type | 帶符號整數(shù)類型陈瘦,保存迭代器距離
value_type | 元素類型
reference | 元素的左值類型;等價(jià)于 value_type&
const_reference | const左值類型
構(gòu)造函數(shù)|
C c; | 默認(rèn)構(gòu)造函數(shù)
C c1(c2); | 拷貝
C c(b, e); | 構(gòu)造 c 酥诽,將迭代器 b 和 e 指定的范圍內(nèi)的元素拷貝到 c (array不支持)
C c{a, b, c,..}; | 列表初始化 c
賦值與swap |
c1 = c2 |
c1 = {a, b, c,...}; |
a.swap(b) | 交換 a 和 b 的元素
swap(a, b) | 與 a.swap(b) 等價(jià)
大小 |
c.size() | c 中元素的數(shù)目
c.max_size() | 最大元素?cái)?shù)目
c.empty() | c 為空?
添加/刪除元素 |
c.insert(args) | 將 args 中的元素拷貝進(jìn) c
c.emplace(inits) | 使用 inits 構(gòu)造 c 中的一個(gè)元素
c.erase(args) | 刪除 args 指定的元素
c.clear() | 刪除所有元素
關(guān)系運(yùn)算符 |
==, != | 所有容器都支持
<, <=, >, >= | 無序關(guān)聯(lián)容器不支持Q凳唷!十酣!
獲取迭代器 |
c.begin(), c.end() | 返回指向 c 的首元素和尾后元素的指針
c.cbegin(), c.cend() | const_iterator
反向容器的額外成員 |
reverse_iterator | 逆序?qū)ぶ吩氐牡?br>
const_reverse_iterator|
c.rbegin(), c.rend()|
c.crbegin(), c.crend()|
迭代器的算術(shù)運(yùn)算只能用于 string 耸采、 vector 搓彻、 deque 和 array 旭贬。迭代器的范圍是一個(gè)左閉合區(qū)間 [begin, end)
- 如果 begin 和 end 相等骑篙,則范圍為空
- 如果 begin 和 end 不等,則范圍至少包含一個(gè)元素
- 可以通過反復(fù)遞增 begin 來到達(dá) end
可以按如下方式處理元素范圍:
while(begin != end){
*begin = val;
++begin;
}
Q蠲台谍! 帶 r 的迭代器版本返回反向迭代器,帶 c 的版本返回 const 迭代器掷伙。
當(dāng)不需要訪問時(shí)任柜,用 cbegin 和 cend
容器的定義和初始化:
C c(b, e) 迭代器 b 和 e 指定范圍中的元素的拷貝逆皮,類型必須相容粹胯。
只有順序容器才能接受大小參數(shù)
C seq(n) 包含 n 個(gè)元素风纠,每個(gè)都是值初始化的
C seq(n, t) 包含 n 個(gè)初始化值為 t 的元素
8渑酢懂酱!標(biāo)準(zhǔn)庫 array 具有固定大小,大小是 array 類型的一部分
array<int, 10>
array<string, 42>
交換容器中的元素:swap 通常比拷貝元素快的多
swap(c1, c2); // c1 和 c2 必須具有相同的類型
c1.swap(c2);
assign操作瞎领,僅順序容器,不適用于關(guān)聯(lián)容器和 array:
seq.assign(b, e); // 將 seq 中的元素替換為 b 和 e 迭代器表示范圍中的元素
seq.assign(il); // 將 seq 中的元素替換為初始化列表 il 中的元素
seq.assign(n, t); // 將 seq 中的元素替換為 n 個(gè)值為 t 的元素
M招蕖乙各!賦值運(yùn)算會使指向左邊容器內(nèi)部的迭代器、引用和指針失效,而swap不會咬荷。
順序容器操作
添加元素(非 array ):
c.push_back(t);
c.emplace_back(args); //在尾部創(chuàng)建一個(gè)值為 t 或由 args 創(chuàng)建的元素幸乒,返回 void
c.push_front(t);
c.emplace_front(args); //在頭部創(chuàng)建一個(gè)值為 t 或由 args 創(chuàng)建的元素聚唐,返回 void
c.insert(p, t);
c.emplace(p, t); //在迭代器 p 之前創(chuàng)建一個(gè)值為 t 的元素杆查,返回指向新元素的迭代器
c.insert(p, n, t); //....n個(gè)值為 t 的元素,返回指向新添加的第一個(gè)元素的迭代器
c.insert(p, b, e); //迭代器 b 和 e 之間的元素插入 p 指向的元素之前
c.insert(p, il); //將花括號包圍的元素列表 il 插入 p 指向的元素之前
向 vector 客峭、 string 、 deque插入元素會使所有指向容器的迭代器备蚓、引用失效
除了 array 和 forward_list 之外,每個(gè)順序容器都支持 push_back
list 虚循、 forward_list 横缔、deque 還支持 push_front
使用 insert 的返回值的技巧:
list<string> lst;
auto iter = lst.begin();
while(cin >> word)
iter = lst.insert(iter, word);
訪問元素:
c.back() // 尾元素的引用
c.front() // 首元素的引用!膛锭!不可以對空容器使用 back 和 front
c[n] // 下標(biāo)為 n 的元素的引用
c.at(n) // 下標(biāo)為 n 的元素的引用
c[n]
操作如果下標(biāo)越界,則是未定義的奢入,而如果使用 c.at(n)
关顷,會返回一個(gè) out_of_range 異常。
刪除元素:
c.pop_back(); // 刪除尾元素聋伦,若c為空則函數(shù)行為未定義觉增。函數(shù)返回 void
c.pop_front();
c.erase(p); // 刪除迭代器p指定的元素,返回被刪元素之后的元素的迭代器嘹履,不能刪除尾后迭代器!
c.erase(b, e); // 刪除迭代器 b 和 e 之間的值
c.clear();
刪除 deque 中除首位元素之外的任何元素都會使所有迭代器焕刮、引用、指針失效溉旋; vector 或 string 中,刪除點(diǎn)之后的會失效恕沫。
pop 操作沒有返回值鲸阔,因此如果要這些值的話类少,要先保存硫狞。
循環(huán)刪除一個(gè) list 中的所有奇數(shù)元素:
list<int> lst = {0,1,2,3,4,5,6,7,8,9};
auto it = lst.begin();
while(it != lst.end())
if (*it % 2)
it = lst.erase(it); // 刪除當(dāng)前迭代器指向的元素财忽,并把 it 移到下一個(gè)位置
else
++it;
刪除一個(gè)范圍內(nèi)的元素:
elem1 = slist.erase(elem1, elem2); // 調(diào)用后 elem1 == elem2
!特殊的 forward_list 操作:
單向鏈表中,插入和刪除操作都是通過前驅(qū)元素完成的铜犬,所以函數(shù)名不同:
lst.insert_after(p,...) // 在迭代器 p 之后插入元素
emplace_after(p, args)
lst.erase_after(...) // 刪除 p 之后的元素权埠,或刪除從 b 之后到 e 之間(不包括 e ) 的元素
如果要刪除第一個(gè)元素攘蔽,要用到首元素之前不存在的元素的迭代器: lst.before_begin()
刪除一個(gè) forward_list<int> 中所有的偶數(shù):
forward_list<int> lst = { 1,2,3,4,5,6,7,8,9,0 };
auto prev = lst.before_begin();
auto curr = lst.begin();
while (curr != lst.end()){
if (*curr % 2){
++curr;
prev = lst.erase_after(prev);
}
else{
prev = curr;
++curr;
}
}
改變?nèi)萜鞔笮?resize :
如果當(dāng)前大小大于所要求的大小作岖,則容器后部的元素會被刪除痘儡;
如果當(dāng)前大小小于新大小,會將新元素添加到容器后部隘擎。
c.resize(n); // 調(diào)整 c 的大小為 n ,若必須添加新元素,則進(jìn)行值初始化
c.resize(n, t); // 任何新添加的元素都初始化為值 t
D岫帷!如果在一個(gè)循環(huán)中插入/刪除 deque 拐邪、 string 或 vector 中的元素,不要緩存 end 返回的迭代器婶芭。必須在每次插入后重新調(diào)用 end()
vector 對象的增長
vector 將元素連續(xù)存儲东臀。
當(dāng)添加元素時(shí),如果沒有空間容納新元素犀农,容器必須分配新的內(nèi)存空間來保存已有元素和新元素惰赋,將已有元素從舊位置移動到新空間中,然后添加新元素赁濒,釋放舊存儲空間。
標(biāo)準(zhǔn)庫的實(shí)現(xiàn):當(dāng)不得不獲取新的內(nèi)存空間時(shí)孟害, vector 和 string 通常會分配比新的空間需求更大(多50%)的內(nèi)存空間拒炎。使得其擴(kuò)張操作通常比 list 和 deque 更快。
容器大小的管理操作:
c.shrink_to_fit() // 將 capacity() 減少為與 size() 相同的大小
c.capacity() // 不重新分配內(nèi)存的話挨务,c 可以保存多少個(gè)元素
c.reserve(n) // 分配至少能容納 n 個(gè)元素的空間
一個(gè)空的 vector 的 size 為 0击你, capacity 也為 0
當(dāng)添加元素時(shí)玉组,size 與添加的元素?cái)?shù)目相等,capacity 至少與 size 一樣大
string 的操作
修改 string 的操作:
s.insert(pos, args) 在 pos 之前插入 args 指定的字符果漾,pos 可以是一個(gè)下標(biāo)或一個(gè)迭代器球切。
s.erase(pos, len) 刪除從位置 pos 開始的 len 個(gè)字符,如果 len 被省略绒障,則刪除 pos 開始到末尾的所有字符,返回一個(gè)指向 s 的引用
s.append(args) +=
s.assign(args) 將 s 中的字符替換為 args
s.replace(range, args) 刪除 s 中范圍 range 內(nèi)的字符捍歪,替換為 args 指定的字符户辱。range 或者是一個(gè)下標(biāo)和一個(gè)長度,或者是一對迭代器糙臼。
例如:
s.insert(s.size(), 5, '!'); // 在s末尾插入5個(gè)感嘆號
s.erase(s.size() - 5, 5); // 刪除末尾5個(gè)元素
const char *cp = "Stately, plump Buck";
s.assign(cp, 7); // s = "Stately"
s.insert(s.size(), cp + 7) // s = "Stately, plump Buck"
substr 操作:
s.substr(pos, n) //返回一個(gè) string庐镐,包含從 pos 開始的 n 個(gè)字符的拷貝,如果不寫 n变逃,默認(rèn)值為 s.size() - pos
replace 操作:
s.replace(range, args) //刪除 s 中范圍 range 內(nèi)的字符必逆,替換為 args 指定的字符。 例如:
s.replace(i, size_old, newVal); // 將 i 開始的 size_old 個(gè)字符替換為 newVal
搜索操作:
返回值均為 string::size_type揽乱,或匹配失敗時(shí) string::npos(這個(gè)值初始化為 -1名眉,由于是 unsigned 的,所以相當(dāng)于 string 的最大可能的大小)
s.find(args) // 查找 s 中 args 第一次出現(xiàn)的位置
s.rfind(args) // 查找 s 中 args 最后一次出現(xiàn)的位置
s.find_first_of(args) // 在 s 中查找 args 中任何一個(gè)字符第一次出現(xiàn)的位置
s.find_last_of(args) // 在 s 中查找 args 中任何一個(gè)字符最后一次出現(xiàn)的位置
s.find_first_not_of(args) // 在 s 中查找第一個(gè)不在 args 中的字符
s.find_last_not_of(args) // 在 s 中查找最后一個(gè)不在 args 中的字符
args 必須是以下形式:
c, pos 從位置 pos (默認(rèn)為0)開始查找字符 c
s2, pos 從位置 pos (默認(rèn)為0)開始查找字符串 s2
cp, pos 從位置 pos 開始查找C風(fēng)格字符串 cp
cp, pos, n 從位置 pos 開始查找指針 cp 指向的數(shù)組的前 n 個(gè)字符
;嗣蕖损拢!循環(huán)檢測所有的子字符串出現(xiàn)的位置:
string::size_type pos = 0;
while((pos = name.find_first_of(numbers, pos)) != string::npos){
cout << "found number at index: " << pos
<< " element is " << name[pos] << endl;
++pos; // 移動到下一個(gè)字符,遞增必不可少H鱿福压!
}
compare 函數(shù):
s.compare(pos1, n1, s2, pos2, n2) // 從 pos1 開始的 n1 個(gè)字符和 s2 中從 pos2 開始的 n2 個(gè)字符比較,返回0或舞、正數(shù)荆姆、負(fù)數(shù)
s.compare(s2)
s.compare(pos1, n1, s2)
s.compare(pos1, n1, cp, n2) // 從 pos1 開始的 n1 個(gè)字符與指針 cp 指向的地址開始的 n2 個(gè)字符進(jìn)行比較
s.compare(cp)
s.compare(pos1, n1, cp)
字符串?dāng)?shù)值轉(zhuǎn)換:
string s = to_string(i) // 將任意算術(shù)類型的數(shù) i 轉(zhuǎn)化為字符串 s
double d = stod(s); // 字符串 s 轉(zhuǎn)化為浮點(diǎn)數(shù) d
stoi.. stol.. stoul..
stof.. stod.. stold..
在字符串中查找到第一個(gè)可能是浮點(diǎn)數(shù)的字符位置:
string s2 = "pi = 3.141";
d = stod(s2.substr(s2.find_first_of("+-.0123456789")));
10.泛型算法
generic algorithm 泛型算法大多數(shù)定義在 algorithm 中,頭文件 numeric 中也還有一些數(shù)值泛型算法映凳。一般情況下胆筒,算法遍歷兩個(gè)迭代器之間的范圍,而不是直接操作容器魏宽。
8骸!迭代器令算法不依賴于容器队询,但算法依賴于元素類型的操作派桩。
算法只運(yùn)行于迭代器之上,本身不會執(zhí)行容器的操作蚌斩,永遠(yuǎn)不會改變底層容器的大小铆惑,可能改變?nèi)萜髦斜4嬖氐闹担部赡茉谌萜鲀?nèi)移動元素,但永遠(yuǎn)不會直接添加或刪除元素员魏。
只讀算法:
find丑蛤, accumulate(定義在 numeric 中) 和 equal
int sum = accumulate(vec.cbegin(), vec.cend(), 0);
// sum 為 vec 中元素的和,和的初值設(shè)為 0
假定元素能執(zhí)行 + 操作撕阎,并且能夠加到 初值 上受裹。
string sum = accumulate(v.cbegin(), v.cend(), string(""));
// 把 vector 中所有的 string 連接起來
string sum = accumulate(v.cbegin(), v.cend(), "");
// 錯誤,const char * 上沒有定義 + 運(yùn)算符
equal 判斷相等虏束,接受3個(gè)參數(shù)棉饶,前兩個(gè)表示第一個(gè)序列的范圍,第三個(gè)表示第二個(gè)序列的首元素镇匀。
equal(roster1.cbegin(), roster1.cend(), roster2.cbegin());
// roster2 中元素應(yīng)該至少和 roster1 中一樣多
// 只要能用 == 判斷就可以照藻,roster1 可以是 vector<string>, roster2 是 list<const char*>
!汗侵!那些只接受單一迭代器表示第二個(gè)序列的算法幸缕,都假定第二個(gè)序列至少和第一個(gè)序列一樣長。
寫容器元素的算法
將新值賦予序列中的元素晰韵。必須確保原序列大小至少不小于要寫入的元素?cái)?shù)目发乔。
fill :把第三個(gè)參數(shù)寫入范圍內(nèi)的每一個(gè)元素
fill(vec.begin(), vec.end(), 0);
fill(vec.begin(), vec.begin() + vec.size()/2, 10);
fill_n :一個(gè)迭代器,一個(gè)長度宫屠,一個(gè)值
fill_n(vec.begin(), vec.size(), 0);
操作兩個(gè)序列的算法一般接受 4 個(gè)迭代器列疗,分別表示兩個(gè)序列的范圍,而 equal 只接受 3個(gè)迭代器浪蹂。
5终弧!注意算法不檢查寫操作坤次,向目的位置迭代器寫入數(shù)據(jù)的算法假定目的位置足夠大古劲,能容納寫入的元素。 不要向空容器寫g趾铩2!
vector<int> vec;
fill_n(vec.begin(), vec.size(), 0); // 將所有元素置 0滑绒,這個(gè)調(diào)用是安全的
fill_n(vec.begin(), 10, 0); // 錯誤C票ぁ!疑故! vec中并沒有10個(gè)元素
插入迭代器:向容器中添加元素的迭代器杠览。
函數(shù) back_inserter (頭文件 inerator):接受一個(gè)指向容器的引用,返回一個(gè)與該容器綁定的插入迭代器纵势。
vector<int> vec; // 空向量
auto it = back_inserter(vec); // 通過它賦值會將元素添加到 vec 中
*it = 42; // vec 中現(xiàn)在有一個(gè)元素 42
u獍ⅰ9芮!通常使用 back_inserter 來創(chuàng)建一個(gè)迭代器:
vector<int> vec;
fill_n(back_inserter(vec), 10, 0); // 添加 10 個(gè)元素到 vec
拷貝算法 copy: 將輸入范圍中的元素拷貝到目標(biāo)序列中
int a1[] = {0, 1, 2, 3, 4, 5};
int a2[sizeof(a1)/sizeof(*a1)]; // a2 的大小與 a1 一致
auto ret = copy(begin(a1), end(a1), a2); // 把 a1 的內(nèi)容拷貝給 a2
// ret 指向拷貝到 a2 的尾元素之后的位置软舌。
多數(shù)算法都有 copy 版本才漆,不覆蓋原值,而是放到第三個(gè)迭代器參數(shù)指定的容器中:
replace(ilst.begin(), ilst.end(), 0, 42); // 將所有0替換為42
replace_copy(ilst.cbegin(), ilst.cend(), back_inserter(ivec), 0, 42); // 把替換后的值放到 ivec 中佛点, ilst 并沒有改變
重排元素
sort:按 < 排序醇滥。
unique:使不重復(fù)的元素出現(xiàn)在開始部分。(只排序超营,不會刪除)
刪除重復(fù)單詞的例子:
void elimDups(vector<string> &words){ // 按字典序排序腺办,刪除重復(fù)單詞
sort(words.begin(), words.end());
// unique 返回一個(gè)指向不重復(fù)區(qū)域之后一個(gè)位置的迭代器
auto end_unique = unique(words.begin(), words.end());
// 刪除重復(fù)單詞
words.erase(end_unique, words.end());
}
定制操作
向算法傳遞函數(shù)
謂詞(predicate):是一個(gè)可調(diào)用的表達(dá)式,返回結(jié)果是一個(gè)能用作條件的值糟描。有兩種:一元謂詞,二元謂詞书妻。
bool isShorter(const string &s1, const string &s2){
return s1.size() < s2.size(); // 按長度排序
}
elimDups(words); // 按字典序排序船响,刪除重復(fù)單詞
stable_sort(words.begin(), words.end(), isShorter);
// stable_sort 會維持相等的元素原有的順序
lambda表達(dá)式
四種可調(diào)用對象: 函數(shù)、函數(shù)指針躲履、重載了函數(shù)調(diào)用運(yùn)算符的類见间、lambda表達(dá)式
lambda 表達(dá)式可以用于給一元謂詞傳遞一個(gè)可調(diào)用表達(dá)式。
一個(gè) lambda 表達(dá)式可以理解為一個(gè)未命名的內(nèi)聯(lián)函數(shù):
[capture list](parameter list) -> return type { function body }
capture list (捕獲列表) 是 lambda 表達(dá)式所在函數(shù)中定義的局部變量的列表工猜;
return type米诉、parameter list、function body 和正常函數(shù)一樣篷帅,是返回類型史侣、參數(shù)列表、函數(shù)體魏身。
參數(shù)列表和返回類型可以忽略惊橱,但是必須要有捕獲列表和函數(shù)體:
auto f = []{ return 42; };
//調(diào)用:
cout << f() << endl;
lambda 實(shí)參和形參類型必須匹配,且不能有默認(rèn)參數(shù)箭昵,所以形參數(shù)與實(shí)參數(shù)相等税朴。
改進(jìn) stable_sort 函數(shù):
stable_sort(words.begin(), words.end(),
[](const string &a, const string &b) { return a.size() < b.size(); });
通過 find_if 調(diào)用:
auto wc = find_if(words.begin(), words.end(),
[sz](const string &a) { return a.size() >= sz; });
// 從函數(shù)體中捕獲 sz,返回 wc 為指向第一個(gè)長度不小于 sz 的元素的迭代器家制。
通過 for_each 打诱帧:
for_each(wc, words.end(),
[](const string &s){ cout << s << ' '; };
cout << endl;
lambda 的捕獲:
-
值捕獲
被捕獲的變量的值是在 lambda 創(chuàng)建時(shí)被拷貝,而不是調(diào)用時(shí)颤殴。
-
引用捕獲
當(dāng)以引用方式捕獲時(shí)觅廓,必須保證在 lambda 執(zhí)行時(shí)變量是存在的。例如傳遞 ostream 對象時(shí)诅病,不能拷貝哪亿,唯一的方法是捕獲其引用粥烁。
for_each(words.begin(), words.end(), [&os, c](cosnt string &s) { os << s << c;});
-
隱式捕獲
[=] 表示值捕獲,[&] 表示引用捕獲蝇棉,或者:
[=, identifier_list] 表示默認(rèn)用值捕獲讨阻,指定的幾個(gè)用引用捕獲,一定要加&
[&, identifier_list] 表示默認(rèn)用引用方式捕獲篡殷,指定的幾個(gè)用值捕獲钝吮,不能用&
指定 lambda 的返回類型:
必須使用尾置的返回類型,例如:
transform(vi.begin(), vi.end(),
[](int i) -> int
{ if (i < 0) return -i; else return i; });
參數(shù)綁定:
!!如果一個(gè) lambda 的捕獲列表為空板辽,通称媸荩可以用一個(gè)函數(shù)來代替它。
標(biāo)準(zhǔn)庫 bind 函數(shù)一般形式為
auto newCallable = bind(callable, arg_list);
newCallable 本身是個(gè)可調(diào)用對象劲弦,當(dāng)調(diào)用它時(shí)耳标,會調(diào)用 callable,并傳遞給它 arg_list 中的參數(shù)邑跪。
arg_list 中可能包含 _n次坡,占據(jù)傳遞給 newCallable 的參數(shù)的“位置”。_1 為 newCallable 的第一個(gè)參數(shù)画畅,_2為第二個(gè)參數(shù)砸琅。
bool check_size(const string &s, string::size_type sz){
return s.size() >= sz;
}
auto check6 = bind(check_size, _1, 6); //check6是一個(gè)可調(diào)用對象
占位符出現(xiàn)在參數(shù)列表第一個(gè)位置,且只有一個(gè)轴踱,表示 check6 的此參數(shù)對應(yīng) check_size 的第一個(gè)參數(shù)症脂,并且 check6 只接受單一參數(shù)。
string s = "hello";
bool b1 = check6(s); // 調(diào)用 bind
RА诱篷!將 lambda 表達(dá)式替換為 bind:
auto wc = find_if(words.begin(), words.end(),
bind(check_size, _1, sz));
placeholder 名字都在 std::placeholder 中,定義在頭文件 functional 中
using namespace std::placeholder
bind 的參數(shù)也可以進(jìn)行順序改變嘁傀,例如將 g(_1, _2)
綁定到 f(a, b, _2, c, _1)
可以改變參數(shù)的順序兴蒸。調(diào)用 g(X, Y)
會調(diào)用 f(a, b, Y, c, X)
!细办!利用這一點(diǎn)可以把 isShorter 改變成 isLonger橙凳。
迭代器
- 插入迭代器(insert iterator)
- 流迭代器(stream iterator)
- 反向迭代器(reverse iterator)
- 移動迭代器(move iterator)
插入迭代器
插入迭代器是一種容器適配器,接受一個(gè)容器笑撞,生成一個(gè)迭代器岛啸。
it = t
在當(dāng)前位置插入值 t,具體調(diào)用的函數(shù)依賴于插入迭代器的不同種類茴肥。
有三種插入迭代器坚踩,差異在于元素插入的位置:
back_insertrer: 使用 push_back 的迭代器
front_inserter: 使用 push_front 的迭代器
inserter: 使用 insert 的迭代器
使用迭代器:
auto it = inserter(c, iter); // 創(chuàng)建一個(gè)插入迭代器 it
*it = val; // 在 iter 前插入 val
//等價(jià)于下面的代碼
it = c.insert(it, val);
++it;
auto it = front_inserter(c);
*it = val; // c.push_front(val);
iostream 迭代器
iostream 不是容器,但是也定義了迭代器瓤狐。
istream_iterator 讀取輸入流
ostream_iterator 向輸出流寫數(shù)據(jù)
從標(biāo)準(zhǔn)輸入讀取數(shù)據(jù)的例子:
istream_iterator<int> in_iter(cin);
istream_iterator<int> eof; // 尾后迭代器
while(in_iter != eof){
vec.push_back(*in_iter++);
}
K仓批幌! 將輸入數(shù)據(jù)求和:
istream_iterator<int> in(cin), eof;
cout<< accumulate(in, eof, 0) << endl;
ostream_iterator 可以提供一個(gè)(可選的)第二參數(shù),是一個(gè)C風(fēng)格的字符串嗓节,每次輸出后都會打印這個(gè)字符串荧缘。輸出值的序列:
ostream_iterator<int> out_iter(cout, " ");
for(auto e : vec)
*out_iter++ = e; // 等價(jià)于直接寫 out_iter = e; * 和 ++ 沒有任何作用
cout << endl;
// 或者直接調(diào)用 copy
copy(vec.begin(), vec.end(), out_iter);
cout << endl;
!拦宣!*out ++out out++
這些運(yùn)算符是存在的截粗,但是不會對 out 做任何事情;而輸入流的迭代器鸵隧,這些運(yùn)算符是有效的绸罗,可以返回流里的值、讀取下一個(gè)豆瘫。
反向迭代器
++it會移動到前一個(gè)元素珊蟀,--it會移動到后一個(gè)元素。
逆序打印一個(gè)數(shù)組:
for(auto r_iter = vec.crbegin(); r_iter != vec.crend(); ++r_iter){
cout << *r_iter;
}
sort本身是遞增順序外驱,可以使用反向迭代器獲得遞減:
sort(vec.rbegin(), vec.rend())
O德濉!不可以在 forward_list 或一個(gè)流迭代器上創(chuàng)建反向迭代器略步。
!定页!反向迭代器的目的是表示元素范圍趟薄,這個(gè)范圍是不對稱的,左閉右開典徊。所以當(dāng)從一個(gè)普通迭代器初始化一個(gè)反向迭代器時(shí)杭煎,結(jié)果指向的和原迭代器不是同一個(gè)元素。
泛型算法結(jié)構(gòu)
迭代器分為5個(gè)類型:
:-:|:-:
輸入迭代器|只讀卒落,不寫羡铲;單遍掃描,只能遞增
輸出迭代器|只寫儡毕,不讀也切;單遍掃描,只能遞增
前向迭代器|可讀寫腰湾;多遍掃描雷恃,只能遞增
雙向迭代器|可讀寫;多遍掃描费坊,可遞增遞減
隨機(jī)訪問迭代器|可讀寫倒槐;多遍掃描,支持全部迭代器運(yùn)算
算法的 _if 版本:接受一個(gè)謂詞替代元素值附井。
find(beg, end, val);
find_if(beg, end, pred); // 查找第一個(gè)令 pred 為真的元素
拷貝元素的版本 _copy :
reverse(beg, end);
reverse_copy(beg, end, dest); // 元素逆序拷貝到 dest 中
鏈表的特殊操作
因?yàn)榇鎯π问降牟煌衷剑琹ist 和 forward_list 類型定義了幾個(gè)不同的算法两残。
:-:|:-:
lst.merge(lst2)| 將來自 lst2 的元素合并入 lst。兩個(gè)list都必須是有序的
lst.merge(lst2, comp) | 元素將從 lst2 中刪除把跨,合并之后 lst2 為空
lst.remove(val) | 調(diào)用 erase 刪除每一個(gè) ==val 的元素
lst.remove_if(pred)|或使一元謂詞為真的每個(gè)元素
lst.reverse() | 翻轉(zhuǎn) lst
lst.sort() | 使用 < 或給定比較操作排序
lst.sort(comp)|
lst.unique()| 刪除同一個(gè)詞的連續(xù)拷貝
lst.unique(pred)| 按給定謂詞刪除連續(xù)拷貝
splice 算法:
lst.splice(args);
lst.splice_after(args);
//args = :
(p, lst2) // p 是一個(gè)指向 lst 的迭代器人弓,將 lst2 中所有元素移動到 p 之前的位置
(p, lst2, p2) // 將 p2 指向的元素移動到 lst 中, _after版本則是將 p2 之后的移動到 lst 中
(p, lst2, b, e) // b e 表示 lst2 中的合法范圍节猿。將給定范圍移動到 lst 中
11.關(guān)聯(lián)容器
map: 關(guān)鍵字-值(key-value
)對票从,關(guān)聯(lián)數(shù)組
set: 支持高效的關(guān)鍵字查詢操作,只包含一個(gè)關(guān)鍵字
有序集合:
:-:|:-:
map | 關(guān)聯(lián)數(shù)組滨嘱;保存關(guān)鍵字-值對
set | 關(guān)鍵字即值峰鄙,只保存關(guān)鍵字的容器
multimap | 關(guān)鍵字可重復(fù)出現(xiàn)的map
multiset | 關(guān)鍵字可重復(fù)出現(xiàn)的set
無序集合:unordered_ 版本,都是哈希組織的
定義關(guān)聯(lián)容器
map<string, size_t> word_count; // 空容器
set<string> exclude = {"the", "an", "a", "A", "The"}; // 列表初始化
map<string, string> authors = { {"Joyce", "James"},
{"Austen", "Jane"} };
用一個(gè)關(guān)鍵字-值對去初始化太雨,{key, value}
有序容器的關(guān)鍵字類型
可以提供一個(gè)自定義的操作來代替 < 運(yùn)算符吟榴,但是必須是嚴(yán)格弱序的
- 兩個(gè)關(guān)鍵字不能同時(shí)“小于等于”對方
- 如果 k1 “小于等于” k2 ,且 k2 “小于等于” k3囊扳,那么 k1 必須“小于等于” k3
- 如果存在兩個(gè)關(guān)鍵字吩翻,任何一個(gè)都不“小于等于”另一個(gè),那么這兩個(gè)關(guān)鍵字是“等價(jià)”的锥咸,“等價(jià)”的關(guān)鍵字之間必須能傳遞等價(jià)性
pair類型
pair類型定義在頭文件 utility 中狭瞎,保存兩個(gè)數(shù)據(jù)成員,類似容器搏予,它是一個(gè)用來生成特定類型的模板熊锭。
pair<string, string> anon;
pair<string, size_t> word_count;
pair<string, vector<int>> line; // 默認(rèn)進(jìn)行值初始化
pair的操作:
pair<T1, T2> p; // 值初始化
pair<T1, T2> p(v1, v2);
pair<T1, T2>p = {v1, v2}; // 等價(jià)于上一句
make_pair(v1, v2); // 返回一個(gè) pair
p.first, p.second;
// 關(guān)系運(yùn)算符
p1 == p2; // 當(dāng) first 和 second 分別相等才為真
比較運(yùn)算: 只有當(dāng) p1.first < p2.first || !(p2.first < p1.first) && p1.second < p2.second
成立時(shí) p1 < p2
才為真。即先比較 first雪侥,如果相等時(shí)再比較 second碗殷。
// < 運(yùn)算符:
return (_Left.first < _Right.first ||
!(_Right.first < _Left.first) && _Left.second < _Right.second);
關(guān)聯(lián)容器的操作
額外的類型別名:
key_type 關(guān)鍵字類型
mapped_type 每個(gè)關(guān)鍵字關(guān)聯(lián)的類型,只適用于 map
value_type 對于 set 速缨,與 key_type 相同锌妻;對于 map ,為 pair<const key_type, mapped_type>
關(guān)鍵字部分是 const 的旬牲,因?yàn)椴荒芨淖?key 的值7麓狻!
map 的迭代器解引用得到的是一個(gè) pair T牍陌!
set 的迭代器是 const 的,雖然有 iterator 和 const_iterator 類型员咽,但是都不能修改毒涧。
!!由于關(guān)聯(lián)容器的關(guān)鍵字是 const 的契讲,所以一般不對關(guān)聯(lián)容器用泛型算法仿吞。查找操作一般用關(guān)聯(lián)容器自己定義的 find
添加元素:
vector<int> ivec = {2, 4, 6, 8, 2, 4, 6};
set<int> set2;
set2.insert(ivec.cbegin(), ivec.cend()); // 插入了 2 4 6 8
set2.insert({1, 3, 5, 7, 1}); // 現(xiàn)在有 12345678
word_map.insert({word, 1}); // 插入的必須是一個(gè) pair 類型
word_map.insert(make_pair(word, 1));
word_map.insert(pair<string, size_t>(word, 1));
word_map.insert(map<string, size_t>::value_type(word, 1));
添加單一元素的 insert 和 emplace 版本返回一個(gè) pair ,告訴我們插入操作是否成功捡偏。這個(gè) pair 返回值的 first 是一個(gè)迭代器唤冈,指向給定關(guān)鍵字的元素; second 成員是一個(gè) bool 值银伟,指出元素是插入成功還是已經(jīng)存在于容器中你虹。如果已經(jīng)在容器中,則 insert 什么也不做彤避,且返回值的 second 成員是一個(gè) false傅物。
// 插入一個(gè)元素,關(guān)鍵字為 word琉预,值為 1
auto ret = word_count.insert({word, 1});
if (!ret.second) // 插入失敗則什么都不做
++ret.first->second; // 等價(jià)于 ++((ret.first)->second);
ret 的類型是: pair<map<string, size_t>::iterator, bool>
刪除元素:
erase 操作董饰,接受一個(gè) key_type 參數(shù)。也可以按迭代器刪除圆米。
對于保存不重復(fù)關(guān)鍵字的容器卒暂,erase 的返回值總是 0 或 1。 0 表示想要刪除的元素不在容器中娄帖,1 表示成功刪除元素也祠;對于保存重復(fù)關(guān)鍵字的容器,返回值是一個(gè) size_type 值近速,指出刪除元素的數(shù)量齿坷。
下標(biāo)操作:
set、multimap数焊、unordered_multimap 不可以使用下標(biāo)操作。const 的 map 也不可以崎场,因?yàn)橄聵?biāo)操作返回的是左值佩耳!
c[k] // 返回關(guān)鍵字為 k 的元素,如果 k 不在 c 中谭跨,則添加一個(gè)關(guān)鍵字為 k 的元素并進(jìn)行值初始化
c.at(k) // 訪問關(guān)鍵字為 k 的元素干厚,帶參數(shù)檢查;若不在 c 中螃宙,拋出一個(gè) out_of_range 異常
訪問元素:
find 和 count:find 返回指向第一個(gè)關(guān)鍵字為 k 的元素的迭代器蛮瞄,找不到則返回尾后迭代器。 count 不僅是查找谆扎,返回關(guān)鍵字為 k 的元素的數(shù)量挂捅。
c.find(k); // 返回的是迭代器
c.count(k); // 返回的是數(shù)量
c.lower_bound(k); // 返回第一個(gè)關(guān)鍵字不小于 k 的元素的迭代器 >=
c.upper_bound(k); // 返回第一個(gè)關(guān)鍵字大于 k 的元素的迭代器 >
c.equal_range(k); // 返回一個(gè)迭代器 pair ,表示關(guān)鍵字等于 k 的元素的范圍 ==
在 multi 版本的關(guān)聯(lián)容器中堂湖,相等的關(guān)鍵字的元素是相鄰存儲的闲先。
三種方法在 multimap 中輸出所有給定關(guān)鍵字的 second:
1状土、count + find 的方法
string search_item("Alain de Botton"); // 要搜索的 key
auto entries = authors.count(search_item); // 元素的數(shù)量
auto iter = authors.find(search_item); // 指向第一個(gè)元素的迭代器
while(entries){
cout<< iter->second << endl;
++iter;
--entries;
}
2、迭代器方法伺糠,結(jié)果的范圍是 [lower_bound, upper_bound)
for(auto beg = authors.lower_bound(search_item), end = authors.upper_bound(search_item);
beg != end; ++beg){
cout << beg->second << endl;
}
3蒙谓、equal_range 函數(shù),返回值和lower训桶、upper_bound的返回值是一樣的累驮。
[equal_range.first, equal_range.second)
for(auto pos = authors.equal_range(search_item);
pos.first != pos.second; ++pos.first){
cout << pos.first->second << endl;
}
無序容器
關(guān)鍵字沒有明顯的序關(guān)系的情況下,無序容器是非常有用的舵揭。通常無序容器更為簡單谤专,也具有更好的性能。
!摘悴!如果關(guān)鍵字類型固有就是無序的悴侵,或者性能測試發(fā)現(xiàn)問題可以用哈希技術(shù)解決,就可以使用無序容器墅垮。
無序容器在存儲上組織為一組桶,每個(gè)桶保存 0 或多個(gè)元素耕漱。無序容器使用一個(gè)哈希函數(shù)將元素映射到桶算色。為了訪問一個(gè)元素,先計(jì)算元素的哈希值螟够,它指出應(yīng)該搜索那個(gè)桶灾梦。無序容器的性能依賴于哈希函數(shù)的質(zhì)量和桶的數(shù)量和大小。
桶的管理操作:
:-:|:-:
桶接口|
c.bucket_count()| 正在使用的桶的數(shù)目
c.max_bucket_count() | 能容納的最多的桶的數(shù)目
c.bucket_size(n) | 第 n 個(gè)桶有多少個(gè)元素
c.bucket(k) | 關(guān)鍵字為 k 的元素在哪個(gè)桶中
桶迭代|
local_iterator| 訪問桶中元素的迭代器類型
const_local_iterator | 桶迭代器的 const 版本
c.begin(n), c.end(n), c.cbegin(n), c.cend(n) | 桶 n 的首元素迭代器和尾后迭代器
哈希策略|
c.load_factor()|每個(gè)桶的平均元素?cái)?shù)量妓笙,返回 float 值
c.max_load_factor() | c 試圖維護(hù)的平均桶大小若河,返回 float 值
c.rehash(n) | 重新存儲,使得 bucket_count>=n,且bucket_count>size/max_load_factor
c.reserve(n) | 重新存儲寞宫,使得 c 可以保存 n 個(gè)元素且不必 rehash
12.動態(tài)內(nèi)存
目前的程序都使用靜態(tài)內(nèi)存或棧內(nèi)存萧福。對于棧對象,僅在其定義的程序塊運(yùn)行時(shí)才存在; static 對象在使用之前分配辈赋,在程序結(jié)束時(shí)銷毀鲫忍。
除了靜態(tài)內(nèi)存和棧內(nèi)存,還有一個(gè)自由空間(或稱為堆 heap)钥屈,用于存儲動態(tài)分配的對象悟民。
通過一對運(yùn)算符 new
和 delete
來管理動態(tài)內(nèi)存。
c++11的兩種智能指針: shared_ptr
允許多個(gè)指針指向同一個(gè)對象篷就; unique_ptr
“獨(dú)占”所指向的對象射亏; weak_ptr
弱引用。三個(gè)都定義在 memory 頭文件中。
shared_ptr類
shared_ptr 也是一個(gè)模板類鸦泳,
shared_ptr<string> p1;
shared_ptr<list<int>> p2;
shared_ptr 和 unique_ptr 都支持的操作:
shared_ptr<T> sp; unique_ptr<T> up; // 空智能指針
p // 將指針用作條件判斷银锻,若 p 指向一個(gè)對象則為 true
*p // 解引用
p->mem // (*p).mem
p.get() // 返回 p 中保存的指針,如果釋放了其對象做鹰,則返回的指針?biāo)赶虻膶ο笠蚕Я?swap(p, q); p.swap(q); // 交換 p 和 q 中的指針
shared_ptr 獨(dú)有的操作:
make_shared<T> (args) // 返回一個(gè) shared_ptr击纬,指向一個(gè)動態(tài)分配的類型為 T 的對象
shared_ptr<T> p(q) // p 是 q 的拷貝,q 中的指針必須能轉(zhuǎn)換成 T*钾麸,此操作會遞增 q 中的計(jì)數(shù)器更振。
p = q // 賦值語句,會遞減 q 的引用計(jì)數(shù)饭尝,遞增 p 的引用計(jì)數(shù)肯腕。若 p 的引用計(jì)數(shù)變?yōu)?0 ,則將其管理的原內(nèi)存釋放
p.unique() // 若 p.use_count() 為 1钥平,返回 true实撒;否則返回 false
p.use_count() // p 共享對象的智能指針數(shù)量,這句比較慢涉瘾,主要用于調(diào)試
最安全的分配和使用動態(tài)內(nèi)存的方法是使用 make_shared
函數(shù)知态。
每個(gè) shared_ptr 都有一個(gè)引用計(jì)數(shù),每當(dāng)發(fā)生拷貝時(shí)立叛,計(jì)數(shù)器都會遞增负敏;當(dāng)給 shared_ptr 賦一個(gè)新值或是 shared_ptr 被銷毀時(shí),計(jì)數(shù)器就會遞減秘蛇。一旦一個(gè) shared_ptr 的計(jì)數(shù)器變?yōu)?0 其做,它就會自動釋放自己所管理的對象。
析構(gòu)函數(shù)(destructor)完成銷毀工作赁还。
Q埂!如果把 shared_ptr 存放于一個(gè)容器之中艘策,而后不再需要全部元素蹈胡,只使用一部分,要記得把不需要的那些 erase 刪除掉柬焕。
使用動態(tài)內(nèi)存的三個(gè)原因:
- 程序不知道自己要使用多少個(gè)對象
- 程序不知道所需對象的準(zhǔn)確類型
- 程序需要在多個(gè)對象間共享數(shù)據(jù)
!梭域!最常見的原因是允許多個(gè)對象共享相同的狀態(tài)
直接管理內(nèi)存
new 分配內(nèi)存斑举, delete 釋放內(nèi)存。
new 無法為其分配的對象命名病涨,而是返回一個(gè)指向該對象的指針富玷,一般情況下,動態(tài)分配的對象是默認(rèn)初始化的。
int *pi = new int;
直接初始化方式:
int *pi = new int(1024); // pi 指向的對象值是 1024
string *ps = new string(10, '9'); // *ps 為 "9999999999"
vector<int> *pv = new vector<int>{0,1,2,3,4,5}; // 列表初始化
值初始化方式赎懦,在類型名之后跟一對空括號的方式:
string *ps1 = new string; // 默認(rèn)初始化為空 string
string *ps2 = new string(); // 值初始化為空 string
int *pi1 = new int; // 默認(rèn)初始化雀鹃, *pi1 未定義
int *pi2 = new int(); // 值初始化,*pi2 為 0
@健黎茎!對于內(nèi)置類型,值初始化的對象有著良好定義的值当悔,而默認(rèn)初始化的對象的值則是未定義的傅瞻。
也可以用 auto 自動推斷:
auto p1 = new auto(obj); // p1 指向一個(gè)與 obj 類型相同的對象,并用 obj 進(jìn)行初始化
動態(tài)分配的 const 對象:new 返回的是一個(gè)指向 const 的指針(底層cosnt)
const int *pci = new const int(1024);
內(nèi)存耗盡:會返回一個(gè) bad_alloc 異常盲憎,可以阻止它拋出這個(gè)異常嗅骄。
int *p1 = new int; // 如果分配失敗,new 拋出 std::bad_alloc
int *p2 = new (nothrow) int; // 如果失敗饼疙,new 返回一個(gè)空指針
D缟!由內(nèi)置指針管理的動態(tài)內(nèi)存窑眯,在被顯式釋放前一直都會存在屏积,不釋放會內(nèi)存泄漏。
指針被 delete 后伸但,就變成空懸指針肾请,指向一塊曾經(jīng)保存數(shù)據(jù)對象但是現(xiàn)在已經(jīng)無效的內(nèi)存的指針「郑可以將 p 再置為 nullptr
用 new 的返回值來初始化 shared_ptr
shared_ptr<int> p(new int(42)); // p 指向一個(gè)值為42的 int
n跆!不要把普通指針轉(zhuǎn)化成智能指針却妨,有可能導(dǎo)致指針被 delete 掉變成空懸指針饵逐。如果把一個(gè)普通指針交給了智能指針,就不要再繼續(xù)用這個(gè)普通指針了彪标。
1度ā!get函數(shù)返回值是普通指針捞烟,永遠(yuǎn)不要用它給智能指針賦值薄声,也不要 delete 掉這個(gè)普通指針
shared_ptr<int> p(new int(42));
int *q = p.get(); // 正確,但是使用時(shí)要注意不要釋放 q 指向的內(nèi)存
shared_ptr<int> qq = p.get(); // 錯誤题画,千萬不要這么做D妗!2韵ⅰK跣摇R贾谩!
調(diào)用 reset 給智能指針賦一個(gè)新的值:
p = new int(1024); // 錯誤表谊,不能將一個(gè)指針賦予 shared_ptr
p.reset(new int(1204)); // 正確钞护,p 指向新的對象,更新引用計(jì)數(shù)爆办,必要時(shí)會刪除原來的對象
D压尽!押逼!改變底層對象的時(shí)候步藕,最好結(jié)合 unique 一起用:
if (!p.unique())
p.reset(new string(*p)); // 我們不是唯一的用戶;分配新的拷貝
*p += newVal; // 我們是唯一的用戶挑格,可以改變對象的值
注意以下幾點(diǎn):
- 不使用相同的內(nèi)置指針值初始化(或 reset )多個(gè)智能指針
- 不 delete get() 返回的指針
- 不使用 get() 初始化或 reset 另一個(gè)智能指針
- 如果使用 get() 返回的指針咙冗,記住當(dāng)最后一個(gè)對應(yīng)的智能指針銷毀后,它就無效了
- 如果使用智能指針管理的資源不是 new 動態(tài)分配的漂彤,記住傳給他一個(gè)刪除器
unique_ptr
和 shared_ptr 不同雾消,沒有類似的 make_share,在初始化時(shí)必須采用直接初始化的形式:
unique_ptr<double> p1; //可以指向一個(gè) double 的 unique_ptr
unique_ptr<int> p2(new int(42)); // p2 指向一個(gè)值為 42 的 int
4焱立润!unique_ptr 不支持拷貝,也不支持賦值媳板。
操作:
u.release(); u 放棄對指針的控制權(quán)桑腮,返回指針,將 u 置為空
u.reset(); 釋放 u 指向的對象
u.reset(q); 釋放 u 蛉幸,如果提供了內(nèi)置指針 q 破讨,則會令 u 指向這個(gè)對象,否則置為空
雖然不能直接拷貝和賦值奕纫,但是可以用 release 和 reset 實(shí)現(xiàn)指針的所有權(quán)的交換:
unique_ptr<string> p2(p1.release()); // release 將 p1 置為空提陶,所有權(quán)交給 p2
unique_ptr<string> p3(new string("Trex"));
p2.reset(p3.release()); // p3 交給了 p2,釋放了原來 p2 指向的內(nèi)存
調(diào)用 release 會切斷智能指針和對象之間的聯(lián)系匹层,但是沒有釋放內(nèi)存O栋省!要記得用返回值 delete 掉:
auto p = p2.release(); // 直接 p2.release() 不會釋放內(nèi)存升筏,會丟失指針
delete p;
3湃帷!我們可以拷貝或賦值一個(gè)將要被銷毀的 unique_ptr 您访,例如從函數(shù)返回:在這種返回情況下铅忿,執(zhí)行的是一種特殊的“拷貝”,可以允許這樣拷貝 unique_ptr
unique_ptr<int> clone(int p) {
return unique_ptr<int>(new int (p));
}
// 或者返回一個(gè)局部對象的拷貝
unique_ptr<int> clone(int p) {
unique_ptr<int> ret(new int (p));
...
return ret;
}
動態(tài)數(shù)組
稱 new T[] 分配的內(nèi)存為“動態(tài)數(shù)組”洋只,然而實(shí)際上這個(gè)數(shù)組并不可見辆沦,返回的是一個(gè)指針,并不是數(shù)組類型J缎椤V丁!
一些初始化方式:
int *pia = new int[10]; // 10 個(gè)未初始化的 int
int *pia2 = new int[10](); // 10 個(gè)值初始化為 0 的 int
int *pia3 = new int[10]{0, 1, 2, 3, 4}; // 列表初始化
可以動態(tài)分配一個(gè)長度任意的數(shù)組担锤,長度甚至可以是 0,但是這樣的動態(tài)分配的數(shù)組不能解引用蔚晨。
釋放動態(tài)數(shù)組,添加一個(gè) [] 肛循,數(shù)組中的元素按逆序銷毀铭腕,先是最后一個(gè),再依次往前多糠。
delete p; // p 必須指向一個(gè)動態(tài)分配的對象或?yàn)榭?delete [] pa; // pa 必須指向一個(gè)動態(tài)分配的數(shù)組或?yàn)榭?
@巯稀!指向數(shù)組的 unique_ptr夹孔,不支持成員訪問運(yùn)算符被盈,點(diǎn)和箭頭
unique_ptr<int[]> up(new int[10]);
up.release(); // 會自動調(diào)用 delete []