模板与泛型编程
- OOP和泛型编程都能处理在编写程序时不知道类型的情况,不同之处在于:OOP能处理在程序运行之前都未知的情况;而在泛型编程中,在编译时就能知道类型。
- 这里有个类模板的例子,实现了矩阵的基本运算:https://blog.youkuaiyun.com/u012526003/article/details/80116295
定义模板
- 编译器可以推断出
函数模板
的类型,即根据传参类型进行推断或者进行隐式转换,但是无法推断出类模板
的类型,因此需要在定义类的时候,在<>
中提供额外的信息。 - 模板类中有静态函数时,可以通过类类型对象来访问它,但是必须引用一个特定的实例,即需要给类对象的参数类型
- 与函数参数相同,声明中的模板参数不必与定义中相同。
- 可以指定默认模板,比如
template <typename T = int>
,表示默认T为int code
template <typename T> class A { public: A(T t) : val(t) { } static void print() { cout << __FUNCTION__ << endl; } private: T val; }; // 默认了类型为T template <typename T = int> class B { public: static void print() { cout << __FUNCTION__ << endl; } private: T val_; }; void test() { A<int> a1(3); a1.print(); // A::print(); //会报错,静态函数也需要提供类的参数类型 A<double>::print(); B<> b1; // 仍然需要尖括号,但是不需要填写类型,因为已经有默认类型 B<>::print(); B<vector<int>>::print(); }
控制实例化
- 模板在使用时才会被实例化,因此相同的实例可能会出现在多个对象文件中,这种实例化相同模板的额外开销可能很大。因此可以通过显示实例化来避免这种开销。
方法
extern template class Name<int>; extern template int compare(cosnt int&, const int&);
- 注意:声明的模板类型必须在程序其他位置进行实例化
- 对于数组实参,如果两个数组大小不同,则它们是不同类型,但是如果被转换为指针,则他们的类型相同,因此主要是看是否可以被转换为指针。
code
template <typename T> void fun1(T a, T b) { } template <typename T> void fun2(const T& a, const T& b) { } void test() { int a[10], b[42]; fun1( a, b ); // 被转换为指针,因此运行没有问题 // fun2(a, b); // 数组大小不同,又无法被拷贝转换为指针,因此无法编译通过 }
函数模板显示实参
- 如果没有实参的类型可以推断出参数模板的类型,则需要我们显示指定类型,注意:需要显示指定的参数类型必须写在template列表的最前面
code
template <typename T1, typename T2, typename T3> T1 fun1(T2 a, T3 b) { return a + b; } template <typename T1, typename T2, typename T3> T3 fun2(T2 a, T1 b) { return a + b; } void test() { auto res = fun1<int>(1, 2); cout << typeid(res).name() << " : " << res << endl; // auto res2 = fun2<int>(1, 2); // 错误,无法推断出res2的类型,需要显式给出全部三个参数类型才可以 }
非模板与模板重载
- 如果模板有多个重载,则编译器会选择最匹配的那一个函数进行调用;如果非模板函数与模板重载的匹配度相同,则编译器会选择最特例化的那个版本,出于此,一个非函数模板函数比一个函数模板更好。
- 在使用多个重载函数时,一定记得声明所有重载的函数版本,这样可以使得编译器找出最匹配、最特例化的函数版本。
code
template <typename T> T fun(T a, T b) { return a + b; } int fun(int a, int b); // 如果没有这个声明,则下面的fun(1,2)只能调用函数模板了 void test() { auto res1 = fun<int>(1, 2); // 只能执行函数模板 auto res2 = fun(1, 2); // 匹配度相同,执行最特例化的版本,可以打断点看具体是调用了哪个函数 } int fun(int a, int b) { return a + b; }
可变参数模板
- 可变参数模板就是一个接受可变数目参数的模板函数或者模板类,可变数目的参数被称为参数包。有2种参数包:模板参数包,表示0互助哦和多个模板函数;函数参数包,表示0或者多个函数参数。可以用
typnename...
指出接下来的参数表示0或者多个类型的列表。sizeof...(Args)
可以用来计算参数包中所含参数的个数。 code
template<typename T, typename... Args> void foo(const T t, Args ...args) { cout << __FUNCTION__ << " : " << sizeof...(Args) << endl; } void test() { foo(1, 2, 3, 4); foo(1, "a", vector<int>()); foo( 1 ); }
输出参数包中的所有参数
code
// 只有一个参数的时候会调用这个函数 template<typename T> ostream& print(ostream& os, const T& t) { os << t; return os; } template<typename T, typename... Args> ostream& print(ostream& os, const T& t, const Args& ...args) { os << t << ", "; return print( os, args... ); } void test() { int a = 0, b = 1; string s1("test1"), s2("test2"); print(cout, a, b, s1, s2); cout << endl; }
函数模板特例化
- 函数模板的一个特例化版本就是模板的一个独立的定义。
code
template<typename T> int cmp(const T& t1, const T& t2) { cout << typeid(t1).name() << endl; if (t1 < t2) return -1; else if (t1 == t2) return 0; else return 1; } // 接受不同长度的字符串常量的比较 // 如果定义不同长度的数组,没有定义这个函数的话,则无法对其进行比较 // char[4]与char[5]是不同的变量类型 template<size_t N, size_t M> int cmp(const char (&s1)[N], const char (&s2)[M]) { cout << typeid(s1).name() << ", " << typeid(s2).name() << endl; return strcmp( s1, s2 ); } void test() { string s1("aaa"), s2("bbb"); cout << cmp( s1, s2 ) << endl; char str1[] = "123"; char str2[] = "4567"; cout << cmp(str1, str2) << endl; cout << cmp("123", "4567") << endl; // 默认会被识别为字符数组而非指针 }