C ++ Primer(第五版)第十三章练习答案

2023-10-30

C ++ Primer(第五版)第十三章练习答案

13.1.1 节练习

练习 13.1

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

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数时拷贝构造函数。

1.一个对象作为函数参数,以值传递的方式传入函数体;
2.一个对象作为函数返回值,以值传递的方式从函数返回;
3.一个对象用于给另外一个对象进行初始化(常称为赋值初始化);
4.用花括号列表初始化一个数组中的元素或一个聚合类成员。

练习 13.2

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

Sales_data::Sales_data(Sales_data rhs);

函数的参数不是引用类型,调用永远不会成功——为了调用拷贝构造函数,必须拷贝它的实参,但为了拷贝实参,又需要调用拷贝构造函数,如此无限循环。

练习 13.3

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

它们都没有拷贝构造函数,StrBlob 中的 data 是 shared_ptr,StrBlobPtr 中 data 的 weak_ptr,则拷贝一个 StrBlob 时,shared_ptr+1,StrBlobPtr 没有。

练习 13.4

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

Point global;
Point foo_bar(Point arg) 
{
	Point local = arg, *heap = new Point(global); 
	*heap = local; 
	Point pa[4] = { local, *heap };
	return *heap; 
}

一共 6 次。可以写一个简单的拷贝构造函数验证。

#include<iostream>
#include<string>

using namespace std;

class Point
{
    public:
    Point(){}
    Point(const Point &);
};

Point::Point(const Point&p)
{
    cout << "使用拷贝构造函数!" << endl;
}

Point global;
Point foo_bar(Point arg) // 1.函数参数
{
	Point local = arg, *heap = new Point(global); // 2.3.赋值初始化
	*heap = local; 
	Point pa[4] = { local, *heap }; // 4、5.列表初始化
	return *heap; // 6.返回值
}

int main()
{
    Point p;
    foo_bar(p);
    return 0;
}

练习 13.5

给定下面的类框架,编写一个拷贝构造函数,拷贝所有成员。你的构造函数应该动态分配一个新的 string,并将对象拷贝到 ps 所指向的位置,而不是拷贝 ps 本身:

class HasPtr {
public:
	HasPtr(const std::string& s = std::string()):
		ps(new std::string(s)), i(0) { }
private:
	std::string *ps;
	int i;
}

HasPtr.h

#ifndef HASPTR_H_
#define HASPTR_H_

#include <string>

class HasPtr
{
public:
    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) {}

private:
    std::string *ps;
    int i;
};

#endif

13.1.2 节练习

练习 13.6

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

拷贝赋值运算符是一个名为 operator= 的函数,它接受与类相同类型的参数;

当赋值发生时使用该运算符;

将右侧运算对象的每个非 static 成员赋予左侧运算对象的对应成员,对于数组类型的成员,逐个赋值数组元素,合成拷贝赋值运算符返回一个指向其左侧运算对象的引用;

如果一个类未定义自己的拷贝赋值运算符,会生成合成拷贝赋值运算符。

练习 13.7

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

同 13.3

练习 13.8

为 13.1.1 节练习 13.5 中的 HasPtr 类编写赋值运算符。类似拷贝构造函数,你的赋值运算符应该将对象拷贝到 ps 指向的位置。

#ifndef HASPTR_H_
#define HASPTR_H_

#include <string>

class HasPtr
{
public:
    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 &);

private:
    std::string *ps;
    int i;
};

HasPtr &HasPtr::operator=(const HasPtr &rhs)
{
    ps = new std::string(*rhs.ps);
    i = rhs.i;
    return *this;
}
#endif

13.1.3 节练习

练习 13.9

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

析构函数是类的一个成员函数,名字由波浪号接类名构成,它没有返回值,也不接受参数,用于释放对象所使用的资源,并销毁对象的非 static 数据成员;

类似拷贝构造函数和拷贝赋值运算符,对于某些类,合成析构函数被用来阻止该类型的对象被销毁,如果不是这种情况,合成析构函数的函数体就为空;

当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数。

练习 13.10

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

销毁 StrBlob 时,shared_ptr 引用计数减一,当计数为零时,对象销毁;销毁 StrBlobPtr 时,对象不会被销毁。

练习 13.11

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

#ifndef HASPTR_H_
#define HASPTR_H_

#include <string>

class HasPtr
{
public:
    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 &);

    ~HasPtr() { delete ps; }

private:
    std::string *ps;
    int i;
};

HasPtr &HasPtr::operator=(const HasPtr &rhs)
{
    ps = new std::string(*rhs.ps);
    i = rhs.i;
    return *this;
}
#endif

练习 13.12

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

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

3 次:离开作用域后 accum、item1 和 item2 被销毁。trans 离开作用域不会执行析构函数(指向一个对象的引用或指针离开作用域)。

练习 13.13

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

struct X {
	X() { std::cout << "X()" << std::endl; }
	X(const X&) { std::cout << "X(const X&)" << std::endl; }
};

给 X 添加拷贝赋值运算符和析构函数,并编写一个程序以不同的方式使用 X 的对象:将它们作为非引用参数传递;动态分配它们;将它们存放于容器中;诸如此类。观察程序的输出,直到你确认理解了什么时候会使用拷贝控制成员,以及为什么会使用它们。当你观察程序输出时,记住编译器可以略过对拷贝构造函数的调用。

#include <iostream>
#include <string>
#include <vector>

struct X
{
    X() { std::cout << "X()" << std::endl; }
    X(const X &) { std::cout << "X(const X&)" << std::endl; }
    X &operator=(const X &x)
    {
        std::cout << "X &operator=(const X &x)" << std::endl;
        return *this;
    }
    ~X() { std::cout << "~X()" << std::endl; }
};

void func1(X x)
{
    std::cout << "void func1(X x)" << std::endl;
}

void func2(X &x)
{
    std::cout << "void func2(X &x)" << std::endl;
}
int main()
{
    X x1; 
    
    X x2 = x1;
    X x3(x1);
    X x4{x1};

    func1(x1); 
    func2(x1); 
    X *x_ptr = new X();
    delete x_ptr;
    std::vector<X> xv;
    xv.push_back(x1);

    x3 = x1;
    return 0;
}

13.1.4 节练习

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

3 次输出同一个 mysn。

练习 13.15

假定 numbered 定义了一个拷贝构造函数,能生成一个新的序列号。这会改变上一题中调用的输出结果吗?如果会改变,为什么?新的输出结果是什么?

会,在拷贝初始化时会调用拷贝构造函数,能生成一个新的序号,同时调用 f 函数时,由于是传递给一个非引用的形参又会调用拷贝构造函数。

3 次输出不一样的 mysn,但不是 a、b、c 的 mysn。

练习 13.16

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

会,在拷贝初始化时会调用拷贝构造函数,能生成一个新的序号,同时调用 f 函数时,由于是传递给一个引用的形参,则不会再调用拷贝函数,f 函数输出的就是实参本身的成员。

3 次输出不一样的 mysn,而且是 a、b、c 本身的 mysn。

练习 13.17

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

#include <iostream>
using namespace std;
static int i = 7;
struct numbered
{
    numbered() { ++i; }
    numbered(const numbered &) { ++i; } //新添加的拷贝构造函数

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

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

int main()
{
    numbered a, b = a, c = b;
    // a、b、c 本身的 mysn 应该是 7、8、9
    f(a);f(b);f(c);
    f2(a);f2(b);f2(c);

    return 0;
}

13.1.6 节练习

练习 13.18

定义一个 Employee 类,它包含雇员的姓名和唯一的雇员证号。为这个类定义默认构造函数,以及接受一个表示雇员姓名的 string 的构造函数。每个构造函数应该通过递增一个 static 数据成员来生成一个唯一的证号。

#ifndef EMPLOYEE_H_
#define EMPLOYEE_H_

#include <string>

class Employee
{
private:
    std::string name;
    int id;
    static int n;

public:
    Employee()
    {
        id = n;
        n++;
    }
    Employee(const std::string &s)
    {
        id = n;
        name = s;
    }
    ~Employee();
};

#endif

练习 13.19

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

不需要,员工在现实中不能复制。

#ifndef EMPLOYEE_H_
#define EMPLOYEE_H_

#include <string>

class Employee
{
private:
    std::string name;
    int id;
    static int n;

public:
    Employee()
    {
        id = n;
        n++;
    }
    Employee(const std::string &s)
    {
        id = n;
        name = s;
    }
    Employee(const Employee &) = delete;
    Employee &operator=(const Employee &) = delete;
    ~Employee();
};

#endif

练习 13.20

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

因为这两个类中使用的是智能指针,因此在拷贝时,类的所有成员都将被拷贝,在销毁时所有成员也将被销毁。

练习 13.21

你认为 TextQuery 和 QueryResult 类需要定义它们自己版本的拷贝控制成员吗?如果需要,为什么?实现你认为这两个类需要的拷贝控制操作。

判断一个类是否需要自己版本的拷贝控制成员,一个基本原则是首先确定这个类是否需要一个析构函数。

TextQuery 和 QueryResult 类使用智能指针,可以自动控制释放内存,因为其不需要自己版本的析构函数,也就不需要自己版本的拷贝控制函数了。

13.2 节练习

练习 13.22

假定我们希望 HasPtr 的行为像一个值。即,对于对象所指向的 string 成员,每个对象都有一份自己的拷贝。我们将在下一节介绍拷贝控制成员的定义。但是,你已经学习了定义这些成员所需的所有知识。在继续学习下一节之前,为 HasPtr 编写拷贝构造函数和拷贝赋值运算符。

同练习 13.11

13.2.1 节练习

练习 13.23

比较上一节练习中你编写的拷贝控制成员和这一节中的代码。确定你理解了你的代码和我们的代码之间的差异。

缺少释放对象指向的 string;

缺少局部的临时对象。

练习 13.24

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

如果未定义析构函数,将会发生内存泄漏,动态内存得不到释放,直到没有内存可以申请;如果未定义拷贝构造函数,指针将被复制,可能会多次释放同一个内存。

练习 13.25

假定希望定义 StrBlob 的类值版本,而且希望继续使用 shared_ptr,这样我们的 StrBlobPtr 类就仍能使用指向 vector的 weak_ptr 了。你修改后的类将需要一个拷贝的构造函数和一个拷贝赋值运算符,但不需要析构函数。解释拷贝构造函数和拷贝赋值运算符必须要做什么。解释为什么不需要析构函数。

拷贝构造函数和拷贝赋值运算符需要使用值新建一个shared_ptr;
当类销毁时,shared_ptr计数减1,当计数为0时,其指向的对象会自动销毁,不用析构函数。

练习 13.26

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

#ifndef STRBLOB_H_
#define STRBLOB_H_

#include <initializer_list>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>

class ConstStrBlobPtr;

class StrBlob
{
public:
    friend class ConstStrBlobPtr;

    typedef std::vector<std::string>::size_type size_type;
    StrBlob();
    StrBlob(std::initializer_list<std::string> il);
    StrBlob(const StrBlob &);            // 拷贝构造函数
    StrBlob &operator=(const StrBlob &); // 拷贝赋值运算符

    size_type size() const { 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;
    ConstStrBlobPtr begin() const;
    ConstStrBlobPtr end() const;

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

StrBlob::StrBlob() : data(std::make_shared<std::vector<std::string>>()) {}
StrBlob::StrBlob(std::initializer_list<std::string> il) : data(std::make_shared<std::vector<std::string>>(il)) {}
StrBlob::StrBlob(const StrBlob &sb) { data = std::make_shared<std::vector<std::string>>(*(sb.data)); }
StrBlob &StrBlob::operator=(const StrBlob &sb)
{
    data = std::make_shared<std::vector<std::string>>(*(sb.data));
    return *this;
}

void StrBlob::check(size_type i, const std::string &msg) const
{
    if (i >= data->size())
    {
        throw std::out_of_range(msg);
    }
}

std::string &StrBlob::front()
{
    check(0, "front on empty StrBlob");
    return data->front();
}

std::string &StrBlob::back()
{
    check(0, "back on empty StrBlob");
    return data->back();
}

void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

class ConstStrBlobPtr
{
public:
    ConstStrBlobPtr() : curr(0) {}
    ConstStrBlobPtr(const StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
    std::string &deref() const;
    ConstStrBlobPtr &incr();

private:
    std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string &) const;
    std::weak_ptr<std::vector<std::string>> wptr;
    std::size_t curr;
};

std::shared_ptr<std::vector<std::string>> ConstStrBlobPtr::check(std::size_t i, const std::string &msg) const
{
    auto ret = wptr.lock();
    if (!ret)
    {
        throw std::runtime_error("unbound ConstStrBlobPtr");
    }
    if (i >= ret->size())
    {
        throw std::out_of_range(msg);
    }
    return ret;
}

std::string &ConstStrBlobPtr::deref() const
{
    auto p = check(curr, "dereference past end");
    return (*p)[curr];
}

ConstStrBlobPtr &ConstStrBlobPtr::incr()
{
    check(curr, "increment past end if ConstStrBlobPtr");
    ++curr;
    return *this;
}

ConstStrBlobPtr StrBlob::begin() const
{
    return ConstStrBlobPtr(*this);
}

ConstStrBlobPtr StrBlob::end() const
{
    auto ret = ConstStrBlobPtr(*this, data->size());
    return ret;
}
#endif

13.2.2 节练习

练习 13.27

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

#ifndef HASPTR_H_
#define HASPTR_H_

#include <string>

class HasPtr
{
public:
    HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0), use(new std::size_t(1)) {}
    HasPtr(const HasPtr &p) : ps(p.ps), i(p.i), use(p.use) { ++*use; }
    HasPtr &operator=(const HasPtr &);

    ~HasPtr();

private:
    std::string *ps;
    int i;
    std::size_t *use;
};

HasPtr &HasPtr::operator=(const HasPtr &rhs)
{
    ++*rhs.use;
    if (--*use == 0)
    {
        delete ps;
        delete use;
    }
    ps = rhs.ps;
    i = rhs.i;
    use = rhs.use;
    return *this;
}

HasPtr::~HasPtr()
{
    if (--*use == 0)
    {
        delete ps;
        delete use;
    }
}
#endif

练习 13.28

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

(a) 
class TreeNode {
pravite:
	std::string value;
	int count;
	TreeNode *left;
	TreeNode *right;	
};
(b)
class BinStrTree{
pravite:
	TreeNode *root;	
};

默认构造函数

TreeNode::TreeNode() : value(""), count(1), left(nullptr), right(nullptr) {}
BinStrTree::BinStrTree() : root(nullptr) {}

拷贝构造函数

void TreeNode::CopyTree(void)
{ // 拷贝以此节点为根的子树,增加引用计数
    if (left)
    {
        left->CopyTree(); //左子树不空,拷贝左子树
    }
    if (right)
    {
        right->CopyTree(); //右子树不空,拷贝右子树
    }
    count++;
}

TreeNode::TreeNode(const TreeNode &rhs) : value(rhs.value), count(1), left(rhs.left), right(rhs.right)
{ // 从某个节点开始拷贝子树
    if (left)
    {
        left->CopyTree(); //左子树不空,拷贝左子树
    }
    if (right)
    {
        right->CopyTree(); //右子树不空,拷贝右子树
    }
}

BinStrTree::BinStrTree(const BinStrTree &bst) : root(bst.root)
{                     //拷贝整棵树
    root->CopyTree(); //应拷贝整棵树,而不是根节点
}

析构函数

int TreeNode::ReleaseTree()
{ // 释放以此节点为根的子树
    if (left)
    { // 如果存在左孩子,遍历释放
        if (!left->ReleaseTree())
        { // 左孩子计数为 0,释放其空间
            delete left;
        }
    }
    if (right)
    {
        if (!right->ReleaseTree())
        {
            delete right;
        }
    }
    --count;
    return count;
}

TreeNode::~TreeNode()
{
    if (count)
        ReleaseTree();
}

BinStrTree::~BinStrTree()
{ // 释放整棵树
    if (!root->ReleaseTree())
    {                // 释放整棵树,而非仅仅根节点
        delete root; // 引用计数为0,释放节点空间
    }
}

13.3 节练习

练习 13.29

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

参数类型不同,不是同一个函数。

练习 13.30

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

#ifndef HASPTR_EX11_H
#define HASPTR_EX11_H

#include <iostream>
#include <string>

class HasPtr
{
    friend void swap(HasPtr &, HasPtr &);

public:
    HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0) {}
    HasPtr(const HasPtr &p) : ps(new std::string(*p.ps)), i(p.i) {}
    HasPtr &operator=(const HasPtr &);
    ~HasPtr() { delete ps; }

private:
    std::string *ps;
    int i;
};

HasPtr &HasPtr::operator=(const HasPtr &rhs_hp)
{
    auto newp = new std::string(*rhs_hp.ps);
    delete ps;
    ps = newp;
    i = rhs_hp.i;
    return *this;
}

inline void swap(HasPtr &lhs, HasPtr &rhs)
{
    using std::swap;
    swap(lhs.ps, rhs.ps);
    swap(lhs.i, rhs.i);
    std::cout << "swap" << std::endl;
}

#endif

练习 13.31

为你的 HasPtr 类定义一个 < 运算符,并定义一个 HasPtr 的 vector。为这个 vector 添加一些元素,并对它执行 sort。注意何时会调用 swap。

#ifndef HASPTR_EX11_H
#define HASPTR_EX11_H

#include <iostream>
#include <string>

class HasPtr
{
    friend void swap(HasPtr &, HasPtr &);
    friend bool operator<(const HasPtr &, const HasPtr &);

public:
    HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0) {}
    HasPtr(const HasPtr &p) : ps(new std::string(*p.ps)), i(p.i) {}
    HasPtr &operator=(const HasPtr &);

    ~HasPtr() { delete ps; }

private:
    std::string *ps;
    int i;
};

HasPtr &HasPtr::operator=(const HasPtr &rhs_hp)
{
    auto newp = new std::string(*rhs_hp.ps);
    delete ps;
    ps = newp;
    i = rhs_hp.i;
    return *this;
}

inline void swap(HasPtr &lhs, HasPtr &rhs)
{
    using std::swap;
    swap(lhs.ps, rhs.ps);
    swap(lhs.i, rhs.i);
    std::cout << "swap" << std::endl;
}

inline bool operator<(const HasPtr &lhs, const HasPtr &rhs)
{
    std::cout << "<" << std::endl;
    return *lhs.ps < *rhs.ps;
}

#endif
#include<algorithm>
#include<vector>
#include"HasPtrV.h"

int main()
{
    HasPtr p1("a"),p2("b");
    std::vector<HasPtr> vp{p1, p2};
    std::sort(vp.begin(), vp.end());
    return 0;
}

练习 13.32

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

不会,类指针就是指针的交换,没有涉及内存分配。

13.4 节练习

练习 13.33

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

因为这两个函数需要更改对应 Folder 的值。

练习 13.34

编写本节所描述的 Message。

#ifndef MESSAGE_H_
#define MESSAGE_H_

#include <set>
#include <string>

class Folder;

class Message
{
    friend class Folder;
    friend void swap(Message &, Message &);

private:
    std::string contents;       // 实际包含的文本
    std::set<Folder *> folders; // 包含本 Message 的 Folder
    // 拷贝构造函数、拷贝赋值运算符和析构函数所使用的工具函数
    // 将本 Message 添加到指向参数的 Folder 中
    void add_to_Folders(const Message &);
    // 从 folders 中的每个 Folder 中删除本 Message
    void remove_from_Folders();

public:
    explicit Message(const std::string &str = "") : contents(str) {}
    // 拷贝控制成员,用来管理指向本 Message 的指针
    Message::Message(const Message &m) : contents(m.contents), folders(m.folders) { add_to_Folders(m); }
    Message &operator=(const Message &);
    ~Message() { remove_from_Folders(); }
    // 从给定 Folder 集合中添加/删除本 Message
    void save(Folder &);
    void remove(Folder &);
};

void Message::save(Folder &f)
{
    folders.insert(&f); // 将给定 Folder 添加到我们的 Folder 列表中
    f.addMsg(this);     // 将本 Message 添加到 f 的Message 集合中
}

void Message::remove(Folder &f)
{
    folders.erase(&f); // 将给定 Folder 从我们的 Folder 列表中删除
    f.remMsg(this);    // 将本 Message 从 f 的 Message 集合中删除
}

// 将本 Message 添加到指向 m 的 Folder 中
void Message::add_to_Folders(const Message &m)
{
    for (auto f : m.folders) // 对每个包含 m 的 Folder
    {
        f->addMsg(this); // 向该 Folder 添加一个指向本 Message 的指针
    }
}

// 从对应的 Folder 中删除本 Message
void Message::remove_from_Folders()
{
    for (auto f : folders) // 对 folders 中每个指针
    {
        f->remMsg(this); // 才该 Folder 中删除本 Message
    }
}

Message &Message::operator=(const Message &rhs)
{
    // 通过先删除指针再插入题目来处理自赋值情况
    remove_from_Folders();   // 更新已有 Folder
    contents = rhs.contents; // 从 rhs 拷贝消息内容
    folders = rhs.folders;   //从 rhs 拷贝 Folder 指针
    add_to_Folders(rhs);     // 将本 Message 添加到那些 Folder 中
    return *this;
}

void swap(Message &lhs, Message &rhs)
{
    using std::swap; // 在本例中严格来说并不需要,但这是个好习惯
    // 将每个消息的指针从它(原来)所在 Folder 中删除
    for (auto f : lhs.folders)
    {
        f->remMsg(&lhs);
    }
    for (auto f : rhs.folders)
    {
        f->remMsg(&rhs);
    }
    // 交换 contents 和 Folder 指针 set
    swap(lhs.folders, rhs.folders);   // 所以 swap(set&, set&)
    swap(lhs.contents, rhs.contents); // swap(string&, string&)
    // 将每个 Message 的指针添加到它的(新)Folder 中
    for (auto f : lhs.folders)
    {
        f->addMsg(&lhs);
    }
    for (auto f : rhs.folders)
    {
        f->addMsg(&rhs);
    }
}
#endif

练习 13.35

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

Message 本身可以拷贝,但 Message 中保存 Folder 信息的 folders 列表与 Folder 中保存的 Message 信息不统一。

练习 13.36

设计并实现对应的 Folder 类。此类应该保存一个指向 Folder 中包含 Message 的 set。

#ifndef MESSAGE_H_
#define MESSAGE_H_

#include <set>
#include <string>

class Folder;

class Message
{
    friend class Folder;
    friend void swap(Message &, Message &);

private:
    std::string contents;
    std::set<Folder *> folders;

    void add_to_Folders(const Message &);
    void remove_from_Folders();

public:
    explicit Message(const std::string &str = "") : contents(str) {}

    Message::Message(const Message &m) : contents(m.contents), folders(m.folders) { add_to_Folders(m); }
    Message &operator=(const Message &);
    ~Message() { remove_from_Folders(); }

    void save(Folder &);
    void remove(Folder &);
};

class Folder
{
    friend class Message;

private:
    std::set<Message *> messages;

    void add_to_Message(const Folder &);
    void remove_from_Message();

public:
    Folder();
    Folder(const Folder &);
    Folder &operator=(const Folder &);
    ~Folder();

    void addMsg(Message *m) { messages.insert(m); }
    void remMsg(Message *m) { messages.erase(m); }
};

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_from_Folders()
{
    for (auto f : folders)
    {
        f->remMsg(this);
    }
}

Message &Message::operator=(const Message &rhs)
{
    remove_from_Folders();
    contents = rhs.contents;
    folders = rhs.folders;
    add_to_Folders(rhs);
    return *this;
}

void swap(Message &lhs, Message &rhs)
{
    using std::swap;

    for (auto f : lhs.folders)
    {
        f->remMsg(&lhs);
    }
    for (auto f : rhs.folders)
    {
        f->remMsg(&rhs);
    }

    swap(lhs.folders, rhs.folders);
    swap(lhs.contents, rhs.contents);

    for (auto f : lhs.folders)
    {
        f->addMsg(&lhs);
    }
    for (auto f : rhs.folders)
    {
        f->addMsg(&rhs);
    }
}

void Folder::add_to_Message(const Folder &f)
{
	for(auto m : f.messages)
		m->save(*this);
}

void Folder::remove_from_Message()
{
	for(auto m : messages)
		m->remove(*this);
}

Folder &Folder::operator=(const Folder &rhs)
{
	remove_from_Message();
	messages = rhs.messages;
	add_to_Message(rhs);
	return *this;
}

#endif

练习 13.37

为 Message 类添加成员,实现向 folders 添加和删除一个给定的 Folder*。这两个成员类似 Folder 类的 addMsg 和 remMsg 操作。
见练习 13.37

练习 13.38

我们并未使用拷贝交换方式来设计 Message 的赋值运算符。你认为其原因是什么?

当涉及到动态分配时,使用拷贝和交换方式来实现赋值运算符是一个很好的方式(因为有共同的 delete 操作)。
但 Message 类并未涉及到动态分配,此时如果使用拷贝和交换的方式就没有意义。

13.5 节练习

练习 13.39

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

#ifndef STRVEC_H_
#define STRVEC_H_

#include <memory>
#include <string>

class StrVec
{
public:
    StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) {}

    StrVec(const StrVec &);
    StrVec &operator=(const StrVec &);
    ~StrVec() { free(); }

    void push_back(const std::string &);
    size_t size() const { return first_free - elements; }
    size_t capacity() const { return cap - elements; }
    std::string *begain() const { return elements; }
    std::string *end() const { return first_free; }

    void reserve(size_t);
    void resize(size_t);

private:
    static std::allocator<std::string> alloc;

    void chk_n_alloc()
    {
        if (size() == capacity())
            reallocate();
    }
    std::pair<std::string *, std::string *> alloc_n_copy(const std::string *, const std::string *);
    void free();
    void reallocate();

    std::string *elements;
    std::string *first_free;
    std::string *cap;
};

void StrVec::push_back(const std::string &s)
{
    chk_n_alloc();
    alloc.construct(first_free++, s);
}

std::pair<std::string *, std::string *> StrVec::alloc_n_copy(const std::string *b, const std::string *e)
{ // 拷贝是有对象元素的这段空间
    auto data = alloc.allocate(e - b);
    return {data, std::uninitialized_copy(b, e, data)};
}

void StrVec::free()
{
    if (elements)
    {
        for (auto p = first_free; p != elements; /* */)
            alloc.destroy(--p);                     // 销毁内存的对象,自动调用对象类型的析构函数,即 ~string()
        alloc.deallocate(elements, cap - elements); // 销毁内存本身
    }
}

StrVec::StrVec(const StrVec &s)
{
    auto newdata = alloc_n_copy(s.begain(), s.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

StrVec &StrVec::operator=(const StrVec &rhs)
{
    auto data = alloc_n_copy(rhs.begain(), rhs.end()); // 在释放前先拷贝,处理自赋值
    free();
    elements = data.first;
    first_free = cap = data.second;
    return *this;
}

void StrVec::reallocate()
{
    auto newcapacity = size() ? 2 * size() : 1;
    auto newdata = alloc.allocate(newcapacity); // 返回新空间开始的位置
    auto dest = newdata;
    auto elem = elements; // 旧空间开始的位置
    for (size_t i = 0; i != size(); ++i)
        alloc.construct(dest++, std::move(*elem++));
    free();
    elements = newdata;
    first_free = dest;
    cap = elements + newcapacity;
}

void StrVec::reserve(size_t n)
{
    if (n <= capacity()) // 如果需求小于或者等于当前容量,什么都不做
        return;
    auto newdata = alloc.allocate(n);
    auto dest = newdata;
    auto elem = elements;
    for (size_t i = 0; i != size(); ++i)
        alloc.construct(dest++, std::move(*elem++));
    free();
    elements = newdata;
    first_free = dest;
    cap = elements + n;
}

void StrVec::resize(size_t n)
{
    if (n < size())
    { // 如果要求的大小比原来小,要删除多余的元素
        while (n < size())
            alloc.destroy(--first_free);
    }
    else if (n > size())
    { // 如果要求的大小比原来小,则增加元素(用 string 的默认构造)
        while (n > size())
            push_back(std::string());
    }
}
#endif

练习 13.40

为你的 StrVec 类添加一个构造函数,它接受一个 initializer_list< string > 参数。

class StrVec
{
public:
    StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) {}
    StrVec(std::initializer_list<std::string> &);
	// 其他不变
	
};

StrVec::StrVec(std::initializer_list<std::string> &il)
{
    auto newdata = alloc_n_copy(il.begin(), il.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

练习 13.41

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

因为 first_free 指向未构造内存的开始,后置递增运算是保证,从当前逐个构造内存空间;如果改为前置递增,构造后会空出一个未构造内存空间。

练习 13.42

在你的 TextQuery 和 QueryResult 类中用你的 StrVec 类代替 vector< string >,以此来测试你的 StrVec 类。

直接替换即可!

// using line_no = vector<string>::size_type;
using line_no = size_t;

// shared_ptr<vector<string>> file;
shared_ptr<StrVec> file;

测试同 12.29

练习 13.43

重写 free 成员,用 for_each 和 lambda 来代替 for 循环 destroy 元素。你更倾向于哪种实现,为什么?

void StrVec::free()
{
    if (elements)
    { 
        /* for (auto p = first_free; p != elements;)
            alloc.destroy(--p); */
        std::for_each(elements, first_free, [this](string &p) { alloc.destroy(&p); });
        alloc.deallocate(elements, cap - elements); 
    }
}

原来的实现容易读懂。

练习 13.44

编写标准库 string 类的简化版本,命名为 String。你的类应该至少有一个默认构造函数和一个接受 C 风格字符串指针参数的构造函数。使用 allocator 为你的 String类分配所需内存。

#ifndef STRING_H_
#define STRING_H_

#include <cstring>
#include <memory>

class String
{
public:
    String();
    String(const char *);
    String(const String &);
    String &operator=(const String &);

    size_t size() const { return first_free - elements; }
    char *begin() const { return elements; }
    char *end() const { return first_free; }

    ~String() { free(); }

private:
    static std::allocator<char> alloc;

    std::pair<char *, char *> alloc_n_copy(const char *, const char *);
    void free();

    char *elements;
    char *first_free;
};

std::pair<char *, char *> String::alloc_n_copy(const char *b, const char *e)
{
    auto str = alloc.allocate(e - b);
    return {str, std::uninitialized_copy(b, e, str)};
}

void String::free()
{
    if (elements)
    {
        for (auto p = first_free; p != elements;)
            alloc.destroy(--p);
        alloc.deallocate(elements, size());
    }
}

String::String(const char *c)
{
    size_t n = strlen(c);
    auto newStr = alloc_n_copy(c, c + n);
    elements = newStr.first;
    first_free = newStr.second;
}

String::String(const String &s)
{
    auto newStr = alloc_n_copy(s.begin(), s.end());
    elements = newStr.first;
    first_free = newStr.second;
}

String &String::operator=(const String &rhs)
{
    auto str = alloc_n_copy(rhs.begin(), rhs.end());
    free();
    elements = str.first;
    first_free = str.second;
    return *this;
}
#endif

13.6.1 节练习

练习 13.45

解释左值引用和右值引用的区别?

左值引用是绑定到左值上的引用,左值持久;
右值引用是绑定到右值上的引用,右值短暂,右值引用可以绑定到要求转换的表达式、字面值常量或是返回右值的表达式上。

练习 13.46

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

int f();
vector<int> vi(100);
int? r1 = f();
int? r2 = vi[0];
int? r3 = r1;
int? r4 = vi[0] * f();
int f();
vector<int> vi(100);
int&& r1 = f();
int& r2 = vi[0];
int& r3 = r1;
int&& r4 = vi[0] * f();

练习 13.47

对你在练习 13.44 中定义的 String 类,为它的拷贝构造函数和拷贝赋值运算符添加一条语句,在每次函数执行时打印一条信息。

String::String(const char *c)
{
    size_t n = strlen(c);
    auto newStr = alloc_n_copy(c, c + n);
    elements = newStr.first;
    first_free = newStr.second;
    std::cout << "String(const char *)" << std::endl;
}

String::String(const String &s)
{
    auto newStr = alloc_n_copy(s.begin(), s.end());
    elements = newStr.first;
    first_free = newStr.second;
    std::cout << "String(const String &)" << std::endl;
}

String &String::operator=(const String &rhs)
{
    auto str = alloc_n_copy(rhs.begin(), rhs.end());
    free();
    elements = str.first;
    first_free = str.second;
    std::cout << "String &operator=(const String &)" << std::endl;
    return *this;
}

练习 13.48

定义一个vector < String >并在其上多次调用 push_back。运行你的程序,并观察 String 被拷贝了多少次。

#include"String.h"
#include<vector>

int main()
{
    std::vector<String> v;
    v.push_back("aaaa");
    v.push_back("aaaa");
    v.push_back("aaaa");
    return 0;
}

运行结果

String(const char *)
String(const String &)
String(const char *)
String(const String &)
String(const String &)
String(const char *)
String(const String &)
String(const String &)
String(const String &)

13.6.2 节练习

练习 13.49

为你的 StrVec、String 和 Message 类添加一个移动构造函数和一个移动赋值运算符。

StrVec 类

StrVec(StrVec &&s) noexcept : elements(std::move(s.elements)), first_free(std::move(s.first_free)), cap(std::move(s.cap)) { s.elements = s.first_free = s.cap; }
StrVec &operator=(StrVec &&) noexcept;
StrVec &StrVec::operator=(StrVec &&rhs) noexcept
{
    if (this != &rhs)
    {
        free();
        elements = rhs.elements;
        first_free = rhs.first_free;
        cap = rhs.cap;
        rhs.elements = rhs.first_free = rhs.cap = nullptr;
    }
    return *this;
}

String 类

String(String &&s) noexcept : elements(std::move(s.elements)), first_free(std::move(s.first_free)) { s.elements = s.first_free = nullptr; }
String &operator=(String &&) noexcept;
String &String::operator=(String&&rhs)noexcept
{
    if (this!=&rhs)
    {
        free();
        elements = rhs.elements;
        first_free = rhs.first_free;
        rhs.elements = rhs.first_free = nullptr;
    }
    return *this;
}

Message 类

Message(Message &&m) noexcept : contents(std::move(m.contents)) { move_Folders(&m); }
Message operator=(Message &&);

void Message::move_Folders(Message*m)
{
    folders = std::move(m->folders);
    for(auto f:folders)
    {
        f->remMsg(m);
        f->addMsg(this);
    }
    m->folders.clear();
}

Message &Message::operator=(Message &&rhs)
{
    if (this!=&rhs)
    {
        remove_from_Folders();
        contents = std::move(rhs.contents);
        move_Folders(&rhs);
    }
    return *this;
}

练习 13.50

在你的 String 类的移动操作中添加打印语句,并重新运行 13.6.1 节的练习 13.48 中的程序,它使用了一个 vector< String >,观察什么时候会避免拷贝。

#include "String.h"
#include <vector>
int main()
{
    std::vector<String> v;
    v.push_back("acac");
    v.push_back("121");
    v.push_back("@@@@@");
    
    return 0;
}

运行结果

String(const char *)      
String(String &&) noexcept
String(const char *)      
String(String &&) noexcept
String(String &&) noexcept
String(const char *)
String(String &&) noexcept
String(String &&) noexcept
String(String &&) noexcept

练习 13.51

虽然 unique_ptr 不能拷贝,但我们在 12.1.5 节中编写了一个 clone 函数,它以值的方式返回一个 unique_ptr。解释为什么函数是合法的,以及为什么它能正确工作。

不能拷贝 unique_ptr 的规则有一个例外:可以拷贝或赋值一个将要销毁的 unique_ptr ,即移动操作。

练习 13.52

详细解释第 478 页中的 HasPtr 对象的赋值发生了什么?特别是,一步一步描述 hp、hp2 以及 HasPtr 的赋值运算符中的参数 rhs 的值发生了什么变化。

hp = hp2;

hp2 是一个左值,因此移动构造函数是不可行的。赋值运算符的参数 rhs 将使用拷贝构造函数来初始化。拷贝构造函数将分配一个新的 string,并拷贝 hp2 指向的 string;

hp = std::move(hp2);

调用 std::move 将一个右值引用绑定到 hp2 上。在此情况下,拷贝构造函数和移动构造函数都是可行的。但是,由于实参是一个右值引用,移动构造函数是精确匹配的。移动构造函数从 hp2 拷贝指针,而不会分配任何内存。

练习 13.53

从底层效率的角度看,HasPtr 的赋值运算符并不理想,解释为什么?为 HasPtr 实现一个拷贝赋值运算符和一个移动赋值运算符,并比较你的新的移动赋值运算符中执行的操作和拷贝并交换版本中的执行的操作。

使用交换 swap 函数,会再次给两个对象赋值,但是赋值运算符本意只需要给左侧对象赋值。

    HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) { p.ps = 0; }
    HasPtr &HasPtr::operator=(HasPtr &&rhs)
{
    if (this != &rhs)
    {
        delete ps;
        ps = std::move(rhs.ps);
        i = rhs.i;
    }
    return *this;
}  

练习 13.54

如果我们为 HasPtr 定义了移动赋值运算符,但未改变拷贝并交换运算符,会发生什么?编写代码验证你的答案。

HasPtrV.h

#ifndef HASPTR_EX11_H
#define HASPTR_EX11_H

#include <iostream>
#include <string>

class HasPtr
{
    friend void swap(HasPtr &, HasPtr &);
    friend bool operator<(const HasPtr &, const HasPtr &);

public:
    HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0) {}
    HasPtr(const HasPtr &p) : ps(new std::string(*p.ps)), i(p.i) {}
    // HasPtr &operator=(const HasPtr &);
    HasPtr &operator=(HasPtr &rhs_hp)
    {
        swap(*this, rhs_hp);
        std::cout << "HasPtr &operator=(HasPtr &rhs_hp)" << std::endl;
        return *this;
    }

    HasPtr(HasPtr &&p) noexcept : ps(p.ps), i(p.i) { p.ps = 0; }
    HasPtr &operator=(HasPtr &&);
    ~HasPtr() { delete ps; }

private:
    std::string *ps;
    int i;
};

/* HasPtr &HasPtr::operator=(const HasPtr &rhs_hp)
{
    auto newp = new std::string(*rhs_hp.ps);
    delete ps;
    ps = newp;
    i = rhs_hp.i;
    return *this;
} */

inline void swap(HasPtr &lhs, HasPtr &rhs)
{
    using std::swap;
    swap(lhs.ps, rhs.ps);
    swap(lhs.i, rhs.i);
    std::cout << "swap" << std::endl;
}

inline bool operator<(const HasPtr &lhs, const HasPtr &rhs)
{
    std::cout << "<" << std::endl;
    return *lhs.ps < *rhs.ps;
}

HasPtr &HasPtr::operator=(HasPtr &&rhs_hp)
{
    if (this != &rhs_hp)
    {
        delete ps;
        ps = std::move(rhs_hp.ps);
        i = rhs_hp.i;
    }
    std::cout << "HasPtr &operator=(HasPtr &&rhs_hp)" << std::endl;
    return *this;
}
#endif

13.54.cpp

#include "HasPtrV.h"
int main()
{
    HasPtr hp1("aaa"), hp2("bbb");

    hp1 = hp2;
    hp1 = std::move(hp2);

    return 0;
}

运行结果

swap
HasPtr &operator=(HasPtr &rhs_hp)
HasPtr &operator=(HasPtr &&rhs_hp)

赋值时会使用拷贝操作,调用 move 时会使用移动操作!

13.6.3 节练习

练习 13.55

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

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

练习 13.56

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

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

无线递归。

练习 13.57

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

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

强制将 *this 类型转换会产生一个右值,右值调用右值版本的 sorted,结果是正常运行。

练习 13.58

编写新版本的 Foo 类,其 sorted 函数中有打印语句,测试这个类,来验证你对前两题的答案是否正确。

Foo.h

#ifndef FOO_H_
#define FOO_H_

#include <algorithm>
#include <iostream>
#include <vector>
class Foo
{
public:
    Foo sorted() &&;
    Foo sorted() const &;

private:
    std::vector<int> data;
};

Foo Foo::sorted() &&
{
    sort(data.begin(), data.end());
    std::cout << "Foo sorted() &&" << std::endl;
    return *this;
}

Foo Foo::sorted() const &
{
    /* Foo ret(*this);
    sort(ret.data.begin(), ret.data.end());
    return ret; */
    std::cout << "Foo sorted() const &;" << std::endl;
    Foo ret(*this);
    return ret.sorted();
}

#endif
#include "Foo.h"

int main()
{
    Foo foo;
    foo.sorted();
    return 0;
}

运行结果

Foo sorted() const &;
Foo sorted() const &;
Foo sorted() const &;
Foo sorted() const &;
Foo sorted() const &;
...(无限循环)

Foo.h

#ifndef FOO_H_
#define FOO_H_

#include <algorithm>
#include <iostream>
#include <vector>
class Foo
{
public:
    Foo sorted() &&;
    Foo sorted() const &;

private:
    std::vector<int> data;
};

Foo Foo::sorted() &&
{
    sort(data.begin(), data.end());
    std::cout << "Foo sorted() &&" << std::endl;
    return *this;
}

Foo Foo::sorted() const &
{
    std::cout << "Foo sorted() const &;" << std::endl;
    /* Foo ret(*this);
    sort(ret.data.begin(), ret.data.end());
    return ret; */
    
    /* Foo ret(*this);
    return ret.sorted(); */

    return Foo(*this).sorted();
}

#endif

运行结果

Foo sorted() const &;
Foo sorted() &&
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C ++ Primer(第五版)第十三章练习答案 的相关文章

  • 将 C++ 代码(本机客户端)移植到浏览器(Web 应用程序)

    我有一个使用 Qt creator SDK 编写的 C 模块 我想将此代码移植到任何网页上运行 而不会对最终用户损害源代码 用户应该能够在任何浏览器 Chrome Firefox Safari Explorer 上看到此模块的输出 而无需安
  • 有没有办法将所有内容都包含在 dbcontext 中?

    当查询一个DbContext急切加载时 需要Include Navigation 为了填充导航属性 然而 在某些情况下 我想简单地Include all实体的导航属性 有没有办法做到这一点 或者有办法做到这一点 我假设你可以反思 但我宁愿避
  • fork 和 exec 之间的区别

    两者有什么区别fork and exec 指某东西的用途fork and exec它体现了 UNIX 的精神 它提供了一种非常简单的方法来启动新进程 The fork调用基本上复制了当前进程 在almost任何方式 并非所有内容都会被复制
  • C++ 天花板函数的奇怪结果

    我一直在尝试天花板功能并得到一些奇怪的结果 如果我对十进制数乘以百执行 ceil 运算 我会得到一定的结果 但是 如果我直接对该乘法的结果执行 ceil 我会得到完全不同的输出 另一个问题是 这些不同的结果仅发生在某些数字上 任何帮助 将不
  • Web 应用程序框架:C++ 与 Python

    作为一名程序员 我熟悉 Python 和 C 我正在考虑编写自己的简单 Web 应用程序 并且想知道哪种语言更适合服务器端 Web 开发 我正在寻找一些东西 它必须是直观的 我认识到 Wt 存在并且它遵循 Qt 的模型 我讨厌 Qt 的一件
  • 委托和接口如何互换使用?

    我可以使用接口方法代替委托吗 如何 我发现搜索接口方法比使用委托更快 我希望有一个简单的代码片段 理论上 可以通过包含单个方法的接口 例如 Java 没有委托 来完成委托完成的所有工作 然而 它使代码变得更加冗长并且没有带来什么好处 话又说
  • OpenGL,如何独立旋转对象?

    到目前为止我的代码 void display void glClear GL COLOR BUFFER BIT GL DEPTH BUFFER BIT Clear Screen And Depth Buffer glLoadIdentity
  • 以概率从列表中选择随机元素

    我有一个包含四个项目 A B C D 的列表 每个项目都有被选择的概率 例如 A 有 74 的机会被选中 B 15 C 7 D 4 我想创建一个函数 根据其概率随机选择一个项目 有什么帮助吗 为您的项目定义一个类 如下所示 class It
  • stl 集的 C# 等效项是什么?

    我想使用 C 将一些值存储在平衡二叉搜索树中 我查看了泛型命名空间中的集合 但没有找到与 stl 集合等效的集合 我可以使用什么通用集合 我不想存储键 值对 只是值 你可以使用HashSet http msdn microsoft com
  • 如何通过C#在SQLite数据库中写入变量DateTime值?

    我很新C and SQLite数据库并有一些变量存储在 SQLite 数据库中TimeStamp 这是我的代码 DateTime now DateTime Now m dbConnection new SQLiteConnection Da
  • 如何获取 C# PriorityQueue 元素的优先级

    我正在初始化一个存储 XY 坐标的优先级队列 根据距原点的欧几里得距离确定优先级 我创建了一个自定义Comparer这使得它作为最大堆运行 PriorityQueue
  • const_iterators 更快吗?

    我们的编码指南更喜欢const iterator 因为它们比正常的要快一点iterator 当您使用时 编译器似乎会优化代码const iterator 这真的正确吗 如果是的话 内部到底发生了什么使得const iterator快点 编辑
  • 有没有办法关闭 Hangfire 使用 Serilog 进行的日志记录?

    有没有办法关闭 Hangfire 使用 Serilog 进行的日志记录 我们正在使用我们自己的抽象 我不希望在使用 Serilog 时来自 Hangfire 记录器的所有额外噪音 INIT call under web project na
  • 二元运算符重载、隐式类型转换

    class my bool private bool value public my bool bool value value value explicit operator bool return value friend my boo
  • 阻止用户取消选择列表框中的项目?

    我有一个列表框 里面有很多项目 用户可以单击某个项目来编辑其内容 如何防止用户取消选择所有项目 即 用户不应该无法选择任何内容 您的情况缺少一个案例 即清除列表后 您将选择列表中不再存在的项目 我通过添加额外的检查来解决这个问题 var l
  • 使用 unrar 库 - 将文件提取到文件流缓冲区中

    我需要的是能够将 rar 文件中的文件提取到流中 我正在创建一个测试用例来了解如何使用解压源文件 http www rarlab com rar unrarsrc 3 9 9 tar gz 我已经搜索和修补了一段时间 但我不知道如何使用该库
  • 为什么 C++ 元组如此奇怪?

    我通常创建自定义structs将不同类型的值分组在一起时 这通常很好 而且我个人发现命名成员访问更容易阅读 但我想创建一个更通用的 API 在其他语言中广泛使用元组后 我想返回类型的值std tuple但发现它们在 C 中使用比在其他语言中
  • 在 C++17 中编译具有非固定基础类型的 constexpr 从 int 静态转换为作用域枚举的未定义行为

    我想知道以下内容是否应该在 C 17 中编译 enum class E A B constexpr E x static cast
  • 定义一个断言,即使定义了 NDEBUG,该断言也有效

    我想定义一个assert与标准相同的宏assert 3 http man7 org linux man pages man3 assert 3 html调用 但它不会被预处理器删除NDEBUG被定义为 这样的呼唤 让我们称之为assert2
  • “while(true) { Thread.Sleep }”的原因是什么?

    我有时会遇到以下形式的代码 while true do something Thread Sleep 1000 我想知道这是否被认为是好的做法还是坏的做法以及是否有任何替代方案 通常我在服务的主函数中 找到 这样的代码 我最近在 Windo

随机推荐

  • 把windows系统的默认编码改成UTF-8

    对于开发人员来说windows的编码的确是神一般的存在 windows老版本语言编码实际上是ascii和gbk编码混用 因此经常乱码 现在UTF 8国际化流行的阶段 windows这么多年 不知道耽误了多少程序员 老朱是linux服务器 数
  • Python数据分析库pandas ------ merge、concatenation 、pd.concat、combine_first、stack、unstack(0)、pivot、drop;合并...

    对于合并操作 熟悉SQL的读者可以将其理解为JOIN操作 它使用一个或多个键把多行数据 结合在一起 事实上 跟关系型数据库打交道的开发人员通常使用SQL的JOIN查询 用几个表共有的引用 值 键 从不同 的表获取数据 以这些键为基础 我们能
  • Java架构直通车——过滤器、拦截器、AOP的区别

    文章目录 过滤器 拦截器 AOP 面向切面 三者使用场景 过滤器 过滤器拦截的是URL Spring中自定义过滤器 Filter 一般只有一个方法 返回值是void 当请求到达web容器时 会探测当前请求地址是否配置有过滤器 有则调用该过滤
  • Vue F11监听切大屏

    用Datav插件做大屏可视化时 有一个全屏需求 我的想法是监听F11键 然后触发浏览器的大屏功能API 百度一下 竟有插件 大喜 插件的好处大家都知道 时间熬出来的 并且BUG极少 代码精简 做了全浏览器兼容 Rec 0001 步骤 1 插
  • 【性能测试】Jenkins+Ant+Jmeter自动化框架的搭建思路

    前言 前面讲了Jmeter在性能测试中的应用及扩展 随着测试的深入 我们发现在性能测试中也会遇到不少的重复工作 比如某新兴业务处于上升阶段 需要在每个版本中 对某些新增接口进行性能测试 有时还需要在一天中的不同时段分别进行性能测试 如果一味
  • idea启动项目很久很慢的一种解决方案

    一 问题描述 IntelliJ idea 在启动项目时 很久很慢 二 解决 在不买个更强更贵的前提下 有以下一种解决方案 1 方案依据 一般地 JVM实例默认最大堆内存是机器的1 64 在启动时会不断地fullGC 不断的申请内存 所以我们
  • FreeBSD SSH配置详解

    ssh config和sshd config都是ssh服务器的配置文件 二者区别在于 前者是针对客户端的配置文件 后者则是针对服务端的配置文件 两个配置文件都允许你通过设置不同的选项来改变客 户端程序的运行方式 下面列出来的是两个配置文件中
  • 【STM32学习】——定时器的编码器接口&正交编码器&编码器接口测速代码实操

    文章目录 前言 一 编码器接口 1 简介 2 正交编码器 二 实操案例 编码器接口测速 总结心得 声明 学习笔记根据b站江科大自化协stm32入门教程编辑 仅供学习交流使用 前言 引入 本实操案例与之前学习外部中断时写的旋转编码器计次的代码
  • 用git bash上传文件到gitee

    本地上传 下载git 官网链接 Git 点击Downloads 选择自己的系统类型 我选的是64 bit Git forWindows Setup 安装git 安装过程中只需额外勾选下图选项即可 这样git bash快捷键将会出现在桌面 会
  • 55. 跳跃游戏 45. 跳跃游戏 II

    55 跳跃游戏 给定一个非负整数数组 nums 你最初位于数组的 第一个下标 数组中的每个元素代表你在该位置可以跳跃的最大长度 判断你是否能够到达最后一个下标 示例 1 输入 nums 2 3 1 1 4 输出 true 解释 可以先跳 1
  • 数据挖掘之C4.5决策树算法

    1 决策树算法实现的三个过程 特征选择 选择哪些特征作为分类的标准是决策树算法的关键 因此需要一种衡量标准来进行特征的确定 不同的决策树衡量标准不同 例如C4 5决策树就是以信息增益率来作为衡量标准 决策树的生成 根据所选择的衡量标准不断递
  • Ubuntu 20系统WIFI断连问题

    最近工作需要购置了一台GPU机器 然后搭建了深度学习的运行环境 在工作中将这台机器当做深度学习的服务器来使用 前期已经配置好多用户以及基础环境 但最近通过xshell连接总是不间断的出现断连现象 下面记录下 为此主要进行的操作 1 IP地址
  • Dubbo的几种序列化协议

    dubbo 支持哪些通信协议 支持哪些序列化协议 说一下 Hessian 的数据结构 PB 知道吗 为什么 PB 的效率是最高面试官心理分析 面试官心理分析 上一个问题 说说 dubbo 的基本工作原理 那是你必须知道的 至少要知道 dub
  • 802.11 - 定向多播服务(Directed multicast service)

    定向多播服务 前言 802 11v指定了定向多播服务 directed multicast service DMS 使客户端设备能够请求AP直接向其发送组播和广播帧 提高了网络效率 DMS的传输速率比定期组播每秒快数百Mb 与非DMS基础设
  • Basic Level 1035 插入与归并 (25分)

    题目 根据维基百科的定义 插入排序是迭代算法 逐一获得输入数据 逐步产生有序的输出序列 每步迭代中 算法从输入序列中取出一元素 将之插入有序序列中正确的位置 如此迭代直到全部元素有序 归并排序进行如下迭代操作 首先将原始序列看成 N 个只包
  • Java的流程控制结构以及程序跳转关键字详解

    本文详细介绍了Java中的流程控制结构 以及跳转关键字break continue return的使用 文章目录 1 流程控制结构分类 2 顺序结构 3 选择结构 3 1 if语句 3 1 1 if 3 1 2 if else 3 1 3
  • 匿名科创无人机学习心得

    1 飞控stm32串口5连接imu 串口五发送的指令会发送到imu中 如果是自定义的用户格式帧 比如 AA FF F1 03 01 01 01 A0 67 会先到imu imu的串口1接stm飞控 串口2接数传 从串口1接收到的数据会通过串
  • element-ui表单仅对el-form表单的部分字段/某个字段进行验证

    根据elementui文档 查询到validateField方法 可以给表单的某个字段添加校验 对表单单个字段进行校验 data rules email required true message 请输入邮箱 trigger change
  • 2023年最新版IDEA安装(超详细)

    个人主页 平行线也会相交 欢迎 点赞 收藏 留言 加关注 本文由 平行线也会相交 原创 收录于专栏 JavaSE primary 写在前面 IDEA的安装是建立在JDK安装好了的前提下 否则IDEA是无法使用的 具体JDK如何安装可以参照此
  • C ++ Primer(第五版)第十三章练习答案

    C Primer 第五版 第十三章练习答案 13 1 1 节练习 练习 13 1 练习 13 2 练习 13 3 练习 13 4 练习 13 5 13 1 2 节练习 练习 13 6 练习 13 7 练习 13 8 13 1 3 节练习 练