第16章 string類和標準模板庫
本章內容包括:
- 標準C++ string類
- 模板auto_ptr,unique_ptr和shared_ptr
- 標準模板庫(STL)
- 容器類
- 迭代類
- 函式物件(functor)
- STL演算法
- 模板initializer_list
16.1 string類
- string類是由標頭檔案string支援的(注意,標頭檔案string.h和cstring支援對C-風格字串進行操作的C庫字串函式,但 不支援string類).
16.1.1 構造字串
- 使用建構函式時都進行了簡化,即隱藏了這樣一個事實:string實際上是模板具體化basic_string的一個typedef,同時省略了與記憶體管理相關的引數.string類將string::npos定義為字串的最大長度,通常為unsigned int的最大值.另外,表各種使用縮寫NBTS(null-terminated string)來表示以空字元結束的字串–傳統的C字串.
- 表16.1 string類的建構函式
-
建構函式 描述 string(const char *s) 將string物件初始化為s指向的NBTS string(size_type n,char c) 建立一個包含n個元素的string物件,其中每個元素都被初始化為字元c string (const string & str) 將一個string物件初始化string物件str(複製建構函式) string() 建立一個預設的string物件,長度為0(預設建構函式) string(const char *s,size_type n) 將string物件初始化為s指向的NBTS的前n個字元,即使超過了NBTS結尾 template<class Iter>string(Iter begin,Iter end) 將string物件初始化為區間[begin,end]內的字元,其中begin和end的行為就像指標,用於制定位置,範圍包括begin在內,但不包括end string(const string & str,string size_type pos=0,size_type n=npos) 將一個string物件初始化為物件str中從位置pos開始到結尾的字元,或從位置pos開始的n個字元 string(string && str) noexcept 這是C++11新增的,它將一個string物件初始化為string物件str,並可能修改str(移動建構函式) string(initializer_list<char> il 這是C++11新增的,它將一個string物件初始化為初始化列表il中的字元 -
程式清單16.1 str1.cpp
- 程式說明
- C++11新增的建構函式
16.1.2 string類輸入
- C和C++的string輸入,它們之間的主要區別在於,string版本的getline()將自動調整目標string物件的大小,使之剛好能夠儲存輸入的字元;
- 在設計方面一個區別是,讀取C-風格字串的函式是istream類的方法,而string版本是獨立的函式.這就是對於C-風格字串輸入,cin是呼叫物件;而對於string獨享輸入,cin是一個函式引數的原因.
- string版本的getline()函式從輸入中讀取字元,並將其儲存到目標string中,直到發生下列三種情況之一:
- 到達檔案為,在這種情況下,輸入流的eofbit將被設定,這意味著方法fail()和eof()都將返回true;
- 遇到分界字元(預設為\n),在這種情況下,將把分界字元從輸入流中刪除,但不儲存它;
- 讀取的字元數達到最大允許值(string::npos和可供分配的記憶體位元組數中較小的一個),在這種情況下,將設定輸入流的failbit,這意味著方法fail()將返回true.
- 程式清單16.2 strfile.cpp
- 注意,將:制定為分界字元後,換行符將被視為常規字元.
16.1.3 使用字串
- size()和length()成員函式都返回字串中的字元數.為什麼這兩個函式完成相同的任務呢?length()成員來自焦躁版本的string類,而size()則是為提供STL相容性而新增的.
// hangman.cpp -- some string methods
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <cctype>
using std::string;
const int NUM = 26;
const string wordlist[NUM] = {"apiary", "beetle", "cereal",
"danger", "ensign", "florid", "garage", "health", "insult",
"jackal", "keeper", "loaner", "manage", "nonce", "onset",
"plaid", "quilt", "remote", "stolid", "train", "useful",
"valid", "whence", "xenon", "yearn", "zippy"};
int main()
{
using std::cout;
using std::cin;
using std::tolower;
using std::endl;
std::srand(std::time(0));
char play;
cout << "Will you play a word game? <y/n> ";
cin >> play;
play = tolower(play);
while (play == 'y')
{
string target = wordlist[std::rand() % NUM];
int length = target.length();
string attempt(length, '-');
string badchars;
int guesses = 6;
cout << "Guess my secret word. It has " << length
<< " letters, and you guess\n"
<< "one letter at a time. You get " << guesses
<< " wrong guesses.\n";
cout << "Your word: " << attempt << endl;
while (guesses > 0 && attempt != target)
{
char letter;
cout << "Guess a letter: ";
cin >> letter;
if (badchars.find(letter) != string::npos
|| attempt.find(letter) != string::npos)
{
cout << "You already guessed that. Try again.\n";
continue;
}
int loc = target.find(letter);
if (loc == string::npos)
{
cout << "Oh, bad guess!\n";
--guesses;
badchars += letter; // add to string
}
else
{
cout << "Good guess!\n";
attempt[loc]=letter;
// check if letter appears again
loc = target.find(letter, loc + 1);
while (loc != string::npos)
{
attempt[loc]=letter;
loc = target.find(letter, loc + 1);
}
}
cout << "Your word: " << attempt << endl;
if (attempt != target)
{
if (badchars.length() > 0)
cout << "Bad choices: " << badchars << endl;
cout << guesses << " bad guesses left\n";
}
}
if (guesses > 0)
cout << "That's right!\n";
else
cout << "Sorry, the word is " << target << ".\n";
cout << "Will you play another? <y/n> ";
cin >> play;
play = tolower(play);
}
cout << "Bye\n";
return 0;
}
16.1.4 string還提供了哪些功能
- 方法capacity()返回當前分配給字串的記憶體塊的大小,而reserve()方法讓您能夠請求記憶體塊的最小長度.
- 程式清單16.4 str2.cpp
16.1.5 字串種類
- 模板basic_string有4個具體化,每個具體化都有一個typedef名稱:
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
typedef basic_string<char16_t> u16string;//C++11
typedef basic_string<char32_t> u32string;//C++11
- 這讓您能夠使用基於型別wchar_t,char16_t,char32_t和char的字串.
- Allocator是一個管理記憶體分配的類.對於各種字元型別,都有預定義的allocator模板具體化,它們都是預設的.它們使用new和delete.
16.2 智慧指標模板類
- 智慧指標是行為類似於指標的類物件,本節介紹三個可幫助管理動態記憶體分配的智慧指標模板.
- 但凡涉及”別忘了”的解決方法,很少是最佳的.
- 模板auto_ptr是C++98提供的解決方案,C++11已將其摒棄,並提供了另外兩種解決方案.然而,雖然auto_ptr被摒棄,但它已使用了多年;同時,如果您的編譯器不支援其他兩種解決方案,auto_ptr將是唯一的選擇.
16.2.1 使用智慧指標
- 這三個智慧指標模板(auto_ptr,unique_ptr和shared_ptr)都定義了類似指標的物件,可以將new獲得(直接或簡介)的地址賦給這種物件.當智慧指標過期時,其解構函式將使用delete來釋放記憶體.因此,如果將new返回的地址賦給這些物件,將無需記住稍後釋放這些記憶體:在智慧指標過期時,這些記憶體將自動被釋放.
- 要建立智慧指標物件,必須包含標頭檔案memory,該檔案模板定義.
- 程式清單16.5 smrtptrs.cpp
- 所有智慧指標類都一個explicit建構函式,該建構函式將指標作為引數.因此不需要自動將指標轉換為智慧指標物件.
- 對全部三種智慧指標都應避免的一點:
- string vaction(“I wandered lonely as a cloud.”);
- shared_ptr pvac(&vacation);//NO!
- pvac過期時,程式將把delete運算子用於非堆記憶體,這是錯誤的.
16.2.2 有關智慧指標的注意事項
- 為何有三種智慧指標呢?實際上有4種,但本書不討論weak_ptr.
- 為什麼摒棄auto_ptr呢?
- 程式清單16.6 fowl.cpp
// fowl.cpp -- auto_ptr a poor choice
#include <iostream>
#include <string>
#include <memory>
int main()
{
using namespace std;
auto_ptr<string> films[5] =
{
auto_ptr<string> (new string("Fowl Balls")),
auto_ptr<string> (new string("Duck Walks")),
auto_ptr<string> (new string("Chicken Runs")),
auto_ptr<string> (new string("Turkey Errors")),
auto_ptr<string> (new string("Goose Eggs"))
};
auto_ptr<string> pwin;
pwin = films[2]; // films[2] loses ownership
cout << "The nominees for best avian baseball film are\n";
for (int i = 0; i < 5; i++)
cout << *films[i] << endl;
cout << "The winner is " << *pwin << "!\n";
// cin.get();
return 0;
}
- 訊息core dumped表明,錯誤地使用auto_ptr可能導致問題(這種程式碼的行為是不確定的,其行為可能隨系統而異).
- 如果在程式清單16.6中使用shared_ptr代替auto_ptr(者要求編譯器支援C++11新增的shared_ptr類),則程式將正常執行
- 如果使用unique_ptr,結果將如何呢?與auto_ptr一樣,unique_ptr也採用所有權模型.但使用unique_ptr時,程式不會等到執行階段崩潰,而在編譯器因下述程式碼行出現錯誤pwin = films[2];
16.2.3 unique_ptr為何由於auto_ptr
- unique_ptr比auto_ptr更安全(編譯階段錯誤比潛在的程式崩潰更安全).
- unique_ptr如何能夠區分安全和不安全的用法呢?答案是它使用了C++11新增的移動建構函式和右值引用.
- 相比於auto_ptr,unique_ptr還有另一個優點.它有一個可用於陣列的辯題.別忘了,必須將delete和new配對,將delete[]和new[]配對.模板auto_ptr使用delete而不是delete[],因此只能與new一起使用,二不能與new[]一起使用.但unique_ptr有使用new[]和delete[]的版本:
std::unique_ptr<double[]>pad(new doule(5));//will use delete []
- 警告:使用new分配記憶體時,才能使用auto_ptr和shared_ptr,使用new[]分配記憶體式,不能使用它們.不使用new分配記憶體時,不能使用auto_ptr,不能使用auto_ptr或shared_ptr;不使用new或new[]分配記憶體時,不能使用unique_ptr.
16.2.4 選擇智慧指標
- 如果程式要使用多個指向同一個物件的指標,應選擇shared_ptr.但不能用於unique_ptr(編譯器發出警告)和auto_ptr(行為不確定).如果您的編譯器沒有提供shared_ptr,可使用Boost庫提供的shared_ptr.
- 如果程式不需要多個指向同一個物件的指標,則可使用unique_ptr.如果函式使用new分配記憶體,並返回指向該記憶體的指標,將其返回型別宣告為unique_ptr是不錯的選擇.
16.3 標準模板庫
- STL提供了一組表示容器,迭代器,函式物件和演算法的模板.STL不是面向物件的程式設計,而是一種不同的程式設計模式—泛型程式設計(generic programming).
16.3.1 模板類vector
- 要建立vector模板物件,可使用通常的表示法來指出要使用的型別.另外,vector模板使用動態記憶體分配,因此可以用初始化引數來指出需要多少向量.
- 分配器:與string類相似,各種STL容器模板都接收一個可選的模板引數,該引數制定使用哪個分配器物件來管理記憶體.例如,vector模板的開頭與下面類似:
template <class T, class Allocator = allocator<T>>
class vector{...
- 如果省略該模板引數的值,則容器模板將預設使用allocator類.這個類使用new和delete.
- 程式清單16.7 vect1.cpp
16.3.2 可對向量執行的操作
- 每個容器類都定義了一個何時的迭代器,該迭代器的型別是一個名為iterator的typedef,其作用域為整個類.
- 可以vector<double>::iterator pd = scores.begin();,也可以使用C++11中的auto pd = scores.begin();//C++11 automatic type deduction
- 注意:區間[it1,it2)由迭代器it1和it2指定,其範圍為it1和it2(不包括it2).
- 程式清單16.8 vect2.cpp
16.3.3 對向量可執行的其他操作
- 因為對有些操作來說,類特定演算法的效率比通用演算法高,因此,vector的成員函式swap()的效率比非成員函式swap()高,但非成員函式讓您能夠交換兩個型別不同的容器的內容.
- Random_shuffle()函式接受兩個制定區間的迭代器引數,並隨機排列該區間中的元素.
- 程式清單16.9 vect3.cpp
16.3.4 基於範圍的for迴圈(C++11)
- 不同於for_ecah(),基於範圍的for迴圈可修改容器的內容,訣竅是制定一個引用引數.例如,假設有如下函式:
- void InflateReview(Review &r){r.rating++;}可使用如下迴圈對books的每個元素執行該函式:for(auto & x :books) InflateReview(x);
16.4 泛型程式設計
- STL是一種泛型程式設計(generic programming).面向物件程式設計關注的是程式設計的資料方面,而泛型程式設計關注的是演算法.它們之間的共同點是抽象和建立可重用程式碼,但它們的理念絕然不同.
- 泛型程式設計旨在編寫獨立於資料型別的程式碼.在C++中,完成通用程式的工具是模板.當然,模板使得能夠按泛型定義函式或類,而STL通過通用演算法更進了一步.模板讓這一切稱為可能,但必須對元素進行仔細地設計.為解模板和設計是如何協同工作的,來看一看需要迭代器的原因.
16.4.1 為何使用迭代器
- 理解迭代器是理解STL的關鍵所在.模板使得演算法獨立於儲存的資料型別,而迭代器使演算法獨立於使用的容器型別.
- 泛型程式設計旨在使用同一個find函式來處理陣列,連結串列或任何其他容器型別.
- 首先是處理容器的演算法,應儘可能用通用的術語來表達演算法,使之獨立於資料型別和容器型別.為使通用演算法能夠適用於具體情況,應定義能夠滿足演算法需求的迭代器,並把要求加到容器設計上.即基於演算法的要求,設計基本迭代器的特徵和容器特徵.
16.4.2 迭代器型別
- STL定義了5種迭代器,並根據所需的迭代器型別對演算法進行了描述.這5種迭代器分別是輸入迭代器,輸出迭代器,正向迭代器,雙向迭代器和隨機訪問迭代器.
- 1.輸入迭代器
- 輸入迭代器可被程式用來讀取容器中的資訊.
- 並不能保證輸入迭代器第二次遍歷容器時,順序不變.另外,輸入迭代器被遞增後,也不能保證其先前的值仍然可以被解除引用.基於輸入迭代器的任何演算法都應當是單通行(single-pass)的,不依賴於錢一次遍歷時的迭代器值,也不依賴於本次遍歷彙總前面的迭代器值.
- 注意,輸入迭代器是單向迭代器,可以遞增,但不能倒退.
- 2.輸出迭代器
- 輸出迭代器與輸入迭代器相似,知識解除引用讓程式能修改容器值,而不能讀取.
- 簡而言之,對於單通行,只讀演算法,可以使用輸入迭代器;而對於單通行,只寫演算法,則可以使用輸出迭代器.
- 3.正向迭代器
- 與輸入和輸出迭代器不同的是,它總是按相同的順序遍歷一系列值.另外,將正向迭代器遞增後,仍然可以對前面的迭代器值解除引用(如果儲存了它),並可以得到相同的值.這些特徵使得多次通行演算法稱為可能.
- 4.雙向迭代器
- 雙向迭代器具有正向迭代器的所有特性,同時支援兩種(字首和字尾)遞減運算子.
- 5.隨機訪問迭代器
- 像a+n這樣的表示式僅當a和a+n都位於容器區間(包括超尾)內時才合法.
16.4.3 迭代器層次結構
- 正向迭代器具有輸入迭代器和輸出迭代器的全部功能,同時還有自己的功能;雙向迭代器具有正向迭代器的全部功能,同時還有自己的功能;隨機訪問迭代器具有正向迭代器的全部功能,同時還有自己的功能.
- 為何需要這麼多迭代器呢?目的是為了在編寫演算法儘可能使用要求最低的迭代器,並讓它適用於容器的最大區間.
- 注意,各種迭代器的型別並不是確定的,而只是一種概念性描述.
16.4.4 概念,改進和模型
- STL文獻使用術語概念concept來描述一系列的要求.
- 如果所設計的容器類需要去哦迭代器,可考慮STL,它包含用於標準種類的迭代器模板.
- 概念可以具有類似繼承的關係.然而,不能將C++繼承機制用於迭代器.有些STL文獻使用術語改進refinement來表示這種概念上的繼承,因此,雙向迭代器是對正向迭代器概念的一種改進.
- 概念的具有實現被稱為模板model.
- 1.將指標用作迭代器
- 迭代器是廣義指標,而指標滿足所有的迭代器要求.迭代器是STL演算法的介面,而指標是迭代器,因此STL演算法可以使用指標來對基於指標的非STL容器進行操作.
- C++支援將超尾概念用於陣列,使得可以將STL演算法用於常規陣列.由於指標是迭代器,而演算法是基於迭代器的,這使得可將STL演算法用於常規陣列.
- 2.其他有用的迭代器
- 注意:rbegin()和end()返回相同的值(超尾),但型別不同(reverse_iterator和iterator).同樣,rend()和begin()也返回相同的值(指向地i鉻元素的迭代器),但型別不同.
- 程式清單16.10 copyit.cpp
// copyit.cpp -- copy() and iterators
#include <iostream>
#include <iterator>
#include <vector>
int main()
{
using namespace std;
int casts[10] = {6, 7, 2, 9 ,4 , 11, 8, 7, 10, 5};
vector<int> dice(10);
// copy from array to vector
copy(casts, casts + 10, dice.begin());
cout << "Let the dice be cast!\n";
// create an ostream iterator
ostream_iterator<int, char> out_iter(cout, " ");
// copy from vector to output
copy(dice.begin(), dice.end(), out_iter);
cout << endl;
cout <<"Implicit use of reverse iterator.\n";
copy(dice.rbegin(), dice.rend(), out_iter);
cout << endl;
cout <<"Explicit use of reverse iterator.\n";
// vector<int>::reverse_iterator ri; // use if auto doesn't work
for (auto ri = dice.rbegin(); ri != dice.rend(); ++ri)
cout << *ri << ' ';
cout << endl;
// cin.get();
return 0;
}
- 提示:可以用insert_iterator將複製資料的演算法轉換為插入資料的演算法.
- 程式清單16.11 inserts.cpp
// inserts.cpp -- copy() and insert iterators
#include <iostream>
#include <string>
#include <iterator>
#include <vector>
#include <algorithm>
void output(const std::string & s) {std::cout << s << " ";}
int main()
{
using namespace std;
string s1[4] = {"fine", "fish", "fashion", "fate"};
string s2[2] = {"busy", "bats"};
string s3[2] = {"silly", "singers"};
vector<string> words(4);
copy(s1, s1 + 4, words.begin());
for_each(words.begin(), words.end(), output);
cout << endl;
// construct anonymous back_insert_iterator object
copy(s2, s2 + 2, back_insert_iterator<vector<string> >(words));
for_each(words.begin(), words.end(), output);
cout << endl;
// construct anonymous insert_iterator object
copy(s3, s3 + 2, insert_iterator<vector<string> >(words, words.begin()));
for_each(words.begin(), words.end(), output);
cout << endl;
// cin.get();
return 0;
}
- 對於這些迭代器,請記住,只要使用就會熟悉它們.另外還請記住,這些預定義迭代器提供了STL演算法的通用性.
16.4.5 容器種類
- STL具有容器概念和容器型別.概念是具有名稱的通用類別;容器型別是可用於建立具體容器物件的模板.以前的11個容器型別分別是deque,list,queue,priority_queue,stack,vector,map,multimap,set,multiset和bitset(它是在位元級處理資料的容器);C++11新增了forward_list,unordered_map,unordered_multimap,unordered_set和unordered_multiset,且不講bitset視為容器,而將其視為一種獨立的類別.
- 1.容器概念
- 容器概念指定了所有STL容器類都必須滿足的一系列要求.
- 儲存在容器中的資料為容器所有,這意味著當容器過期時,儲存在容器中的資料也將過期(然而,如果資料是指標的化,則它指向的資料並不一定過期).
- 不能將任何型別的物件儲存在容器中,具體地說,型別必須是可複製構造的和可賦值的.基本型別滿足這些要求;只要類定義沒有將複製建構函式和賦值運算子宣告為私有或保護的,則也滿足這種要求.C++改進了這些概念,添加了術語可複製插入copyInsertable和可移動插入MoveInsertable.
- 表16.5 一些基本的容器特徵
- 其中,X表示容器型別,a和b表示型別為X的值;r表示型別為X&的值;u表示型別為X的識別符號.
-
表示式 返回型別 說明 複雜度 X::iterator 指向T的迭代器型別 滿足正向迭代器要求的任何迭代器 編譯時間 X::value_type T T的型別 編譯時間 X u; 建立一個名為u的空容器 固定 X(); 建立一個匿名的空容器 固定 X u(a); 呼叫複製建構函式後u==a 線性 X u=a; 作用同X u(a); 線性 r=a; X& 呼叫賦值運算子後r==a 線性 (&a)->~X(); void 對容器中每個元素應用解構函式 線性 a.begin() 迭代器 返回指向容器第一個元素的迭代器 固定 a.end() 迭代器 返回超尾值迭代器 固定 a.size() 無符號整型 返回元素個數,等價於a.end()-a.begin() 固定 a.swap(b) void 交換a和b的內容 固定 a==b 可轉換為bool 如果a和b的長度相同,且a中每個元素都等於(==為真)b中相應的元素,則為幀 線性 a!=b 可轉換bool 返回!(a==b) 線性 - ”複雜度”一列描述了執行操作所需的時間.這個表列出了3種可能性,從快到慢依次為:編譯時間;固定時間;線性時間,如果複雜度為編譯時間,則操作將在編譯時執行,執行時間為0.固定複雜度以為這操作發生在執行階段,但獨立於物件中的元素數目.線性複雜度意味著時間與元素數目成正比.即如果a和b都是容器,則a==b具有線性複雜度,因為==操作必須用於容器中的每個元素.實際上,這是最糟糕的情況.如果兩個容器的長度不同,則不需要做任何的單獨比較.
- 複雜度要求是STL特徵,雖然實現細節可以隱藏,但效能規格應公開,以便程式設計師能夠指導完成特定操作的計算成本.
-
2.C++11新增的容器要求
- 表16.6 C++11新增的基本容器要求,在這個表中,rv表示型別為X的非常量右值,如函式的返回值.
-
表示式 返回型別 說明 複雜度 X u(rv); 呼叫移動建構函式後,u的值與rv的原始值相同 線性 X u=rv; 作用同Xu(rv); a=rv; X& 呼叫移動賦值運算子後,u的值與rv的原始值相同 線性 a.cbegin() const_iterator 返回指向容器第一個元素的const迭代器 固定 a.cend const_iterator 返回超尾值const迭代器 固定 -
如果源物件是臨時的,移動操作的效率將高於常規復制.
- 3.序列
- 序列概念增加了迭代器至少是正向迭代器這樣的要求,這保證了元素將按特定順序排列,不會在兩次迭代之間發生變化.
- 介紹這7種序列容器型別:
- vector
- vector是陣列的一種類表示,它提供了自動記憶體管理功能,可以動態地改變vector物件的長度,並隨著元素的新增和刪除而增大和縮小.它提供了對元素的隨機訪問.在尾部新增和刪除元素的時間是固定的,但在頭部或中間插入和刪除元素的複雜度為線性時間.
- 除序列外,vector還是可反轉容器reversible container概念的模型.
- deque
- deque模板類(在deque標頭檔案中宣告)表示雙端佇列double-ended queue,通常被簡稱為deque.
- list
- list模板類(在list標頭檔案中宣告)表示雙向連結串列.
- 與vector不同的是,list不支援陣列表示法和隨機訪問.
- 程式清單 16.12 list.cpp
- 程式說明
- insert()和splice()之間的主要區別在於:insert()將原始區間的副本插入到目標地址,而splice()則將原始區間移到目標地址.
- 注意,unique()只能將相鄰的相同值壓縮為單個值.
- list工具箱
- forward_list(C++11)
- C++11新增了容器類forward_list,它實現了單鏈表.
- queue
- queue模板類(在標頭檔案queue(以前為queue.h)中宣告)是一個介面卡類.
- queue模板的限制比deque更多.它不僅不允許隨機訪問佇列元素,甚至不允許遍歷佇列.
- priority_queue
- 和queue的區別在於,在priority_queue中,最大的元素被移到隊首(生活不總是公平的,佇列也一樣).內部區別在於,預設的底層類是vector.
- stack
- 它不僅不允許隨機訪問棧元素,甚至不允許遍歷棧.它把使用限制在定義棧的基本操作上,即可以將儼如推導棧頂,從棧頂彈出元素,檢視棧頂的值,檢查元素數目和測試棧是否為空.
- array(C++11)
- vector
16.4.4 關聯容器
- 關聯容器associative constainer是對容器概念的另一個改進.
- STL提供了4種關聯容器:set,multiset,map和multimap.
- 1.set示例
- 程式清單16.13 setops.cpp
- 2.multimap示例
- 程式清單16.14 multmap.cpp
16.4.5 無序關聯容器(C++11)
- 無序關聯容器是對容器概念的另一種改進,底層的差別在於,關聯容器是基於樹結構的,而無序關聯容器是基於資料結構雜湊表的.
- 有4種無序關聯容器,它們是unordered_set,unordered_multiset,unordered_map和unordered_multimap.
16.5 函式物件
- 函式物件—也叫函式符functor.函式符是可以以函式方式與()結合使用的任意物件.
16.5.1 函式符概念
- 生成器generator是不用引數就可以呼叫的函式符.
- 一元函式unary function是用一個引數可以呼叫的函式符
- 二元函式binary function是用兩個引數可以呼叫的函式符.
- 當然,這些概念都有相應的改進版:
- 返回bool值的醫院函式是為此predicate
- 返回bool值的二元函式是二元為此binary predicate
- 程式清單16.15 functor.cpp
16.5.2 預定義的函式符
- 標頭檔案functional(以前為function.h)定義了多個模板類函式物件,其中包括plus<>().
- 對於所有內建的算術運算子,關係運算符和邏輯運算子,STL都提供了等價的函式符.
-
警告:老式C++實現使用函式符名times,而不是multiplies.
16.5.3 自適應函式符和函式介面卡 -
STL有5個相關的概念:自適應生成器adaptable generator,自適應一元函式adaptable unary function,自適應二元函式adaptable binary function,自適應為此adaptable predicate和自適應二元為此adaptable binary predicate.
- 使用函式符稱為自適應的原因是,它攜帶了標識引數型別和返回型別的typedef成員.這些成員分別是result_type,first_argument_type和second_argument_type.
- 函式符自適應性的意義在於:函式介面卡物件可以使用函式物件,並認為存在這些typedef成員.
- 程式清單16.16 funadap.cpp
16.6 演算法
- 對於演算法函式設計,有兩個主要的通用部分.首先,它們都使用模板來提供泛型;其次,它們都使用迭代器來提供訪問容器中資料的通用表示.
16.6.1 演算法組
- STL將演算法庫分成4組(前3組在標頭檔案algorithm(以前為algo.h)中描述,第4組是專用於數值資料的,有自己的標頭檔案,稱為numeric(以前它們也位於algol.h中)):
- 非修改式序列操作;
- 修改式序列操作;
- 排序和相關操作;
- 通用數字運算.
16.6.2 演算法的通用特徵
- STL函式使用迭代器和迭代器區間.區間引數必須是輸入迭代器或更高級別的迭代器,而指示結果儲存位置的跌大旗必須是輸出迭代器或更高級別的迭代器.
- 對演算法進行分類的方式之一是按結果放置的位置進行分類.(就地演算法in-place algorithm,複製演算法copying algorithm).有些演算法有兩個版本:就地版本和複製版本.STL的約定是,複製版本的名稱將以_copy結尾.複製版本將接受一個額外的輸出迭代器引數,該引數制定結果的放置位置.
- 注意:replace_copy()的返回型別為OutputIterator.對於複製演算法,統一的約定是:返回一個迭代器,該迭代器指向複製的最後一個值後面的一個位置.
- 另一個常見的變體是:有些函式有這樣的版本,即根據將函式應用於容器元素得到的結果來執行操作.這些版本的名稱通常以_if結尾.
- 請記住,遂讓文件可指出迭代器或函式符需求,但編譯器不會對此進行檢查.如果您使用了錯誤的迭代器,則編譯器檢視例項化模板時,將顯示大量的錯誤資訊.
16.6.3 STL和string類
- string類雖然不是STL的組成部分,但設計它時考慮到了STL.
- 程式清單16.17 strngst1.cpp
- 注意,演算法next_permutation()自動提供唯一的排列組合.
16.6.4 函式和容器方法
- 有時可以選擇使用STL方法或STL函式.通常方法是更好的選擇.首先,它更適合於特定的容器;其次,作為成員函式,它可以使用模板類的記憶體管理工具,從而在需要時調整容器的長度.
- 程式清單16.18 listrmv.cpp
- 儘管方法通常更適合,但非方法函式更通用.正如您看到的,可以將它們用於陣列,string物件,STL容器,還可以用它們來處理混合的容器型別,例如,將向量容器中的資料儲存到連結串列或集合中.
16.6.5 使用STL
- STL是一個庫,其組成部分被設計成協同工作.STL元件是工具,但也是建立其他工具的基本元件.
- map類有一個有趣的特徵:可以用陣列表示法(將鍵用作索引)來訪問儲存的值.
- 程式清單16.19 usealgo.cpp
//usealgo.cpp -- using several STL elements
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <map>
#include <iterator>
#include <algorithm>
#include <cctype>
using namespace std;
char toLower(char ch) { return tolower(ch); }
string & ToLower(string & st);
void display(const string & s);
int main()
{
vector<string> words;
cout << "Enter words (enter quit to quit):\n";
string input;
while (cin >> input && input != "quit")
words.push_back(input);
cout << "You entered the following words:\n";
for_each(words.begin(), words.end(), display);
cout << endl;
// place words in set, converting to lowercase
set<string> wordset;
transform(words.begin(), words.end(),
insert_iterator<set<string> > (wordset, wordset.begin()),
ToLower);
cout << "\nAlphabetic list of words:\n";
for_each(wordset.begin(), wordset.end(), display);
cout << endl;
// place word and frequency in map
map<string, int> wordmap;
set<string>::iterator si;
for (si = wordset.begin(); si != wordset.end(); si++)
wordmap[*si] = count(words.begin(), words.end(), *si);
// display map contents
cout << "\nWord frequency:\n";
for (si = wordset.begin(); si != wordset.end(); si++)
cout << *si << ": " << wordmap[*si] << endl;
// cin.get();
// cin.get();
return 0;
}
string & ToLower(string & st)
{
transform(st.begin(), st.end(), st.begin(), toLower);
return st;
}
void display(const string & s)
{
cout << s << " ";
}
16.7 其他庫
16.7.1 vector,valarray和array
- C++為何提供三個陣列模板,這些類是由不同的小組開發的,用於不同的目的.vertor模板來是一個容器類和算法系統的一部分,它支援面向容器的操作.而valarray類模板是面向數值計算的,不是STL的一部分.最後,array是為替代內建陣列而設計的,它通過提供更好,更安全的街擴,讓陣列更緊湊,效率更高.
- valaarya的介面更簡單是否意味著效能更高呢?在大多數情況下,答案是否定的.簡單表示法通常是使用類似於您處理常規陣列時使用的迴圈實現的.然而,有些硬體設計允許在執行向量操作時,同時將一個數組中的值載入到一組暫存器中,然後並行的進行處理.從原則上說,valarray操作也可以實現成利用這樣的設計.
- C++11提供了接受valarray物件作為引數的模板函式begin()和end().
- 程式清單16.20 valvect.cpp
- 程式清單16.21 vslice.cpp
16.7.2 模板initializer_list(C++11)
16.7.3 使用initializer_list
- 要在程式碼中使用initializer_list物件,必須包含標頭檔案initializer_list.
- 程式清單16.22 ilist.cpp
- 可按值傳遞initializer_list物件,也可按引用傳遞.這種物件本身很小,採用的傳遞方式不會帶來重大的效能影響.STL按值傳遞它們.
16.8 總結
- string類提供了自動記憶體管理功能以及眾多處理字串的方法和函式.
- 諸如auto_ptr以及C++11新增的shared_ptr和unique_ptr等職能指標模板使得管理由new分配的記憶體更容易.如果使用這些職能指標(而不是常規指標)來儲存new返回的地址,則不必在以後使用刪除運算子.職能指標物件過期時,其解構函式將自動呼叫delete運算子.
- STL是一個容器類模板,迭代器類模板,函式物件模板和演算法函式模板的集合,他們的設計是一致的,都是基於泛型程式設計原則的.演算法使用模板,從而獨立於所儲存的物件的型別;通過使用迭代器介面,從而獨立於容器的型別.迭代器是廣義指標.
- STL使用術語”概念”來描述一組要求.概念真正的實現方式被稱為概念的”模型”.
- 容器和演算法都是由其提供或需要的迭代器型別表徵的.應當檢查容器是否具備支援演算法要求的迭代器概念.
- STL還提供了函式物件(函式符),函式物件是衝在了()運算子(即定義了operator()()方法)的類.
- 模板類complex和valarray支援複數和陣列的數值運算.
16.9 複習題