16.1
知识点:当我们调用一个模板函数时,即向一个模板传递实参,编译器用此函数实参来推断模板实参,并将该模板实参(即实参的类型)绑定到模板参数(即T)。
实例化:编译器用模板实参代替对应的模板参数来创建出一个新“实例”。譬如用int代替T,创建出一个新函数实例。
16.2
templatebool 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#include #include using namespace std;template
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 vec{2, 1, 6, 4, 8, 7}; list 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
templatevoid 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
一个模板,它接受各种类型的数组,返回相应类型的指针。
templateconst T* begin1(const T (&arr)[N]){ return arr;} template const T* end1(const T (&arr)[N]){ return arr + N - 1;}
16.7
//way1 templateconstexpr size_t size1(const T (&arr)[N]){ return N;}//way2 template constexprsize_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
templateclass Blob {public: //其他部分与书中相同 const T& back() const; const T& operator[](size_type) const;};template const T& Blob ::back() const { check(0, "back on empty Blob"); return data->back();}template const T& Blob ::operator[](size_type i) const{ check(i, "subscript out of range"); return (*data)[i];}
16.13
友好关系:限定在用相同类型实例化
16.14
templateclass Screen {public: Screen() = default;private: unsigned height = H; unsigned weight = W;};int main(){ Screen<2, 3> s; return 0; }
16.15
templateclass Screen {public: Screen() = default; template friend istream& operator>>(istream &is, Screen &scr); //友元 template friend ostream& operator<<(ostream &os, const Screen &scr); //友元private: unsigned height = H; unsigned weight = W;};template istream& operator>>(istream &is, Screen &scr){ //语句 }template friend ostream& operator<<(ostream &os, const Screen &scr){ //语句 }int main(){ Screen<2, 3> s; Screen<5, 6> s2; cin >> s >> s2; cout << s << endl; return 0; }
友元的必要性:访问类的私有成员,并且因为该类型是模板,所以传入该类型作为形参时,相应的输入输出运算符也要作为函数模板。
16.16
templateclass 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 alloc; void chk_n_alloc(){ if (size() == capacity()) reallocate(); } pair 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#include #include using namespace std;template
void print(T &c){ for (typename T::size_type i = 0; i != c.size(); ++i) cout << c[i] << endl;}int main(){ vector vec{0, 1, 2, 3}; print(vec); return 0; }
16.20
templatevoid print(T &c){ for (auto it = c.begin(); it != c.end(); ++it) cout << *it << endl;}
16.21
class DebugDelete {public: templatevoid 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 p(new int, DebugDelete()); //shared_ptrp(new T, DebugDelete());
16.23
显然会在最后一个指向该对象的shared_ptr被销毁时调用
16.24
templateclass Blob {public: template Blob(const TT&, const TT&); };template template Blob ::Blob(const TT &b, const TT &e): data(make_shared >(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: templatevoid operator()(const T *p) const { cout << "释放动态内存\n"; delete p; } };int main(){ unique_ptr p1(new int,DebugDelete()); shared_ptr p2(new int, DebugDelete()); //shared_ptr 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
可以,只要显示指定模板类型参数,则函数实参就能进行正常的类型转换
templateT Max(const T &a, const T &b){ return a >= b ? a : b;}int main(){ auto c = Max(2, 3.0); //错误 auto i = Max (2, 3.0); //3.0将转换为3 return 0; }
16.38
这样才能将内置指针转换为智能指针
16.39
显示指定模板类型,字符串字面常量可以是string,也可以是const char*
templateint 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 ("kzw", "ly"); auto d = compare ("kzw", "ly"); //与目的相悖,比的是地址 return 0; }
16.40
合法,但是实参类型指向的对象必须定义了+运算符,并且可以与整数0相加(譬如string类型就不可以)
decltype(*beg + 0) <====> decltype((*beg) + 0)
16.41
templateauto 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;}templatevoid flip(F f, T1 &&t1, T2 &&t2){ f(std::forward (t2), std::forward (t1));}
16.48
templatestring debug_rep(const T &s){ ostringstream ret; ret << t; return ret.str();}template 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
#includeusing namespace std;template void f(T t) { cout << "f(T): " << t << endl; } template void f(const T *t) { cout << "f(const T *): " << *t << endl; } template void g(T t) { cout << "g(T): " << t << endl; } template 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
#includeusing namespace std;template 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
#includeusing namespace std;template ostream& print(ostream &os, const T &t){ os << t; return os;}template 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#include using namespace std;template ostream& print(ostream &os, const T &t){ os << t; return os;}template ostream& print(ostream &os, const T &t, const Args&... rest){ os << t << "\t"; return print(os, rest...);}template string debug_rep(const T &s){ ostringstream ret; ret << s; return ret.str();}template 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 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
templatevoid StrVec::emplace_back(Args&&... args){ chk_n_alloc(); alloc.construct(first_free++, std::forward (args)...);}
16.59
在construct调用中的模式会扩展为std::forward<string&>(s)
16.60
可接受可变参数模板,转发其参数初始化一个内存于内存空间,返回一个shared_ptr
16.62