C++Primer習題第十二章
我終於快要跟上我之前看書的進度了!!!感覺做題做了好久。。。
練習12.1:在此程式碼的結尾,b1和b2各包含多少個元素?
StrBlob b1;
{
StrBlob b2 = { "a", "an", "the" };
b1 = b2;
b2.push_back( "about" );
}
b1有4個元素。
b2被銷燬。但是元素底層元素並沒有被銷燬。
練習12.2:編寫你自己的StrBlob類,包含const版本的front和back。
一開始我忘了包含一系列的標頭檔案,導致測試這個StrBlob類的時候,編譯不通過:主要是說什麼vector不是一個型別啊,make_shared不知道啥玩意兒啊之類的錯誤資訊。
所以別忘了包含標頭檔案哦。
#ifndef STRBLOB_H_INCLUDED #define STRBLOB_H_INCLUDED #include<vector> #include<string> #include<stdexcept> #include<initializer_list> #include<memory> using namespace std; class StrBlob{ public: //建構函式 StrBlob(); StrBlob( initializer_list<string> il ); //容器大小訪問 vector<string>::size_type size() const { return data->size(); } bool empty() const { return data->empty(); } //新增和刪除元素 void push_back( const std::string &str ) { data->push_back( str ); } void pop_back(); //元素訪問 string& front(); const string& front() const; string& back(); const string& back() const; private: std::shared_ptr<std::vector<std::string>> data; void check( vector<string>::size_type i, const std::string &msg ) const ; }; StrBlob::StrBlob(): data( make_shared<vector<string>>() ) { } StrBlob::StrBlob(initializer_list<string> il): data( make_shared<vector<string>> (il) ) { } void StrBlob::check( vector<string>::size_type i, const std::string &msg ) const { if( i >= data->size() ) throw out_of_range( msg ); } void StrBlob::pop_back() { check( 0, "pop_back on empty StrBlob" ); data->pop_back(); } string& StrBlob::front() { check( 0, "front on empty StrBlob" ); return data->front(); } const string& StrBlob::front() const { check( 0, "front on empty StrBlob"); return data->front(); } string& StrBlob::back() { check( 0, "back on empty StrBlob" ); return data->back(); } const string& StrBlob::back() const { check( 0, "back on empty StrBlob" ); return data->back(); } #endif // STRBLOB_H_INCLUDED
練習12.3:StrBlob需要const版本的push_back()和pop_back()嗎?如果需要,新增進去。否則,解釋為什麼不需要。
不需要。因為常量的StrBlob物件是不應被允許修改vector物件內容的。
練習12.4:在我們的check函式中,沒有檢查i是否大於0。為什麼可以忽略這個檢查?
我們將check定義為私有成員函式,也就是,他只會被StrBlob類的成員函式呼叫,而不會被使用者程式呼叫。因此,我們很容易地保證傳遞給它的i值符合要求,而不必進行檢查。
練習12.5:我們未編寫接受一個initializer_list explicit引數的建構函式。討論這個設計策略的優點和缺點。
(標準)
答:
未編寫接受一個初始化列表引數的顯示建構函式,意味著可以進行列表向StrBlob的隱式型別轉換,也就是,在需要StrBlob的地方(如函式的引數),可以使用列表進行替代。而且,可以進行拷貝形式的初始化(如賦值)。這令程式編寫更為簡單方便。
但這種隱式轉換並不總是好的。例如,列表中可能並非都是合法的值。再如,對於接受StrBlob的函式,傳遞給它一個列表,會建立一個臨時的StrBlob物件,用列表對其初始化,然後將其傳遞給函式,當函式完成後,此物件將被丟棄,再也無法訪問了。對於這些情況,我們可以定義顯式的建構函式,禁止隱式類型別轉換。
練習12.6:編寫函式,返回一個動態分配的int的vector。將此vector傳遞給另一個函式,這個函式讀取標準輸入,將讀入的值儲存在vector元素中。再將vector傳遞給另外一個函式,列印讀入的值。記得在恰當的時刻delete vector 。
#include<iostream>
#include<vector>
using namespace std;
vector<int> *new_vector( void );
void read( vector<int> *pv );
void print( vector<int> *pv );
int main()
{
auto pv = new_vector();
if( pv == nullptr ){
cerr << "建立動態分配的vector失敗!";
return -1;
}
cout << "請輸入一個int的序列:" << endl;
read( pv );
cout << endl;
print( pv );
delete pv;
pv = nullptr;
return 0;
}
vector<int> *new_vector( void )
{
return new ( nothrow) vector<int>;
}
void read( vector<int> *pv )
{
int i;
while( cin >> i )
pv->push_back( i );
}
void print( vector<int> *pv )
{
cout << "容器中的資料是:" << endl;
for( const auto &i : *pv )
cout << i << " ";
cout << endl;
}
練習12.7:重做上一題,這次使用share_ptr而不是內建指標。
#include<iostream>
#include<memory>
#include<vector>
using namespace std;
shared_ptr<vector<int>> new_vector( void );
void read( shared_ptr<vector<int>> );
void print( shared_ptr<vector<int>> );
int main()
{
auto spv = new_vector();
cout << "請輸入一個int的序列:" << endl;
read( spv );
cout << endl;
print( spv );
return 0;
}
shared_ptr<vector<int>> new_vector( void )
{
return make_shared<vector<int>>();
}
void read( shared_ptr<vector<int>> pv )
{
int i;
while( cin >> i )
pv->push_back( i );
}
void print( shared_ptr<vector<int>> pv )
{
cout << "容器中的資料是:" << endl;
for( const auto &i : *pv )
cout << i << " ";
cout << endl;
}
練習12.8:下面的函式是否有錯誤?如果有,解釋錯誤的原因。
bool b() {
int* p = new int;
//...
return p;
}
程式的意圖是:
當分配記憶體成功,則new返回一個合法指標,轉換為布林值為true。
但若是new分配記憶體失敗,會丟擲一個異常。而不是返回一個nullptr。轉換為false。
修改為:
int* p = new ( nothrow ) int;
練習12.9:解釋下面程式碼執行的結果。
int *q = new int(42), *r = new int(100);// 動態分配,q指向一個值為42的int,r指向一個值為100的int。
r = q;//令r指向q。
原先r指向的那塊記憶體依然存在,但是無法再訪問了。(記憶體洩漏)
------------------------------------------------------------------------------------------------------
auto q2 = make_shared_ptr<int>(42), r2 = make_shared_ptr<int>(100);//智慧指標q2指向值為42的int
//智慧指標r2指向值為100的int
r2 = q2;//r2指向q2,r2原先指向的記憶體被釋放。
練習12.10:下面的程式碼呼叫了第413頁中定義的process函式,解釋此呼叫是否正確。如果不正確,應如何修改?
shared_ptr<int> p( new int(42) );
process( shared_ptr<int>(p) );
此呼叫是正確的。
引數位置的shared_ptr<int>(p),會建立一個臨時的shared_ptr賦予process的引數ptr,引用計數值加1,當呼叫所在的表示式結束時,這個臨時變數就會被銷燬,引用計數減1。同時將值拷貝給ptr,計數值加1。但函式結束後ptr被銷燬,引用計數減1。
最終p的引用計數還是1。記憶體不會被釋放。
練習12.11:如果我們像下面這樣呼叫process,會發生什麼?
process( shared_ptr<int>( p.get() ) );
這是一個嚴重的錯誤。
引數部分的 shared_ptr<int>( p.get() )會建立一個臨時的shared_ptr指向p.get()返回的內建指標。但是,這個臨時的shared_ptr和p是兩個獨立的shared_ptr,卻指向了同一片記憶體。當執行完畢後,ptr被銷燬,同時,它管理的記憶體也將被銷燬,此時p將管理一片被釋放了的記憶體。
練習12.12:p和q的定義如下,對於接下來的對process的每個呼叫,如果合法,解釋它做了什麼,如果不合法,解釋錯誤原因。
auto p = new int();
auto sp = make_shared_ptr<int>();
(a)process(sp); //合法。
(b)process( new int() );
//不合法。new建立了一個int物件,指向它的指標被用來建立了一個shared_ptr,傳遞給process的引數ptr,引用計數為1。當process執行完畢,ptr被銷燬,引用計數變為0,臨時int物件因而被銷燬。不存在記憶體洩漏和空懸指標的問題。
(c)process( p ); //不合法。不能將int*型別轉換為shared_ptr<int>。
(d)process( shared_ptr<int>( p ) ); //合法但是,process執行完畢後會使得p成為空懸指標。
練習12.13:如果執行下面的程式碼,會發生什麼?
auto sp = make_shared<int>();
auto p = sp.get();
delete p;
sp會成為一個類似空懸指標的shared_ptr。
練習12.14:編寫你自己版本的用shared_ptr管理connection的函式。
---------------------------------------------------------------------------------------------------------
插播一條PS:
如果你使用的智慧指標不是new分配的記憶體,記住傳遞它一個刪除器。
---------------------------------------------------------------------------------------------------------
#include<iostream>
#include<memory>
using namespace std;
struct destination{ };//表示我們正在連線什麼
struct connection{ };//使用連線所需的資訊
connection connect( destination* );//開啟連線
void disconnect( connection );//關閉給定的連線
void f( destination& );
void f1( destination & );
void end_connection( connection *p );
int main()
{
destination d;
f1( d );
f( d );
return 0;
}
void end_connection( connection *p )
{
disconnect( *p );
}
connection connect( destination *pd )
{
cout << "開啟連線" << endl;
return connection();
}
void disconnect( connection c )
{
cout << "連線關閉" << endl;
}
void f( destination &d )
{
//獲得一個連線
connection c = connect( &d );
cout << "用shared_ptr管理connect" << endl;
shared_ptr<connection> sp( &c, end_connection );
/* 獲得連線後的操作 */
//忘記呼叫disconnect
//當f退出時,不管是正常退出還是異常退出,connection都會被正確關閉。
}
void f1( destination &d )
{
cout << "直接管理connect" << endl;
connection c = connect( &d );
//忘記呼叫disconnect
cout << endl;
}
練習12.15:重寫第一題的程式,用lambda代替end_connection函式。
#include<iostream>
#include<memory>
using namespace std;
struct destination{ };//表示我們正在連線什麼
struct connection{ };//使用連線所需的資訊
connection connect( destination* );//開啟連線
void disconnect( connection );//關閉給定的連線
void f( destination& );
void f1( destination & );
//void end_connection( connection *p );
int main()
{
destination d;
f1( d );
f( d );
return 0;
}
/*
void end_connection( connection *p )
{
disconnect( *p );
}
*/
connection connect( destination *pd )
{
cout << "開啟連線" << endl;
return connection();
}
void disconnect( connection c )
{
cout << "連線關閉" << endl;
}
void f( destination &d )
{
//獲得一個連線
connection c = connect( &d );
cout << "用shared_ptr管理connect" << endl;
shared_ptr<connection> sp( &c, []( connection *p ){ disconnect( *p ); } );
/* 獲得連線後的操作 */
//忘記呼叫disconnect
//當f退出時,不管是正常退出還是異常退出,connection都會被正確關閉。
}
void f1( destination &d )
{
cout << "直接管理connect" << endl;
connection c = connect( &d );
//忘記呼叫disconnect
cout << endl;
}
-------------------------------------------------------------------------------------------------------------------------------------------------------
unique_ptr不支援普通的拷貝和賦值!(原因是unique_ptr“擁有”它指向的物件)
但我們可以拷貝或賦值一個即將銷燬的unique_ptr。例如:返回一個unique_ptr。(編譯器將執行一種“特殊”的拷貝)
--------------------------------------------------------------------------------------------------------------------------------------------------------
練習12.16:如果你試圖拷貝或賦值unique_ptr,編譯器並不總是能給出易於理解的錯誤資訊。編寫包含這種錯誤的程式,觀察編譯器是如何診斷這種錯誤的。
先略了。
練習12.17:下面的unique_ptr宣告中,哪些是合法的,哪些可能導致後續的程式錯誤?解釋每個錯誤的問題在哪裡。
int ix = 1024, *pi = &ix, *pi2 = new int(2048);
typedef unique_ptr<int> IntP;
(a) IntP p0(ix);
不合法。當我們定義一個unique_ptr時,需要將其繫結到一個new返回的指標上。
(b)IntP p1(pi);
不合法。不是new返回的指標。
合法。但是邏輯上是錯誤的。它用一個普通的int變數的地址初始化p1,p1銷燬時會釋放此記憶體,其行為是未定義的。
(c)IntP p2(pi2);
合法。
(d)IntP p3( &ix );
合法。但是同以上(b)的問題。
(e)IntP p4( new int(2048) );
合法。
(f)IntP p5( p2.get() );
合法。但是有問題。會造成兩個unique_ptr指向相同的記憶體地址。當其中一個unique_ptr被銷燬時(或者被reset釋放物件時),該記憶體被釋放,另一個unique_ptr會變成空懸指標。
練習12.18:shared_ptr為什麼沒有release成員。
unique_ptr不能賦值和拷貝,而release成員是用來轉移物件的所有權的。
shared_ptr可以通過簡單的賦值和拷貝轉移實現共享所有權。
練習12.19:定義你自己版本的StrBlobPtr,更新StrBlob類,加入恰當的friend宣告及begin和end成員。
#ifndef STRBLOB_H_INCLUDED
#define STRBLOB_H_INCLUDED
#include<vector>
#include<string>
#include<stdexcept>
#include<initializer_list>
#include<memory>
using namespace std;
class StrBlobPtr;//友元類外部宣告。
class StrBlob{
public:
//友元類宣告
friend class StrBlobPtr;
//建構函式
StrBlob();
StrBlob( initializer_list<string> il );
//容器大小訪問
vector<string>::size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
//新增和刪除元素
void push_back( const std::string &str ) { data->push_back( str ); }
void pop_back();
//元素訪問
string& front();
const string& front() const;
string& back();
const string& back() const;
//返回指向首元素和尾元素的StrBlobPtr
StrBlobPtr begin();
StrBlobPtr end();
private:
std::shared_ptr<std::vector<std::string>> data;
void check( vector<string>::size_type i, const std::string &msg ) const ;
};
inline StrBlob::StrBlob(): data( make_shared<vector<string>>() ) { }
inline StrBlob::StrBlob(initializer_list<string> il): data( make_shared<vector<string>> (il) ) { }
inline void StrBlob::check( vector<string>::size_type i, const std::string &msg ) const
{
if( i >= data->size() )
throw out_of_range( msg );
}
inline void StrBlob::pop_back()
{
check( 0, "pop_back on empty StrBlob" );
data->pop_back();
}
inline string& StrBlob::front()
{
check( 0, "front on empty StrBlob" );
return data->front();
}
inline const string& StrBlob::front() const
{
check( 0, "front on empty StrBlob");
return data->front();
}
inline string& StrBlob::back()
{
check( 0, "back on empty StrBlob" );
return data->back();
}
inline const string& StrBlob::back() const
{
check( 0, "back on empty StrBlob" );
return data->back();
}
//StrBlobPtr類
class StrBlobPtr{
friend bool eq( const StrBlobPtr&, const StrBlobPtr& );
public:
StrBlobPtr():curr(0) { }
StrBlobPtr( StrBlob &a, size_t sz = 0 ): wptr( a.data ), curr( sz ) { }
string& deref() const;//解引用
StrBlobPtr& Incr();//字首遞增
private:
shared_ptr<vector<string>> check( size_t, const string& ) const;
weak_ptr<vector<string>> wptr;
size_t curr;
};
inline shared_ptr<vector<string>> StrBlobPtr::check( size_t i, const string &msg ) const
{
auto ret = wptr.lock();
if( !ret )
throw runtime_error( "unbound StrBlobPtr" );
if( i >= ret->size() )
throw out_of_range( msg );
return ret;
}
inline string& StrBlobPtr::deref() const
{
auto sp = check( curr, "dereference pass end" );
return (*sp)[ curr ];
}
inline StrBlobPtr& StrBlobPtr::Incr()
{
auto sp = check( curr, "increment past end of StrBlobPtr" );
++curr;
return *this;
}
inline StrBlobPtr StrBlob::begin()
{
return StrBlobPtr( *this );
}
inline StrBlobPtr StrBlob::end()
{
auto ret = StrBlobPtr( *this, data->size() );
return ret;
}
inline bool eq( const StrBlobPtr &lhs, const StrBlobPtr &rhs )
{
auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
if( l == r )
return ( !r || lhs.curr == rhs.curr );
else
return false;
}
inline bool uneq( const StrBlobPtr &lhs, const StrBlobPtr &rhs )
{
return !eq( lhs, rhs );
}
#endif // STRBLOB_H_INCLUDED
練習12.20:編寫程式,逐行讀入一個輸入檔案,將內容存入一個StrBlob中,用一個StrBolbPtr打印出StrBlob中的每個元素。
見上題。
練習12.21:也可以這樣編寫StrBlobPtr的redef成員:
std::string& deref() const
{ return ( *check( curr, "deference past end"))[curr]; }
你認為哪個版本更好?為什麼?
第一個版本比較好,可讀性更好。也更容易修改。
練習12.22:為了能讓StrBlobPtr使用const StrBlob,你覺得應該如何修改?定義一個名為ConstStrBlobPtr的類,使其能夠指向const StrBlob。
為StrBlob的建構函式定義能夠接受const StrBlob&引數的版本。
然後為StrBlob定義const版本的begin和end成員。
練習12.23:編寫一個程式,連線兩個字串字面常量,將結果儲存在一個動態分配的char陣列中。重寫這段程式,連線兩個標準庫string物件。
c_str() 返回string物件的記憶體地址。
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
int main()
{
const char *s1 = "Hello ";
const char *s2 = "world";
char *r = new char[ strlen(s1) + strlen(s2) + 1 ];
strcpy( r, s1 );
strcat( r, s2 );
cout << r << endl;
string str1 = "Fuck U ";
string str2 = "Bitch";
strcpy( r, (str1 + str2).c_str() );
cout << r << endl;
delete []r;
return 0;
}
練習12.24:編寫一個程式,從標準輸入讀取一個字串,存入一個動態分配的字元陣列中。描述你的程式如何處理變長輸入。測試你的程式,輸入一個超出你分配的陣列長度的字串。
處理變長輸入的方法是:根據動態分配的字元陣列的大小確定字串長度的閾值,當讀取的字元數超過閾值時,停止讀取,也就是採取了截斷的方式。
程式略了。
練習12.25:給定下面的new表示式,你應該如何釋放pa?
int *pa = new int[10];
delete [] pa;
練習12.26:用allocator重寫427頁的程式。
#include<iostream>
#include<memory>
using namespace std;
int main()
{
allocator<string> alloc;
auto const p = alloc.allocate( 100 );
string word;
string *q = p;
while( cin >> word && q != p + 100 )
alloc.construct( q++, word );
auto sz = q - p;
cout << "alloc的資料是:" << endl;
for( size_t i = 0; i != sz; ++i )
cout << p[i] << " ";
cout << endl;
while( q != p )
alloc.destroy( --q );
alloc.deallocate( p, 100 );
return 0;
}
練習12.27:TextQuery和QueryResult類只使用了我們已經介紹過的語言和標準庫特性。不要提前看後續章節內容,只用已經學到的知識對這兩個類編寫你自己的版本。
值得一提的是:這個檢索工具對標點符號敏感:也就是 "me," 和 "me"不是對應同一個關鍵字。
類的定義。
#ifndef TEXTQUERY_H_INCLUDED
#define TEXTQUERY_H_INCLUDED
#include<vector>
#include<fstream>
#include<memory>
#include<sstream>
#include<string>
#include<map>
#include<set>
using namespace std;
class QueryResult; //前向宣告
class TextQuery{
public:
//類型別名
typedef vector<string>::size_type line_no;
//建構函式。
TextQuery( ifstream& );
QueryResult query( const string& ) const;
private:
shared_ptr<vector<string>> file;
map<string, shared_ptr<set<line_no>>> wordMap;
};
//建構函式定義。讀取檔案按行存入vector,並且建立單詞與行號對映。
TextQuery::TextQuery( ifstream &fin ): file( new vector<string> )
{
string line;
//為了使得行號從1開始對應。我把vector的位置0先設為空串。
file->push_back( "" );
while( getline( fin, line ) ){
file->push_back( line );
unsigned lineN = file->size() - 1; //lineN用來儲存行號。
istringstream sin( line );
string word;
while( sin >> word ){
auto &lines = wordMap[ word ]; //lines是一個shared_ptr
if( !lines )
lines.reset( new set<line_no> ); //如果lines為空指標(set沒有元素),分配一個新的set
lines->insert( lineN );
}
}
}
class QueryResult{
friend ostream& print( ostream&, const QueryResult& );
public:
QueryResult( const string& str, shared_ptr<set<TextQuery::line_no>> l,
shared_ptr<vector<string>> f ): sought( str ), lines( l ), file( f ) { }
private:
string sought;
shared_ptr<set<TextQuery::line_no>> lines;
shared_ptr<vector<string>> file;
};
QueryResult TextQuery::query( const string &s ) const
{
static shared_ptr<set<line_no>> nodata( new set<line_no> );
auto loc = wordMap.find( s );
if( loc == wordMap.end() )
return QueryResult( s, nodata, file );
else
return QueryResult( s, loc->second, file );
}
ostream& print( ostream &os, const QueryResult &qr )
{
os << qr.sought << " occurs " << qr.lines->size()
<< ( ( qr.lines->size() > 1 ) ? " times" : " time" )
<< endl;
for( auto num : *( qr.lines ) )
os << "\t(line " << num << ") " << *( qr.file->begin() + num ) << endl;
return os;
}
#endif // TEXTQUERY_H_INCLUDED
測試程式。
#include<iostream>
#include"TextQuery.h"
#include<fstream>
using namespace std;
int main()
{
ifstream fin;
fin.open( "T11_9_data.txt" );
if( !fin ){
cerr << "cant open file!";
return -1;
}
TextQuery tq( fin );
print( cout, tq.query( "me" ) );
return 0;
}
練習12.28:編寫程式實現文字查詢,不要定義類來管理資料。你的程式應該接受一個檔案,並與使用者互動來查詢單詞。使用vector、map和set容器來儲存來自檔案的資料並生成查詢結果。
#include<iostream>
#include<fstream>
#include<vector>
#include<sstream>
#include<map>
#include<set>
#include<cctype>
using namespace std;
typedef vector<string>::size_type line_no;
//使用全域性變數。省卻引數傳遞。
vector<string> file;
map<string, set<line_no>> wordMap;
string cleanup_str( const string &str );
void input_text( ifstream &fin );
ostream& query_and_print( ostream &os, const string &sought );
void runRequiries( ifstream &fin );
int main()
{
ifstream fin;
fin.open( "T11_9_data.txt" );
if( !fin ){
cerr << "cant open file!";
return -1;
}
runRequiries( fin );
fin.close();
return 0;
}
//改善程式,忽略大小寫,忽略標點符號
string cleanup_str( const string &str )
{
string result; //空串,儲存轉換後的字串
for( const auto &c : str )
if( !ispunct( c ) )
result += tolower( c );
return result;
}
//把文字按行儲存在vector 和 建立每個單詞的對映
void input_text( ifstream &fin )
{
string line;
while( getline( fin, line ) ){
file.push_back( line );
auto lineN = file.size() - 1;
istringstream sin( line );
string word;
while( sin >> word )
wordMap[ cleanup_str( word ) ].insert( lineN );
}
}
ostream& query_and_print( ostream &os, const string &sought )
{
auto it = wordMap.find( sought );
if( it == wordMap.end() ){
os << "cant find " << sought << endl;
return os;
}
os << sought << " occurs " << (it->second).size()
<< ( ( (it->second).size() > 1 )? "times":"time" ) << endl;
for( const auto &index : it->second )
os << "\t(line " << ( index + 1 ) << ") "
<< file[ index ] << endl;
os << endl;
return os;
}
void runRequiries( ifstream &fin )
{
input_text( fin );
string word;
cout << "請輸入你要查詢的單詞:( q to quit )" << endl;
while( 1 ){
if( !(cin >> word ) || word == "q" )
break;
query_and_print( cout, word );
}
cout << "程式結束,拜拜!" << endl;
}
練習12.29:我們曾經用do while迴圈來編寫管理使用者互動的迴圈。用do while迴圈重寫本節程式,解釋你傾向於哪個版本,為什麼?
兩者差異不大。略了。
練習12.30:定義你自己版本的TextQuery和QueryResult類,並執行12.3.1節中的runRequery函式。
見練習12.27.(12.27的版本不是使用者輸入查詢的,是程式設計師自己驗證一種情況的版本)
練習12.31:如果用vector代替set儲存行號,會有什麼差別?哪種方法更好?為什麼?
vector儲存行號:
因為插入行號的過程中,是按行號順序插入的,所以行號的排序並不是問題。
但是vector儲存行號的話會儲存重複插入相同的行號(但是可以通過簡單操作來避免)。
而vector插入元素的時間複雜度是常數時間,set是紅黑樹實現的,插入操作時間複雜度是O(logn)。vector的插入效能更好。
練習12.32:重寫TextQuery和QueryResult類,用StrBlob代替vector<string>儲存輸入檔案。
先略了。(標記一下,之後回來做)
練習12.33:在第15章中我們將擴充套件查詢系統,在QueryResult類中將會需要一些額外的成員。新增名為begin和end的成員,返回一個迭代器,指向一個給定查詢返回的行號的set中的位置。在新增一個名為get_file的成員,返回一個shared_ptr,指向QueryResult物件中的檔案。
先擱置。