《C++ Primer》第16章 16.4节习题答案

《C++ Primer》第16章 模板与泛型编程

16.4节可变参数模板 习题答案

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

【出题思路】

理解可变参数模板。

【解答】

对4个调用,sizeof…(Args)和sizeof…(rest)分别返回:

3  3
2  2
1  1
0  0

练习16.52:编写一个程序验证你对上一题的答案。

【出题思路】

理解可变参数模板。

【解答】

#include <iostream>
#include <string>

using std::cout;
using std::endl;
using std::string;

template <typename T, typename... Args>
void foo(const T &t, const Args&... rest)
{
    cout << sizeof...(Args) << "  ";//模板类型参数的数目
    cout << sizeof...(rest) << endl;//函数参数的数目
}

int main()
{
    int i = 0;
    double d = 3.14;
    string s = "hello world";
    foo(i, s, 42, d);   //包中有三个参数
    foo(s, 42, "hi");   //包中有两个参数
    foo(d,s);           //包中有一个参数
    foo("hi");          //空包
    return 0;
}

运行结果:

练习16.53:编写你自己版本的print函数,并打印一个、两个及五个实参来测试它,要打印的每个实参都应有不同的类型。

【出题思路】

参考书中本节内容编写即可。

【解答】

#include <iostream>
#include <string>

using namespace std;

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

//包中除了最后一个元素之外的其他元素都会调用这个版本的print
template<typename T, typename... Args>
ostream &print(ostream &os, const T &t, const Args&... rest)//扩展Args
{
    os << t << ", ";//打印第一个实参
    return print(os, rest...);//扩展rest,递归打印其他参数
}

int main()
{
    int i = 0;
    string s = "Hello";
    print(cout, i);
    print(cout, i, s);
    print(cout, i, s, 42.1, 'A', "End");
    return 0;
}

运行结果:

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

【出题思路】

理解模板对类型参数的要求。

【解答】

由于print要求函数参数类型支持<<运算符,因此会产生编译错误。

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

【出题思路】

理解模板对类型参数的要求。

【解答】

将非可变参数版本放在可变参数版本之后,也属于“定义可变参数版本时,非可变参数版本声明不在作用域中”的情况。因此,可变参数版本将陷入无限递归。注意,这里的无限递归并不是运行时的无限递归调用,而是发生在编译时递归的包扩展。例如,调用print(cout, i, s, 42),正常的包扩展过程是:

print(cout, s, 42);
print(cout, 42);

最后一步与非可变参数版本匹配。但当非可变参数版本不在作用域中时,还会继续扩展为:

print(cout);

这就无法与任何模板匹配了,从而产生编译错误。

练习16.56:编写并测试可变参数版本的errorMsg。

【出题思路】

理解并练习包扩展。

【解答】

参考书中本节内容编写即可。需要注意的是,print要求参数类型支持<<运算符,因此需为Sales_data定义<<运算符:

ostream &operator<<(ostream &os, const Sales_data &item)
{
	os << item.isbn() << " " << item.units_sold << " "
	   << item.revenue << " " << item.avg_price();
	return os;
}

练习16.57:比较你的可变参数版本的errorMsg和6.2.6节(第198页)中的error_msg函数。两种方法的优点和缺点各是什么?

【出题思路】

理解可变参数模板。

【解答】

相对于6.2.6节的版本,本节的版本不要求参数具有相同类型。当不知道实参数目也不知道它们的类型时,本节的版本非常有用。而6.2.6节的版本只能适用于相同类型的多个参数的情形,对另一种类型,就要为其编写新的版本,编程工作量大。当然,本节的版本较为复杂,需要更多模板相关的知识来确保代码正确,例如复杂的扩展模式。

练习16.58:为你的StrVec类及你为16.1.2节(第591页)中练习编写的Vec类添加emplace_back函数。

【出题思路】

本题练习转发参数包。

【解答】

参照书中本节内容编写即可。为主程序添加如下测试代码:

cout << "emplace " << svec.size() << endl;
svec.emplace_back("End");
svec.emplace_back(3, '!');
print(svec);

练习16.59:假定s是一个string,解释调用svec.emplace_back(s)会发生什么。

【出题思路】

本题要求理解参数包转发过程。

【解答】

由于s是一个左值,经过包扩展,它将以如下形式传递给construct。

std::forward<string>(s);

forward<string>的结果类型是string&,因此,construct将得到一个左值引用实参,它继续将此参数传递给string的拷贝构造函数来创建新元素。

练习16.60:解释make_shared(参见12.1.1节,第401页)是如何工作的。

【出题思路】

本题要求理解参数包转发过程。

【解答】

make_shared的工作过程类似emplace_back。它接受参数包,经过扩展,转发给new,作为vector的初始化参数。

练习16.61:定义你自己版本的make_shared。

【出题思路】

本题练习定义参数包转发。

【解答】

参考emplace_back的设计,可以很容易地编写出make_shared:

template <typename T, class... Args>
SP<T> make_SP(Args&&... args)
{
	return SP<T>(new T(std::forward<Args>(args)...));
}

在练习16.30的主程序的基础上增加如下测试代码即可:

//测试make_SP
vector<string> vs = {"Hello", "World", "!"};
Blob<string> b3(vs.begin(), vs.end());
cout << b3.size() << endl;
for(size_t i = 0; i < b3.size(); ++i)
    cout << b3.at(i) << " ";
cout << endl << endl;

string as[3] = {"This", "is", "end"};
Blob<string> b4(as, as + 3);
cout << b4.size() << endl;
for(size_t i = 0; i < b4.size(); ++i)
cout << b4.at(i) << " ";
cout << endl << endl;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值