C++Primer

C++Primer,第1张

练习13.1 拷贝构造函数是什么?什么 时候使用它?

拷贝构造函数第一个参数是一个类类型的引用,其他的参数都有默认值。


当拷贝初始化发生时,该拷贝初始化需要拷贝构造函数或移动构造函数。


1.拷贝初始化当使用=时会发生。


2.将一个对象作为实参传递给一个非引用类型的形参

3.从一个返回类型为非引用类型的函数返回一个对象

4.用花括号列表初始化一个数组中的元素或一个聚合类中的成员。


练习13.2 解释为什么下面的声明是非法的:

                        Sales_data::Sales_data(Sales_data ths);

  我们需要调用拷贝构造函数,但是永远都不会成功,因为其自身的参数也是非引用类型,为了调用它,必须拷贝其实参,而为了拷贝其实参,又需要调用拷贝构造函数,也就是自身,从而造成死循环。


练习13.3 当我们拷贝一个StrBlob类时,会发生什么?拷贝一个StrBlobPtr呢?

当我们拷贝一个StrBlob时,shared_ptr指针的use_count会加1。


拷贝一个StrBlobPtr没有变化。


练习13.4  假定 Point 是一个类类型,它有一个public的拷贝构造函数,指出下面程序片段中哪些地方使用了拷贝构造函数:

Point global;
Point foo_bar(Point arg) //非引用,这个地方需要拷贝进来
{
    Point local=arg,*heap=new Point(global);//将arg拷贝给local,后面这个new也需要拷贝
    *heap=local;//将local拷贝给*heap
    Point pa[4]={local,*heap};//将local和*heap拷贝给pa的前两个元素
    return *heap//函数的返回需要拷贝
}

 练习13.5 给定下面的类框架,编写一个拷贝构造函数,拷贝所有成员。


你的构造函数应该动态分配一个新的string,并将对象拷贝到ps所指向的位置,而不是拷贝ps本身:

class HasPtr{
public:
    HasPtr(const string &s = string()):ps(new string(s)), i(0){ }
    HasPtr(const HasPtr &hp):ps(new string(*hp.ps)),i(hp.i) { }

private:
    string *ps;
    int i;
};

 练习13.6 拷贝赋值运算符是什么?什么时候使用它?合成拷贝赋值运算符完成什么工作?什么时候会生成合成拷贝赋值运算符?

拷贝运算符就是一个operator=的一个函数,返回一个本对象的引用。


在类对象发生赋值时使用拷贝赋值运算符。


 

它会将右侧运算对象的每个非static成员赋予左侧运算符对应成员。


在一个类没有定义自己的拷贝赋值运算符时,编译器会为它生成一个合成拷贝赋值运算符。


练习13.7 当我们将一个StrBlob赋值给另一个StrBlob时,会发生什么?赋值StrBlobStr呢?

 对于StrBlob,shared_ptr的use_count会加一。


赋值给StrBlobStr不会加一。


练习13.8 为12.1.1节(第433页)练习13.5中的HasPtr类编写赋值运算符。


类似拷贝构造函数,你的赋值运算符应该将对象拷贝到ps指向的位置。


public:
    HasPtr(const string &s = string()):ps(new string(s)), i(0){ }
    HasPtr(const HasPtr &hp):ps(new string(*hp.ps)),i(hp.i) { }
    HasPtr& operator= (const HasPtr& hp){
        ps = new string(*hp.ps);
        i = hp.i;
    }
private:
    string *ps;
    int i;
};
class HasPtr{
public:
    HasPtr(const string &s = string()):ps(new string(s)), i(0){ }
    HasPtr(const HasPtr &hp):ps(new string(*hp.ps)),i(hp.i) { }
    HasPtr& operator= (const HasPtr& hp){
        ps = new string(*hp.ps);
        i = hp.i;
        return *this;
    }
    string get_ps() const {
        return *ps;
    }
private:
    string *ps;
    int i;
};

练习13.9 析构函数是什么?合成析构函数完成什么工作?什么时候会生成合成析构函数?

 析构函数是用来释放对象使用资源,并摧毁对象的非static数据成员的一个函数。


在类没有自己定义析构函数的时候,这个类会自己合成析构函数。


练习13.10 当一个StrBlob对象销毁时会发生什么?一个StrBlobPtr对象销毁时呢?

 shared_ptr的use_count将会减少,当use_count为0时便会被释放。


对象被销毁时,weak_ptr所指向的内存不会被释放。


练习13.11 为前面练习中的HasPtr类添加一个析构函数。


 ~HasPtr(){delete ps;}

练习13.12 在下面的代码片段中会发生几次析构函数的调用?

bool fcn(const Sales_data *trans, Sales_data accum)
{
    Sales_data item1(*trans), item2(accum);
    return item1.isbn() != item2.isbn();
}

accum item1 和 item2三次。


练习13.13 理解拷贝控制成员和构造函数的一个好方法是定义一个简单的类,为该类定义这些成员,每个成员都打印自己的名字:

给X添加拷贝赋值运算符和析构函数,并编写一个程序以不同方式使用X的对象:将它们作为非引用和引用参数传递;动态分配它们;将它们存放于容器中;诸如此类。


观察程序的输出,直到你确认理解了什么时候会使用拷贝控制成员,以及为什么会使用它们。


当你观察程序输出时,记住编译器可以略过对拷贝构造函数的调用。


练习13.14 假定numbered是一个类,它有一个默认构造函数,能为每个对象生成一个唯一的序号,保存在名为mysn的数据成员中。


假定numbered使用合成拷贝控制成员,并给定如下函数:

void f(numbered s){cout << s.mysn << endl;}

则下面的代码输出什么内容?

numbered a, b = a, c = b;

f(a) , f(b), f(c); 

三个相同的数。


练习13.15 假定numbered定义了一个拷贝构造函数,能生成一个新的序号。


这会改变上一题中调用的输出结果吗?如果会改变,为什么?新的输出结果是什么? 

构造的时候给了不同的值,输出了三个不同的结果。


练习13.16 如果f中的参数是const numbered&,将会怎样?这会改变输出结果吗?如果会改变,为什么?新的输出结果是什么? 

 不会改变输出结果,如果是默认拷贝构造函数的话。


练习13.17 分别编写前三题中所描述的numbered和f,验证你是否正确预测了输出结果。


struct numbered{
    int mysn;

};
void f(numbered s){cout<
struct numbered{
    int mysn;
    numbered():mysn(0){}
    numbered& operator= (numbered &n){mysn = n.mysn + 1; return *this;}
    numbered(numbered& n):mysn(n.mysn + 1){}
};
void f(numbered s){cout<
struct numbered{
    int mysn;

};
void f(const numbered & s){cout<

 练习13.18 定义一个Employee类,它包含雇员的姓名和唯一的雇员证号。


为这个类定义默认构造函数,以及接受一个表示雇员姓名的string的构造函数。


每个构造函数应该通过递增一个static数据成员来生成一个唯一的证号。


class Employee{
private:
    string name;
    static int jishu;
    int id;
public:
    Employee();
    Employee(const string &name):name(name){id = jishu++;}

};
Employee::Employee() {{id = jishu++;}}
int Employee::jishu = 0;
int main(){
    Employee e1("meng");
    Employee e2("xiang");
    Employee e3("zhi");
    return 0;
}

练习13.19 你的Employee类需要定义它自己的拷贝控制函数吗?如果需要,为什么?如果不需要,为什么?实现你认为Employee需要的拷贝控制成员。


 不需要,因为没有实际意义。


练习13.20:解释当我们拷贝、赋值或销毁TextQuery和QueryResult类对象时会发生什么?

 当拷贝和赋值时,shared_ptr指向的对象的的use_count会加1。


其他成员变量值会进行拷贝。


销毁时,shared_ptr指向的对象的的use_count会减1。


练习13.21 你认为Text和QueryResult类需要定义它们自己版本的拷贝控制成员吗?如果需要,为什么?如果不需要,为什么?

不用,合成版本就能完成任务了。


 

练习13.24 如果本节中的HasPtr版本未定义析构函数,将会发生什么?如果未定义拷贝构造函数,将会发生什么?

如果没有定义析构函数,将会删除不了动态内存,发生内存泄漏。


如果没有定义拷贝构造函数,那么指针将传递地址值,行为就不像值了变得像指针。


练习13,25 假定希望定义StrBlob的类值版本,而且希望继续使用shared_ptr,这样我们的StrBlobPtr类就仍能使用指向vector的weak_ptr了。


你修改后将需要拷贝构造函数和一个拷贝赋值运算符,但不需要析构函数。


解释拷贝构造函数和拷贝赋值运算符必须要做什么。


解释为什么不需要析构函数。


 类值版本要为shared_ptr型的指针赋值时重新new一个动态内存。


因为如果shared_ptr指向的对象消失等到use_count的值为0的时候,内存会自动释放。


练习13.26 对上一题中描述的StrBlob类,编写你自己的版本。


class StrBlob1{
public:
    typedef std::vector::size_type size_type ;
    StrBlob1();
    StrBlob1(StrBlob1& b):data(std::make_shared>(*data)){}
    StrBlob1 operator=(StrBlob1& sb1){auto d = std::make_shared>(*sb1.data); data = d;}
    StrBlob1(std::initializer_list il);
    size_type size(){return data->size();}
    bool empty() const {return data->empty();}

    void push_back(const std::string &t){data->push_back(t);}
    void pop_back();

    std::string& front();

    std::string& back();

    const std::string &front() const;

    const std::string &back() const;
private:
    std::shared_ptr> data;
    void check(size_type i, const std::string &msg) const;
};

练习13.27 定义你自己的使用引用计数版本的HasPtr。


class HasPtr{
public:
    HasPtr(const string &s = string()):ps(new string(s)), i(0){ use = new size_t(0);}
    HasPtr(const HasPtr &hp):ps(new string(*hp.ps)),i(hp.i),use(hp.use) {++(*use); }
    HasPtr& operator=(const HasPtr& hp){
        if(--*use == 0){
            delete ps;
            delete use;
        }
        ps = hp.ps;
        i = hp.i;
        use = hp.use;
        ++(*use);
        return *this;
    }
    ~HasPtr(){
        if(--*use == 0){
            delete ps;
            delete use;
        }
    }
private:
    string *ps;
    int i;
    size_t *use;
};

 练习13.28 给定下面的类,为其实现一个默认构造函数和必要的拷贝控制成员。


class TreeNote{
private:
    string value;
    int* count;
    TreeNote *left;
    TreeNote *right;
public:
    TreeNote():value(""), count(new int(1)),left(nullptr), right(nullptr){}
    TreeNote(TreeNote &tn){
        ++*tn.count;
        if (--*count == 0){
            delete count;
            delete left;
            delete right;
        }
        count = tn.count;
        value = tn.value;
        left = tn.left;
        right = tn.right;
    }
    ~TreeNote() {
        if (--*count == 0) {
            delete left;
            delete right;
            delete count;
        }
};
class BinStrTree{
private:
    TreeNote *root;
public:
    BinStrTree():root(new TreeNote()){}
    BinStrTree(BinStrTree& r):root(r.root){}
    BinStrTree& operator=(const BinStrTree &bst){
        TreeNote *new_root = new TreeNote(*bst.root);
        delete root;
        root = new_root;
        return *this;
    };
    ~BinStrTree() { delete root; }
};

 练习13.29 解释swap(HasPtr&, HasPtr&)中对swap的调用会不会导致递归循环。


 其中没有嵌套,所以不会导致递归循环。


练习13.30 为你的类值版本的HasPtr编写swap函数,并测试它,为你的swap函数添加一个打印语句,指出函数什么时候执行。


class HasPtr {
public:
    friend void swap(HasPtr&, HasPtr&);
    HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0) { }
    HasPtr(const HasPtr &hp) : ps(new std::string(*hp.ps)), i(hp.i) { }
    HasPtr& operator=(const HasPtr &hp) {
        auto new_p = new std::string(*hp.ps);
        delete ps;
        ps = new_p;
        i = hp.i;
        return *this;
    }
    ~HasPtr() {
        delete ps;
    }

    void show() { std::cout << *ps << std::endl; }
private:
    std::string *ps;
    int i;
};
void swap(HasPtr& hp1,HasPtr& hp2){
    using std::swap;
    swap(hp1.ps, hp2.ps);
    swap(hp1.i,hp2.i);
    cout<<"going swap!"<

 练习13.31 为你的HasPtr类定义一个<运算符,并定义一个HasPtr的vector,为这个vector添加一些元素,并对他执行sort。


注意何时会调用swap。


bool operator<(HasPtr& hp1, HasPtr& hp2){
    return *hp1.ps<*hp2.ps;
}

练习13.32 类指针的HasPtr版本会从swap函数受益吗?如果会,得到了什么益处?如果不是,为什么?

@Mooophy:

Essentially, the specific avoiding memory allocation is the reason why it improve performance. As for the pointerlike version, no dynamic memory allocation anyway. Thus, a specific version for it will not improve the performance.不用内存分配,所以不会有提升。


 练习13.33 为什么Message的成员save和remove的参数是一个Folder&?为什么我们不将参数定义为Folder或是const Folder&?

首先不能是穿值,因为要对一个Folder对象的值进行更改,传值的话,更改不了。


也不能是const因为要对Folder对象的值进行修改。


练习13.34 编写本节所描述的Message。


 

class Message{
    friend class Folder;
public:
    explicit Message(const string &str = ""):contents(str){}
    Message(const Message& );
    Message& operator=(const Message&);
    ~Message();
    void save(Folder&);
    void remove(Folder&);
private:
    string contents;
    set folders;
    void add_to_Folders(const Message&);
    void remove_to_Folders();
};
Message::Message(const Message &m):contents(m.contents),folders(m.folders) {add_to_Folders(m);}
Message& Message::operator=(const Message &m) {
    remove_to_Folders();
    contents = m.contents;
    folders = m.folders;
    add_to_Folders(m);
}
Message::~Message() { remove_to_Folders();}
void Message::save(Folder &f) {
    folders.insert(&f);
    f.addMsg(this);
}
void Message::remove(Folder &f) {
    folders.erase(&f);
    f.remMsg(this);
}
void Message::add_to_Folders(const Message &m) {
    for (auto f: m.folders) {
        f->addMsg(this);
    }
}
void Message::remove_to_Folders() {
    for (auto f:folders) {
        f->remMsg(this);
    }
}

练习13.35 如果Message使用合成的拷贝控制成员,将会发生什么? 

 发生拷贝 *** 作时,folder不能进行相应的更新。


练习13.36 设计并实现对应的Folder类。


此类应该保存一个指向Folder中包含的Message的set。


class Folder{
public:
    void addMsg(Message *m){
        messages.insert(m);
    }
    void remMsg(Message *m){
        messages.erase(m);
    }
private:
    set messages;

};

练习13.37 为Message类添加成员,实现向Folders添加或删除一个给定的Folders*。


这两个成员类似Folder类的addMsg和remMsg *** 作。


void addfolders(Folder* f){
        folders.insert(f);
    }
    void remfolders(Folder* f){
        folders.erase(f);
    }

练习13.38 我们未使用拷贝并交换方式来设计Message的赋值运算符。


你认为其原因是什么? 

 我觉得课能使拷贝交换的形式可能浪费内存。


@Mooophy The copy and swap is an elegant way when working with dynamicly allocated memory. In the Message class , nothing is allocated dynamically. Thus using this idiom makes no sense and will make it more complicated to implement due to the pointers that point back.

@pezy In this case, swap function is special. It will be clear two Message's folders , then swap members, and added themselves to each folders. But, Message assignment operator just clear itself, and copy the members, and added itself to each folders. The rhs don't need to clear and add to folders. So, if using copy and swap to define, it will be very inefficiency.

练习13.39 编写你自己版本的StrVec,包括自己版本的reserve、capacity和resize。


class StrVec{
public:
    StrVec():elements(nullptr),first_free(nullptr),cap(nullptr){}
    StrVec(const StrVec&);
    StrVec &operator=(const StrVec&);
    ~StrVec();
    void push_back(const string&);
    size_t size() const{ return first_free - elements;}
    size_t capaticy() const{ return first_free - cap;}
    void reserve(size_t n);
    void resize(size_t n);
    string *begin() const{return elements;}
    string *end() const{return first_free;}

private:
    static allocator alloc;//分配内存使用。


void chn_n_alloc(){ if (size() == capaticy()) reallocate(); } pairalloc_n_copy(const string*, const string*); void free(); void reallocate(); string *elements; //指向数组第一个元素的指针 string *first_free;//指向数组第一个空闲元素的指针 string *cap; //指向数组尾后位置的指针 }; void StrVec::reserve(size_t n) { if (!size()) { elements = alloc.allocate(n); first_free = elements; cap = elements + n; } else{ auto newdata = alloc.allocate(n); auto dest = newdata; auto elem = elements; for (auto i = 0 ; i != size(); ++i) { alloc.construct(dest++,std::move(*elem++)); } free(); elements = newdata; first_free = dest; cap = elements + n; } } void StrVec::push_back(const string &s ) { chn_n_alloc(); alloc.construct(elements++,s); } pair StrVec::alloc_n_copy(const string *b, const string *e) {//这个不是很懂。


auto data = alloc.allocate(e - b); return {data, uninitialized_copy(b,e,data)}; } void StrVec::free() { if (elements){ for (auto p = first_free;p != elements;) { alloc.destroy(--p); } alloc.deallocate(elements,cap - elements); } } StrVec::StrVec(const StrVec &s) { auto newdata = alloc_n_copy(s.begin(), s.end()); elements = newdata.first; first_free = cap = newdata.second; } StrVec::~StrVec() {free();} StrVec &StrVec::operator=(const StrVec &rhs) { auto data = alloc_n_copy(rhs.begin(), rhs.end()); free(); elements = data.first; first_free = cap = data.second; return *this; } void StrVec::resize(size_t n){ if (size() > n){ for(auto p = first_free; p != elements + n;) alloc.destroy(--p); first_free = elements + n; } else { if (n >capaticy()) { reserve(n);} for (auto p = first_free; p != elements + n ; ++p) { alloc.construct(p, nullptr); } first_free = elements + n; } } void StrVec::reallocate() { auto newcapacity = size()?size()*2:1; auto newdata = alloc.allocate(newcapacity); auto dest = newdata; auto elem = elements; for (auto i = 0 ; i != size(); ++i) { alloc.construct(dest++,std::move(*elem++)); } free(); elements = newdata; first_free = dest; cap = elements + newcapacity; }

 练习13.40 为你的StrVec类添加一个构造函数,它接受一个initializer_list参数。


class StrVec{
public:
    StrVec():elements(nullptr),first_free(nullptr),cap(nullptr){}
    StrVec(initializer_list&);
    StrVec(const StrVec&);
    StrVec &operator=(const StrVec&);
    ~StrVec();
    void push_back(const string&);
    size_t size() const{ return first_free - elements;}
    size_t capaticy() const{ return first_free - cap;}
    void reserve(size_t n);
    void resize(size_t n);
    string *begin() const{return elements;}
    string *end() const{return first_free;}

private:
    static allocator alloc;//分配内存使用。


void chn_n_alloc(){ if (size() == capaticy()) reallocate(); } pairalloc_n_copy(const string*, const string*); void free(); void reallocate(); string *elements; //指向数组第一个元素的指针 string *first_free;//指向数组第一个空闲元素的指针 string *cap; //指向数组尾后位置的指针 }; StrVec::StrVec(initializer_list& sl){ reserve(sl.size()); auto p = elements; for (auto it = sl.begin(); it != sl.end(); ++it) { alloc.construct(p++,*it); } first_free = elements + sl.size(); } void StrVec::reserve(size_t n) { if (!size()) { elements = alloc.allocate(n); first_free = elements; cap = elements + n; } else{ auto newdata = alloc.allocate(n); auto dest = newdata; auto elem = elements; for (auto i = 0 ; i != size(); ++i) { alloc.construct(dest++,std::move(*elem++)); } free(); elements = newdata; first_free = dest; cap = elements + n; } } void StrVec::push_back(const string &s ) { chn_n_alloc(); alloc.construct(elements++,s); } pair StrVec::alloc_n_copy(const string *b, const string *e) {//这个不是很懂。


auto data = alloc.allocate(e - b); return {data, uninitialized_copy(b,e,data)}; } void StrVec::free() { if (elements){ for (auto p = first_free;p != elements;) { alloc.destroy(--p); } alloc.deallocate(elements,cap - elements); } } StrVec::StrVec(const StrVec &s) { auto newdata = alloc_n_copy(s.begin(), s.end()); elements = newdata.first; first_free = cap = newdata.second; } StrVec::~StrVec() {free();} StrVec &StrVec::operator=(const StrVec &rhs) { auto data = alloc_n_copy(rhs.begin(), rhs.end()); free(); elements = data.first; first_free = cap = data.second; return *this; } void StrVec::resize(size_t n){ if (size() > n){ for(auto p = first_free; p != elements + n;) alloc.destroy(--p); first_free = elements + n; } else { if (n >capaticy()) { reserve(n);} for (auto p = first_free; p != elements + n ; ++p) { alloc.construct(p, nullptr); } first_free = elements + n; } } void StrVec::reallocate() { auto newcapacity = size()?size()*2:1; auto newdata = alloc.allocate(newcapacity); auto dest = newdata; auto elem = elements; for (auto i = 0 ; i != size(); ++i) { alloc.construct(dest++,std::move(*elem++)); } free(); elements = newdata; first_free = dest; cap = elements + newcapacity; }

 练习13.41 在push_back中,我们为什么在construct调用中使用后置运算?如果使用前置递增 运算的话,会发生什么?

会产生一个错位。


first_free的位置还会存东西,而element指向的内存却为空了。


练习13.43 重写free 成员,用for_each和lambda来代替for循环destroy元素。


你更倾向哪种实现,为什么? 

for_each(elements, first_free, [this](std::string &rhs){ alloc.destroy(&rhs); });
template
  Function for_each(InputIterator first, InputIterator last, Function fn)
{
  while (first!=last) {
    fn (*first);
    ++first;
  }
  return fn;      // or, since C++11: return move(fn);
}

首先这个地方for_each是一个模板,实现如上,输入两个迭代器,在这个范围的迭代器执行fn函数,for_each更加的整洁。


我倾向于for_each。


练习13.44 编写标准库string类的简化版本,命名为String。


你的类应该至少有一个默认构造函数和一个接受C风格字符串参数的构造函数。


使用allocator为你的String类分配所需的内存。


 空一下。


练习13.45 解释右值引用和左值引用的区别。


 左值持久;右值短暂。


左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。


练习13.46 什么类型的引用可以绑定到下面的初始化器上?

int f();
vector vi(100);
int ? r1= f();//右值引用
int ? r2 = vi[0];//左值引用
int ? r3 = r1;//左值引用
int ? r4 - vi[0]*f();//右值引用

练习13.55 为你的StrBlob添加一个右值引用版本的push_back。


    void push_back(std::string &&sb){data->push_back(std::move(sb));}

 练习13.56 如果sorted定义如下,会发生什么?

Foo Foo::sorted() const &{
    Foo ret(*this);
    return ret.sorted();    
}

 会进入嵌套循环。


跑不出来

练习13.57 如果sorted定义如下,会发生什么?

Foo Foo::sorted() const &{  
    return Foo(*this).sorted();    
}

 会调用右值版本的sorted

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/562453.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-01
下一篇 2022-04-01

发表评论

登录后才能评论

评论列表(0条)