C++ Primer(第五版)|练习题答案与解析(第十六章:模板与泛型编程)

C++ Primer(第五版)|练习题答案与解析(第十六章:模板与泛型编程)

本博客主要记录C++ Primer(第五版)中的练习题答案与解析。
参考:C++Primer
C++ Primer

练习题16.1

给出实例化定义

P579,当调用一个函数模板时,编译器会使用实参的类型来确定绑定到模版参数T上的类型。之后编译器利用推断出的模版参数来实例化一个特定版本的函数,这个过程被称之为实例化。

练习题16.2

给出实例化定义

#include <iostream>
using std::cout;
using std::endl;
#include <vector>
using std::vector;
template<typename T>
int compare(const T& lhs, const T& rhs)
{
    if(lhs < rhs) return -1;
    if(rhs < lhs) return  1;
    return 0;
}

int main()
{
    // 测试 compare 函数
    cout << compare(1, 0) << endl;
    vector<int> vec1{ 1, 2, 3 }, vec2{ 4, 5, 6 };
    cout << compare(vec1, vec2) << endl;

    return 0;
}

测试:

1
-1

练习题16.3

对两个Sales_data对象调用你的compare函数,观察编译器在实例化过程中如何处理错误。
在这里插入图片描述

练习题16.4

编写行为类似标准库find算法的模板。函数需要两个模板类型参数,一个表示函数的迭代器参数。另一个表示值的类型,使用你的函数在一个vector<int>和list<string>中查找给定值。

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

namespace ch16
{
	template<typename Iterator, typename Value>
	auto find(Iterator first, Iterator last, Value const& value)
	{
		for (; first != last && *first != value; ++first);
		return first;
	}
}
int main()
{
	std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	auto is_in_vector = v.cend() != ch16::find(v.cbegin(), v.cend(), 5);
	std::cout << (is_in_vector ? "找到\n" : "未找到\n");
	std::list<std::string> l = { "aa", "bb", "cc", "dd", "ee", "ff", "gg" };
	auto is_in_list = l.cend() != ch16::find(l.cbegin(), l.cend(), "zz");
	std::cout << (is_in_list ? "找到\n" : "未找到\n");
	return 0;
}

测试:

找到
未找到

练习题16.5

为6.2.4节中的print函数编写模板版本,它接受一个数组的引用,能处理任意大小、元素类型的数组。

#include <iostream>
#include <string>
template<typename Arr>
void print(Arr const& arr)
{
	for (auto const& elem : arr)
		std::cout << elem << " ";
	std::cout << std::endl;
}
int main()
{
	std::string s[] = { "test", "train", "CNN" };
	char c[] = { 'a', 'b', 'c', 'd' };
	int  i[] = { 1, 20, 5 };
	print(i);
	print(c);
	print(s);
	return 0;
}

测试:

1 20 5
a b c d
test train CNN

练习题16.6

你认为接受一个数组实参的标注库函数begin和end是如何工作的?定义你自己版本的begin和end。

  • std::begin是一个模板函数,它引用一个数组。它将这个引用作为迭代器返回,迭代器指向这个数组中的第一个元素。
  • std::end是一个模板函数,它获取一个数组的引用并捕获大小。它返回这个引用和指向最后一个元素的迭代器的大小。
#include <iostream>
#include <vector>
#include <list>
#include <string>
// 和std::begin相同
template<typename T, unsigned size>
T* begin_def(T(&arr)[size])
{
	return arr;
}
// the same as std::end
template<typename T, unsigned size>
T* end_def(T(&arr)[size])
//我们通常不使用与标准libary函数相同的函数名
//这不应该是const
{
	return arr + size;
}
int main()
{
	std::string s[] = { "a","b","c","d" };
	std::cout << *begin_def(s) << std::endl;
	std::cout << *(begin_def(s) + 1) << std::endl;
	std::cout << *(end_def(s) - 1) << std::endl;
	return 0;
}

测试:

a
b
d

练习题16.7

编写一个constexpr模板,返回给定数组的大小。

#include <iostream>
#include <vector>
#include <list>
#include <string>
template<typename T, unsigned size>
constexpr unsigned getSize(const T(&)[size])
{
	return size;
}
int main()
{
	std::string s[] = { "test" };
	std::cout << getSize(s) << std::endl;
	char c[] = "t";
	std::cout << getSize(c) << std::endl;
	// 输出为2,因为“\0”被添加到数组的末尾
	return 0;
}

测试:

1
2

练习题16.8

在97页关键概念中,注意到,C++程序员喜欢用!=而不喜欢<,解释原因。

原因是更多的类定义了“!=”而不是<。这样做可以减少与模板类一起使用的类的需求数量。

练习题16.9

什么是函数模板?什么是类模板?

P583

  • 函数模板是一个公式,可以从中生成该函数的特定类型版本。
  • 类模板是生成类的蓝图。类模板与函数模板的区别在于,编译器无法推断类模板的参数类型。相反,要使用类模板,必须在模板名称后面的尖括号内提供附加信息(3.3,第97页)。

练习题16.10

什么是函数模板?什么是类模板?

编译器使用显示模版实参初始化一个类。

练习题16.11

下面List定义是错误的,应该如何修正它?

template <typename elemType> class ListItem;
template <typename elemType> class List
{
public:
    List<elemType>();
    List<elemType>(const List<elemType> &);
    List<elemType>& operator=(const List<elemType> &);
    ~List();
    void insert(ListItem<elemType> *ptr, elemType value);
    //模板不是类型,必须提供类型

private:
    ListItem<elemType> *front, *end;
    //模板不是类型,必须提供类型
};

练习题16.12

编写你自己版本的Blob和BlobPtr模板,包含书中未定义的多个const成员。

Blob.h

#include <memory>
#include <vector>
template<typename T> class Blob
{
public:
    typedef T value_type;
    typedef typename std::vector<T>::size_type size_type;
    // constructors
    Blob();
    Blob(std::initializer_list<T> il);
    // number of elements in the Blob
    size_type size() const { return data->size(); }
    bool      empty() const{ return data->empty(); }
    void push_back(const T& t) { data->push_back(t); }
    void push_back(T&& t)      { data->push_back(std::move(t)); }
    void pop_back();
    // element access
    T& back();
    T& operator[](size_type i);
    const T& back() const;
    const T& operator [](size_type i) const;
private:
    std::shared_ptr<std::vector<T>> data;
    // throw msg if data[i] isn't valid
    void check(size_type i, const std::string &msg) const;
};
// constructors
template<typename T>
Blob<T>::Blob() : data(std::make_shared<std::vector<T>>())
{ }
template<typename T>
Blob<T>::Blob(std::initializer_list<T> il):
    data(std::make_shared<std::vector<T>>(il)){ }
template<typename T>
void Blob<T>::check(size_type i, const std::string &msg) const
{
    if(i >= data->size())
        throw std::out_of_range(msg);
}
template<typename T>
T& Blob<T>::back()
{
    check(0, "back on empty Blob");
    return data->back();
}
template<typename T>
const T& Blob<T>::back() const
{
    check(0, "back on empty Blob");
    return data->back();
}
template<typename T>
T& Blob<T>::operator [](size_type i)
{
    // if i is too big, check function will throw, preventing access to a nonexistent element
    check(i, "subscript out of range");
    return (*data)[i];
}
template<typename T>
const T& Blob<T>::operator [](size_type i) const
{
    // if i is too big, check function will throw, preventing access to a nonexistent element
    check(i, "subscript out of range");
    return (*data)[i];
}
template<typename T>
void Blob<T>::pop_back()
{
    check(0, "pop_back on empty Blob");
    data->pop_back();
}

blobptr.h

#include "Blob.h"
#include <memory>
#include <vector>
template <typename> class BlobPtr;
template <typename T>
bool operator ==(const BlobPtr<T>& lhs, const BlobPtr<T>& rhs);
template <typename T>
bool operator < (const BlobPtr<T>& lhs, const BlobPtr<T>& rhs);
template<typename T> class BlobPtr
{
    friend bool operator ==<T>
    (const BlobPtr<T>& lhs, const BlobPtr<T>& rhs);
    friend bool operator < <T>
    (const BlobPtr<T>& lhs, const BlobPtr<T>& rhs);
public:
    BlobPtr() : curr(0) { }
    BlobPtr(Blob<T>& a, std::size_t sz = 0) :
        wptr(a.data), curr(sz) { }
    T& operator*() const
    {
        auto p = check(curr, "dereference past end");
        return (*p)[curr];
    }
    // prefix
    BlobPtr& operator++();
    BlobPtr& operator--();
    // postfix
    BlobPtr operator ++(int);
    BlobPtr operator --(int);
private:
    // returns  a shared_ptr to the vector if the check succeeds
    std::shared_ptr<std::vector<T>>
         check(std::size_t, const std::string&) const;
    std::weak_ptr<std::vector<T>> wptr;
    std::size_t curr;
};
// prefix ++
template<typename T>
BlobPtr<T>& BlobPtr<T>::operator ++()
{
    // if curr already points past the end of the container, can't increment it
    check(curr, "increment past end of StrBlob");
    ++curr;
    return *this;
}
// prefix --
template<typename T>
BlobPtr<T>& BlobPtr<T>::operator --()
{
    -- curr;
    check(curr, "decrement past begin of BlobPtr");
    return *this;
}
// postfix ++
template<typename T>
BlobPtr<T> BlobPtr<T>::operator ++(int)
{
    BlobPtr ret = *this;
    ++*this;
    return ret;
}
// postfix --
template<typename T>
BlobPtr<T> BlobPtr<T>::operator --(int)
{
    BlobPtr ret = *this;
    --*this;
    return ret;
}
template<typename T> bool operator==(const BlobPtr<T> &lhs, const BlobPtr<T> &rhs) {
    if (lhs.wptr.lock() != rhs.wptr.lock()) {
		throw runtime_error("ptrs to different Blobs!");
	}
	return lhs.i == rhs.i;
}
template<typename T> bool operator< (const BlobPtr<T> &lhs, const BlobPtr<T> &rhs) {
	if (lhs.wptr.lock() != rhs.wptr.lock()) {
		throw runtime_error("ptrs to different Blobs!");
	}
	return lhs.i < rhs.i;
}

练习题16.13

解释你BlobPtr的相等和关系运算符选择那种类型的友好关系?

选择一对一关系,否则不同类型的实例会错误相等。

练习题16.14

编写Screen模板,用非类型参数定义Screen的高和宽

Screen.h

#include <string>
#include <iostream>
template<unsigned H, unsigned W>
class Screen {
public:
    typedef std::string::size_type pos;
    Screen() = default; // 需要,因为Screen有另一个构造函数
    // 游标被其类内初始化程序初始化为0
    Screen(char c):contents(H * W, c) { }
    char get() const              // 在光标处获取字符
        { return contents[cursor]; }       // 隐式内联
    Screen &move(pos r, pos c);      // 能够被后面内联
    friend std::ostream & operator<< ( std::ostream &os , const Screen<H, W> & c ) 
    {
        unsigned int i, j;
        for( i=0 ;i<c.height; i++ )
        {
                os<<c.contents.substr(0, W)<<std::endl;
        }
        return os;
    }
    friend std::istream & operator>> ( std::istream &is , Screen &  c )
    {
            char a;
            is>>a;
            std::string temp(H*W, a);
            c.contents = temp;
            return is;
    }
private:
    pos cursor = 0;
    pos height = H, width = W;
    std::string contents;
};
template<unsigned H, unsigned W>
inline Screen<H, W>& Screen<H, W>::move(pos r, pos c)
{
    pos row = r * width;
    cursor = row + c;
    return *this;
}

mian.cpp

#include "Screen.h"
#include <iostream>

int main()
{
    Screen<5, 5> scr('c');
    Screen<5, 5> scr2;
    // 将src输出到Screen
    std::cout<<scr;
    // 输入connet到src
    std::cin>>scr2;
    // 测试输入
    std::cout<<scr2;
    return 0;
}

测试:

ccccc
ccccc
ccccc
ccccc
ccccc
a
aaaaa
aaaaa
aaaaa
aaaaa
aaaaa

练习题16.15

编写Screen模板,用非类型参数定义Screen的高和宽

根据14.2.1的章节,<<和<<这个类应该是这个类的友元。

练习题16.16

将StrVec类重写为模板,命名为Vec。

#include <memory>

/**
 *  @brief a vector like class
 */
template<typename T>
class Vec
{
public:
    Vec():element(nullptr), first_free(nullptr), cap(nullptr){ }
    Vec(std::initializer_list<T> l);
    Vec(const Vec& v);

    Vec& operator =(const Vec& rhs);

    ~Vec();

    // memmbers
    void push_back(const T& t);

    std::size_t size() const    { return first_free - element; }
    std::size_t capacity()const { return cap - element; }

    T* begin() const { return element;      }
    T* end()   const { return first_free;   }

    void reserve(std::size_t n);

    void resize(std::size_t n);
    void resize(std::size_t n, const T& t);

private:
    // data members
    T* element;
    T* first_free;
    T* cap;

    std::allocator<T> alloc;

    // utillities
    void reallocate();
    void chk_n_alloc()  { if(size()==capacity()) reallocate(); }
    void free();

    void wy_alloc_n_move(std::size_t n);

    std::pair<T*, T*> alloc_n_copy(T* b, T* e);
};


// copy constructor
template<typename T>
Vec<T>::Vec(const Vec &v)
{
    std::pair<T*, T*> newData = alloc_n_copy(v.begin(), v.end());

    element = newData.first;
    first_free = cap = newData.second;
}
// constructor that takes initializer_list<T>
template<typename T>
Vec<T>::Vec(std::initializer_list<T> l)
{
    // allocate memory as large as l.size()
    T* const newData = alloc.allocate(l.size());

    // copy elements from l to the address allocated
    T* p = newData;
    for(const auto &t : l)
        alloc.construct(p++, t);

    // build data structure
    element = newData;
    first_free = cap = element + l.size();
}
// operator =
template<typename T>
Vec<T>& Vec<T>::operator =(const Vec& rhs)
{
    // allocate and copy first to protect against self_assignment
    std::pair<T*, T*> newData = alloc_n_copy(rhs.begin(), rhs.end());
    // destroy and deallocate
    free();
    // update data structure
    element = newData.first;
    first_free = cap = newData.second;
    return *this;
}
// destructor
template<typename T>
Vec<T>::~Vec()
{
    free();
}
template<typename T>
void Vec<T>::push_back(const T &t)
{
    chk_n_alloc();
    alloc.construct(first_free++, t);
}
template<typename T>
void Vec<T>::reserve(std::size_t n)
{
    // if n too small, just return without doing anything
    if(n <= capacity()) return;
    // allocate new memory and move data from old address to the new one
    wy_alloc_n_move(n);
}
template<typename T>
void Vec<T>::resize(std::size_t n)
{
    resize(n, T());
}
template<typename T>
void Vec<T>::resize(std::size_t n, const T &t)
{
    if(n < size())
    {
        // destroy the range [element+n, first_free) using destructor
        for(auto p = element + n; p != first_free;   )
            alloc.destroy(p++);
        // update first_free to point to the new address
        first_free = element + n;
    }
    else if(n > size())
    {
        for (auto i = size(); i != n; ++i)
            push_back(t);
    }
}
template<typename T>
std::pair<T*, T*>
Vec<T>::alloc_n_copy(T *b, T *e)
{
    // calculate the size needed and allocate space accordingly
    T* data = alloc.allocate(e-b);
    return { data, std::uninitialized_copy(b, e, data) };
    //            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // which copies the range[first, last) to the space to which
    // the starting address data is pointing.
    // This function returns a pointer to one past the last element
}
template<typename T>
void Vec<T>::free()
{
    // if not nullptr
    if(element)
    {
        // destroy it in reverse order.
        for(auto p = first_free; p != element;    )
            alloc.destroy(--p);
        alloc.deallocate(element, capacity());
    }
}
template<typename T>
void Vec<T>::wy_alloc_n_move(std::size_t n)
{
    // allocate as required.
    std::size_t newCapacity = n;
    T* newData = alloc.allocate(newCapacity);

    // move the data from old place to the new one
    T* dest = newData;
    T* old  = element;
    for(std::size_t i = 0; i != size(); ++i)
        alloc.construct(dest++, std::move(*old++));
    free();
    // update data structure
    element     =   newData;
    first_free  =   dest;
    cap         =   element + newCapacity;
}
template<typename T>
void Vec<T>::reallocate()
{
    // calculate the new capacity required
    std::size_t newCapacity = size() ? 2 * size() : 1;
    // allocate and move old data to the new space
    wy_alloc_n_move(newCapacity);
}

练习题16.17

声明为typename的类型参数和声明为class的类型参数有什么不同?什么时候必须使用typename?

P593
当我们希望通知编译器一个名字表示类型时,必须使用关键字typename,而不能使用class。

练习题16.18

解释下面每个函数模板声明并指出它们是否违法。更正你发现的每个错误?

(a)template <typename T, U, typename V> void f1(T, U, V);错误,不能连续声明模板参数 改为:template<typename T,typename U,typename V> void f1(T,U,V);
(b)template<typename T>T f2(int &T);错误,模板参数名不能作为变量名 改为:template<typename T> T f2(T &);
(c)inline template<typename T>T foo(T, unsigned int*);错误,内联声明位置错误 改为:template<typename T> inline T foo(T,unsigned int *);
(d)template<typename T> f4(T,T);错误,缺少返回类型 改为:template<typename> T f4(T,T);
(e)typedef char Ctype; template<typename Ctype>Ctype f5(Ctype a);参考P592,建议修改为:typedef char C;或者是template<typename C> C f5(C a);

练习题16.19

编写函数,接受一个容器的引用,打印容器中的元素。使用容器的size_type和size成员来控制打印元素的循环。

练习题16.20

重写上题,使用begin和end返回的迭代器来控制循环

#include <iostream>
#include <vector>
#include <list>
// ex16.19
template<typename Container>
std::ostream& print(Container const& container, std::ostream& os)
{
    for(typename Container::size_type i = 0; i != container.size(); ++ i)
        os << container[i] << " ";
    return os;
}
// ex16.20
template<typename Container>
std::ostream& print2(Container const& container, std::ostream &os)
{
    for(auto curr = container.cbegin(); curr != container.cend(); ++curr)
        os << *curr << " ";
    return os;
}
int main()
{
    std::vector<int> v = { 1, 23, 6, 4, 5, 7, 4 };
    std::list<std::string> l = { "ss", "sszz", "saaas", "s333s", "ss2"," sss" };
    print2(v, std::cout) << std::endl;
    print2(l, std::cout) << std::endl;

    return 0;
}

不知为何,编译会报错。

练习题16.21

编写你自己的DebugDelete版本

//.h文件
#include<iostream>
using namespace std;
class Debugdelete
{
public:
	Debugdelete(ostream &s = cerr) :os(s) {}//构造函数
	template <typename T> void operator()(T *p) const//const表示该函数不会修改类的成员数据
	{
		os << "deleting..." << endl;//额外信息,我们的删除器可以做用于任何版本类型
		delete p;//接受一个指针作为参数,并且删除该指针
	}
private:
	ostream &os;//私有成员为一个输出流
};
//测试文件
#include <iostream>
#include <vector>
#include <list>
#include <string>
using namespace std;
int main(int argc, char** argv)
{
	double *p = new double;//新分配一个double对象
	Debugdelete d;//创建一个删除器对象
	d(p);//调用定义的模版函数operator (),对p进行释放
	cin.get();
	return 0;
}

测试:

deleting...
1

练习题16.22

修改12.3节中你的TextQuery程序,令sharped_成员使用DebugDelete作为它们的删除器。

unique_ptr<int,Debugdelete> m(new int,Debugdelete());//P418页见表12.4
shared_ptr<int> n(new int,Debugdelete());//P412页见表12.3

练习题16.24

为你的Blob模板添加一个构造函数,它接受两个迭代器

template<typename T> class Blob
{
public:
    typedef T value_type;
    typedef typename std::vector<T>::size_type size_type;

    // constructors
    Blob();
    Blob(std::initializer_list<T> il);

    // 构造函数:它接受两个迭代器
    template<typename It>
    Blob(It b, It e);
    // number of elements in the Blob
    size_type size() const { return data->size(); }
    bool      empty() const{ return data->empty(); }

    void push_back(const T& t) { data->push_back(t); }
    void push_back(T&& t)      { data->push_back(std::move(t)); }
    void pop_back();
    // element access
    T& back();
    T& operator[](size_type i);

    const T& back() const;
    const T& operator [](size_type i) const;
private:
    std::shared_ptr<std::vector<T>> data;
    // throw msg if data[i] isn't valid
    void check(size_type i, const std::string &msg) const;
};
// constructor taking two iterators
template<typename T>    //for class
template<typename It>   //for this member
Blob<T>::Blob(It b, It e) :
    data(std::make_shared<std::vector<T>>(b, e))
{ }

练习题16.25

解释下面这些声明的含义:

(a)extern template class Blob<string>;实例化class声明
(b)template int compare(const int&, const int&);实例化compare函数定义

练习题16.26

假设NoDefault是一个没有默认构造函数的类,我们可以显式实例化vector<NoDefault>吗?请解释。

是不可以的,因为其所有成员函数在实例化过程中都将被实例化。因此Nodefault也需要实例化,没有默认构造函数是不行的

练习题16.27

对下面每条带标签的语句,解释发生了什么样的实例化(如果有的话)。如果一个模板被实例化,解释为什么;如果未实例化,解释为什么没有。

template <typename T> class Stack {};
void f1(Stack<char>);//a.没有实例化,只有在有数据时才会实例化
class Exercise {
	Stack<double> &rsd;//b.没有实例化,引用并不会实例化,因为没有数值存在
	Stack<int> si;//c.实例化出一个Stack<int>的实例
};
int main(int argc, char** argv)
{
	Stack<char> *sc;//d.没有实例化,指针不会实例化,指针包含的是地址
	f1(*sc);//e.实例化出一个Stack<char>的实例,因为函数接收到数据,而且是按值传递
	int iObj = sizeof(Stack<string>);//f.实例化出一个Stack<string>的实例,
	return 0;
}

练习题16.28

对下面每条带标签的语句,解释发生了什么样的实例化(如果有的话)。如果一个模板被实例化,解释为什么;如果未实例化,解释为什么没有。

太长了,可以查看C++ Primer,下面两题类似。

练习题16.31

如果将DebugDelete与unique_ptr一起使用,解释编译器将删除器处理为内联形式的可能方式。

unique_p是保存删除器函数的指针,所以需要一次跳转操作,并不会内联而是跳转。

练习题16.32

在模板实参推断过程中发生了什么?

从函数参数确定模板参数的过程称为模板参数演绎。在模板参数演绎期间,编译器使用调用中的参数类型来查找模板参数,这些模板参数生成与给定调用最匹配的函数版本。

练习题16.33

指出在模板实参推断过程中允许对函数实参进行的两种类型转换。

  • const转换:指向const的引用(或指针)的函数参数可以作为指向非const对象的引用(或指针)传递(4.11.2,第162页)。
  • 数组或函数到指针的转换:如果函数参数不是引用类型,则常规的指针转换将应用于数组或函数类型的参数。数组参数将被转换为指向其第一个元素的指针。类似地,函数参数将被转换为指向函数类型的指针(4.11.2,第161页)。

练习题16.34

对下面的代码解释每个调用是否合法。如果合法,T的类型是什么?如果不合法,为什么?
template int compare(const T&, const T&);

(a)compare("hi", "world");不合法,因为两种类型是不同的,第一种类型是char[3],第二种类型是char[6]。这里的字符串并不是string类型,而是根据长度判断的char [n],若长度不同,则类型也不同,并且非const类型的引用或指针可以转换为const类型。
(b)compare("bye", "dad");合法,类型应该是指向char的指针,即char*。

练习题16.35

下面调用中哪些是错误的?如果调用合法,T的类型是什么?如果调用不合法,问题何在?
template<typename T> int calc(T, int);
template<typenameT> int fcn(T, T);
double d; float f; char c;

(a )calc(c, 'c');合法,第一个为char,即为T,第二个为char [1],可以进行类型转换。
(b )calc(d, f);合法,T为double,clac第二个参数为普通类型的int,可以进行算数类型转换。
(c )fcn(c, 'c');不合法,一个char,一个char[1]。
(d )fcn(d, f);不合法,一个double一个float,无法进行类型转换。

练习题16.36

进行下面的调用会发生什么:
template int f1(T, T);
template<typename T1, typename T2> int f2(T1, T2);
int i = 0, j = 42, *p1 = &i, *p2 = &j;
const int *cp1 = &i, *cp2 = &j;

(a )f1(p1, p2);合法,T为 int*。f1<int*>(int*, int*)
(b )f2(p1, p2);合法,T1和T2都为int*。f2<int*, int*>(int*, int*)
(c )f1(cp1, cp2);合法,顶层const会被忽略掉,T为int*。f1<int const*>(int const*, int const*)
(d )f2(cp1, cp2);合法,T1和T2都为int*。f2<int const*, int const*>(int const*, int const*)
(e )f1(p1, cp1);不合法,首先需要判断实参的类型是否相同,再判断类型是否可转换,两参数一个为const一个非const。deduced conflicting types for parameter 'T'
(f )f2(p1, cp1);合法,T1和T2都为int*。f2<int*, int const*>(int*, int const*)

练习题16.37

标准库max函数有两个参数,它返回实参中的较大者。此函数有一个模板类型参数。你能调用max时传递它一个int和doubule么?如果可以如何做?不可以,为什么?

可以,只提供一个明确的模板参数,如:

int a = 1;
double b = 2;
std::max<double>(a, b);

练习题16.38

当调用make_sharped时,必须提供一个显式模板实参。解释为什么需要显示模板实参以及它是如何使用的。

如果没有指定给定的类型,make_shared就不可能确定它应该分配多大的大小,这就是原因。根据指定的类型,make_shared分配适当大小的内存空间,并返回指向它的适当类型的shared_ptr。

练习题16.39

对16.1.1节中原始版本的compare函数,使用一个显式模板实参,使得可以向函数传递两个字符串的字面常量。

#include <iostream>
template <typename T>
int compare(const T &v1, const T &v2)
{
	if (v1 < v2) return -1;
	if (v2 < v1) return 1;
	return 0;
}
int main()
{
	std::cout << compare<std::string>("test", "train") << "\n";
}

测试:-1

练习题16.40

下面的函数是否合法?解释原因。
template
auto fcn3(It beg, It end) -> decltype(*beg + 0)
{
//处理序列
return *beg;
}

合法。
P604,当我们想指定函数的返回类型时,使用显式模版实参是非常有效的,但是可能会给用户带来额外的负担,可以使用尾置返回类型,decltype(something)来获取该something的类型,something的类型是函数的返回值类型。由于需要decltype推断传入表达式的类型,所以该实参类型需要支持迭代器+的操作(该操作是右结合律,首先要进行迭代器加的操作,再解引用)。
如下所示,只能传递支持此+ 0操作的类型。返回类型取决于操作符+返回的类型。在下面的例子中,返回类型是Bar。

#include <iostream>
#include <vector>
#include <string>
class Bar { };
Bar operator +(Bar lhs, int)
{
	return lhs;
}
template <typename It>
auto fcn3(It beg, It end) -> decltype(*beg + 0)
{
	return *beg;  // 从范围中返回元素的副本
}
int main()
{
	std::vector<Bar> v;
	v.push_back(Bar());
	Bar b = fcn3(v.begin(), v.end());
}

练习题16.41

编写一个新的sum函数,它的返回类型保证足够大,足以容纳加法结果。

#include <iostream>
#include <vector>
#include <string>
template<typename T>
auto sum(T lhs, T rhs) -> decltype(lhs + rhs)
{
	return lhs + rhs;
}
int main()
{
	auto s = sum(123456789123456789, 123456789123456789);
	std::cout << s;

}

测试:246913578246913578

练习题16.42

对下面每个调用,确定T和val的类型:
template void g(T&& val);
int i = 0; const int ci = i;

(a )g(i);因为i是左值,T 为int &,经折叠,val为int &。
(b )g(ci);因为ci是左值,T 为const int & ,经折叠,val为const int &。
(c )g(i * ci);因为i * c是右值,T 为int &&,经折叠,val为int &&i 。

练习题16.43

使用上一题定义的函数,如果调用g(i = ci), g的模板参数将是什么?

(i = ci)返回指向对象i的左值,所以为int &,val上的任何变化都会改变对象i。

练习题16.44

使用第一题中相同的三个调用,如果g的函数参数声明为T(而不是T&&),确定T的类型。如果g的函数参数是const T& 呢?

如果g的函数参数被声明为T(不是T&&)。
g(i); – T被推断成int
g(ci); – T被推断成int, const被忽略。
g(i * ci); – T被推断成int, (i * ci)返回值被复制到T的右值。
如果g的函数参数是const t&。
g(i) – T被推断成int , val : const int&
g(ci) – T被推断成int , val : const int&
g(i * ci) – T被推断成int , val : const int&(see example on page 687)

练习题16.45

给定下面的模板,如果给定对一个像42这样的字面常量调用g,解释会发生什么?如果我们对一个int类型的变量调用g呢?
template <typename T> void g(T&& val) { vector<T> v; }

传递42,为右值,T被解析为右值,即int &&,折叠后,仍未int &&,正常。
传递int 类型变量,为左值,T被解析为int &,折叠后,为int & ,这时将int & 传递给vector会出错,因为该左值没有初始化。

练习题16.46

解释下面的循环,它来自13.5节(P469)中的StrVec::reallocate:
for (size_t i = 0; i != size(); ++i)
{
alloc.construct(dest++, std::move(*elem++));
}

在每次迭代中,解引用运算符*返回一个左值,该左值被std::move更改为右值,因为成员函数构造采用的是右值引用而不是左值引用。

练习题16.47

编写你自己版本的翻转函数,通过调用接受左值和右值的引用参数的函数来测试它。

#include <iostream>
#include <memory>
void func_lvalue(std::string& lhs, std::string& rhs)
{
	lhs = "Test\n";
	rhs = "Train\n";
}
void func_rvalue(int&& lhs, int&& rhs)
{
	// 分配足够的空间
	std::allocator<int> alloc;
	int* data(alloc.allocate(3));
	// 移动到新分配的空间
	alloc.construct(data, lhs);
	alloc.construct(data + 1, 0);
	alloc.construct(data + 2, rhs);
	// 打印
	for (auto p = data; p != data + 3; ++p)
		std::cout << *p << "\n";
	// 销毁和回收
	for (auto p = data + 3; p != data; )
		alloc.destroy(--p);
	alloc.deallocate(data, 3);
}
template<typename F, typename T1, typename T2>
void flip(F f, T1&& t1, T2&& t2)
{
	f(std::forward<T2>(t2), std::forward<T1>(t1));
}
int main()
{
	// 测试左值引用
	std::cout<<"测试左值引用"<<std::endl;
	std::string s1, s2;
	flip(func_lvalue, s1, s2);
	std::cout << s1 << s2;
	// 测试右值引用
	std::cout<<"测试右值引用"<<std::endl;
	flip(func_rvalue, 20, 18);
}

测试:

测试左值引用
Train
Test
测试右值引用
18
0
20

练习题16.48

编写你自己版本的debug_rep函数。

#include <iostream>
#include <memory>
#include <sstream>
// 总是先声明:
template <typename T> std::string debug_rep(const T& t);
template <typename T> std::string debug_rep(T* p);
std::string debug_rep(const std::string &s);
std::string debug_rep(char* p);
std::string debug_rep(const char *p);
// 打印任何我们不需要的类型。
template<typename T> std::string debug_rep(const T& t)
{
    std::ostringstream ret;
    ret << t;
    return ret.str();
}
// 将指针打印为它们的指针值,后跟指针指向的对象
template<typename T> std::string debug_rep(T* p)
{
    std::ostringstream ret;
    ret << "pointer: " << p;
    if(p)
        ret << " " << debug_rep(*p);
    else
        ret << " null pointer";
    return ret.str();
}

// 非模板版本
std::string debug_rep(const std::string &s)
{
    return '"' + s + '"';
}
// 将字符指针转换为字符串,并调用字符串版本的debug_rep
std::string debug_rep(char *p)
{
    return debug_rep(std::string(p));
}
std::string debug_rep(const char *p)
{
    return debug_rep(std::string(p));
}

练习题16.49

解释下面每个调用会发生什么:

练习题16.50

定义上一个练习中的函数,令它们打印一条身份信息。运行该联系中的代码。如果函数调用的行为与你的预期不符,确定你了解的原因。

#include <iostream>
#include <memory>
#include <sstream>
template <typename T> void f(T)
{
	std::cout << "f(T)\n";
}
template <typename T> void f(const T*)
{
	std::cout << "f(const T*)\n";
}
template <typename T> void g(T)
{
	std::cout << "template <typename T> void g(T)\n";
}
template <typename T> void g(T*)
{
	std::cout << "template <typename T> void g(T*)\n";
}
int main()
{
	int i = 42, *p = &i;
	const int ci = 0, *p2 = &ci;
	std::cout << "g(42):"; g(42); std::cout << std::endl;   //template <typename T> void g(T )被调用
	std::cout << "g(p):"; g(p); std::cout << std::endl;     //template <typename T> void g(T*)被调用
	std::cout << "g(ci):"; g(ci); std::cout << std::endl;   //template <typename T> void g(T) 被调用
	std::cout << "g(p2):"; g(p2); std::cout << std::endl;   //template <typename T> void g(T*)被调用
	std::cout << "f(42):"; f(42); std::cout << std::endl;   //f(T)
	std::cout << "f(p):"; f(p); std::cout << std::endl;     //f(T)
	std::cout << "f(ci):"; f(ci); std::cout << std::endl;   //f(T)
	std::cout << "f(p2):"; f(p2); std::cout << std::endl;    //f(const T*)
}

测试:

g(42):template <typename T> void g(T)

g(p):template <typename T> void g(T*)

g(ci):template <typename T> void g(T)

g(p2):template <typename T> void g(T*)

f(42):f(T)

f(p):f(T)

f(ci):f(T)

f(p2):f(const T*)

练习题16.51

调节本节中的每个foo,确定sizeof…(Args)和sizeof…(rest)分别返回什么

练习题16.52

编写程序验证上题的答案

#include <iostream>
template<typename T, typename ...Args>
void foo(T t, Args ...args)
{
	std::cout << sizeof...(Args) << std::endl;
	std::cout << sizeof...(args) << std::endl;
}
int main()
{	
	std::cout << "foo(1, 2)" << std::endl;
	foo(1, 2);
	std::cout << "foo(1, 2, 3, 4, 5)" << std::endl;
	foo(1, 2, 3, 4, 5);
}

测试:

foo(1, 2)
1
1
foo(1, 2, 3, 4, 5)
4
4

练习题16.53

编写你自己版本的print函数,并打印1、2及5个实参类测试它,并且每个实参都有不同的类型。

#include <iostream>
// trivial case
template<typename Printable>
std::ostream& print(std::ostream& os, Printable const& printable)
{
    return os << printable;
}
// recursion
template<typename Printable, typename... Args>
std::ostream& print(std::ostream& os, Printable const& printable, Args const&... rest)
{
    return print(os << printable << ", ", rest...);
}
int main()
{
    print(std::cout, 1) << std::endl;
    print(std::cout, 1, 2.5) << std::endl;
    print(std::cout, 1, 2.5, 3.1, 4, "sss", 42.4242) << std::endl;
    return 0;
}

测试:

1
1, 2.5
1, 2.5, 3.1, 4, sss, 42.4242

练习题16.54

如果我们对一个没有<<运算符的类型调用print会发生什么?、

它没有编译

练习题16.55

如果我们的可变参数版本print的定义之后声明非可变参数版本,解释可变参数会如何执行。

报错,error: no matching function for call to 'print(std::ostream&)'。在其后定义,先前的版本找不到非可变参数版本的print函数,造成无限递归。

练习题16.56

编写你自己版本的errorMsg。

#include <iostream>
#include <memory>
#include <sstream>

// always declare first:
template <typename T>
std::string debug_rep(const T& t);
template <typename T>
std::string debug_rep(T* p);

std::string debug_rep(const std::string &s);
std::string debug_rep(char* p);
std::string debug_rep(const char *p);

// print any type we don't otherwise.
template<typename T>
std::string debug_rep(const T& t)
{
    std::ostringstream ret;
    ret << t;
    return ret.str();
}
// 将指针打印为它们的指针值,后跟指针指向的对象
template<typename T>
std::string debug_rep(T* p)
{
    std::ostringstream ret;
    ret << "pointer: " << p;

    if (p)
        ret << " " << debug_rep(*p);
    else
        ret << " null pointer";

    return ret.str();
}
// 没有模板版本
std::string debug_rep(const std::string &s)
{
    return '"' + s + '"';
}
//将字符指针转换为字符串,并调用字符串版本的debug_rep
std::string debug_rep(char *p)
{
    return debug_rep(std::string(p));
}

std::string debug_rep(const char *p)
{
    return debug_rep(std::string(p));
}

// 函数结束递归并打印最后一个元素
// 此函数必须在定义可变参数打印版本之前声明
template<typename T>
std::ostream& print(std::ostream& os, const T& t)
{
    return os << t;
    //           ^
    // 包中的最后一个元素之后没有分隔符
}

// 除了包中的最后一个元素外,其他所有元素都将调用此版本的print
template<typename T, typename... Args>
std::ostream& print(std::ostream &os, const T &t, const Args&... rest)
{
    // 打印第一个参数
    os << t << ",";
    // 递归调用;打印其他参数
    return print(os, rest...);
}
// call debug_rep on each argument in the call to print
template<typename... Args>
std::ostream& errorMsg(std::ostream& os, const Args... rest)
{
    return print(os, debug_rep(rest)...);
}
int main()
{
    errorMsg(std::cout, 1, 2.5, 31, 5.4, 9.0f, "test", "train");
    return 0;
}

测试:1,2.5,31,5.4,9,"test","train"

练习题16.57

比较你的errorMsg和6.2.6节(P198)页中的errorMsg函数。两种函数优缺点各是什么?

errorMsg将初始值设定项列表作为参数。因此,只有存储在其中的元素必须是相同的,或者至少是可转换的。相比之下,可变参数版本提供了更好的灵活性。

练习题16.58

为你的StrVec类及你为16.1.2节(P591)练习中编写的Vec类添加emplace_back函数。

vec.h

#include <memory>
template<typename T>
class Vec
{
public:
    Vec():element(nullptr), first_free(nullptr), cap(nullptr){ }
    Vec(std::initializer_list<T> l);
    Vec(const Vec& v);
    Vec& operator =(const Vec& rhs);
    ~Vec();
    // memmbers
    void push_back(const T& t);
    template<typename... Args>
    void emplace_back(Args&&...);
    std::size_t size() const    { return first_free - element; }
    std::size_t capacity()const { return cap - element; }
    T* begin() const { return element;      }
    T* end()   const { return first_free;   }
    void reserve(std::size_t n);
    void resize(std::size_t n);
    void resize(std::size_t n, const T& t);
private:
    // data members
    T* element;
    T* first_free;
    T* cap;
    std::allocator<T> alloc;
    // utillities
    void reallocate();
    void chk_n_alloc()  { if(size()==capacity()) reallocate(); }
    void free();
    void wy_alloc_n_move(std::size_t n);
    std::pair<T*, T*> alloc_n_copy(T* b, T* e);
};
// copy constructor
template<typename T>
Vec<T>::Vec(const Vec &v)
{
    std::pair<T*, T*> newData = alloc_n_copy(v.begin(), v.end());
    element = newData.first;
    first_free = cap = newData.second;
}
// constructor that takes initializer_list<T>
template<typename T>
Vec<T>::Vec(std::initializer_list<T> l)
{
    // allocate memory as large as l.size()
    T* const newData = alloc.allocate(l.size());
    // copy elements from l to the address allocated
    T* p = newData;
    for(const auto &t : l)
        alloc.construct(p++, t);
    // build data structure
    element = newData;
    first_free = cap = element + l.size();
}
// operator =
template<typename T>
Vec<T>& Vec<T>::operator =(const Vec& rhs)
{
    // allocate and copy first to protect against self_assignment
    std::pair<T*, T*> newData = alloc_n_copy(rhs.begin(), rhs.end());
    // destroy and deallocate
    free();
    // update data structure
    element = newData.first;
    first_free = cap = newData.second;
    return *this;
}
// destructor
template<typename T>
Vec<T>::~Vec()
{
    free();
}
template<typename T>
void Vec<T>::push_back(const T &t)
{
    chk_n_alloc();
    alloc.construct(first_free++, t);
}
template<typename T>        //for the class  template
template<typename... Args>  //for the member template
inline void
Vec<T>::emplace_back(Args&&...args)
{
    chk_n_alloc();
    alloc.construct(first_free++, std::forward<Args>(args)...);
}
template<typename T>
void Vec<T>::reserve(std::size_t n)
{
    // if n too small, just return without doing anything
    if(n <= capacity()) return;

    // allocate new memory and move data from old address to the new one
    wy_alloc_n_move(n);
}
template<typename T>
void Vec<T>::resize(std::size_t n)
{
    resize(n, T());
}
template<typename T>
void Vec<T>::resize(std::size_t n, const T &t)
{
    if(n < size())
    {
        // destroy the range [element+n, first_free) using destructor
        for(auto p = element + n; p != first_free;   )
            alloc.destroy(p++);
        // update first_free to point to the new address
        first_free = element + n;
    }
    else if(n > size())
    {
        for (auto i = size(); i != n; ++i)
            push_back(t);
    }
}
template<typename T>
std::pair<T*, T*>
Vec<T>::alloc_n_copy(T *b, T *e)
{
    // calculate the size needed and allocate space accordingly
    T* data = alloc.allocate(e-b);
    return { data, std::uninitialized_copy(b, e, data) };
    //            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    // which copies the range[first, last) to the space to which
    // the starting address data is pointing.
    // This function returns a pointer to one past the last element
}

template<typename T>
void Vec<T>::free()
{
    // if not nullptr
    if(element)
    {
        // destroy it in reverse order.
        for(auto p = first_free; p != element;    )
            alloc.destroy(--p);

        alloc.deallocate(element, capacity());
    }
}
template<typename T>
void Vec<T>::wy_alloc_n_move(std::size_t n)
{
    // allocate as required.
    std::size_t newCapacity = n;
    T* newData = alloc.allocate(newCapacity);
    // move the data from old place to the new one
    T* dest = newData;
    T* old  = element;
    for(std::size_t i = 0; i != size(); ++i)
        alloc.construct(dest++, std::move(*old++));
    free();
    // update data structure
    element     =   newData;
    first_free  =   dest;
    cap         =   element + newCapacity;
}
template<typename T>
void Vec<T>::reallocate()
{
    // calculate the new capacity required
    std::size_t newCapacity = size() ? 2 * size() : 1;

    // allocate and move old data to the new space
    wy_alloc_n_move(newCapacity);
}

strvec.h

#include <string>
class StrVec
{
public:
    // Big 3/5.
    StrVec():
        element(nullptr), first_free(nullptr), cap(nullptr)
    { }
    StrVec(std::initializer_list<std::string> l);
    StrVec(const StrVec& s);
    StrVec&
    operator =(const StrVec& rhs);
    ~StrVec();
    // public members
    void push_back(const std::string &s);
    // a variadic member template using its argumenst to construct
    // an element directly in space managed by the constainer
    template<typename... Args>
    void emplace_back(Args&&...);
    std::size_t size() const        { return first_free - element; }
    std::size_t capacity() const    { return cap - element; }
    std::string* begin() const      { return element; }
    std::string* end() const        { return first_free; }
    // preallocate enough memory for specified number of elements
    void reserve(std::size_t n);
    // resize as required.
    void resize(std::size_t n);
    void resize(std::size_t n, const std::string& s);
private:
    // data members
    std::string* element;       //  pointer to the first element
    std::string* first_free;    //  pointer to the first free element
    std::string* cap;           //  pointer to one past the end
    std::allocator<std::string> alloc;
    // utilities for Big 3/5
    void reallocate();
    void chk_n_alloc()      { if (size() == capacity()) reallocate(); }
    void free();
    // utilities added
    // used in reallocate() reserve() and resize().
    void wy_alloc_n_move(std::size_t n);
    std::pair<std::string*, std::string*>
    alloc_n_copy (std::string* b, std::string* e);
};
// call the constructors of the type to construct this element
// and push it back.
template<typename... Args>
inline void
StrVec::emplace_back(Args&&... args)
{
    // reallocate if necessary
    chk_n_alloc();
    alloc.construct(first_free++, std::forward<Args>(args)...);
}

练习题16.59

假设s是一个string,解释调用sevc.emplace_black(s)会发生什么。

s作为参数被转发

练习题16.60

解释make_shared(12.1.1节,P401)如何工作的。

make_shared shoudl是一个可变模板函数,它将所有参数转发给底层构造函数,底层构造函数在动态内存中分配和初始化对象,最后通过包装原始指针构建shared_ptr。

练习题16.61

定义你自己版本的make_shared

#include <iostream>
#include <memory>
#include <string>

namespace ch16 //to differ from std::make_shared
{
	template <typename T, typename ... Args>
	auto make_shared(Args&&... args) -> std::shared_ptr<T>
	{
		return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
	}
}
struct Foo
{
	explicit Foo(int b) : bar(b) { }
	int bar;
};
int main()
{
	auto num = ch16::make_shared<int>(20);
	std::cout << *num << std::endl;
	auto str = ch16::make_shared<std::string>(8, 'a');
	std::cout << *str << std::endl;
	auto foo = ch16::make_shared<Foo>(78);
	std::cout << foo->bar << std::endl;
	return 0;
}

测试:

20
aaaaaaaa
78

练习题16.62

定义你自己版本的hash<Salse_data>,并定义一个Salse_data的unordered_multiset。将多条交易记录保存到容器中,并打印其内容。

Salse_data.h

#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
#include <iostream>
// unchanged from ch14 except for added friend declaration for hash.
class Sales_data {
friend std::hash<Sales_data>;
friend std::ostream &operator<<
                         (std::ostream&, const Sales_data&);
friend std::istream &operator>>(std::istream&, Sales_data&);
friend bool operator==(const Sales_data &, const Sales_data &);

friend std::ostream &print(std::ostream&, const Sales_data&);
friend std::istream &read(std::istream&, Sales_data&);
public:
	// constructors
	Sales_data() = default;
	Sales_data(const std::string &s): bookNo(s) { }
	Sales_data(const std::string &s, unsigned n, double p):
	           bookNo(s), units_sold(n), revenue(p*n) { }
	Sales_data(std::istream &);

	std::string isbn() const { return bookNo; }
	Sales_data& operator+=(const Sales_data&);
private:
	double avg_price() const;  
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
};
// non-member Sales_data operations
inline
bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)
{ return lhs.isbn() < rhs.isbn(); }
inline
bool operator==(const Sales_data &lhs, const Sales_data &rhs)
{
	return lhs.isbn() == rhs.isbn() && 
	       lhs.units_sold == rhs.units_sold && 
	       lhs.revenue == rhs.revenue;
}
inline
bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
	return !(lhs == rhs);
}
// old versions
Sales_data add(const Sales_data&, const Sales_data&);
std::ostream &print(std::ostream&, const Sales_data&);
std::istream &read(std::istream&, Sales_data&);
// new operator functions
Sales_data operator+(const Sales_data&, const Sales_data&);
std::ostream &operator<<(std::ostream&, const Sales_data&);
std::istream &operator>>(std::istream&, Sales_data&);
// specialize std::hash
// note : template specialization should be put in the header!
namespace std {
template<>
struct hash<Sales_data>
{
    typedef size_t result_type;
    typedef Sales_data argument_type;
    size_t  operator()(const Sales_data& s) const
    {
        return hash<string>()(s.bookNo) ^
                hash<unsigned>()(s.units_sold) ^
                hash<double>()(s.revenue);
    }
};
}   //std

#endif

Sales_data.cc

#include "Sales_data.h"
#include <string>
using std::istream;
using std::ostream;
Sales_data::Sales_data(istream &is) 
{
	is >> *this; // read a transaction from is into this object
}
double Sales_data::avg_price() const
{
	if (units_sold)
		return revenue/units_sold;
	else
		return 0;
}
// member binary operator: left-hand operand is bound to the implicit this pointer
// assumes that both objects refer to the same book
Sales_data& Sales_data::operator+=(const Sales_data &rhs)
{
	units_sold += rhs.units_sold;
	revenue += rhs.revenue;
	return *this;
}
// assumes that both objects refer to the same book
Sales_data 
operator+(const Sales_data &lhs, const Sales_data &rhs)
{
	Sales_data sum = lhs;  // copy data members from lhs into sum
	sum += rhs;            // add rhs into sum
	return sum;
}
istream &operator>>(istream &is, Sales_data &item)
{
	double price;  // no need to initialize; we'll read into price before we use it
	is >> item.bookNo >> item.units_sold >> price;
	if (is)        // check that the inputs succeeded
    	item.revenue = item.units_sold * price;
	else
    	item = Sales_data(); // input failed: give the object the default state
	return is;
}
ostream &operator<<(ostream &os, const Sales_data &item)
{
	os << item.isbn() << " " << item.units_sold << " " 
	   << item.revenue << " " << item.avg_price();
	return os;
}
// operators replace these original named functions
istream &read(istream &is, Sales_data &item)
{
	double price = 0;
	is >> item.bookNo >> item.units_sold >> price;
	item.revenue = price * item.units_sold;
	return is;
}
ostream &print(ostream &os, const Sales_data &item)
{
	os << item.isbn() << " " << item.units_sold << " " 
	   << item.revenue << " " << item.avg_price();
	return os;
}
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{
	Sales_data sum = lhs;  // copy data members from lhs into sum
	sum += rhs;            // add rhs into sum
	return sum;
}

mian.cpp

#include <iostream>
#include <memory>
#include <unordered_set>
#include "Sales_data.h"

int main()
{
    // test for ex16.62
    std::unordered_multiset<Sales_data> mset;
    Sales_data sd("Bible", 10, 0.98);

    mset.emplace(sd);
    mset.emplace("C++ Primer", 5, 9.99);

    for(const auto &item : mset)
        std::cout << "the hash code of " << item.isbn()
                  <<":\n0x" << std::hex << std::hash<Sales_data>()(item)
                  << "\n";

    return 0;
}

测试:

the hash code of Bible:
0x510aa3808419421
the hash code of C++ Primer:
0xfabe44c826d056e7

练习题16.63

定义一个函数模板,统计一个给定值在一个vector中出现的次数。测试你的函数,分别传递给它一个double的vector,一个int的vector以及string的vector。

练习题16.64

未上一题中的模板编写特列化版本来处理vector<const char*>。编写。

#include <iostream>
#include <vector>
#include <cstring>
// 模板
template<typename T>
std::size_t  count(std::vector<T> const& vec, T value)
{
	auto count = 0u;
	for (auto const& elem : vec)
		if (value == elem) ++count;
	return count;
}
// 模板特例化
template<>
std::size_t count(std::vector<const char*> const& vec, const char* value)
{
	auto count = 0u;
	for (auto const& elem : vec)
		if (0 == strcmp(value, elem)) ++count;
	return count;
}
int main()
{
	// 练习题16.63
	std::vector<double> vd = { 1.1, 3.8, 2.3, 4,3.8 };
	std::cout << count(vd, 3.8) << std::endl;
	// 练习题16.64
	std::vector<const char*> vcc = { "test", "train", "test", "train", "test" };
	std::cout << count(vcc, "test") << std::endl;
	return 0;
}

测试:

2
3

练习题16.65

在16.3节(P617)中定义了两个重载的debug_rep版本,一个接受const char参数,一个接受char参数,将这两个函数重写成特例化版本。

#include <iostream>
#include <vector>
#include <cstring>
#include <sstream>
// 模板
template <typename T>
std::string debug_rep(T* t);
// 模板特例化 T=const char*  ,  char*  respectively.
template<>
std::string debug_rep(const char* str);
template<>
std::string debug_rep(      char *str);
int main()
{
    char p[] = "train";
    std::cout << debug_rep(p) << "\n";
    return 0;
}
template <typename T>
std::string debug_rep(T* t)
{
    std::ostringstream ret;
    ret << t;
    return ret.str();
}
// 模板特例化
// T = const char*
template<>
std::string debug_rep(const char* str)
{
    std::string ret(str);
    return str;
}
// 模板特例化
// T =       char*
template<>
std::string debug_rep(      char *str)
{
    std::string ret(str);
    return ret;
}

测试:train

练习题16.66

重载debug_rep函数与特例化它相比,有何优点和缺点?

重载会改变函数匹配。

练习题16.67

定义特例化版本会影像debug_rep的函数匹配吗?解释原因。

不会改变的。特例化的模板匹配优先度和模板级别一致。

发布了76 篇原创文章 · 获赞 44 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_24739717/article/details/104607547