标签: C++Primer 学习记录 模板 泛型编程
在做这一章的笔记时,因为有很多内容也是在看 C++ Primer这本书时第一次接触到,所以需要记录大段文字。挨个字敲,又太累,所以就想有没有什么高效的输入手段。后面想到了语音输入,对比了搜狗输入法和讯飞输入法,发现讯飞输入法对于专业术语也能翻译的很好。这样一来,遇到整段文字就再也不用烦心了。果然,想偷懒,才能提高效率嘛!
template <unsigned N, unsigned M> int compare(const cahr (&p1)[N], const cahr (&p2)[M]) [ return strcmp(p1, p2); } compare("hi", "mom"); // 上式调用会实例化处如下版本,注意字符串字面常量的末尾有一个空字符! int compare(const cahr (&p1)[3], const cahr (&p2)[4])
template <typename T>
ret-type Blob<T>::member-name(parm-list)
template <typename T>
// 返回类型,处于类的作用域之外,需要提供模板实参
BlobPtr<T> BlobPtr<T>::operator++(int)
{
// 函数体内,处于类的作用域之内
BlobPtr ret = *this;
...
}
// 为了在 Blob中声明友元,需要前置声明
template<typename T> class BlobPtr;
template<typename T> class Blob; // 声明运算符 ==中的参数所需要的
template<typename T>
bool operator==(const Blob<T> &lhs, const Blob<T> &rhs);
template <typename T>
class Blob
{
// 每个 Blob实例将访问权限授予用相同类型实例化的 BlobPtr和相等运算符
friend class BlobPtr<T>;
friend bool operator==<T>
(const MyBlobPtr &lhs, const MyBlobPtr &rhs);
// 其它成员定义
};
// BlobPtr<char>的成员可以访问 ca(或任何其它 Blob<char>对象)的非 public部分
Blob<char> ca;
Blob<int> ia;
// 前置声明,在将模板的一个特定实例声明为友元时要用到
template <typename T> class Pal;
class C { // C是一个普通的非模板类
friend class Pal<C>; // 用类 C实例化的 Pal是 C的一个友元
// Pal2的所有实例都是 C的友元,这种情况无须前置声明
template<typename T> friend class Pal2;
};
template<typename T> class C2 { // C2本身是一个模板
// C2的每个实例将相同实例化的 Pal声明为友元
friend class Pal<T>; // Pal的模板声明必须在作用域之内
// Pal2的所有实例都是 C2的每个实例的友元,不需要前置声明
template<typename X> friend class Pal2;
// Pal3是一个非模板类,它是 C2所有实例的友元
friend class Pal3; // 不需要 Pal3的前置声明
};
template <typename T> using twin = pair<T, T>;
twin<int> win_loss; // win_loss是一个 pair<int, int>
template <typename T> using partNo = pair<T, unsigned>;
partNo<string> books; // books pair<string, unsigned>
template <typename T> class Foo {
public:
static std::size_t count() { return ctr; }
// 其它接口成员
private:
static std::size_t ctr;
// 其它数据成员
};
// 所有三个对象共享相同的 Foo<int>::ctr和 Foo<int>::count成员
Foo<int> fi, fi2, fi3;
Foo<int> fi; // 实例化 Foo<int>类和 static数据成员 ctr
auto ct = Foo<int>::count(); // 实例化 Foo<int>::count
ct = fi.count(); // 使用 Foo<int>::count
ct = Foo::count(); // 错误,无法确定使用哪个模板实例化的 count
typedef double A;
template <typename A, typename B> void f(A a, B b)
{
A tmp = a; // tmp的类型为模板参数 A的类型,而非 double
double B; // 错误,重声明模板参数 B
}
template <typename T> class Blob; // 声明但不定义
template <typename T>
// 返回一个成员类型
typename T::value_type top(const T &c)
{
if (!c.empty())
return c.back();
else
return typename T::value_type();
}
// compare有一个默认模板实参 less<T>和一个默认函数实参 F()
int compare(const T &v1, const T &v2, F f = F())
{
if (f(v1, v2)) return -1;
if (f(v2, v1)) return 1;
return 0;
}
template <typename T = int> class Numbers {
public:
Numbers(T v = 0) : val(v) { }
// 对数值的各种操作
private:
T val;
}
Numbers<long double> lots_of_precision;
Numbers<> integer;
// 函数对象类,对给定指针执行 delete
class DebugDelete
{
public:
DebugDelete(std::ostream &s = std::cerr) : os(s) {}
template<typename T> void operator()(T *p) const
{
os << "deleting unique_ptr" << std::endl;
delete p;
}
private:
std::ostream &os;
};
// 在一个临时 DebugDelete对象上调用 operator()(double*)
double *p = new double;
DebugDelete() (d);
// 重载 unique_ptr的删除器,在尖括号内给出删除器类型,并在构造函数中提供一个这种类型的对象
unique_ptr<int, DebugDelete> p(new int, DebugDelete());
// 在销毁 p指向的对象时,实例化 DebugDelete::operator<int>(int *)
template <typename T> class Blob {
template <typename It> Blob(It b, It e);
//...
};
// 类外定义
template <typename T> // 类的类型参数
template <typename It> // 构造函数的类型参数
Blob<T>::Blob(It b, It e) : data(make_shared<vector<T>>(b, e)) {}
extern template class Blob<string> // 声明
template int compare(const int&, const int&); // 定义
// Application.cc
// 这些模板类型必须在程序其它位置进行实例化
extern template class Blob<string>;
extern template int compare(const int&, const int&);
Blob<string> sa1, sa2; // 实例化会出现在其他位置
// Blob<int>及其接受 initializer_list的构造函数在本文件中实例化
Blob<int> a1 = {0, 1, 2, 3, 4};
Blob<int> a2(a1); // 拷贝构造函数在本文件中实例化
int i = compare(a1[0], a2[0]); // 实例化出现在其他位置
// templateBuild.cc
// 实例化文件必须为每个在其他文件中声明为 extern的类型和函数提供一个(非 extern)的定义
template int compare(const int&, const int&);
template class Blob<string>;
template <typename T> T fobj(T, T) // 实参被拷贝 template <typename T> T fref(const T&, const T&) // 引用 string s1("a value"); const string s2("another value"); fobj(s1, s2); // 调用 fobj(string, string),const被忽略 fref(s1, s2); // 调用 fobj(const string&, cosnt string&) int a[10], b[42]; fobj(a, b); // 调用 f(int*, int*) fref(a, b); // 错误,数组大小不同,是不同类型,与模板参数类型不匹配
template <typename T> void f(T &p)
。实参必须是一个左值。如果实参是 const的,则 T将被推断为 const类型。// 对 f1的调用使用实参所引用的类型作为模板参数类型 f1(i); // i是 int,模板参数 T是 int,函数参数是 int& f1(ci); // ci是 const int,模板参数 T是 const int,函数参数是 const int& f1(5); // 错误
template <typename T> void f(const T &p)
。实参可以是任意类型(包括右值在内),即使实参是 const的,T的推断类型也不会是一个 const类型。// 在下列调用中,函数参数都是 const f2(i); // i是 int,模板参数 T是 int f2(ci); // ci是 const int,模板参数 T是 int f2(5); // 实参为 int类型的右值,模板参数 T是 int
template <typename T> void f3(T&&); f3(42); // 实参为 int类型的右值,模板参数 T是 int
f3(i); // 实参是 int类型的左值,模板参数 T是 int& f3(ci); // 实参为 const int类型的左值,模板参数 T是 const int&
// 该模板将两个额外参数逆序传递给指定的可调用对象
template<typename F, typename T1, typename T2>
void flip1(F f, T1 t1, T2 t2)
{
f(t2, t1);
}
// flip1一般情况下工作的很好,但是当用它调用一个接受引用参数的函数时会出现问题
void f(int v1, int &v2)
{
cout << v1 << " " << ++v2 << endl;
}
f(42, i); // f改变了实参 i
flip1(f, j, 42) // j的值不会改变
// 该模板将两个额外参数逆序传递给指定的可调用对象
template<typename F, typename T1, typename T2>
void flip2(F f, T1 &&t1, T2 &&t2)
{
f(t2, t1);
}
// flip2对接受左值引用函数工作的很好,但不能用于接受右值引用的函数
void g(int &&v1, int &v2)
{
cout << v1 << " " << v2 << endl;
}
g(42, i); // 正确
flip1(g, i, 42) // 错误,g中接收到的 “42”是左值
forward
会保持实参类型的所有细节。与 move不同,forward必须通过显式模板实参来调用。下面使用 forward重写翻转函数。template<typename F, typename T1, typename T2>
void flip3(F f, T1 &&t1, T2 &&t2)
{
f( std::forward<T2>(t2), std::forward<T1>(t1) );
}
// 通用模板,返回 T型对象t的 string表示
template <typename T>
string debug_rep(const T &t)
{
std::ostringstream ret;
ret << t;
return ret.str();
}
// 通用模板,返回 T型指针 p的 string表示
template <typename T>
string debug_rep(T *p)
{
std::ostringstream ret;
// 打印指针本身的值
ret << "pointer: " << p;
// p不为空,则打印 p指向的值
if (p)
ret << " " << debug_rep(*p);
else
ret << " null pointer";
return ret.str();
}
// 对于下面的代码调用,只会使用第一个模板
string s("hi");
cout << debug_rep(s) << endl;
// 对于下面的代码调用,最终会调用第二个模板,具体原因见下面第 2条。
cout << debug_rep(&s) << endl;
// 对于下面的代码调用,最终会调用第二个模板,具体原因见下面第 3条。
const string *sp = "hi";
cout << debug_rep(sp) << endl;
// 再定义一个普通非模板函数,打印双引号包围的 string
string debug_rep(const string &s)
{
cout << '"' + s + '"';
}
// 对于下面的代码调用,会使用普通非模板函数
cout << debug_rep(s) << endl;
// 对于下面的代码调用,最终会调用第二个模板,具体原因见下面第 4条。
cout << debug_rep("hi") << endl;
const T &t
,当实例化 string *
参数时,模板参数是 string *
,而函数参数是 string * const &t
,表示 t是引用,引用自 string型指针(本身是常量)。在进行模板实参推断之后会进行普通函数的函数匹配过程。而 string * const &t
中的顶层 const
属性也会被略去,即 f(string * const &t)
和 f(string *t)
存在二义性。此时后者更特例化,所以编译器实际执行的是后者。
const T &t
,当实例化 const string *
参数时,模板参数是 const string *
,而函数参数是 const string * const &t
,表示 t是引用,引用自 string型指针(指向常量,且本身是常量)。所以,同样地,f(const string * const &t)
和 f(const string *t)
存在二义性。此时后者更特例化,所以编译器实际执行的是后者。
print(cout, i, s, 42)
,其调用过程如下:
template <typename T> struct std::hash; class Sales_data { friend struct std::hash<Sales_data>; // 其它数据成员 }; // 为了使 Sales_data能存储在无序容器中,特例化 hash模板 // 注意, Sales_data类应支持 == 操作 namespace std { template <> struct hash<Sales_data> { typedef size_t result_type; typedef Sales_data argument_type; size_t operator()(const Sales_data &s) const; }; inline size_t hash<Sales_data>::operator()(const Sales_data & s) const { std::cout << "hash模板的 Sales_data特例化版本" << std::endl; return hash<string>()(s.bookNo) ^ hash<unsigned>()(s.units_sold) ^ hash<double>()(s.revenue); } }