C++ Primer 筆記-第17章 特殊庫特殊設(shè)施

17.1?tuple
類型
我們可以將
tuple
看作一個“快速而隨意”的數(shù)據(jù)結(jié)構(gòu)。可以有任意數(shù)量的成員,且成員的類型不受限制。
17.1.1 定義和初始化tuple
當(dāng)我們創(chuàng)建一個
tuple
時,需要指出每個成員的類型。創(chuàng)建一個
tuple
對象時,可以使用它的默認(rèn)構(gòu)造函數(shù),它會對每個成員進(jìn)行值初始化。當(dāng)使用初始值初始化對象時,構(gòu)造函數(shù)是
explicit
。因此必須使用直接初始化語法:

tuple<size_t, int, double> item = {1, 2, 3.0}; // 錯誤
tuple<size_t, int, double> item(1, 2, 3.0); ? ?// 正確
// 如果不知道tuple準(zhǔn)確的類型細(xì)節(jié)信息,
// 可以用兩個輔助類模板來查詢tuple成員的數(shù)量和類型
typedef decltype(item) trans;// trans是item的類型
// 返回trans類型對象中成員的數(shù)量:
size_t sz = tuple_size<trans>::value; ? // 返回3
// cnt的類型與item中第二個成員相同
tuple_element<1, trans>::type cnt = get<1>(item); ? // cnt是一個int

由于
tuple
定義了 < 和 == 運(yùn)算符,我們可以將tuple
序列傳遞給算法,并且可以在無序容器中將tuple
作為關(guān)鍵字類型。

17.2?bitset
類型
17.2.1 定義和初始化bitset
定義一個
bitset
需要聲明它包含多好個二進(jìn)制位。string
的下標(biāo)編號習(xí)慣與bitset
恰好相反:string
中下標(biāo)最大的字符(最右字符)用來初始化bitset
中的低位(下標(biāo)為0的二進(jìn)制位)。
17.2.2?bitset
操作
編寫一個程序表示30給學(xué)生的測試結(jié)果-“通過/失敗”

bool status;
// 使用位運(yùn)算符的版本
unsigned long quizA = 0; ? ? // 此值被當(dāng)作位集合使用
quizA |= 1UL << 27; ? ? ? ? ?// 指出第27個學(xué)生通過了測試
status = quizA & (1UL << 27);// 檢查第27個學(xué)生是否通過了測驗(yàn)
quizA &= ~(1UL << 27); ? ? ? // 第27個學(xué)生未通過測驗(yàn)
// 使用標(biāo)準(zhǔn)庫bitset完成等價的工作
bitset<30> quizB; ? ? ? ? ? // 每個學(xué)生分配一位,所有位都被初始化為0
quizB.set(27); ? ? ? ? ? ? ?// 指出第27個學(xué)生通過了測試
status = quizB[27]; ? ? ? ? // 檢查第27個學(xué)生是否通過了測驗(yàn)
quizB.reset(27); ? ? ? ? ? ?// 第27個學(xué)生未通過測驗(yàn)


17.3 正則表達(dá)式
正則表達(dá)式(regular expression)?是一種描述符號序列的方法,是一種極其強(qiáng)大的計(jì)算工具。
17.3.1 使用正則表達(dá)式庫
使用正則表達(dá)式來查找違反拼寫規(guī)則“i除非在c之后,否則必須在e之前”單詞的例子:

// 查找不在字符c之后的字符串ei
string pattern("[^c]ei");
// 我們需要包含pattern的整個單詞
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern); ? // 構(gòu)造一個用于查找模式的regex
smatch results; ? ? // 定義一個對象保存搜索結(jié)果
// 定義一個string保存與模式匹配和不匹配的文本
string test_str = "receipt freind theif receive";
// 用r在test_str中查找與pattern匹配的子串
if(regex_search(test_str, results, r)) ?// 如果有匹配字串
? ?cout << results.str() << endl; ? ? ?// 打印匹配的單詞

一個正則表達(dá)式所表示的“程序”是在運(yùn)行時而非編譯時編譯的。表達(dá)式的編譯是一個很慢的操作,特別是在使用了擴(kuò)展的或者復(fù)雜的語法時。因此應(yīng)該避免創(chuàng)建不必要的
regex
正則表達(dá)式。我們可以搜索多種類型的輸入序列,重點(diǎn)在于我們使用的RE庫類型必須與輸入序列類型匹配:

regex r("[[:alnum:]] +\\.(cpp|cxx|cc)$", regex::icase);
// 將匹配string輸入序列,而不是char*
smatch results;
if(regex_search("myfile.cc", result, r)) // 錯誤:輸入為char*
? ?cout << results.str() << endl;
// 將匹配字符數(shù)組輸入序列
cmatch results;
if(regex_search("myfile.cc", result, r))
? ?cout << results.str() << endl; ? ? ? // 打印當(dāng)前匹配

17.3.2 匹配與Regex
迭代器類型
擴(kuò)展之前的程序,使用
sregex_iterator
來進(jìn)行搜索。

// 查找前一個不在字符c之后的字符串ei
string pattern("[^c]ei");
// 我們需要包含pattern的整個單詞
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern, regex::icase); ? // 進(jìn)行匹配時將忽略大小寫
// 它將返回利用regex_search來尋找文件中的所有匹配
for(sregex_iterator it(file.begin(), file.end(), r), end_it;
? ? ? ?it != end_it; ++it)
? ? ? ?cout << it->str() << endl; ?// 匹配的單詞

17.3.3 使用子表達(dá)式
正則表達(dá)式中的模式通常包含一個或多個子表達(dá)式(subexpression)。一個子表達(dá)式是模式的一部分,本身也具有意義。正則表達(dá)式語法通常用括號表示子表達(dá)式。
17.3.4 使用regex_replace
正則表達(dá)式不僅用在我們希望查找一個給定序列的時候,還用在當(dāng)我們想將找到的序列替換為另一個序列的時候。
就像標(biāo)準(zhǔn)庫定義標(biāo)志來指導(dǎo)如何處理正則表達(dá)式一樣,標(biāo)準(zhǔn)庫還定義了用來在替換過程中控制匹配或格式的標(biāo)志。

17.4 隨機(jī)數(shù)
17.4.1 隨機(jī)數(shù)引擎和分布
使用隨機(jī)數(shù)引擎生成10個隨機(jī)數(shù):

default_random_engine e; ? ?// 生成隨機(jī)無符號數(shù)
for(size_t i = 0; i < 10; ++i)
? ?// e()"調(diào)用"對象來生成下一個隨機(jī)數(shù)
? ?cout << e() << " ";

為了得到一個指定范圍內(nèi)的數(shù),使用一個分布類型的對象:

// 生成0到9之間(包含)均勻分布的隨機(jī)數(shù)
uniform_int_distribution<unsigned> u(0, 9);
default_random_engine e; ? ?// 生成隨機(jī)無符號數(shù)
for(size_t i = 0; i < 10; ++i)
? ?// 將u作為隨機(jī)數(shù)源
? ?// 每個調(diào)用返回在指定范圍內(nèi)并服從均勻分布的值
? ?cout << u(e) << " ";
// 注意,我們傳遞給分布對象的是引擎對象本身,即u(e)。
// 如果我們將調(diào)用寫成u(e()),含義就變?yōu)閷生成的下一個值傳遞給u,
// 會導(dǎo)致一個編譯錯誤。

當(dāng)我們說隨機(jī)數(shù)發(fā)生器時,是指分布對象和引擎對象的組合。
隨機(jī)數(shù)發(fā)生器有一個特性經(jīng)常會使新手迷惑:即使生成的數(shù)看起來是隨機(jī)的,但對一個給定的發(fā)生器,每次運(yùn)行程序它都會返回相同的數(shù)值序列。序列不變這一事實(shí)在調(diào)試時非常有用。
一個函數(shù)如果定義了局部的隨機(jī)數(shù)發(fā)生器,應(yīng)該將其(包括引擎和分布對象)定義為
static
的。否則,每次調(diào)用函數(shù)都會生成相同的序列。一旦我們的程序調(diào)試完畢,我們通常希望每次運(yùn)行程序都會生成不同的隨機(jī)結(jié)果,可以通過提供一個**種子(seed)**來達(dá)到這一目的。引擎可以利用它從序列中一個新的位置重新開始生成隨機(jī)數(shù)。
選擇一個好的種子,與生成好的隨機(jī)數(shù)所涉及到的其他大多數(shù)事情相同,是極其困難的??赡茏畛S玫姆椒ㄊ钦{(diào)用系統(tǒng)函數(shù)
time
。如果程序作為一個自動過程的一部分返回運(yùn)行,將
time
的返回值作為種子的方法就失效了;它可能多次使用的都是相同的種子。
17.4.2 其他隨機(jī)數(shù)分布
由于引擎返回相同的隨機(jī)數(shù)序列,所以我們必須在循環(huán)外聲明引擎對象。否則,每次循環(huán)都會創(chuàng)建一個新引擎,從而每步循環(huán)都會生成相同的值。類似的,分布對象也要保持狀態(tài),因此也應(yīng)該在循環(huán)外定義。

17.5?IO
庫再探
17.5.1 格式化輸入和輸出
除了條件狀態(tài)外,每一個iostream對象還維護(hù)一個格式狀態(tài)來控制IO如何格式化的細(xì)節(jié)。格式狀態(tài)控制格式化的某些方面,如整型值是幾進(jìn)制、浮點(diǎn)數(shù)的精度、一個輸出元素的寬度等。
標(biāo)準(zhǔn)庫定義了一組**操縱符(manipulator)**來修改流的格式狀態(tài)。
操縱符用于兩大類輸出控制:控制數(shù)值的輸出形式以及控制補(bǔ)白的數(shù)量和位置。
當(dāng)操縱符改變流的格式狀態(tài)時,通常改變后的狀態(tài)對所有后續(xù)IO都生效。
默認(rèn)情況下,整型值的輸入輸出使用十進(jìn)制。我們可以使用操作符
hex
、oct
和dec
將其改為十六進(jìn)制、八進(jìn)制或者改回十進(jìn)制。這些操縱符只影響整型運(yùn)算對象,浮點(diǎn)值的表示形式不受影響。當(dāng)對流應(yīng)用
showbase
操縱符時,會在輸出結(jié)果中顯示進(jìn)制,它遵循與整型常量中指定進(jìn)制相同的規(guī)范。noshowbase
恢復(fù)狀態(tài),不再顯示整型值進(jìn)制。默認(rèn)情況下,精度會控制打印的數(shù)字總數(shù),我們可以通過調(diào)用IO對象的
precision
成員或使用setprecision
操縱符來改變精度。除非你需要控制浮點(diǎn)數(shù)的表達(dá)形式(如,按列打印數(shù)據(jù)或打印表示金額或百分比的數(shù)據(jù)),否則由標(biāo)準(zhǔn)庫選擇計(jì)數(shù)法是最好的方式。
通過使用恰當(dāng)?shù)牟僮鞣?,我們可以?qiáng)制一個流使用科學(xué)計(jì)數(shù)法(scientific)、定點(diǎn)十進(jìn)制(fixed)或是十六進(jìn)制(hexfloat)計(jì)數(shù)法。
17.5.2 未格式化的輸入/輸出操作
標(biāo)準(zhǔn)庫提供了一組底層操作,支持**未格式化IO(unformatted IO)。這些操作允許我們將一個流當(dāng)作一個無解釋的字節(jié)序列來處理。
get
將分隔符留作istream
中下一個字符,而getline
則讀取并丟棄分隔符。一個常見的錯誤是本想從流中刪除分隔符,但卻忘了做。
17.5.3 流隨機(jī)訪問
各種流類型通常都支持對流中數(shù)據(jù)的隨機(jī)訪問。我們可以重定位流,使之跳過一些數(shù)據(jù),首先讀取最后一行,然后讀取第一行,以此類推。
標(biāo)準(zhǔn)庫提供了一對函數(shù),來定位(seek)到流中給定的位置,以及告訴(tell)我們當(dāng)前的位置。
由于
istream
和ostream
類型通常不支持隨機(jī)訪問,所以隨機(jī)訪問功能通常適用于fstream
和stream
類型。
