前言

学到这里,面对对象应该也是入门了,所以我们可以做到用一下资料去查阅自学了,这里我觉得这个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;

  1. 去找有没有int Max(int,int)这个函数,
  2. 如果没有去找没有用对应的模板,
  3. 先找有没有对应int这个类型的特定模板
  4. 没有就就按照模板生成函数,有则按照特定的模板生成
  5. 当编译结束的时候,程序里是没有模板函数这个代码的,只有对应类型生成的代码

如果我们直接定义了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;
    } 
}

结语

当然类模板,远远不止这些,但是由于篇幅限制,还是先总结导到这里吧。

努力成长的程序员