一 C++ 模板介绍?
C++ 为什么引入模板?
我的理解是:
C++ 引入模板的概念,是为了复用重复的代码,当某些代码除了操作的数据类型不同以外,其他逻辑全都相同,此时就适合采用模板的方式。
定义模板类或者模板函数时,只是定义了一个代码的架子,使用时需要配合上实际的数据类型,数据类型可以是基本数据类型也可以是用户自定义的类型。
官方一点的说法:
所谓模板,实际上是建立一个通用函数或类,其类内部的类型和函数的形参类型不具体指定,用一个虚拟的类型来代表。这种通用的方式称为模板。
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
这种模板的设计在许多编程语言中是由类似的,比如 Java 、Scala 中都有模板的概念。
二 class template 类模板
类模板例子:
// complex.h
template<typename T>
class Complex
{
public:Complex(T re = 0, T im = 0):m_re(re), m_im(im){}Complex& operator+=(const Complex& other);T real() const{return m_re;}T imag() const{return m_im;}private:T m_re;T m_im;
};template<typename T>
Complex<T>& Complex<T>::operator+=(const Complex& other)
{this->m_re += other.m_re; // 任意一个类都是自己的 friend class,所以可以使用 private 成员变量this->m_im += other.m_im;return *this;
}// main.cpp
#include"complex.h"int main()
{Complex<double> c1(1, 2);Complex<int> c2(1.1, 2.2);Complex<float> c3(1.1, 2.2);return 0;
}
上面是一个复数 Complex模板类的简单实现。
三 function template 函数模板
// complex.h
template<typename T>
class Complex
{
public:Complex(T re = 0, T im = 0):m_re(re), m_im(im){}bool operator<(const Complex& other) const{if(this->m_re < other.m_re){return true;}else if(this->m_re > other.m_re){return false;}else{if(this->m_im < other.m_im){return true;}else{return false;}}}T real() const{return m_re;}T imag() const{return m_im;}private:T m_re;T m_im;
};// main.cpp
#include <iostream>
#include"complex.h"// 模板函数
template<typename T>
const T& myMin(const T& a, const T& b)
{return a < b ? a : b;
}int main()
{int a1 = 11;int b1 = 22;std::cout << myMin(a1, b1) << std::endl;double a2 = 1.1;double b2 = 2.2;std::cout << myMin(a2, b2) << std::endl;Complex<int> c11(11, 2);Complex<int> c12(1, 22);auto cc = myMin(c11, c12);std::cout << cc.real() << ", " << cc.imag() << std::endl;return 0;
}
以上代码定义了一个模板函数 myMin ;一个模板类 Complex,自定义了一个 operator< 成员函数。
从代码中我们发现一个问题,模板类要用尖括号指明实际的元素类型,而模板函数并不需要尖括号指明实际的元素类型。这是因为在 C++ 中,模板函数在调用时,可以通过传入的实参推断出形参的类型,因而不需要指明实际的元素类型。
模板函数 myMin 传入的类型需要有比较的能力,c++ 基本类型天然就具有比较的能力,而用户自定义类型需要实现 operator< 成员函数,才能在编译到 < 那行代码时能够通过,若是自定义类型没有实现 operator< 成员函数时,编译会出现错误。
四 member template 成员模板
成员模板也是一种函数模板,只不过针对的是构造函数的模板。
// base.h
class Base2
{};class Derive2:public Base2
{};class Base3
{};class Derive3:public Base3
{};// my_pair.h
template<typename T1, typename T2>
class MyPair
{
public:typedef T1 firstArgumentType;typedef T1 secondArgumentType;MyPair():first(T1()), second(T2()){}MyPair(const T1& arg1, const T2& arg2):first(arg1), second(arg2){}// 成员模板template<typename U1, typename U2>MyPair(const MyPair<U1, U2>& other):first(other.first),second(other.second){}
public:T1 first;T2 second;
};// main.cpp
#include"my_pair.h"
#include"base.h"int main()
{MyPair<Base2, Base3> pair1;MyPair<Derive2, Derive3> pair2;MyPair<Base2, Base3> pair3(pair2);return 0;
}
五 模板特化(specialization)与偏特化(partial specialization)
1. 模板特化 (specialization)
模板特化指得是将模板类或者模板函数特化到某些特定的指定类型,使得在编译器编译时,实际编译执行的代码是特化后的版本。
// my_hash.h
#include<iostream>
#include<iostream>template <class T>
struct my_hash
{T operator()(T x){std::cout << "template <class T> struct my_hash" << std::endl;return x;}
};// 以下为三个特化版本
template<>
struct my_hash<int>
{int operator()(int x){std::cout << "template <> struct my_hash<int>" << std::endl;return x;}
};template<>
struct my_hash<char>
{char operator()(char x){std::cout << "template <> struct my_hash<char>" << std::endl;return x;}
};template<>
struct my_hash<long>
{long operator()(long x){std::cout << "template <> struct my_hash<long>" << std::endl;return x;}
};
// main.cpp#include"my_hash.h"int main()
{std::cout << my_hash<std::string>()("abcd") << std::endl;std::cout << my_hash<char>()('a') << std::endl;std::cout << my_hash<int>()(66) << std::endl;std::cout << my_hash<long>()(6666) << std::endl;return 0;
}
输出:
2. 模板偏特化(partial specialization)
2.1 个数偏特化
模板的个数变化时的特化版本
// my_vector.h
#include<iostream>template<typename T, typename Alloc>
class my_vector
{
public:my_vector(){std::cout << "template<typename T, typename Alloc> class my_vector" << std::endl;}
};template<typename Alloc>
class my_vector<bool, Alloc>
{
public:my_vector(){std::cout << "template<typename Alloc> class my_vector<bool, Alloc>" << std::endl;}
};// main.cpp
#include"my_vector.h"int main()
{my_vector<std::string, std::allocator<std::string>> vec1;my_vector<bool, std::allocator<std::string>> vec2;return 0;
}
输出:
2.2 范围偏特化
通过特化模板类型的指针来实现指针的特化
// e.h
#include<iostream>template<typename T>
class E
{
public:E(){std::cout << "template<typename T> class E" << std::endl;}
};template<typename T>
class E<T*>
{
public:E(){std::cout << "template<typename T> class E<T*>" << std::endl;}
};// main.cpp
#include"e.h"int main()
{E<std::string> e1;E<std::string*> e2;return 0;
}
输出:
六 模板模板参数(template template parameter)
模板模板参数其实就是模板的其中参数又是另外一个模板参数,示例代码如下:
XCLS 类 的第一个模板参数是 T ,第二个模板参数是一个模板类 Container,我们可以定义一个 XX 模板类来组合使用 XCLS ,将 XX 传入即可;
也可以使用容器类 list 传入,但是如果直接传入编译是会出错的,比如下面
XCLS<std::string, std::list> xcls3; 编译不过
编译不过的原因是,std::list 实际上是两个模板参数,编译器检查时发现少给了一个模板参数 _Alloc ,所以编译不过。
template<typename _Tp, typename _Alloc = std::allocator<_Tp> >class list;
可以通过如下定义 Lst 来将模板类 list 的模板参数限定为 一个
template<typename T>
using Lst = std::list<T, std::allocator<T>>;
// xcls.h
#include<iostream>template<typename T, template<typename U> class Container>
class XCLS
{
public:XCLS(){std::cout << "template<typename T, template<typename U> class Container> class XCLS" << std::endl;}
private:Container<T> c;
};template<typename T>
class XX
{
public:XX(){std::cout << "template<typename T> class XX" << std::endl;}
};// main.cpp
#include"xcls.h"int main()
{ // 模板模板的参数使用XCLS<std::string, XX> xcls1;XCLS<std::string, Lst> xcls2;// XCLS<std::string, std::list> xcls3; 编译不过return 0;
}
输出: