第十六章 模板与泛型编程

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

 

posted @ 2017-11-04 15:02  GGBeng  阅读(252)  评论(0编辑  收藏  举报