模板机制
编程范式分为两种:
1:面向对象
封装
继承
多态
运行时抽象
2:泛型编程
类模板
函数模板
概念
编译时抽象
一般编程中,较少使用到泛型编程,很多人认为泛型编程属于“专家编程”,实际工作中,基本都是绕着走,基本不用。
泛型编程应该成为正常编程活动的一部分————Bjarne stroustrup
模板
C++模板是一种编译时机制,编译时生成具体代码,使用实参模板定义实例化为具体的类型或函数,C++支持两种模板:
1:类模板
2:函数模板
模板实例化时,编译器会对实参类型进行检查,确保实参符合对模板参数的操作要求。C++模板参数支持两种:
1:类型参数,可隐式约束,也可显式约束
2:值参数,编译时常量,或constexpr函数。不同值参数是不同类型
3:可为模板参数提供默认值
对模板参数进行显式约束,即C++ 20的概念
模板类成员
1:普通成员:使用与主模板相同类型模板参数,包括:
数据成员:变量,常量
成员函数
静态成员:数据或函数
成员类型别名
成员类型
2:成员模板:使用与主模板不同的类型参数
成员模板不能定义虚函数(模板实例化会导致连接器不断为虚表增加虚函数增项)
注意:所有普通类成员规则同样适用于模板类成员
//template <typename T=int, long Size=10> //T是类型参数,size是值参数
成员(数据、类型、函数、成员函数/类型、别名)
#include <iostream>
using namespace std;
template <typename T>
class Complex{
T re, im; //数据成员
public:
//成员类型别名,以前用typedef,现在统一使用using
// using SType=typename SumTraits<T>::SumType; //简单理解:using SType=T;
using SType=T; //简单理解:using SType=T;
//struct Point{int x, int y};
//如上写法提示出错:expected unqualified-id before ‘int’,为了和C一致,修改如下吧
struct Point{
int x;
int y;
};//类型成员
Complex(const Complex&)=default;
T real() const {return re;} //函数成员
T imag() const {return im;}
Complex(T _re, T _im):re{_re}, im{_im}
{}
virtual void print(){
cout<<"["<<re<<","<<im<<"]"<<endl;
}
//以上是模板成员,都只使用T===以下是成员模板,不能是虚函数
template<typename U> //U是一个新的类型————成员模板
Complex(const Complex<U>& c):
re{static_cast<T>(c.real())},
im{static_cast<T>(c.imag())}
{
}
template<typename U>
void add(U r, U i) // 不可以是虚函数
{
this->re+=r;
this->im+=i;
}
};
int main(){
Complex<int> c1{100,200};
Complex<double> c2{34.7, 89.9};
Complex<int> c3=c2;
c1.print();
c2.print();
c3.print();
c2.add(100,200);
c2.add(100.234,200.234);
c2.print();
}
//编译执行:
kongcb@tcu-pc:~/testcode/template$ g++ member_template.cpp -o member_template
kongcb@tcu-pc:~/testcode/template$ ./member_template
[100,200]
[34.7,89.9]
[34,89]
[234.934,490.134]
类模板(虚函数)
#include <iostream>
using namespace std;
template <typename T>
class Array {
private:
T* ptr;
int size;
public:
Array(){}
Array(T arr[], int s);
void print();
virtual void show();
};
template <typename T>
Array<T>::Array(T arr[], int s)
{
ptr = new T[s];
size = s;
for (int i = 0; i < size; i++)
ptr[i] = arr[i];
}
template <typename T>
void Array<T>::print()
{
for (int i = 0; i < size; i++)
cout << " " << *(ptr + i); //此处如果+写错成*,只要不调用,也不报错
cout << endl;
}
template <typename T>
void Array<T>::show() //虚函数无论是否调用,一定实例化
{
for (int i = 0; i < size; i++)
cout << " " << *(ptr + i); //如果此处+写错*,无论是否调用,报错,
cout << endl;
}
//如果上面show写错了,此处可以强制显式实例化,检查错误
template class Array<int>; //显式实例化模板
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
Array<int> a(arr, 5); //int替换T——模板实例化
a.print(); //如果不调用print,即使print有错误,不会报错
}
//调试输出如下:
kongcb@tcu-pc:~/testcode/template$ ./template_class
1 2 3 4 5
类模板(传参数)
#include <iostream>
#include <array>
using namespace std;
//值参数不能是浮点数或类对象,以及编译时不能确定的值不行
//template <typename T=int, long Size=10> //size类型和下面函数template不一致,
//之后定义类成员函数提示:对不完全的类型‘class Array<T, Size>’的非法使用——调试了好一会
template <typename T=int, size_t Size=10> //类型参数T,size值参数,提供默认参数
class Array {
private:
T* ptr;
public:
Array(){}
Array(T arr[]); //也可以Array(T* pArr);
void print();
};
template <typename T, size_t Size>
Array<T,Size>::Array(T arr[]) //类成员函数需要在上面template
{
ptr = new T[Size];
for (int i = 0; i < Size; i++)
ptr[i] = arr[i];//*(pArr+i);//arr[i];
}
template <typename T, size_t Size>
void Array<T,Size>::print()
{
for (int i = 0; i < Size; i++)
cout << " " << *(ptr + i);
cout << endl;
}
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
Array<int ,10> arrInt{arr}; //类型及相关参数传递
arrInt.print();
// Array<int, 100> arrLong{arr};//和Array<int ,10>是不同类型
}
//调试运行结果:
kongcb@tcu-pc:~/testcode/template$ g++ template_arg_value.cpp -o template_arg_value
kongcb@tcu-pc:~/testcode/template$ ./template_arg_value
1 2 3 4 5 32767 0 0 4196832 0 //只保证arr[5],后面的值随机
类型别名与模板别名
1:为模板类使用指定别名
类型别名(alias type):指定所有模板参数,得到完整类型
//using svector=vector<string>; //指定所有参数,完整类型
//可以直接使用:svector s{"hello"s, "boolan"s, "cpp"s, "camp"s};
模板别名(alias template):指定部分参数,得到模板类型
//template<typename T>
//using SmallVec=vector<T, SmallAlloca<T>>;//指定部分参数,模板类型
//使用时需要填充类型:SmallVec<int> si{1,2,3,4,5};
成员类型别名:类模板中,通过定义类型别名,来定义“关联类型”
别名与原始模板完全等价:包括使用模板特化时(但不支持特化别名)
2:优先使用using而不是typedef
两者都可以声明类型别名、成员类型别名
using可以定义别名模板,而typedef不能
using可以免掉类型内typdef要求的typename前缀,和::type 后缀
别名示例(类型别名+模板别名)
#include <iostream>
#include <string>
#include <memory>
#include <vector>
using namespace std;
// template<typename T, typename Alloca=alloctor<T>>
// vector;
using svector=vector<string>; //指定所有参数,完整类型————类型别名
template<typename T>
using SmallVec=vector<T, SmallAlloca<T>>;//指定部分参数,模板类型————模板别名
void process(){
svector s{"hello"s, "boolan"s, "cpp"s, "camp"s};
SmallVec<int> si{1,2,3,4,5}; //模板别名
}
template <typename T>
class Array {
private:
T* ptr;
int size;
public:
using value_type=T; //成员类型别名
using iterator=Array_Iter<T>; //成员类型,使用了另外一个模板类
};
int data;
Array<int>::value_type data; //value_type此处就是int
模板参数类型自动推导
1:C++模板编译时支持对类型参数的自动推导
普通函数参数
类成员函数参数
构造函数参数(C++17),模板所有类型参数都有值
2:模板类型推导时
引用性被忽略:引用类型实参当非引用类型处理
转发引用:左值按照左值,右值按照右值
值传递:实参中const/volatile修饰会去掉
自动推导示例
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <utility>
using namespace std;
template<typename T>
void process1(T& value) //函数模板
{
cout<<value<<endl;
}
template<typename T>
void process2(T value)
{
cout<<value<<endl;
}
template<typename T>
void invoke(T&& value)
{
//do(std::forward<T>(value));
}
int main()
{
vector<int> c1(100);//显式类模板推导
vector c2{10,20,30};//自动推导,C++17支持
pair<int, string> keyv1{100, "C++"s};
pair keyv2{200,"Java"s};
auto keyv3=make_pair(300,"GO"s);//函数模板,C++17之前就支持
keyv3=keyv2;
cout<<keyv3.first<<":"<<keyv3.second<<endl;
int data1=100;
int& data2=data1;
const int data3=data1;
const int& data4=data1;
//template<typename T>
//void process1(T& value);————传引用
process1(data1);// T是int, value 是int&
process1(data2);// T是int, value 是int& ———— 引用性被忽略
process1(data3);// T是const int, value 是const int&
process1(data4);// T是const int, value 是const int&
//template<typename T>
//void process2(T value)————传值
process2(data1);// T 是int, value 是 int
process2(data2);// T 是int, value 是 int ********** 引用性被忽略
process2(data3);// T 是int, value 是 int ********** 传值时,const/volatile修饰去掉了
process2(data4);// T 是int, value 是 int **********
}
//执行结果
kongcb@tcu-pc:~/testcode/template$ g++ type_infer.cpp -o type_infer
kongcb@tcu-pc:~/testcode/template$ ./type_infer
200:Java
100
100
100
100
100
100
100
100