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;