C++学习笔记-模板

模板

模板是一种通用的描述机制,也就是说,使用模板允许使用通用类型来定义函数或类等,在使用时,通用类型可被具体的类型,如int、double甚至是用户自定义的类型来代替。简单来说,模板就是数据类型参数化,当然不光是数据,算法也行。

为什么要模板

形象地说,把函数比喻为一个游戏过程,函数的流程就相当于游戏规则,在以往的函数定义中,总是指明参数是int型还是double型等等,这就像是为张三(好比int型)和李四(好比double型)比赛制定规则。可如果王五(char*型)和赵六(bool型)要比赛,还得提供一套函数的定义,这相当于又制定了一次规则,显然这是很麻烦的。模板的的引入解决了这一问题,不管是谁和谁比赛,都把他们定义成A与B比赛,制定好了A与B比赛的规则(定义了关于A和B的函数)后,比赛时只要把A替换成张三,把B替换成李四就可以了,大大简化了程序代码量,维持了结构的清晰,大大提高了程序设计的效率。该过程称为“类型参数化”。

强类型程序设计中,参与运算的所有对象的类型在编译时即确定下来,并且编译程序将进行严格的类型检查。为了解决强类型的严格性和灵活性的冲突。有以下3中方式解决:

  • 宏替换:不做类型检查
  • 函数重载:为每个类型提供一个重载版本,程序自己维护这些重载的版本。是一种’类型参数化’的多态。函数版本编译器决定,属于静态多态。
  • 函数模板:为相同逻辑提供一个模板,将类型当做参数来传递,让编译器实例化对应版本的函数 来处理(让编译器维护不同的重载函数,程序维护一份函数模板),结合了宏替换和函数重载。

模板形参在模板中作为一种类型使用,可以用于函数形参、函数返回值和函数局部变量;

每个模板形参都要在函数的“形参列表”中至少出现一次

模板参数名的作用域局限于函数模板范围内。

关键字typename

用以说明后面的东西是个类型,不是数据成员

template<typename T>
class MyClass3
{
private:
//  T::SubType * ptr1_; // T里面的静态数据成员与ptr_相乘,与意图不符
    typename T::SubType * ptr_; // 加上typename关键字后,就会被解析成T里面的'子类型'的一个指针
};
派生类和模板
  • 为了运行效率,类模板是相互独立的,即独立设计,没有使用继承的思想。对类模板的扩展是采用适配器(adapter)来完成的。通用性是模板库的设计出发点,这是由泛型算法和函数对象等手段达到的。
  • 派生类的目标之一是代码的复用和程序的通用性,派生类的优点是可以由简到繁,逐步深入,程序编制过程中,可以充分利用前面的工作,一步步完成一个复杂的任务,这种方式有一定的开销。
  • 模板最求的是运行效率。而派生追求的是编程的效率.
面向对象与泛型
  • 面向对象,通过基类接口,使用一套代码,处理不同的派生类;泛型是使用一套代码[模板]处理不同的数据类型

函数模板

函数模板的使用规则和普通函数是相同的,在使用函数模板之前,必须对函数模板进行声明,此声明必须在外部进行,也就是说不能在任何一个函数(包括main函数)中声明,声明的格式为:
template <class T1[,class T2,……]>
函数原型

函数模板的模板参数可以是:类型参数(数据结构/容器也可以看成类型)、非类型参数(只能是int),需要注意的是,函数模板的模板参数不能有默认值。

函数模板实际上不是个完整的函数定义,因为其中的类型参数还不确定,只是定义了某些类型的角色(或变量)在函数中的操作形式,因此,必须将模板参数实例化才能使用函数,用模板实例化后的函数也称为模板函数。分为隐式实例化(依靠编译器的类型推导)和显式实例化

  • 整个编译过程中,模板被编译了两次:

    • 实例化之前,检查模板代码本身,查看语法是否正确;
    • 实例化期间,检查模板代码,查看是否所有调用都有效。
  • 普通函数只需要声明,即可顺利编译,而模板的编译需要查看模板的定义。C++是分离编译,头文件和实现文件是单独编译的,如果把模板的声明和实现分开,在编译的时候就没法推导出模板函数,即无法实例化,链接的时候就会出错。 因此模板的声明和实现必须在一起,不能像一般函数那样分开

函数模板特化

针对某种数据类型进行特化,还是模板函数

重载函数模板

函数模板支持重载,既可以模板之间重载(同名模板),也可以是模板和普通函数间的重载

遇到重载时,一般函数优先于模板函数的执行

int Max(int i1,int i2)
{
    cout<<"Normal Max"<<endl;
    return i1>i2? i1:i2;
}

template<class T> 
T Max(T t1, T t2)
{
    cout<<"Template Max,sizeof(t1):"<<sizeof(t1)<<endl;
    return t1>t2? t1:t2;
}

// 与上面的构成重定义
// template<class T1> 
// T1 Max(T1 t1, T1 t2)
// {
//  cout<<"Template Max,sizeof(t1):"<<sizeof(t1)<<endl;
//  return t1>t2? t1:t2;
// }

// Max<int>(int, int) 与 Max(int, int)不构成重定义,遇到这种情况,优先调用普通(非模板)函数。

再看个例子:
max.hpp:

#include <cstring>

template <typename T> // 函数模板的参数列表
const T& Max(const T& a, const T& b)
{
    return a > b ? a : b;
}

// 函数模板重载,不同的形参个数
template <typename T>
const T& Max(const T& a, const T& b, const T& c)
{
    return Max(a,b) > c ? Max(a,b) : c;
}

// 模板特化,针对char*这种数据类型。
template<> // 也可以不加这个,那就成了非模板函数,只是构成重载
const char* const& Max(const char* const& a, const char* const &b)
{
    return strcmp(a, b) < 0 ? b : a;
}

// 非模板函数,与函数模板构成重载
const int& Max(const int& a, const int& b)
{
    cout << "no template function" << endl;
    return a > b ? a : b;
}

main.cpp:

#include <iostream>
using std::cout;
using std::endl;
#include "max.hpp"

class Test
{

};

class Test2
{
public:
    friend bool operator> (const Test2& a, const Test2& b)
    {
        return true;
    }
};

int main(void)
{
    // ::表示全局函数,一般用于自己函数与库函数名相同,调用自己的函数。
    // 这里没有在<>中实例化数据类型,实际上是编译器自己根据传的实参推导出了模板函数。
    double res = ::Max(3.6, 5.4); 
    char ch = Max('A', 'a');

    cout.setf(std::ios::fixed);
    cout.precision(4);
    cout << res << ch << endl;


//    Test t1, t2;
//    Test ret = Max(t1, t2); // 调用无效,Test不支持>运算符
    Test2 t1, t2;
    Test2 ret = Max(t1, t2); // 调用有效效,Test2支持>运算符

    const char* str1 = "zzz";
    const char* str2 = "aaa";
    // 此时比较的是指针变量,而不是字符串内容,与预期不符,可以通过模板特化来修正。
    cout << Max(str1, str2) << endl; 

    cout << Max(1.0, 5.0, 3.0) << endl;

    cout << Max('a', 50) << endl; // 调用的是重载的非模板函数
    // 调用的是模板函数,数据类型是显示给定的,不是编译器自己推导的
    cout << Max<int>('a', 50) << endl; 
//    cout << Max<>('a', 50) << endl; // 调用的是模板函数,只是要由编译器自己推导数据类型,
                                    // 如果推导不出来,就会编译出错。

    return 0;
}

类模板

模板的引入使得类的定义更为灵活,可以在类创建时指明其中的元素类型T,以及非类型常量num的大小,需要注意的是,不管是类定义还是成员函数定义,都要遵守模板的定义形式。

类模板的模板参数可以是:类型参数(数据结构也算类型,如vector<int>)、非类型参数(只能是int)。需要注意的是,类模板的模板参数可以有默认值,而函数模板的模板参数不能有默认值。

类模板和成员函数模板不是传统意义上的类定义和成员函数定义,由类模板实例化而生成的具体类称为模板类,然后使用模板类才可以实例化对象:类名<数据类型实参表> 对象名称,包含两个过程:类模板实例化为模板类 -> 模板类实例化一个对象

注意:类模板只能显示实例化为模板类,不能像函数模板那样由编译器自动推导(隐式)。

stack.hpp:
普通版本:

#include <stdexcept>

template<typename T>
class Stack
{
public:
    explicit Stack(int max_size); // 最大值通过构造函数传入,存入数据成员
    ~Stack();

    void Push(const T& elem);
    void Pop();
    T& Top();
    const T& Top() const;
    bool IsEmpty() const;
private:
    T* elems_;
    int max_size_;
    int top_;
};

// 成员函数的定义格式,注意类名限定是Stack<T>,也就是模板类名,而不是类模板名
template<typename T> 
Stack<T>::Stack(int max_size) : max_size_(max_size), top_(-1)
{
    elems_ = new T[max_size_];
}

template<typename T>
Stack<T>::~Stack()
{
    if (elems_)
    {
        delete[] elems_;
    }
}

template<typename T>
void Stack<T>::Push(const T& elem)
{
    if (top_ + 1 >= max_size_) {
        throw std::out_of_range("Stack<>::Push() stack full");
    }

    elems_[++top_] = elem;
}

template<typename T>
void Stack<T>::Pop()
{
    if (top_ < 0) {
        throw std::out_of_range("Stack<>::Pop() stack empty");
    }

    --top_;
}

template<typename T>
T& Stack<T>::Top()
{
    if (top_ < 0) {
        throw std::out_of_range("Stack<>::Pop() stack empty");
    }

    return elems_[top_];
}

template<typename T>
const T& Stack<T>::Top() const
{
    if (top_ < 0) {
        throw std::out_of_range("Stack<>::Pop() stack empty");
    }

    return elems_[top_];
}

template<typename T>
bool Stack<T>::IsEmpty() const
{
    return top_ < 0;
}

int main()
{
    Stack<int> stack(10); // 必须显示实例化
    stack.Push(1);
    stack.Push(2);
    stack.Push(3);

    while(!stack.IsEmpty()) {
        cout << stack.Top() << endl;
        stack.Pop();
    }

    return 0;
}

非类型模板参数版本:

#include <stdexcept>

// 最大值改为作为模板参数,编译期替换,不需要额外的数据成员来保存,与宏类似。
// 类模板的模板参数可以指定默认值,默认值只需要在声明处提供一次就行,类成员
// 函数定义处就不需要了。
template<typename T, int MAX_SIZE = 10> 
class Stack2
{
public:
    explicit Stack2(); // 不需要再传最大值这个参数
    ~Stack2();

    void Push(const T& elem);
    void Pop();
    T& Top();
    const T& Top() const;
    bool IsEmpty() const;
private:
    T* elems_;
    int top_;
};

// 末模板参数变了,这里的形式也就跟着变了,但格式是一致的
// 这里不用再指定默认值
template<typename T, int MAX_SIZE>
Stack2<T,MAX_SIZE>::Stack2() : top_(-1)
{
    elems_ = new T[MAX_SIZE];
}

template<typename T, int MAX_SIZE>
Stack2<T,MAX_SIZE>::~Stack2()
{
    if (elems_)
    {
        delete[] elems_;
    }
}

template<typename T, int MAX_SIZE>
void Stack2<T,MAX_SIZE>::Push(const T& elem)
{
    if (top_ + 1 >= MAX_SIZE) {
        throw std::out_of_range("Stack2<>::Push() stack full");
    }

    elems_[++top_] = elem;
}

template<typename T, int MAX_SIZE>
void Stack2<T,MAX_SIZE>::Pop()
{
    if (top_ < 0) {
        throw std::out_of_range("Stack2<>::Pop() stack empty");
    }

    --top_;
}

template<typename T, int MAX_SIZE>
T& Stack2<T,MAX_SIZE>::Top()
{
    if (top_ < 0) {
        throw std::out_of_range("Stack2<>::Pop() stack empty");
    }

    return elems_[top_];
}

template<typename T, int MAX_SIZE>
const T& Stack2<T,MAX_SIZE>::Top() const
{
    if (top_ < 0) {
        throw std::out_of_range("Stack2<>::Pop() stack empty");
    }

    return elems_[top_];
}

template<typename T, int MAX_SIZE>
bool Stack2<T,MAX_SIZE>::IsEmpty() const
{
    return top_ < 0;
}

int main(void)
{
    Stack2<int, 10> stack; // max_size以模板参数的形式传递给类模板
    stack.Push(1);
    stack.Push(2);
    stack.Push(3);

    while (!stack.IsEmpty()) {
        cout << stack.Top() << endl;
        stack.Pop();
    }

    return 0;
}

使用标准库已有数据结构的版本:

#include <stdexcept>
#include <deque> // 对标准库容器封装一下

// 可以传数据结构(这里的容器,其实也是一个类型)进来
template<typename T, typename CONT = std::deque<T>>
class Stack3
{
public:
    explicit Stack3();
    ~Stack3();

    void Push(const T& elem);
    void Pop();
    T& Top();
    const T& Top() const;
    bool IsEmpty() const;
private:
    CONT container_;
};

template<typename T, typename COUNT>
Stack3<T,COUNT>::Stack3()
{
    container_.clear();
}

template<typename T, typename COUNT>
Stack3<T,COUNT>::~Stack3()
{
}

template<typename T, typename COUNT>
void Stack3<T,COUNT>::Push(const T& elem)
{
    container_.push_back(elem);
}

template<typename T, typename COUNT>
void Stack3<T,COUNT>::Pop()
{
    container_.pop_back();
}

template<typename T, typename COUNT>
T& Stack3<T,COUNT>::Top()
{
    return container_.back();
}

template<typename T, typename COUNT>
const T& Stack3<T,COUNT>::Top() const
{
    return container_.back();
}

template<typename T, typename COUNT>
bool Stack3<T,COUNT>::IsEmpty() const
{
    return container_.empty();
}

模板嵌套-成员模板

模板的套嵌可以理解成在另外一个模板里面定义一个模板。以模板(类,或者函数)作为另一个模板(类,或者函数)的成员,也称成员模板。

成员模版是不能声明为virtual的。

函数成员模板

在成员函数中使用类模板形参列表中未声明的参数类型(如不同的模板参数的模板类对象之间相互赋值时的赋值运算符),需要将成员函数写成函数模板:

template<typename T>
class MyClass
{
public:
    // 相同类型的类(模板类)对象赋值,可以访问私有成员
    void Assign(const MyClass<T>& other) {
        value_ = other.value_;
    }
private:
    T value_;
};

template<typename T>
class MyClass2
{
public:
    // 函数成员模板
    template<class X>
    // MyClass2<X>与MyClass2<T>有可能是不同类型,所以不能直接访问私有成员,
    // 需要提供公有接口用于访问私有成员,可以访问私有成员.
    void Assign(const MyClass2<X>& other) {
        value_ = other.GetValue();
    }
    T GetValue() const {
        return value_;
    }
private:
    T value_;
};
对象成员模板

类模板的定义可以放在另一个类中,实例化后的模板类对象可以作为另一个类的成员:

#include <iostream>
using namespace std;

template<class T>
class Outside
{
public:
    template <class R>
    class Inside
    {
    private:
        R r;
    public:
        Inside(R x) // 模板类的成员函数可以在定义时实现
        {
            r = x;
        }

        void disp() 
        { 
            cout << "Inside: " << r << endl; 
        }
    };

    Outside(T x) : t(x)
    {}

    void disp()
    {
        cout << "Outside:";
        t.disp();
    }

private:
    Inside<T> t;
};

// 模板类的成员函数也可以在定义外实现,
// 但必须是在所有类定义的外边,不能放在Outside内Inside外去实现.
//template<class T>
//template<class R>
//void Outside<T>::Inside<R>::disp() 
//{ 
//  cout<<"Inside: "<<Outside<T>::Inside<R>::r<<endl;
//}

//template<class T>
//void Outside<T>::disp()
//{
//  cout<<"Outside:";
//  t.disp();
//}

int main()
{
    Outside<int>::Inside<double> obin(3.5); // 声明Inside类对象obin
    obin.disp();

    Outside<int> obout(2); //创建Outside类对象obout
    obout.disp();

    getchar();
    return 0;
}

模板类的成员函数可以在定义时实现,也可以在定义外实现,但必须是在所有类定义的外边,不能放在Outside内Inside外去实现。注意在外部实现时的格式。

在Outside类内使用Inside<T> t;语句声明了Inside<T>类的对象,在Outside模板类对象创建时,首先采用隐式实例化先生成Inside<T>类的定义,而后根据此定义创建对象成员t。


模板做模板参数

模板包含类型参数(如class Type)和非类型参数(如int NUM,NUM是常量),实际上,模板的参数可以是另一个模板,也就是说,下述形式是合法的:
template<template <typenameT1> typenameT2, typenameT3, int Num>;

#include <iostream>
using namespace std;

template <typename T, int num>
class Stack
{
private:
    T sz[num];
public:
    int ReturnNum();
};

template<typename T1, int num1> // 参数列表不要求字字相同,但形式要相同
int Stack<T1, num1>::ReturnNum()
{
    return num1;
}

// 注意模板做模板参数时的格式
template<template<typename Type, int NUM> typename TypeClass, typename T1, int N>
void disp()
{
    TypeClass<T1, N> ob;
    cout << ob.ReturnNum() << endl;
}

int main()
{
    disp<Stack, int, 8>();

    return 0;
}

模板实现的单例模式

#include <cstdlib>

// 模板类的包装器,用它可以产生单例模式的类,返回的是T
template<typename T>
class Singleton
{
public:
    static T& GetInstance()
    {
        Init();
        return *instance_;
    }

private:
    static void Init()
    {
        if (instance_ == NULL) {
            instance_ = new T;
            atexit(Destroy); // 每注册一次,就会在程序结束的时候调用一次;可以注册多个,后注册的会先调用
        }
    }
    static void Destroy()
    {
        delete instance_;
    }
    Singleton();
    Singleton(const Singleton& other);
    Singleton&operator=(const Singleton& other);
    ~Singleton();

    static T* instance_;
};

template<typename T>
T* Singleton<T>::instance_ = NULL;


class ApplicationImpl
{
public:
    ApplicationImpl()
    {
        cout << "Application ..." << endl;
    }
    ~ApplicationImpl()
    {
        cout << "~Application ..." << endl;
    }
    void Run()
    {
        cout << "Run ..." << endl;
    }
};

// ApplicationImpl本身不是单例模式,但是经过包装器Singleton包装后就是单例模式了.
typedef Singleton<ApplicationImpl> Application;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值