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

《C++ Primer》第16章 16.2节习题答案,第1张

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

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

16.2节习题答案

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

【出题思路】

理解模板实参推断。

【解答】

对一个函数模板,当我们调用它时,编译器会利用调用中的函数实参来推断其模板参数,这些模板实参实例化出的版本与我们的函数调用应该是最匹配的版本,这个过程就称为模板实参推断。

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

【出题思路】

理解模板实参推断过程中的类型转换。

【解答】

在模板实参推断过程中,如果函数形参的类型使用了模板类型参数,则只允许进行两种类型转换。1.const转换:可以将一个非const对象的引用(或指针)传递给一个const对象(或指针)形参。2.数组或函数到指针的转换:如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转换。一个数组实参可以转换为一个指向其首元素的指针。类似的,一个函数实参可以转换为一个该函数类型的指针。

练习16.34:对下面的代码解释每个调用是否合法。如果合法,T的类型是什么?如果不合法,为什么?

template  int compare(const T&, const T&);
(a) compare("hi", "world");
(b) compare("bye", "dad");

【出题思路】

理解模板实参推断过程中的类型转换。

【解答】

在模板实参推断过程中,允许数组到指针的转换。但是,如果形参是一个引用,则数组不会转换为一个指针。因此,两个调用都是非法的。

练习16.35:下面调用中哪些是错误的(如果有的话)?如果调用合法,T的类型是什么?如果调用不合法,问题何在?

template  T calc(T, int);
template  T fcn(T, T);
double d; float f; char c;
(a) calc(c, 'c');             (b) calc(d, f);
(c) fcn(c, 'c');              (d) fcn(d, f);

【出题思路】

理解有普通类型形参情况下的类型转换。

【解答】

(a)调用合法。因为calc的第二个参数是int类型,所以可以进行正常的类型转换,将char转换为int。而T被推断为char。

(b)调用合法。同(a),对f进行正常的类型转换,将float转换为int,T被推断为double。

(c)调用合法。c和'c'都是char类型,T被推断为char。(d)调用非法。d为double类型,f为float类型,T无法推断为适配的类型。

注意,上述“合法”的判定都是基于模板实参满足函数对类型的需求的前提下做出的。否则,调用仍然是非法的。例如,如果calc中对第一个参数进行了成员访问 *** 作a.x++,即要求T是类类型,且包含成员x,x的类型支持++运算符,此时前两个调用就是非法的。

练习16.36:进行下面的调用会发生什么:

template  f1(T, T);
template  f2(T1, T2);
const int *cp1 = &i, *cp2 = &j;
(a) f1(p1, p2);        (b) f2(p1, p2);         (c) f1(cp1, cp2);
(d) f2(cp1, cp2);      (e) f1(p1, cp1);        (f) f2(p1, cp1);

【出题思路】

理解函数参数类型涉及多模板参数情况下的类型转换。

【解答】

(a)调用合法。T被推断为int*。

(b)调用合法。T1和T2都被推断为int *。(c)调用合法。T被推断为const int *。

(d)调用合法。T1和T2都被推断为const int *。

(e)调用非法。T被推断为int *或是const int *都不能匹配调用。

(f)调用合法。T1被推断为int *,T2被推断为const int *。

练习16.37:标准库max函数有两个参数,它返回实参中的较大者。此函数有一个模板类型参数。你能在调用max时传递给它一个int和一个double吗?如果可以,如何做?如果不可以,为什么?【出题思路】

理解显式指定模板实参。

【解答】

可以用一个int和一个double调用max,显式指定模板实参即可:

auto m = max(1, 2.0);

练习16.38:当我们调用make_shared(参见12.1.1节,第401页)时,必须提供一个显式模板实参。解释为什么需要显式模板实参以及它是如何使用的。

【出题思路】

理解显式指定模板实参。

【解答】

在调用make_shared时,有时不给出参数,表示进行值初始化。有时给出的参数与维护的动态对象的类型不直接相关,如make_shared(10,'9')创建值为"9999999999"的string。因此,编译器无法从参数推断模板实参,就需要显式指定模板实参。

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

【出题思路】

本题练习显式指定模板实参。

【解答】

compare("Hello", "World");

练习16.40:下面的函数是否合法?如果不合法,为什么?如果合法,对可以传递的实参类型有什么限制(如果有的话)?返回类型是什么?

template 
auto fcn3(It beg, It end) -> decltype(*beg + 0)
{
    //处理序列
    return *beg;//返回序列中一个元素的拷贝 
}

【出题思路】

熟悉尾置返回类型。

【解答】

函数是合法的,但用decltype(*beg + 0)作为尾置返回类型,导致两个问题:

1.序列元素类型必须支持+运算符。

2.*beg + 0是右值,因此fcn3的返回类型被推断为元素类型的常量引用。

练习16.41:编写一个新的sum版本,它的返回类型保证足够大,足以容纳加法结果。

【出题思路】

本题练习定义尾置返回类型。

【解答】

令sum有两个模板参数,分别表示两个加法运算对象的类型,当然它们应该是相容的类型。在设计尾置返回类型时,首先计算sum的两个函数参数的和,然后对它们应用decltype来获得足以容纳和的返回类型。

#include 

using namespace std;

template 
auto sum(T1 a, T2 b)->decltype(a + b)
{
    return a + b;
}

int main()
{
    auto a = sum(1, 1);
    cout << a << " " << sizeof(a) << endl;
    auto b = sum(1, 1.1);
    cout << b << " " << sizeof(b) << endl;
    auto c = sum(1, 1.1f);
    cout << c << " " << sizeof(c) << endl;
    return 0;
}

运行结果:

练习16.42:对下面每个调用,确定T和val的类型: 

template  void g(T&& val);
int i = 0; const int ci = i;
(a) g(i);        (b) g(ci);        (c) g(i * ci);

【出题思路】

理解函数模板的函数参数是右值引用时,如何进行类型推断。

【解答】

(a)T为int &,val的类型为int &。原因是,当实参为一个左值时,编译器推断T为实参的左值引用类型,而非左值类型。而int& &&在引用折叠规则的作用下,被折叠为int&。

(b)T为const int &,val的类型为const int &。原因同上一题。

(c)T为int,val的类型为int &&。原因是,实参是一个右值,编译器就推断T为该右值的类型,因此val的类型就是右值类型的右值引用。

一个有意思的问题是,对此题,如何验证你的答案?类似书中本节的例子,我们可以在g中声明类型为T的局部变量v,将val赋值给它。然后打印v和val的地址,即可判断T为int &还是int。还可通过对v赋值来判断T是否为const的。

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

【出题思路】

深入理解类型推断。

【解答】

注意,这里是g(i = ci),而不是g(i == ci)。因此,实参不是一个右值,而是一个左值——i = ci返回的是左值i(你可以尝试打印i和i = ci的左值来验证)。因此,最终T被推断为int &,val经过引用折叠被确定为int &。

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

【出题思路】

理解函数参数是左值引用时的类型推断。

【解答】

当g的函数参数声明为T时,表明参数传递是传值的,三个调用情况如下:

(a)T为int,val的类型为int。

(b)T为int,val的类型为int。

(c)T为int,val的类型为int。

当g的函数参数声明为const T&时,表明可以传递给它任何类型的实参,而T的类型推断结果也不会是const的,因此,三个调用情况如下:

(a)T为int,val的类型为const int &。

(b)T为int,val的类型为const int &。

(c)T为int,val的类型为const int &。

练习16.46:解释下面的循环,它来自13.5节(第469页)中的StrVec::reallocate:

template  void g(T&& val) { vector v; }

 【出题思路】

理解std::move的工作原理。

【解答】

此循环将elem开始的内存中的string对象移到dest开始的内存中。每个循环步中,调用construct在新内存空间中创建对象。若第二个参数是一个左值,则进行拷贝动作。但在上面的代码中,用std::move将一个左值转换为右值引用,这样,construct会调用string的移动构造函数将数据从旧内存空间移动而不是拷贝到新的内存空间中,避免了不必要的数据拷贝 *** 作。

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

【出题思路】

本题练习转发的设计。

【解答】

参考书中本节内容编写,与配套网站中的代码进行对照即可。完整程序如下所示。读者要特别注意flip(g, i, 42),可尝试改为flip1或flip2,编译程序,观察现象。

#include 
#include 

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

//模板接受一个可调用对象和两个参数,将两个参数“翻转”后用来调用给定的可调用对象
template 
void flip(F f, T1 && t1, T2 &&t2)
{
    f(std::forward(t2), std::forward(t1));
}

void f(int v1, int &v2)//注意,v2是一个引用
{
    cout << v1 << " " << ++v2 << endl;
}

void g(int &&i, int& j)
{
    cout << i << " " << j << endl;
}

//flip1实现不完整:顶层const和引用都丢掉了
template 
void flip1(F f, T1 t1, T2 t2)
{
    f(t2, t1);
}

template 
void flip2(F f, T1 &&t1, T2 &&t2)
{
    f(t2, t1);
}

int main()
{
    int i = 0, j = 0, k = 0, l = 0;
    cout << i << " " << j << " " << k << " " << l << endl;

    f(42, i);//f改变春实参i
    flip1(f, j, 42);//通过flip1调用f不会改变j
    flip2(f, k, 42);//正确:k被改变了
    g(1, i);
    flip(g, i, 42);//正确:第三个参数的右值属性被保留了
    cout << i << " " << j << " " << k << " " << l << endl;
    return 0;
}

运行结果:

 

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/zaji/4948823.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-11-13
下一篇2022-11-13

发表评论

登录后才能评论

评论列表(0条)

    保存