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
#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;
}

练习12.7:重做上一题,这次使用share_ptr而不是内置指针。

#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;
}

练习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(42), r2 = make_shared_ptr(100);//智能指针q2指向值为42的int

//智能指针r2指向值为100的int

r2 = q2;//r2指向q2,r2原先指向的内存被释放。


练习12.10:下面的代码调用了第413页中定义的process函数,解释此调用是否正确。如果不正确,应如何修改?

shared_ptr p( new int(42) );

process( shared_ptr(p) );

此调用是正确的。

  参数位置的shared_ptr(p),会创建一个临时的shared_ptr赋予process的参数ptr,引用计数值加1,当调用所在的表达式结束时,这个临时变量就会被销毁,引用计数减1。同时将值拷贝给ptr,计数值加1。但函数结束后ptr被销毁,引用计数减1。

最终p的引用计数还是1。内存不会被释放。


练习12.11:如果我们像下面这样调用process,会发生什么?

process( shared_ptr( p.get() ) );

这是一个严重的错误。

参数部分的 shared_ptr( 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();

(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( p ) ); //合法但是,process执行完毕后会使得p成为空悬指针。


练习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;
}


练习12.15:重写第一题的程序,用lambda代替end_connection函数。

#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 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
#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.24:编写一个程序,从标准输入读取一个字符串,存入一个动态分配的字符数组中。描述你的程序如何处理变长输入。测试你的程序,输入一个超出你分配的数组长度的字符串。

处理变长输入的方法是:根据动态分配的字符数组的大小确定字符串长度的阈值,当读取的字符数超过阈值时,停止读取,也就是采取了截断的方式。

程序略了。



练习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

using namespace std;

class QueryResult; //前向声明

class TextQuery{
public:
    //类型别名
    typedef vector::size_type line_no;
    //构造函数。
    TextQuery( ifstream& );
    QueryResult query( const string& ) const;
private:
    shared_ptr> file;
    map>> wordMap;

};

//构造函数定义。读取文件按行存入vector,并且建立单词与行号映射。
TextQuery::TextQuery( ifstream &fin ): file( new vector )
{
    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 ); //如果lines为空指针(set没有元素),分配一个新的set
            lines->insert( lineN );
        }
    }
}


class QueryResult{
    friend ostream& print( ostream&, const QueryResult& );
public:
    QueryResult( const string& str, shared_ptr> l,
                shared_ptr> f ): sought( str ), lines( l ), file( f ) { }
private:
    string sought;
    shared_ptr> lines;
    shared_ptr> file;

};

QueryResult TextQuery::query( const string &s ) const
{
    static shared_ptr> nodata( new set );

    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
#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
#include
#include

using namespace std;

typedef vector::size_type line_no;
//使用全局变量。省却参数传递。
vector file;
map> 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保存输入文件。

先略了。(标记一下,之后回来做)


练习12.33:在第15章中我们将扩展查询系统,在QueryResult类中将会需要一些额外的成员。添加名为begin和end的成员,返回一个迭代器,指向一个给定查询返回的行号的set中的位置。在添加一个名为get_file的成员,返回一个shared_ptr,指向QueryResult对象中的文件。

先搁置。





你可能感兴趣的:(C++primer习题)