第一章 函数模板
函数模板初探
定义模板
定义一个返回最大值的函数模板
template<typename T>
T max(T a, T b)
{
return b < a ? a : b;
}
这里T表示类型参数,是模板参数。
使用模板
#include "max1.hpp"
#include <iostream>
int main()
{
int a = 1, b = 2;
std::cout << ::max(a, b) << "\n";
return 0;
}
这里max使用了域限定符::,表示从全局命名空间查找,避免和std中的max函数冲突。
两阶段编译
模板实参推导简介
当我们以实参调用max()
等函数模板,模板参数由所传递的实参来决定。
对于:
template<typename T>
T max(const T& a, const T& b)
{
return b < a ? a : b;
}
int main()
{
int a = 3, b = 4;
::max(a, b);
return 0;
}
如果传递类型为int的a和b,T仍然推导为int。
- 类型推导中的类型转换
- 当声明调用参数是按引用传递时,模板参数T声明的两个实参必须匹配。
- 当声明调用参数是按值传递时,仅支持退化的简单转换:忽略const和volatile限定符。
例如
template<typename T>
T max(T a, T b)
{
return b < a ? a : b;
}
int main()
{
int a = 1;
const int b = 2;
max(a, b); // 正确:T推导为int(忽略b的const限定符)
std::string s;
max("hello", s); // 错误 T可以推导为const[6]或std::string(表示hello字符串推导有歧义)
return 0;
}
- 默认实参的类型推导
类型推导对默认调用实参不起作用,例如:
template<typename T>
void f(T a = "");
template<typename T = std::string> // 添加默认实参
void g(T a = "");
int main()
{
f(1); // 正确推导
f(); // 错误推导
g(); // 正确推导
return 0;
}
多模板参数
模板参数:
template<typename T> // T是模板参数
调用参数
T max(T a, T b) // a和b是调用参数
template<typename T1, typename T2>
T1 max(T1 a, T2 b)
{
return b < a ? a : b;
}
int main()
{
std::cout << ::max(3, 3.2) << "\n"; // 输出3
std::cout << ::max(3.5, 2) << "\n"; // 输出3.5
return 0;
}
对于上述代码,由于有两个模板参数,并自定义了返回类型,因此在某些情况下,会出现类型转换。
返回类型的模板参数
template<typename T1, typename T2, typename RT>
RT max1(T1 a, T2 b);
template<typename RT, typename T1, typename T2> // 更改模板参数的顺序
RT max2(T1 a, T2 b);
int main()
{
::max1<int, double, double>(4, 7.2); // 正确,但需要显式指定返回类型,太麻烦了。
::max2<double>(4, 7.2); // 正确,返回类型
}
推导返回类型
通过auto关键字指定返回类型,让编译器自行推导
template<typename T1, typename T2>
auto max(T1 a, T2 b)
{
return b < a ? a : b;
}
也可以使用尾置返回类型语法
template<typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(b < a ? a : b)
{
return b < a ? a : b;
}
注意: 这里的返回类型由?:表达式决定,因为它会返回一个公共的算数类型,例如:
int a = 3;
double b = 3.5;
auto sb = true ? a : b; // 这里a返回的double而不是int
因此,上述模板声明也可如下声明:
template<typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(true ? a : b);
该定义有显著的缺点:返回类型可能是引用类型。
例如:
template <typename T1, typename T2>
auto max(T1 a, T2 b) -> decltype(true ? a : b) {
return (a > b) ? a : b;
}
int main()
{
int x = 10;
int& refX = x;
int y = 20;
auto result = max(refX, y); // max会被推导为int&, 但auto初始化时,其类型始终是退化之后的类型,即为int。
return 0;
}
使用类型特征返回退化后的类型:
template <typename T1, typename T2>
auto max(T1 a, T2 b) -> typename std::decay<decltype(true ? a : b)>::type
{
return (a > b) ? a : b;
}
int main()
{
int x = 10;
int& refX = x;
int y = 20;
auto result = max(refX, y); // max会被推导为int
return 0;
}
返回类型为公共类型
C11开始,可以通过std::common_type<>::type萃取作为模板实参传递的不同类型的公共类型,例如:
template <typename T1, typename T2>
typename std::common_type<T1, T2>::type max(T1 a, T2 b)
{
return (a > b) ? a : b;
}
可以将typename std::common_type<T1, T2>::type
省略写为std::common_type_t<T1, T2>
默认模板实参简介
可以为模板参数定义默认值,称为默认模板实参。可以将多个实参的公共类型作为返回值类型的默认实参。
方式一:
template <typename T1, typename T2, typename RT = std::decay_t<decltype(true ? T1() : T2())>>
RT max(T1 a, T2 b)
{
return (a > b) ? a : b;
}
这里调用了传递类型的默认构造函数。
方式二:
使用过std::common_type<>来指定
template <typename T1, typename T2, typename RT = std::common_type_t<T1, T2>>
RT max(T1 a, T2 b)
{
return (a > b) ? a : b;
}
最好的方法让编译器来推导。
重载函数模板简介
template <typename T>
T max(T a, T b)
{
return (a > b) ? a : b;
}
int max(int a, int b)
{
return b < a ? a : b;
}
int main()
{
::max(1, 2); // 调用非模板函数
::max(7.2, 1.1);
::max('a', 'n');
::max<>(1, 2);
::max<double>(1, 2);
::max('a', 1.1); // 调用非模板函数
return 0;
}
对于两个求最大值的模板:
template <typename T1, typename T2>
auto max(T1 a, T2 b)
{
return (a > b) ? a : b;
}
template <typename RT, typename T1, typename T2>
RT max(T1 a, T2 b)
{
return b < a ? a : b;
}
int main()
{
auto a = ::max(4, 7.2); //使用第一个模板
auto b = ::max<long double>(7.2, 4); //使用第二个模板(因为7.2被推导为double,返回值显示指定为long double)
auto c = ::max<int>(7, 4); // 错误,两个模板都匹配
return 0;
}
关于为指针和普通的C string 重载这个求最大值的模板:
template <typename T>
T max(T a, T b)
{
return (a > b) ? a : b;
}
template <typename T>
T* max(T* a, T* b)
{
return (*a > *b) ? a : b;
}
const char* max(const char* a, const char* b)
{
return std::strcmp(b, a) < 0 ? a : b;
}
int main()
{
int a = 7, b = 42;
auto m1 = ::max(a, b); //1
std::string s1 = "hey";
std::string s2 = "you";
auto m2 = ::max(s1, s2); //1
int* p1 = &b;
int* p2 = &a;
auto m3 = ::max(p1, p2); //2
const char* x = "hello";
const char* y = "sb";
auto m4 = ::max(x, y); //3
return 0;
}
上述都是按值传递的。
关于使用三个实参来比较C string的最大值
template <typename T>
const T& max(const T& a, const T& b)
{
std::cout << "1\n";
return (a > b) ? a : b;
}
const char* max(const char* a, const char* b)
{
std::cout << "2\n";
return std::strcmp(b, a) < 0 ? a : b;
}
// 求三个任意类型值得最大值
template <typename T>
const T& max(const T& a, const T& b, const T& c)
{
std::cout << "3\n";
return ::max(::max(a, b), c);
}
int main()
{
//auto m1 = ::max(1, 2, 3); //正确
const char* s1 = "ddfg";
const char* s2 = "zzxc";
const char* s3 = "acvbcvbvcb";
auto m2 = ::max(s1, s2, s3); // 运行时错误
return 0;
}
为啥会出现运行时错误呢?
调用过程:
::max(s1, s2, s3)会调用第三个模板,返回 ::max(::max(a, b), c),这里首先调用::max(a, b),匹配第二个模板函数:const char max(const char a, const char* b) ,返回值是一个指针(可以先不管它是不是指针,就把它当作一个数值而已,但它的值是如0x......的值,对其解引用后能得到相应字符串的值),然后再次调用第二个模板函数,同样也返回了一个指针。
问题出现了:由于指针也是一个数值,因此三参数模板会产生一个局部变量来存放该指针(数值),而该局部变量被当作引用来返回,导致返回的引用指向了一个临时对象,但该临时对象在函数调用结束后就被销毁了。
这里的临时对象是保存指针的局部变量,指针本身指向的内容不是临时对象,而是因为保存指针的是一个局部变量。
当改变任意顺序时,可能会导致调用顺序不一致:
template <typename T>
T max(T a, T b)
{
std::cout << "1\n";
return (a > b) ? a : b;
}
template <typename T>
T max(T a, T b, T c)
{
return ::max(::max(a, b), c);
}
int max(int a, int b)
{
std::cout << "2\n";
return (a > b) ? a : b;
}
int main()
{
::max(47, 11, 33); // 不会调用普通函数max,因为它的声明太靠后了,导致三参数模板没使用
return 0;
}
第二章 类模板
类模板Stack的实现
template<typename T>
class Stack
{
private:
std::vector<T> elems;
public:
void push(const T& elem); //压入元素
void pop(); // 弹出元素
const T& top() const;
bool empty() const
{
return elems.empty();
}
};
template<typename T>
void Stack<T>::push(const T& elem)
{
elems.push_back(elem);
}
template<typename T>
void Stack<T>::pop()
{
assert(!elems.empty());
elems.pop_back();
}
template<typename T>
const T& Stack<T>::top() const
{
assert(!elems.empty());
return elems.back();
}
类模板的声明
Stack(const Stack&); // 拷贝构造
Stack& operator=(const Stack&);
等同于
Stack(const Stack<T>&);
Stack<T>& operator=(const Stack<T>&);