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的函数匹配吗?解释原因。
不会改变的。特例化的模板匹配优先度和模板级别一致。