我终于快要跟上我之前看书的进度了!!!感觉做题做了好久。。。
练习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
#include
#include
#include
#include
using namespace std;
class StrBlob{
public:
//构造函数
StrBlob();
StrBlob( initializer_list il );
//容器大小访问
vector::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> data;
void check( vector::size_type i, const std::string &msg ) const ;
};
StrBlob::StrBlob(): data( make_shared>() ) { }
StrBlob::StrBlob(initializer_list il): data( make_shared> (il) ) { }
void StrBlob::check( vector::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
#include
using namespace std;
vector *new_vector( void );
void read( vector *pv );
void print( vector *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 *new_vector( void )
{
return new ( nothrow) vector;
}
void read( vector *pv )
{
int i;
while( cin >> i )
pv->push_back( i );
}
void print( vector *pv )
{
cout << "容器中的数据是:" << endl;
for( const auto &i : *pv )
cout << i << " ";
cout << endl;
}
#include
#include
#include
using namespace std;
shared_ptr> new_vector( void );
void read( shared_ptr> );
void print( shared_ptr> );
int main()
{
auto spv = new_vector();
cout << "请输入一个int的序列:" << endl;
read( spv );
cout << endl;
print( spv );
return 0;
}
shared_ptr> new_vector( void )
{
return make_shared>();
}
void read( shared_ptr> pv )
{
int i;
while( cin >> i )
pv->push_back( i );
}
void print( shared_ptr> pv )
{
cout << "容器中的数据是:" << endl;
for( const auto &i : *pv )
cout << i << " ";
cout << endl;
}
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
//智能指针r2指向值为100的int
r2 = q2;//r2指向q2,r2原先指向的内存被释放。
练习12.10:下面的代码调用了第413页中定义的process函数,解释此调用是否正确。如果不正确,应如何修改?
shared_ptr
process( shared_ptr
此调用是正确的。
参数位置的shared_ptr
最终p的引用计数还是1。内存不会被释放。
练习12.11:如果我们像下面这样调用process,会发生什么?
process( shared_ptr
这是一个严重的错误。
参数部分的 shared_ptr
练习12.12:p和q的定义如下,对于接下来的对process的每个调用,如果合法,解释它做了什么,如果不合法,解释错误原因。
auto p = new int();
auto sp = make_shared_ptr
(a)process(sp); //合法。
(b)process( new int() );
//不合法。new创建了一个int对象,指向它的指针被用来创建了一个shared_ptr,传递给process的参数ptr,引用计数为1。当process执行完毕,ptr被销毁,引用计数变为0,临时int对象因而被销毁。不存在内存泄漏和空悬指针的问题。
(c)process( p ); //不合法。不能将int*类型转换为shared_ptr
(d)process( shared_ptr
练习12.13:如果执行下面的代码,会发生什么?
auto sp = make_shared
auto p = sp.get();
delete p;
sp会成为一个类似空悬指针的shared_ptr。
练习12.14:编写你自己版本的用shared_ptr管理connection的函数。
---------------------------------------------------------------------------------------------------------
插播一条PS:
如果你使用的智能指针不是new分配的内存,记住传递它一个删除器。
---------------------------------------------------------------------------------------------------------
#include
#include
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 sp( &c, end_connection );
/* 获得连接后的操作 */
//忘记调用disconnect
//当f退出时,不管是正常退出还是异常退出,connection都会被正确关闭。
}
void f1( destination &d )
{
cout << "直接管理connect" << endl;
connection c = connect( &d );
//忘记调用disconnect
cout << endl;
}
#include
#include
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 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
(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
#include
#include
#include
#include
using namespace std;
class StrBlobPtr;//友元类外部声明。
class StrBlob{
public:
//友元类声明
friend class StrBlobPtr;
//构造函数
StrBlob();
StrBlob( initializer_list il );
//容器大小访问
vector::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> data;
void check( vector::size_type i, const std::string &msg ) const ;
};
inline StrBlob::StrBlob(): data( make_shared>() ) { }
inline StrBlob::StrBlob(initializer_list il): data( make_shared> (il) ) { }
inline void StrBlob::check( vector::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> check( size_t, const string& ) const;
weak_ptr> wptr;
size_t curr;
};
inline shared_ptr> 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
#include
#include
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.25:给定下面的new表达式,你应该如何释放pa?
int *pa = new int[10];
delete [] pa;
练习12.26:用allocator重写427页的程序。
#include
#include
using namespace std;
int main()
{
allocator 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
#include
#include
#include
#include
#include
#include
#include"TextQuery.h"
#include
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
#include
#include
#include
#include
练习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
先略了。(标记一下,之后回来做)
练习12.33:在第15章中我们将扩展查询系统,在QueryResult类中将会需要一些额外的成员。添加名为begin和end的成员,返回一个迭代器,指向一个给定查询返回的行号的set中的位置。在添加一个名为get_file的成员,返回一个shared_ptr,指向QueryResult对象中的文件。
先搁置。