16.1
知识点:当我们调用一个模板函数时,即向一个模板传递实参,编译器用此函数实参来推断模板实参,并将该模板实参(即实参的类型)绑定到模板参数(即T)。
实例化:编译器用模板实参代替对应的模板参数来创建出一个新“实例”。譬如用int代替T,创建出一个新函数实例。
16.2
template <typename T>
bool cmp(const T &a, const T &b)
{
return a < b;
}
int main()
{
cout << boolalpha << cmp('a', 'b') << " " << cmp(3, 2) << endl;
return 0;
}
16.4
#include <iostream>
#include <vector>
#include <list>
using namespace std;
template <typename T, typename V>
bool find1(const T &beg, const T &end, const V &val)
{
T it = beg;
while (it != end) {
if (*it == val)
return true;
++it;
}
return false;
}
int main()
{
vector<int> vec{2, 1, 6, 4, 8, 7};
list<string> lst{"pine", "cake", "fine", "kzw"};
bool bl1 = find1(vec.begin(), vec.end(), 6);
bool bl2 = find1(lst.begin(), lst.end(), "tiple");
cout << boolalpha << bl1 << endl << bl2 << endl;;
return 0;
}
16.5
template<typename T, size_t N>
void print(const T (&arr)[N])
{
for (auto elem : arr)
cout << elem << endl;
}
int main()
{
int a[] = {2, 3, 4, 5, 6};
char c[] = {'h', 'e', 'l', 'l', 'o'};
char c1[] = "hello";
print(a);
print(c); //非类型参数N的值为5
print(c1); //非类型参数N的值为6
return 0;
}
16.6
一个模板,它接受各种类型的数组,返回相应类型的指针。
template<typename T, size_t N>
const T* begin1(const T (&arr)[N])
{
return arr;
}
template<typename T, size_t N>
const T* end1(const T (&arr)[N])
{
return arr + N - 1;
}
16.7
//way1
template<typename T, size_t N> constexpr
size_t size1(const T (&arr)[N])
{
return N;
}
//way2
template<typename T> constexpr
size_t size2(const T &arr)
{
return sizeof(arr) / sizeof(arr[0]);
}
16.8
有些类型并未定义<运算符,而大多数类型都定义了!=运算符(其中所有的标准库容器都有提供==与!=运算符)
16.9
知识点:一个模板就是一个创建类或函数的蓝图或者说公式。
函数模板:一个公式,可用来生成针对特定类型的函数版本
类模板:一个用来生成类的蓝图
16.10
当一个类模板被实例化时,编译器会重写类模板,将模板参数T的每个实例替换为给定的模板实参,生成一个类型确定的类
16.11
将List类和ListItem类定义为友元
friend class ListItem<elemType>;
16.12
template <typename T>
class Blob {
public:
//其他部分与书中相同
const T& back() const;
const T& operator[](size_type) const;
};
template <typename T>
const T& Blob<T>::back() const
{
check(0, "back on empty Blob");
return data->back();
}
template <typename T>
const T& Blob<T>::operator[](size_type i) const
{
check(i, "subscript out of range");
return (*data)[i];
}
16.13
友好关系:限定在用相同类型实例化
16.14
template <unsigned H, unsigned W>
class Screen {
public:
Screen() = default;
private:
unsigned height = H;
unsigned weight = W;
};
int main()
{
Screen<2, 3> s;
return 0;
}
16.15
template <unsigned H, unsigned W>
class Screen {
public:
Screen() = default;
template <unsigned HH, unsigned WW>
friend istream& operator>>(istream &is, Screen<HH, WW> &scr); //友元
template <unsigned HH, unsigned WW>
friend ostream& operator<<(ostream &os, const Screen<HH, WW> &scr); //友元
private:
unsigned height = H;
unsigned weight = W;
};
template <unsigned HH, unsigned WW>
istream& operator>>(istream &is, Screen<HH, WW> &scr)
{
//语句
}
template <unsigned HH, unsigned WW>
friend ostream& operator<<(ostream &os, const Screen<HH, WW> &scr)
{
//语句
}
int main()
{
Screen<2, 3> s;
Screen<5, 6> s2;
cin >> s >> s2;
cout << s << endl;
return 0;
}
友元的必要性:访问类的私有成员,并且因为该类型是模板,所以传入该类型作为形参时,相应的输入输出运算符也要作为函数模板。
16.16
template <typename T>
class Vec {
public:
Vec(): elements(nullptr), first_free(nullptr), cap(nullptr) { }
Vec(const Vec&);
Vec &operator=(const Vec&);
~Vec();
void push_back(const T&);
size_t size() const { return first_free - elements; }
size_t capacity() const { return cap - elements; }
T* begin() const { return elements; }
T* end() const { return first_free; }
private:
T *elements;
T *first_free;
T *cap;
static allocator<T> alloc;
void chk_n_alloc(){ if (size() == capacity()) reallocate(); }
pair<T*, T*> alloc_n_copy(const T*, const T*);
void free();
void reallocate();
};
16.17
没有不同。
我们希望使用一个模板类型参数的类型成员,就必须显式告诉编译器改名字是一个类型,而只能通过关键字typename来实现。
如,模板类型参数为T,我们在模板中有语句:T::size_type s,我们的本意是用域运算符访问T的类型成员size_type,然而C++默认域运算符访问的名字是static数据成员,故我们要达到目的,需定义如下语句:typename T::size_type s
16.19
#include <iostream>
#include <vector>
#include <list>
using namespace std;
template <typename T>
void print(T &c)
{
for (typename T::size_type i = 0; i != c.size(); ++i)
cout << c[i] << endl;
}
int main()
{
vector<int> vec{0, 1, 2, 3};
print(vec);
return 0;
}
16.20
template <typename T>
void print(T &c)
{
for (auto it = c.begin(); it != c.end(); ++it)
cout << *it << endl;
}
16.21
class DebugDelete {
public:
template <typename T>
void operator()(const T *p) const
{
cout << "释放动态内存\n";
delete p;
}
};
int main()
{
string *ps = new string("kzw");
DebugDelete d;
d(ps); //等价于delete ps;
return 0;
}
16.22
shared_ptr<int> p(new int, DebugDelete()); //shared_ptr<T> p(new T, DebugDelete());
16.23
显然会在最后一个指向该对象的shared_ptr被销毁时调用
16.24
template <typename T>
class Blob {
public:
template <typename TT>
Blob(const TT&, const TT&);
};
template <typename T>
template <typename TT>
Blob<T>::Blob(const TT &b, const TT &e): data(make_shared<vector<T>>(b, e)) { }
16.25
实例化声明,表明在程序其他位置有该实例化的一个非extern声明(定义),即本文件中不要对vector<string>类进行实例化
实例化定义,表明该文件将包含vector<string>类的定义
16.26
不可以,我们显式实例化一个类模板时,会实例化该模板的所有成员。于是NoDefault也会被实例化,但它没有默认构造函数,故实例化失败。
16.27
a:没有实例化,只有在有数据时才会实例化
b:没有实例化,引用并不会实例化,因为没有数值存在
c:实例化出一个Stack<int>的实例
d:没有实例化,指针不会实例化,指针包含的是地址
e:实例化出一个Stack<char>的实例,因为函数接收到数据,而且是按值传递
f:实例化出一个Stack<string>的实例
16.28
class DebugDelete {
public:
template <typename T>
void operator()(const T *p) const
{
cout << "释放动态内存\n";
delete p;
}
};
int main()
{
unique_ptr<int, DebugDelete> p1(new int,DebugDelete());
shared_ptr<int> p2(new int, DebugDelete()); //shared_ptr<T> p(new T, DebugDelete());
return 0;
}
16.31
删除器类型是unique_ptr类型的一部分,因此删除器成员的类型在编译时是知道的,从而删除器可以直接保存在unique_ptr对象中,而定义在类内的方法默认是内联的。
16.32
模板实参推断过程:从函数实参来确定模板实参的过程,编译器使用函数调用中的实参类型来寻找模板实参,用这些模板实参生成的函数版本与给定的函数调用最为匹配
16.33
将实参传递给带模板类型的函数形参时,能够自动应用的类型转换只有const转换及数组或函数到指针的转换。
const转换:顶层const会被忽略;可以将一个非const对象的引用(或指针)传递给一个const的引用(或指针)形参
数组或函数指针转换:函数形参不是引用类型,一个数组实参可以转换为一个指向其首元素的指针,而一个函数实参可以转换为一个该函数类型的指针
16.34
实参类型是const char*,而形参类型这是引用类型,故不合法
这里的字符串的类型是根据长度判断的char [n],若长度不同,则类型也不同
(a):不合法,两个实参类型不同,分别为char [3]、char [6](非const类型的引用或指针可以转换为const类型)
(b):合法,两个实参类型相同,都是char [4]
16.35
(a)、(b)、(c)都合法
(d):不合法,fcn只有一个类型的形参,故传递给该函数的实参必须具有相同的类型
16.36
(a):f1(int*, int*)
(b):f2(int*, int*)
(c):f1(const int*, const int*)
(d):f2(const int*, const int*)
(e):f1(const int*, const int*) 不合法,两个实参的类型不同(先判断类型是否相同,再考虑类型转换)
(f ):f2(int*, const int*)
16.37
可以,只要显示指定模板类型参数,则函数实参就能进行正常的类型转换
template <typename T>
T Max(const T &a, const T &b)
{
return a >= b ? a : b;
}
int main()
{
auto c = Max(2, 3.0); //错误
auto i = Max<int>(2, 3.0); //3.0将转换为3
return 0;
}
16.38
这样才能将内置指针转换为智能指针
16.39
显示指定模板类型,字符串字面常量可以是string,也可以是const char*
template <typename T>
int compare(const T &a, const T &b)
{
if (a > b) return 1;
else if (a < b) return -1;
return 0;
}
int main()
{
auto c = compare("kzw", "ly"); //[Error] no matching function for call to 'compare(const char [4], const char [3])'
auto i = compare<string>("kzw", "ly");
auto d = compare<const char *>("kzw", "ly"); //与目的相悖,比的是地址
return 0;
}
16.40
合法,但是实参类型指向的对象必须定义了+运算符,并且可以与整数0相加(譬如string类型就不可以)
decltype(*beg + 0) <====> decltype((*beg) + 0)
16.41
template <typename T>
auto Sum(const T &a, const T &b) -> decltype(a + b)
{
return a + b;
}
16.42
(a):T的类型是int&,val的类型是int& &&
(b):T的类型是const int&,val的类型是const int& &&
(c):T的类型是int(i * ci为右值),val的类型是int &&
16.43
模板参数T是int&
16.44
函数参数声明为T:
(a):T的类型是int
(b):T的类型是const int T的类型为int,因为函数参数声明不是T&,而是值传递,故const将被忽略
(c):T的类型是int
函数参数声明为const T&:
(a):T的类型是int
(b):T的类型是int
(c):T的类型是int,一个const &参数可以绑定到一个右值
16.45
对一个字面常量调用g:如字面常量是int类型,则模板参数T是int
对一个int类型的变量调用g:T为int&
16.46
将从长度为size()的(string的)vector中,从elem指向的string开始,逐个移动拷贝到dest指向的容器中
std::move(*elem):将elem指向的string移动到某一位置
16.47
void f(int &&i, int &j)
{
cout << i << " " << j << endl;
}
template <typename F, typename T1, typename T2>
void flip(F f, T1 &&t1, T2 &&t2)
{
f(std::forward<T2>(t2), std::forward<T1>(t1));
}
16.48
template <typename T>
string debug_rep(const T &s)
{
ostringstream ret;
ret << t;
return ret.str();
}
template <typename T>
string debug_rep(T *p)
{
ostringstream ret;
ret << "pointer: " << p << " " << debug_rep(*p);
return ret.str();
}
string debug_rep(const string &s)
{
return '"' + s + '"';
}
string debug_rep(char *p)
{
return debug_rep(string(p));
}
string debug_rep(const char *p)
{
return debug_rep(string(p));
}
16.49
f(p):调用f(T),T相应的变为int*
16.50
#include <iostream>
using namespace std;
template<typename T> void f(T t) {
cout << "f(T): " << t << endl;
}
template<typename T> void f(const T *t) {
cout << "f(const T *): " << *t << endl;
}
template<typename T> void g(T t) {
cout << "g(T): " << t << endl;
}
template<typename T> void g(T *t) {
cout << "g(T *): " << *t << endl;
}
int main()
{
int i = 42, *p = &i;
const int ci = 0, *p2 = &ci;
g(42); //g(T)
g(p); //g(T*)
g(ci); //g(T)
g(p2); //g(T*)
f(42); //f(T)
f(p); //f(T),p为指针,匹配f(T t),T为int*
f(ci); //f(T)
f(p2); //f(const T*)
return 0;
}
16.52
#include <iostream>
using namespace std;
template<typename T, typename...Args>
void foo(const T &t, const Args &...rest)
{
cout << sizeof...(Args) << endl;
cout << sizeof...(rest) << endl;
}
int main()
{
foo(2, 9.0, "kzw", 33);
foo("ly", 33, 6.6);
foo(4.3, 7);
foo("520");
return 0;
}
16.53
#include <iostream>
using namespace std;
template <typename T>
ostream& print(ostream &os, const T &t)
{
os << t;
return os;
}
template <typename T, typename...Args>
ostream& print(ostream &os, const T &t, const Args &...rest)
{
os << t << "\t";
return print(os, rest...);
}
int main()
{
print(cout, 2, "kzw", 520, 13.14);
return 0;
}
16.54
报错:[Error] cannot bind 'std::ostream {aka std::basic_ostream<char>}' lvalue to 'std::basic_ostream<char>&&'
16.55
在执行到函数参数包为空时,将无限递归调用自身
16.56
#include <iostream>
#include <sstream>
using namespace std;
template <typename T>
ostream& print(ostream &os, const T &t)
{
os << t;
return os;
}
template <typename T, typename... Args>
ostream& print(ostream &os, const T &t, const Args&... rest)
{
os << t << "\t";
return print(os, rest...);
}
template <typename T>
string debug_rep(const T &s)
{
ostringstream ret;
ret << s;
return ret.str();
}
template <typename T>
string debug_rep(T *p)
{
ostringstream ret;
ret << "pointer: " << p << " " << debug_rep(*p);
return ret.str();
}
string debug_rep(const string &s)
{
return '"' + s + '"';
}
string debug_rep(char *p)
{
return debug_rep(string(p));
}
string debug_rep(const char *p)
{
return debug_rep(string(p));
}
template <typename... Args>
ostream& errorMsg(ostream &os, const Args&... rest)
{
return print(os, debug_rep(rest)...);
}
int main()
{
errorMsg(cout, "ss", 2, "kzw");
return 0;
}
16.57
error_msg只可接受相同类型的实参
16.58
template <typename... Args>
void StrVec::emplace_back(Args&&... args)
{
chk_n_alloc();
alloc.construct(first_free++, std::forward<Args>(args)...);
}
16.59
在construct调用中的模式会扩展为std::forward<string&>(s)
16.60
可接受可变参数模板,转发其参数初始化一个内存于内存空间,返回一个shared_ptr
16.62