模板
学习目的:
- 了解并理解模板的概念;
- 熟悉函数模板的使用;
- 熟悉类模板的使用;
通过模板的使用最大化代码的重用
内容:
模板概念 函数模板(** ) 类模板(** ) 模板与友元
模板的概念
模板与泛型编程:
- 泛型编程:编写与类型无关的逻辑代码(只关注代码逻辑,不关注类型)。
- 模板:实现代码重用的一种工具或方法,作用是实现类型参数化(类型可以作为参数进行传递)。
模板是实现泛型编程的先决条件。
模板分类:
- 函数模板:是模板的一种,可以用函数模板定义出模板函数;
- 类模板:是模板的一种,可以用类模板定义出模板类;
函数模板
模板函数:用函数的模板写出来的模板函数,数据类型需要通过参数传进来,在此之前模板函数是不完整的,编译器不会将其生成可执行代码。
语法:
// 通过 函数模板 定义 模板函数
template <typename Type_1, ..., typename Type_n>
返回值类型 函数名(形参列表)
{
函数体;
}
// 示例
template <typename T1, typename T2>
T1 add(T1 a, T2 b)
{
return a+b;
}
// typename 也可以换成 class
template <class TT1, class TT2>
void test(TT1 tt1, TT2 tt2)
{
cout<<"tt1 = "<< tt1 << endl
<<"tt2 = "<< tt2 << endl;
}
// 调用
函数名<类型列表>(实参表)
模板函数调用:
#include <iostream>
using namespace std;
template<typename Type>
Type add(Type a, Type b)
{
return a + b;
}
template <class TT1, class TT2>
void test(TT1 tt1, TT2 tt2)
{
cout << "tt1 = " << tt1 << "\t\t"
<< "tt2 = " << tt2 << endl;
}
int main()
{
// 隐式调用
cout << "******** 隐式调用 *********" << endl;
test(1, 2);
test(1.123, 2.246);
test("name", 'S');
cout << add(3, 4) << endl;
// 编译器报错(类型推到错误)
// cout << add(2.14, 4) << endl;
// 类型传输 函数名<类型列表>(实参表)
cout << "******** 显示调用 *********" << endl;
cout << add<int>(2.14, 4) << endl;
cout << add<double>(2.14, 4) << endl;
cout << "*****************" << endl;
test<const char*, char>("Hello", 'C');
return 0;
}
输出:
函数模板和普通函数:
- 和普通函数一样也可以构成重载;
- 普通函数和模板函数构成重载的情况下,优先调用普通函数;
- 强制调用模板函数,显示调用即可;
模板的局限性:
对于自定义的类,可以通过重载函数进行解决
#include <iostream>
using namespace std;
class MyData
{
public:
MyData(int n = 0) : num(n){}
int num;
};
template <typename T>
bool func(const T & a,const T & b)
{
return (a == b ? true : false);
}
// 重载一次函数用于比较自定义类的对象
template<> bool func(const MyData & obj1, const MyData & obj2)
{
return(obj1.num == obj2.num ? true : false);
}
int main()
{
int num, val;
num = 8;
val = 8;
cout << ( func(num, val) ? "true" : "false" ) << endl;
cout << (func<int>(num, val) ? "true" : "false") << endl;
MyData obj_0, obj_2(5); // 也可以重载"=="用于比较自定义的类
cout << (func(obj_0, obj_2) ? "true" : "false") << endl;
cout << (func<MyData>(obj_0, obj_2) ? "true" : "false") << endl;
return 0;
}
输出:
类模板
与函数模板类似,所定义的模板类一样是不完整的类。
语法:
template <类型参数列表>
class 模板类名
{
成员;
};
类型参数列表:<class Type1, ..., class Type2>
示例:
#include <iostream>
using namespace std;
//template <class Type1, class Type2>
template <class Type1 = int, class Type2 = double> // 类型参数缺省必须从后往前
class MyData
{
public:
MyData(Type1 n = 0, Type2 v = 1.0);
Type1 getNum() { return num; }
Type2 getVal() { return val; }
void setNum(Type1 n);
void setVal(Type2 v) { val = v; }
// 类外实现
void showData();
private:
Type1 num;
Type2 val;
};
// 构造函数必须与模板类的类型名定义方式相同
template <class Type1, class Type2>
MyData<Type1, Type2>::MyData(Type1 n, Type2 v) : num(n), val(v)
{
}
// 类外定义带参数的函数时,也需要与模板类的类型名定义方式相同(不相同应该也可以,这块儿的逻辑没盘明,需进一步了解学习)
template <class Type1, class Type2>
void MyData<Type1, Type2>::setNum(Type1 n)
{
num = n;
}
// 此处类型的定义方式就与模板类的名字不同
template <typename T1, typename T2>
void MyData<T1, T2>::showData()
{
cout << "num = " << num << " val = " << val << endl;
}
int main()
{
MyData<> data_1(3, 3.14);
data_1.showData();
MyData<char> data_2(65, 5.5);
data_2.showData();
return 0;
}
输出:
类模板做函数的参数(多种不同的方式)
#include <iostream>
using namespace std;
//template <class Type1, class Type2>
template <class Type1 = int, class Type2 = double> // 类型参数缺省必须从后往前
class MyData
{
public:
MyData(Type1 n = 0, Type2 v = 1.0);
Type1 getNum() { return num; }
Type2 getVal() { return val; }
void setNum(Type1 n);
void setVal(Type2 v) { val = v; }
// 类外实现
void showData();
private:
Type1 num;
Type2 val;
};
// 构造函数必须与模板类的类型名定义方式相同
template <class Type1, class Type2>
MyData<Type1, Type2>::MyData(Type1 n, Type2 v) : num(n), val(v)
{
}
// 类外定义带参数的函数时,也需要与模板类的类型名定义方式相同(不相同应该也可以,这块儿的逻辑没盘明,需进一步了解学习)
template <class Type1, class Type2>
void MyData<Type1, Type2>::setNum(Type1 n)
{
num = n;
}
// 此处类型的定义方式就与模板类的名字不同
template <typename T1, typename T2>
void MyData<T1, T2>::showData()
{
cout << "num = " << num << " val = " << val << endl;
}
// 类模板作为函数的参数 法一
void testFunc_1(MyData<int, double>& obj)
{
obj.showData();
}
// 类模板作为函数的参数 法二:写成函数模板的形式
template <typename TT1,typename TT2 = double> // TT2可缺省
void testFunc_2(MyData<TT1, TT2>& obj)
{
obj.showData();
}
// 类模板作为函数的参数 法三:将类作为类型
template <class ClassType>
void testFunc_3(ClassType& obj)
{
obj.showData();
}
int main()
{
MyData<> data_1(3, 3.14);
testFunc_1(data_1);
cout << "***********************" << endl;
MyData<char> data_2(65, 5.5);
MyData<int, double>data_3(65, 5.5);
testFunc_2(data_2);
testFunc_2<char, double>(data_2);
testFunc_2<int, double>(data_3);
cout << "***********************" << endl;
testFunc_3(data_2); // 隐式调用
testFunc_3(data_3);
// 两层<> 是正确的,理解并熟悉
testFunc_3<MyData<char,double>>(data_2); // 显示调用
return 0;
}
输出:
类模板和继承
#include <iostream>
using namespace std;
template <class F_Type>
class Father
{
public:
F_Type m_F_val;
};
// 子类继承时指定父类模板的参数类型即可
class Son : public Father<double>
{
public:
int m_S_val;
};
// 子类也定义成模板的形式
template <class S_Type1, class S_Type2>
class Son_1 : public Father<S_Type1>
{
public:
S_Type2 m_S_val1;
};
int main()
{
Son_1<int, double>obj_son;
obj_son.m_F_val = 1;
obj_son.m_S_val1 = 2;
return 0;
}
注(***):
类模板的声明和成员函数的实现要写在同一个文件里面;
文件后缀名为".hpp";
模板和友元
注释有很多需要注意的地方:
#include <iostream>
using namespace std;
template <class T = int>
class A;
template <typename Type>
void show_2(A<Type>& obj); // 声明show_2()时,类A不认识,因此也需要对class A 进行一次声明
template <class T = int>
class A
{
public:
A(T t = 0) : m_a(t){}
// 1、类体里定义友元函数
friend void show_1(A<T>& obj)
{
cout << obj.m_a << endl;
}
// 2、类外实现
friend void show_2<>(A<T>& obj); // 声明时在函数名的后面加上<>,表示其为模板类的友元(***)
private:
T m_a;
};
// 因为编译器不认识 show_2() 因此需要在最上面对其进行一次声明;
template <typename Type>
void show_2(A<Type>& obj)
{
cout << obj.m_a << endl;
}
int main()
{
A<int> obj_a(666);
show_1(obj_a);
show_2(obj_a);
}
<未完待续>