前言
学到这里,面对对象应该也是入门了,所以我们可以做到用一下资料去查阅自学了,这里我觉得这个C++ 参考手册颇为好用,关键是中文,省去我们查阅谷歌翻译_。
何为模板
什么是模板 (Template),就我个人来理解的话,就是一个蓝图,就函数来说,我们在写函数的时候,操作什么类型我们是定义好的,但是在很多时候,我们展示不用确定要操作数据类型,就比如说我们排序吧,我们原先写的是对int 类型的 但是我们要操作float 型,所以我们就可以写一个模板。就和我们在写思想汇报一样,可以抄网上的模板。模板主要分为 类模板和函数模板
模板是一种对类型进行参数化的工具。
函数模板
如何定义
关键字 template class
template <class 模板参数1,class 模板参数2.....> <返回类型> <函数名>(<形参列表>)
{
// 函数的主体
}
通过例子去理解
#include <iostream>
#include <string>
using namespace std;
template <class T>
T Max (T a, T b)
{
return a < b ? b:a;
}
int main ()
{
int i = 39;
int j = 20;
cout << "Max(i, j): " << Max(i, j) << endl;
double f1 = 13.5;
double f2 = 20.7;
cout << "Max(f1, f2): " << Max(f1, f2) << endl;
string s1 = "Hello";
string s2 = "World";
cout << "Max(s1, s2): " << Max(s1, s2) << endl;
return 0;
}
我们直接去看主函数这语句
cout << "Max(i, j): " << Max(i, j) << endl;
我们在编译阶段的时候,就会寻找int Max(int,int);这个函数,但是事实上是没有的,所以他再去看看有没有叫Max的模板,当然是有的,找到之后,根据传入的参数就可以决定函数模板的不确定类型T为int,然后就可以生成int Max(int,int).
从这里我们就可以理解模板函数的强大之处,我们写了一遍函数,我们就可以对于任何类型进行操作,再一次提高了代码的重用率,当然我们也可以用宏定义去实现或者是重载函数,但是宏定义会涉及到参数检查,,重载呢,我们又要写好几遍,太麻烦了。
模板特化
当然我们会遇到一些问题,我们用到一些算法,但是对于某些类型无法使用,所以我们可以写一个特化。我们这个案列就对char * 就不能实用,如下
template <>
const char* Max (const char* a, const char* b)
{
return strcmp(a,b)<0 ? b:a;
}
这样我们就用可以完美解决这个问题了。当然我们还可以直接定义一个函数,我们模板函数可以和和他同名函数是同时存在的。
const char* Max (const char* a, const char* b)
{
return strcmp(a,b)<0 ? b:a;
}
当然这里会涉及一个问题---函数模板在编译完之后是不存在的
函数模板在编译完之后是不存在的
首先我们在前面分析了编译器遇到模板函数他到底在做什么,前面的是很不完善的,我们重新屡一下。
编译器遇到这个语句
cout << "Max(i, j): " << Max(i, j) << endl;
- 去找有没有int Max(int,int)这个函数,
- 如果没有去找没有用对应的模板,
- 先找有没有对应int这个类型的特定模板
- 没有就就按照模板生成函数,有则按照特定的模板生成
- 当编译结束的时候,程序里是没有模板函数这个代码的,只有对应类型生成的代码
如果我们直接定义了const char* Max (const char* a, const char* b) ;这个函数,无论我们是否定义和这个函数,这段代码一定会在我们生成的程序里面的,这是很不明智的,所以对于这样的情况,我们尽量还是使用特定模板。
简单总结一句
模板函数就是指导编译器生成模板函数。
何为模板函数
简单理解的话,编译器按照我们的模板生成的函数就是模板函数。
类模版
和函数模板一样,我们是在定义类时候,数据成员类型也是可以是不确定,或者成员函数参数也是不一定的,类模板如此的强大。
如何定义
template <class 类型参数1, class类型参数2, ...>
class 类模板名{
<成员函数和成员变量>
};
如何用了类模板去定义对象
类模板名<真实类型参数表> 对象名(构造函数实际参数表);
其实我们也可以直接把类模板名<真实类型参数表>看成类的名字。
和模板函数一样,编译器在编译阶段,就根据我们写的类模板来生成类了的。
继承问题
- 如果父类自定义了构造函数,记得子类要使用构造函数列表来初始化
继承的时候,如果子类不是模板类,则必须指明当前的父类的类型,因为要分配内存空间 - 继承的时候,如果子类是模板类,要么指定父类的类型,要么用子类的泛型来指定父类。
类模板中的成员函数还可以是一个函数模板。成员函数模板只有在被调用时才会被实例化。
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
using namespace std;
template <class T>
class Stack {
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{ // 如果为空则返回真。
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem)
{
// 追加传入元素的副本
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop ()
{
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
// 删除最后一个元素
elems.pop_back();
}
template <class T>
T Stack<T>::top () const
{
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最后一个元素的副本
return elems.back();
}
int main()
{
try {
Stack<int> intStack; // int 类型的栈
Stack<string> stringStack; // string 类型的栈
// 操作 int 类型的栈
intStack.push(7);
cout << intStack.top() <<endl;
// 操作 string 类型的栈
stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const& ex) {
cerr << "Exception: " << ex.what() <<endl;
return -1;
}
}
结语
当然类模板,远远不止这些,但是由于篇幅限制,还是先总结导到这里吧。