C++类模板
第二节:类模板,变量模板,别名模板
类模板的基本范例和模板参数的推断
**类模板:**是产生类的模具,通过给定的模板参数,生成具体的类,也就是实例化一个特定的类。
比如:vector,引入类模板的目的,减少代码冗余
基本范例
namespace _nmsp1
{
//类模板定义
template <typename T> // T是类型模板参数,表示myvector这个容器所保存的元素类型,typename和 // class都可以
class myvector // myvector可以称为类名或者类模板 , myvector<T>可以称为类型名(myvector后面带了 // 尖括号,表示的就是 一个具体类型了)
{
public:
typedef T* myiterator; // 迭代器
public:
myvector(); // 构造函数
myvector(T tmpt)
{
}
//myvector& operator=(const myvector&); // 赋值运算符重载
myvector<T>& operator=(const myvector&); // 在类模板里面,可以写<T>也可以不写赋值运算符重载
public:
void myfunc()
{
cout << "myfunc()被调用" << endl;
}
static void mystaticfunc()
{
cout << "mystaticfunc()被调用" << endl;
}
public:
// 迭代器接口
myiterator mybegin(); // 迭代器起始位置
myiterator myend(); // 迭代器结束位置
};
// 类模板实现
template <typename T>
myvector<T>::myvector() // 类外构造函数的实现
{
}
}
int main()
{
_nmsp1::myvector<int> tmpvar; // 类型模板T被替换成int
tmpver.myfunc(); // 调用普通成员函数
_nmsp1::myvector<string>mystaticfunc(); // 调用静态成员函数
}
myvector可以称为类名或者类模板 , myvector可以称为类型名(myvector后面带了尖括号,表示的就是 一个具体类型了)
- 类模板中,只有被调用的成员函数,编译器才会产生出这些函数的实例化代码
- 类模板中,如果有静态成员函数,不调用,不会实例化代码。
模板参数的推断
C++17中,类模板的类型模板参数也能推断了。
namespace _nmsp1
{
//类模板定义
template <typename T> //T是类型模板参数,表示myvector这个容器所保存的元素类型
class myvector //myvector可以称为类名或者类模板 , myvector<T>可以称为类型名(myvector后面带了尖括号,表示的就是 一个具体类型了)
{
public:
typedef T* myiterator; //迭代器
public:
myvector(); //构造函数
myvector(T tmpt)
{
}
//myvector& operator=(const myvector&); //赋值运算符重载
myvector<T>& operator=(const myvector&); //赋值运算符重载
public:
void myfunc()
{
cout << "myfunc()被调用" << endl;
}
static void mystaticfunc()
{
cout << "mystaticfunc()被调用" << endl;
}
public:
//迭代器接口
myiterator mybegin(); //迭代器起始位置
myiterator myend(); //迭代器结束位置
};
// 类模板实现
template <typename T>
myvector<T>::myvector() //类外构造函数的实现
{
}
}
int main()
{
_nmsp1::myvector tmpvec2(12); // 无需指定模板参数->可以不写<int>
tmpvec2.myfunc(); // 调用类模板中的普通成员函数
return 0;
}
推断指南(deduction guide)概要了解
c++17新概念:主要用来在推断类模板参数时提供推断指引。
-
隐式的推断指南
namespace _nmsp2 { template<typename T> struct A { A(T val1, T val2) { cout << "A::A(T val1,T val2)执行了!" << endl; } A(T val) { cout << "A::A(T val)执行了!" << endl; } }; } int main() { _nmsp2::A aobj1(15, 16);// (T->int T->int)->A<int> _nmsp2::A aobj2(15); // (T->double)->A<double> //_nmsp2::A* aobj3 = nullptr; //error推断指南 return 0; }
针对类模板A的每个构造函数,都有一个隐式的模板参数推断机制存在,这个机制,被称为隐式的推断指南
template<typename T> // A(T,T)->A<T>; // 表达出现->左侧部分内容或者形式时,请推断成->右侧的类型。右侧类型也被称为“指南类型” // ->左侧部分:该推断指南所对应的构造函数的函数声明,多个参数之间用,分隔。 // ->右侧部分:类模板名,接着一个尖括号,尖括号中是模板参数名。 // 整个推断指南的含义:当用调用带两个参数的构造函数通过类模板A创建相关对象时, // 请用所提供的构造函数的实参来推断类模板A的模板参数类型, // 一句话:推断指南的存在意义就是让编译器 能够把模板参数的类型推断出来。 template<typename T> // A(T,T)->A<double>;重点是推断出来的类型 // 那么:A aobj1(15, 16);代码行 相当于A<double> aobj1(15, 16);
-
自定义的推断指南
namespace _nmsp2 { template<typename T> struct A { A(T val1, T val2) { cout << "A::A(T val1,T val2)执行了!" << endl; } A(T val) { cout << "A::A(T val)执行了!" << endl; } }; // 自定义推断 template<typename T> A(T)->A<T>; } namespace _nmsp3 { template<typename T> struct B { T m_b; T m_b2; }; template<typename T> B(T)->B<T>; template<typename T> B(T,T)->B<T>; } int main() { _nmsp3::B<int> bobj1; // 需要明确指定模板参数 _nmsp3::B<int> bobj2{15}; // 可以用初始化列表的方式来定义对象bobj2,成员变量m_b = 15; _nmsp3::B bobj3{15}; // 错误,无法推导“_nmsp3::B”的模板参数,加了自定义推断指南就没错 误了 _nmsp3::B bobj4{ 15,20 }; // 如果想要它成功编译,B(T, T)->B<T> return 0; }
_nmsp3::B bobj3{ 15 };
不报错的原因
-
bobj3{ 15 }
这种形式正好就相当于调用了类模板B的带一个参数(15)的构造函数,尽管类模板B中实际并不存在构造函数。 -
因为
template<typename T>B(T)->B<T>;
推断指南的存在,当调用了类模板B带一个参数的构造函数时,推断出来的类型为B<T>
,所以最终推断出来的类型为
B<int>
类型。
类模板的各种特化
一般来讲,所写的类模板都是泛化的类模板;
特化的类模板是通过泛化的类模板来生成的,所以:得先要有泛化版本,才能有特化版本。
所谓特化版本,就是特殊对待的版本;
类模板的全特化
全特化:就是把TC这个泛化版本中的所有模板参数都用具体的类型来代替构成一个特殊的版本(全特化版本)。
在理解上:泛化版本的类模板与全特化版本的类模板,只是名字相同(都叫TC),在其他方面,可以把实例化后的他们理解成是两个完全不同的类。
namespace _nmsp1
{
//泛化:大众化,常规
template <typename T,typename U>
struct TC
{
TC()
{
cout << "TC泛化版本构造函数" << endl;
}
void functest1()
{
cout << "functest1泛化版本" << endl;
}
static int m_stc; //声明一个静态成员变量
};
//-------------------
template <> // 全特化:所有类型模板参数都yoghurt具体类型代表,所以<>里就空了
struct TC<int, int>
{
TC()
{
cout << "TC<int,int>特化版本构造函数" << endl;
}
void functest1();
/*{
cout << "functest1特化版本" << endl;
}*/
void functest2();
/*{
cout << "functest2特化版本" << endl;
}*/
};
//template <>
void TC<int, int>::functest1()
{
cout << "functest1特化版本" << endl;
}
void TC<int, int>::functest2()
{
cout << "functest2特化版本" << endl;
}
//-------------------------
template <typename U>
struct TC<float, U>
{
TC()
{
cout << "TC<float,U>偏特化版本构造函数" << endl;
}
void functest1();
};
template <typename U>
void TC<float, U>::functest1()
{
cout << "TC<float,U>::functest1偏特化版本" << endl;
}
//-------------------------
template <typename T, typename U>
struct TC<const T, U*>
{
TC()
{
cout << "TC<const T,U*>偏特化版本构造函数" << endl;
}
void functest1();
};
template <typename T, typename U>
void TC<const T, U*>::functest1()
{
cout << "TC<const T,U*>::functest1偏特化版本" << endl;
}
}
int main()
{
_nmsp1::TC<int, float> mytc;
mytc.functest1();
_nmsp1::TC<int, int> mytc2;
mytc2.functest1(); // error
return 0;
}