当我们调用一个函数模板的时候,编译器用函数实参来为我们推断模板实参。
然后编译器利用推断出的模板参数来为我们创建出模板的一个新的“实例”
16.2 编写并测试你自己版本的compare函数template<typename T>
int compare(const T& v1, const T& v2)
{
if (less<T>()(v1, v2)) return -1;
if (less<T>()(v2, v1)) return 1;
}
16.3 对两个Sales_data对象调用你的compare函数,观察编译器在实例化过程中如何处理错误
错误 C2676 二进制“<”:“const _Ty”不定义该运算符或到预定义运算符可接收的类型的转换
16.4 编写行为类似标准库find算法的模板。函数需要两个模板类型参数,一个表示函数的迭代器参数,另一个表示值得类型。
使用你的函数在一个vector和一个list中查找给定值
template<typename InputIterator, typename T>
InputIterator my_find(InputIterator first, InputIterator last, const T& val)
{
while (first != last)
{
if (*first == val)
return first;
++first;
}
return last;
}
16.5 为6.2.4节(195页)中的print函数编写模板版本,它接受一个数组的引用,能处理任意大小,任意元素类型的数组
template<typename T>
void print(const T& arr) //arr是一个引用 绑定const T类型对象
{
for (const auto elem : arr)
cout << elem << endl;
}
16.6 你认为接受一个数组实参的标准库函数begin和end是如何工作的?定义自己的begin和end
标准库begin和end应该是返回数组首元素指针和尾元素指针
template<typename T, unsigned N>
T* my_begin(T (&arr)[N]) // 形参arr是一个引用绑定一个个数为N,元素类型为T 的数组
{
return arr;
}
template<typename T,unsigned N>
T* my_end(T (&arr)[N])
{
return arr + N;
}
16.7 编写一个constexpr模板返回给定数组大小
template<typename T, unsigned N>
size_t get_size(const T(&arr)[N])
{
return N;
}
16.8 C++程序员喜欢用 != 而不喜欢<。请解释这个习惯的原因
因为通常来说==运算符被定义,所以!=也会被定义,而 < 在大多数情况下没有被类型定义。
一个函数模板就是一个公式,可用来生成针对特定类型的函数版本。
类模板是用来生成类的蓝图的,**与函数模板不同之处是,编译器不能为类模板推断参数类型,**如我们多次看到的那样,为了使用类模板,我们必须在模板名后的尖括号中提供额外信息。
一个类模板的每个实例都形成一个独立的类
16.11 下面List的定义时错误的。应该如何修正它?
用传入的模板实参替换掉模板参数列表的形参
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* ptr, elemType value);
private:
ListItem* front, * end;
};
错误处在List类内使用ListItem没有给定模板实参,应修改为:
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.13 解释你为BlobPtr的相等和关系运算符选择哪种类型友好关系
每个BlobPtr实例将访问权限授予用相同那个类型实例化的BlobPtr和相等运算符。
即选择一对一友好关系
16.17 声明为typename 的类型参数和声明为class的类型参数有什么不同?什么时候必须使用typename?-
typename和class声明类型参数没有区别
-
当在模板类中通知编译器一个名字表示类型而不是一个static数据成员时,要使用typename,而不能用class
template<typename T> typename T::value_type top(cont T&c); //表明valuetype是T的一个类型成员而不是一个static数据成员。
更正你发现的每个错误。
答:
(a)template void f1(T, U, V);
(b)template T f2(int &T);
(c)template T foo(T, unsigned int *);
(d)template T f(T, T);
(e)typedef char Ctype;//被覆盖掉
template Ctype f5(Ctype a);
16.22 修改12.3节中TextQuery程序,令shared_ptr成员使用DebugDelete作为它们的删除器。
TextQuery.h
#pragma once
#ifndef TEXTQUERY_H
#define TEXTQUERY_H
#include
#include
#include
#include
#include
#include
#include
#include
#include"StrBlob.h"
using std::cin; using std::cout; using std::endl;
using std::string; using std::vector; using std::map; using std::set;
using std::istringstream; using std::ifstream; using std::ofstream; using std::ostream;
using std::shared_ptr; using std::make_shared;
class DebugDelete {
public:
DebugDelete(ostream& s = std::cerr) :os(s) {}
//与普通的函数模板相同,T的类型由编译器推断,成员模板
template<typename T> void operator()(T* p) const
{
os << "deleting unique_ptr" << endl;
delete p;
}
private:
ostream& os;
private:
};
class QueryResult;
class TextQuery {
public:
using line_no = vector<string>::size_type;
TextQuery(ifstream& infile);
//查询某个单词是否出现过
QueryResult query(const string& sought) const;
private:
//存储文本的每一行,在动态内存创建一个默认初始化的vector对象
shared_ptr<vector<string>> file;
//存储单词以及【出现的行号列表的指针】(有序,不可重复)
map<string, shared_ptr<set<line_no>>> dic;
};
//存放查询结果的类
class QueryResult {
friend ostream& print(ostream& os, const QueryResult& q);
public:
using line_no = vector<string>::size_type;
QueryResult(string s, shared_ptr<set<line_no>> l, shared_ptr<vector<string>> f) :
sought(s), lines(l), file(f) {}
//返回查询结果第一次出现和最后一次出现的行号的迭代器
set<line_no>::iterator begin() { return lines->begin(); }
set<line_no>::iterator end() { return lines->end(); }
shared_ptr<vector<string>> get_file() { return file; }
private:
//查询的单词
string sought;
//指向存放行号的set的智能指针
shared_ptr<set<line_no>> lines;
//存储每一行输入文件
shared_ptr<vector<string>> file;
};
#endif
TextQuery.cpp
#include "TextQuery.h"
//这个构造函数初始值列表让智能指针指向一个合法的动态内存
TextQuery::TextQuery(ifstream& infile):file(new vector<string>)
{
string line, word;
//读取每一行
while (getline(infile,line))
{
file->push_back(line); //存储每一行的内容,从下标0存起,但对应行号为1
int n = file->size() - 1;//当前行号,从0开始
istringstream is(line);
while (is >> word)
{
//如果单词不在dic中,以其为关键字添加一项
shared_ptr<set<line_no>> &lines = dic[word];
if (!lines) //第一次遇到单词时,指针lines为空
lines.reset(new set<line_no>, DebugDelete()); //16.22本题修改
lines->insert(n);
}
}
}
QueryResult TextQuery::query(const string& sought) const
{
//如果未找到给定单词sought,我们返回一个指向空set的指针
//局部static对象,不管经过几次,只初始化一次,声明周期知道程序结束
static shared_ptr<set<line_no>> nodata(new set<line_no>, DebugDelete());
map<string, shared_ptr<set<line_no>>>::const_iterator loc = dic.find(sought);
if (loc == dic.end()) // 没找到
{
return QueryResult(sought, nodata, file);
}
else
return QueryResult(sought, loc->second, file);
}
string make_plural(size_t ctr, const string& word, const string& ending)
{
return (ctr > 1) ? word + ending : word;
}
ostream& print(ostream& os, const QueryResult &q)
{
os << q.sought << " occurs " << q.lines->size() << " "
<< make_plural(q.lines->size(), "time", "s")<<endl;
for (vector<string>::size_type l : *q.lines)
os << "\t" << "(line " << l+1 << " ) " << (*q.file)[l] << endl;
return os;
}
16.25 解释下面这些声明的含义
(a)extern template class vector<string>;
(b)template class vector<Sales_data>;
(a) 显示的进行实例化声明,声明模板类vector的string实例,不会再本文件中生成实例化代码,将一个实例化声明为extern就表示承诺在程序其他位置有该实例化的一个非extern声明(定义)
(b) 定义模板类vector的Sales_data实例,并且编译器会为它生成代码。
。
vector
吗?如果不可以为什么?
我认为不可以,因为创建vector对象可能要用对应类型的默认构造函数。
如:
vector<NoDefault> vec(10);
16.27 对下面每条带标签语句,解释发生了什么样的实例化(如果有的话),如果一个模板被实例化,解释为什么,如果没有,解释为什么?
#include
#include
using std::string;
template<typename T>class Stack {};
void f1(Stack<char>); //a
class Exercise {
Stack<double>& rsd; //b
Stack<int> si; //c
};
int main()
{
Stack<char>* sc; //d
f1(*sc); //e
int iObj = sizeof(Stack<string>); //f
return 0;
}
a没有实例化,因为只有在运行的时候模板才会被实例化
b没有实例化,因为是引用
c进行了实例化
d没有进行实例化,因为是指针
e进行了函数调用要用到Stack对象,实例化出一个Stack实例
f会用string替换模板形参来实例化对象,实例化出一个Stack实例。
在模板实参推断过程中,编译器使用函数调用中实参类型来推断出模板的实参并可能发生两种类型转换。
可能进行了const转换:可以将非const对象的引用(或指针)转换为const对象的引用或指针
或者进行了数组或函数指针转换:数组实参转换为指向数组首元素的指针,一个函数实参可以转换为该函数类型的指针
16.34 对下面的代码解释每个调用是否合法。如果合法,T的类型是什么?如果不合法,为什么?
template<class T> int compare(const T&, const T&);
(a) compare("hi", "world");
(b) compare("bye", "dad");
(a)调用不合法,"hi"的类型为char[3],而"world"的类型推断为 char[6]不统一
(b)调用合法,“bye” 和"dad"推断出类型均为char[4]
16.35 下面哪些调用是错误的?如果调用合法T类型是什么?如果不合法,问题何在template<typename T> T calc(T, int);
template<typename T> T fcn(T, T);
void test02()
{
double d; float f; char c;
calc(c, 'c'); //T为char
calc(d, f); //T为double,普通函数实参可以用正常类型转换,
fcn(c, 'c');//T为char
fcn(d, f); //不合法,实参d推断出double,实参f推断出float
}
16.36 进行下面调用会发生什么
template<typename T>T f1(T, T);
template<typename T1, typename T2>T1 f2(T1, T2);
void test03()
{
int i = 0, j = 42, * p1 = &i, * p2 = &j;
const int* cp1 = &i, * cp2 = &j;
f1(p1, p2); //T为int *
f2(p1, p2); //T1为int *,T2 为int*
f1(cp1, cp2);//T为const int *
f2(cp1, cp2);//T1和T2都为const int *
f1(p1, cp1); //p1推出int *,cp1推出const int *,不相同,和模板形参列表不一致,出错
f2(p2, cp2); //p2推出int*, cp2推出 const int *
}
16.37 标准库max函数有两个参数,它返回实参中较大者,此函数有一个模板类型参数。你能在调用时传给它一个int和一个double吗?如果可以,如何做?如果不可以,为什么?
可以,提供显示的模板实参
int a = 1; double b = 2.2;
max<double>(a ,b);
16.38 当我们调用make_shared
时,必须提供一个显示模板实参。解释为什么需要显示模板实参以及它是如何使用的。
因为make_shared是一个模板类,提供显式模板实参才能够告诉编译器要分配并创建何种类型的对象。
其返回指向创建对象的shared_ptr。
否则make_shared无法推断要分配多大内存空间
16.39 对578页中原始版本的compare函数使用一个显示模板实参,使得可以像函数传递两个字符串字面常量compare<string>("hello", "dji");
16.40 下面的函数是否合法?如果不合法,为什么?如果合法,对可以传递的实参类型有什么限制?返回类型是什么?
template<typename It>
auto fcn3(It beg, It end) -> decltype(*beg + 0)
{
return *beg;
}
合法,传递的实参类型要求是相同类型的指针或迭代器类型,并且指向的元素要支持+ *** 作,返回值是指针指向的元素类型(非引用)
16.42 对下面每个调用确定T和val的类型template<typename T> void g(T&& val);
void p16_42()
{
int i = 0; const int ci = i;
g(i); //T是int &, val是int & (int& &&折叠为int&)
g(ci);//T是const int&(const int类型的引用), val是 const int &
g(i * ci); //T是int , val是 int &&
}
16.43 使用上一题定义的函数,如果我们调用g(i = ci)
g的模板参数将是什么?
i = ci 返回的是左值,故g的模板参数为int &
16.44 使用与16.42中相同的三个调用,如果g函数参数声明为T(而不是T&&),确定T的类型。如果g的函数参数是const T&呢?
如果g函数参数声明为T,三个调用T的类型都是int, val都为int
如果g函数参数是const T&
,三个调用中T的类型都是int, val都为 const int&
template <typename T> void g(T&& val)
{
vector<T> v;
}
当使用字面常量42调用g时,模板参数T推断为int,val为 int &&,函数体正常创建一个vector对象。
当使用int变量调用g时,推断出T为int&, val为int &(进行了折叠),而无法创建vector
StrVec::rellocate:
for(size_t i = 0;i != size(); ++i)
alloc.construct(dest++, std::move(*elem++));
在每个循环中,对elem解引用 *** 作中会返回一个左值,std::move
函数将该左值转换为右值提供给construct函数
template<typename T>void f(T)
{
cout << "void f(T)" << endl;
}
template<typename T>void f(const T*)
{
cout << "void f(const T*)" << endl;
}
template<typename T>void g(T)
{
cout << "void g(T)" << endl;
}
template<typename T>void g(T*)
{
cout << "void g(T*)" << endl;
}
void p16_49()
{
int i = 42, * p = &i;
const int ci = 0, * p2 = &ci;
g(42); //调用g(T)实例化,模板参数推断出T为int
g(p); //调用g(T*)实例化根据实参推断出T为int
g(ci); //调用g(T)实例化,推断出T为int
g(p2); //调用g(T*)实例化,推断出T为const int
f(42); //精确匹配f(T),T推断为int
f(p); //匹配f(T),T推断为int*,因为如果匹配f(const T*)还要发生非const指针到const指针的转换
f(ci); //匹配f(T),T推断为int,忽略顶层const
f(p2); //精确匹配f(const T*),T推断为int
}
16.51 调用本节中的每个foo,确定sizeof…(Args)和sizeof…(rest)分别返回什么?
//Args是一个模板参数包;rest是一个函数参数包
//Args表示0个或多个模板类型参数
//rest表示0个或多个函数参数
template<typename T, typename...Args>
void foo(const T& t, const Args&... rest)
{
//查看模板参数包中的模板参数个数
cout << sizeof...(Args) << endl;
cout << sizeof...(rest) << endl;
}
void p15_51()
{
int i = 0; double d = 3.14; string s = "how now brown cow";
foo(i, s, 42, d); // 3 3
foo(s, 42, "hi"); //2 2
foo(d, s); //1 1
foo("hi");//0 0
}
16.52 编写你自己版本的print函数,并打印一个、两个及五个实参来测试它,要打印的每个实参都应该有不同的类型
//用来终止递归并打印最后一个元素的函数
//此函数必须在可变参数版本的print定义之前声明
template<typename T>
ostream& print(ostream& os, const T& t)
{
return os << t; //包中最后一个元素之后不打印分隔符
}
//包中除了最后一个元素之外其他元素都会调用该版本print
template<typename T, typename...Args>
ostream& print(ostream& os, const T& t, Args...rest)
{
os << t << ", "; //打印第一个实参
return print(os, rest...); //递归调用打印其他实参
}
void p16_53()
{
int i = 22;
string s = "hello";
print(cout, i) << endl;
print(cout, i, s, 42) << endl;
print(cout, i, s, 42, 22.2, 'a', "ca") << endl;
}
16.54 如果我们对一个没有<<运算符的类型调用print,会发生什么
编译阶段就报错
16.55 如果我们的可变参数版本print定义之后声明非可变参数版本,解释可变参数的版本如何执行最后函数参数包rest里参数个数为0时,仍调用print(ostream&, const T&, Args…rest),此时只有一个参数,第二个形参没有实参传递。
两种方法优缺点是什么?
errorMsg优点在于可以接受任何类型参数,只要其定义了<<运算符
而error_msg只能接受string类型对象或可以转换为string类型的对象,
errorMsg缺点是递归耗费内存
16.59 假定s是一个string,解释调用svec.emplace_back(s)会发生什么由于s是一个string左值,因此Args中的内容为一个类型,为string&,与&&折叠后 为string&
由chk_n_alloc()确定空间足够后,通过std::forward
来保持原类型转发一个左值类型传给
alloc.construct构造一个string对象。
make_shared
是如何工作的
make_shared(args)是一个可变模板参数,它将参数包转发然后构造一个对象,返回指向此对象的shared_ptr
template<typename T, typenmae...Args>
auto make_shared(Args&&...args)->std::shared_ptr<T>
{
return std::shared_ptr<T>(new T(std::forward<Args>(args)...))
}
16.62 定义你自己版本的hashSales_data.h
#pragma once
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include
#include
using std::ostream;
using std::istream;
using std::string;
using std::cin;
using std::cout;
using std::cerr;
using std::endl;
class Sales_data; //必须要提前声明,告诉编译器,后面有出现Sales_data是合法的。
//可以类比cin返回的类型理解
istream& read(istream& is, Sales_data& item);
ostream& print(ostream& os, const Sales_data& item);
Sales_data add(const Sales_data& lhs, const Sales_data& rhs);
bool operator==(const Sales_data& lhs, const Sales_data& rhs);
template <class T> class std::hash;//友元声明所需要的外部声明
class Sales_data {
//声明友元函数,这里声明了之后,外面最好还是要保留一下初始声明
//友元函数可以访问类中所有级别的成员
friend class std::hash<Sales_data>;
friend istream& read(istream& is, Sales_data& item);
friend ostream& print(ostream& os, const Sales_data& item);
friend bool operator==(const Sales_data& lhs, const Sales_data& rhs);
friend Sales_data add(const Sales_data& lhs, const Sales_data& rhs);
public:
//Sales_data() = default;
Sales_data(const string& s, unsigned n, double p) :
bookNo(s), units_sold(n), revenue(p* n) {}
//定义了一个默认构造函数(因为此时三个成员变量都有了默认初始值),令其与只接受一个string实参的构造函数功能相同
Sales_data(string s = ""):Sales_data(s,0,0) {}
//Sales_data(const string& s) :bookNo(s) {}
Sales_data(istream& is):Sales_data()
{
//编译器跑到Sales_data.cpp文件中找函数的实现(定义)
read(is, *this); //从键盘上输入的数据输入到调用该函数的对象中
}
string isbn() const;
Sales_data& combine(const Sales_data& rhs);
private:
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
inline
double avg_price() const;
};
#endif
Sales_data.cpp
#include "Sales_data.h" // 通过这个包含就带有一系列的#include、using std::....;
string Sales_data::isbn() const
{
return bookNo;
}
inline //内联函数,可以在调用点处展开,直接将调用替换为结果值
double Sales_data::avg_price() const
{
return units_sold ? revenue / units_sold : 0;
}
Sales_data& Sales_data::combine(const Sales_data& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
Sales_data add(const Sales_data& lhs, const Sales_data& rhs)
{
Sales_data sum = lhs;//对类对象的拷贝,实际上是将lhs成员变量bookNo、units_sold、revenue拷贝到sum的对应成员上
sum.combine(rhs);
return sum;
}
//可以类比cin返回的类型理解这里read返回类型
istream& read(istream& is, Sales_data& item)
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = item.units_sold * price;
return is;
}
ostream& print(ostream& os, const Sales_data& item)
{
os << item.isbn() << " " << item.units_sold << " "
<< item.revenue << " " << item.avg_price();//尽量不要在这里换行
return os;
}
bool operator==(const Sales_data& lhs, const Sales_data& rhs)
{
return lhs.isbn() == rhs.isbn() &&
lhs.units_sold == rhs.units_sold &&
lhs.revenue == rhs.units_sold;
}
测试代码:
#include"Sales_data.h"
#include
#include
#include
#include
using std::ostringstream;
using std::vector;
using std::count;
using std::unordered_multiset;
//打开std命名空间,以便特例化std::hash
namespace std {
template <> //我们正在定义一个特例化版本,模板参数为Sales_data
struct hash<Sales_data>
{
//用来散列一个无序容器的类型必须定义下列类型
typedef size_t result_type;
typedef Sales_data argument_type; //默认情况下需要==
size_t operator()(const Sales_data& s) const;
};
size_t hash<Sales_data>::operator()(const Sales_data& s)const
{
return hash<string>()(s.bookNo) ^
hash<unsigned>()(s.units_sold) ^
hash<double>()(s.revenue);
}
}
void p16_62()
{
unordered_multiset<Sales_data> SDset;
SDset.insert(Sales_data("0-201-2938", 4, 5));
SDset.insert(Sales_data("0-202-28832", 8, 3.2));
cout << std::hex << std::hash<string>()("002-22") << endl;
cout << std::hex << std::hash<unsigned>()(1) << endl;
cout << std::hex << std::hash<double>()(100) << endl;
for (const Sales_data& item : SDset)
{
print(cout, item) << endl;
}
}
16.63 定义一个函数模板,统计一个给定值在一个vector中出现次数。测试你的函数分别传递给它一个double、int、string的vector。
并且编写特例化版本处理vector 不会影响,特例化的模板匹配优先级和模板实例化的匹配优先级一致 欢迎分享,转载请注明来源:内存溢出
16.65 在16.3节中我们定义了两个重载的debug_rep版本,一个接受const char *参数,另一个接受char *参数,将这两个函数重写为特例化
template<typename T>
int occurs(const vector<T>& c, T t)
{
size_t count = 0;
auto iter = c.begin();
do {
iter = std::find(iter, vt.end(), t);
if (iter != c.end())
{
++count;
++iter;
}
} while (iter != c.end());
return count;
}
size_t occurs(const vector<string>& c, const char* p)
{
size_t count = 0;
auto iter = c.begin();
string s(p);
do {
//在[iter,c.end()) 区间查找s,返回找到的位置的迭代器
iter = std::find(iter, c.end(), s);
if (iter != c.end())
{
++count;
++iter;
}
} while (iter != c.end());
return count;
}
//函数模板只能完全特例化
template<>
int occurs(const vector<const char*>& c, const char* t)
{
size_t count = 0;
auto iter = c.begin();
do {
iter = std::find(iter, c.end(), t);
if (iter != c.end())
{
++count;
++iter;
}
} while (iter != c.end());
return count;
}
void p16_63()
{
vector<int> ivec = { 1, 2, 3, 5, 5, 9, 2, 1 };
vector<double> dvec = { 3.14, 2.98, 3.14, 2.99, 9.13, 3.14 };
vector<string> svec = { "apple", "banana", "orange", "grape", "banana", "peach" };
vector<const char*> cvec = { "a", "bb", "ccc", "dddd" };
cout << occurs(ivec, 1) << endl;
cout << occurs(dvec, 1.1) << endl;
cout << occurs(svec, "banana") << endl;
cout << occurs(cvec, "b") << endl;
}
16.66 重载 debug_rep函数与特例化它相比,有何优点和缺点
template<>
string debug_rep(char* p)
{
return debug_rep(string(p));
}
template<>
string debug_rep(const char* p)
{
return debug_rep(string(p));
}
16.67 定义特例化版本会影响debug_rep的函数匹配吗?如果不影响,为什么?
评论列表(0条)