第十六章 模板与泛型编程
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