【C++实战三】模板入门

【C++实战三】模板入门

1. 函数模板

**函数模板定义:**建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。

函数模板语法:

template <类型形式参数表>
类型 函数名(形式参数表)
{
	语句序列
}

例:
template <typename T>
void swap(T &a, T &b)
{
	T t;
	t = a;
	a = b;
	b = t ;
}

函数模板要点:

  • 函数模板定义由模板说明函数定义组成。
  • 模板说明的类属参数必须在函数定义中至少出现一次
  • 函数参数表可以使用类属类型的参数,也可以用一般类型的参数。

1.1 一般模板函数

举一个简单的例子,当我们在进行数值大小的判断时,如果输入值的类型不同时,我们的比较函数需要编写多次。而使用模板函数,可以通过输入值的类型进行虚拟类型的代表,最后实现一个函数适用于各类输入数据。

// typename是对函数模板的定义,T指根据情况指定的虚拟类型
// 一般模板函数只能在.h头文件进行定义
template <typename T>
int compare(const T& v1, const T& v2){
    if(v1<v2) return -1;
    if(v1>v2) return 1;
    return 0;
}
int main()
{
     // 定义int, double, char三种类型变量的比较
     int n1 = 5, n2 = 3;
     double d1 = 1.1, d2 = 5.9;
     const char * v1 = "bbb";
     const char * v2 = "aaa";
     // 输出字符串地址
     cout << (int *)v1 << endl;
     cout << (int *)v2 << endl;
     // 输出比较结果
     cout << compare(n1,n2) << endl;
     cout << compare(d1,d2) << endl;
     cout << compare(v1,v2) << endl;
     return 0;
}

运行结果:

0x405069

0x40506d

1

-1

-1

1.2 特化模板函数

虽然一般模板函数可以实现功能的通用,但难免有时候我们会对一些类型提供特殊待遇。比如上述的例子中,字符串不能单纯采用 ”<“ 和 “>” 来比较,因为这将比较的是字符串的地址,而不是字符串的内容这时候就可以使用特化模板函数另写一个实现方法:

//模板特化函数的声明
//特化函数的实现只能写在CPP内(不同于一般模板函数)
template <>
int compare<const char *>(const char* const& v1, const char * const& v2) 
{
    //调用字符串比较函数
    return strcmp(v1,v2); 
}

0x405069

0x40506d

1

-1

1

2. 模板类

模板函数可以减少对于多个功能相似函数的编写;在工程项目中,同样要白那些多个形式和功能都相似的类,于是c++引入了模板类的概念。

函数类语法:

template <类型参数表>
class 类模板名{
    成员函数和成员变量
};

2.1 使用模板类构建Queue队列数据结构

管理队列元素类QueueItem的构造:

// 模板类
// 提前声明Queue,在管理队列元素类时会用到
template<class Type> class Queue;
template<class Type> 
class QueueItem
{
    // 实现构造器
    QueueItem(const Type& t) : item(t), next(0) {}
    // 队列元素
    Type item;
    // 队列指针
    QueueItem* next;
    // 友元类
    friend class Queue<Type>;
    //输出运算符的重载
    friend ostream& operator<<(ostream& os, const Queue<Type>& q);
    // 指针地址的++
    QueueItem<Type>* operator++()
    {
        return next;
    }
    // 取值运算符的重载
    Type& operator*() 
    {
        return item;
    }
};

对列Queue类的构造:

template<class Type> 
class Queue 
{
public:
    // 参数列表构造器;初始化head指针;初始化tail指针
    Queue() : head(0), tail(0) {}
    // 拷贝构造器
    Queue(const Queue& q) : head(0), tail(0)
    {
        copy_items(q);
    }
    //成员函数模板
    template <class It>
    // 制定范围
    Queue(It beg, It end) : head(0), tail(0)
    {
        copy_items(beg, end); 
    }
    template<class It> void assign(It beg, It end);
    Queue& operator=(const Queue&);
    //析构函数
    ~Queue() { destroy(); }
    // 返回队列首元素
    Type& front() { return head -> item; }
    const Type& front() const { return head -> item; }
    //元素入队
    void push(const Type&);
    //队尾元素出队
    void pop();
    bool empty() const { return head == 0; }
    //输出符重载(第二个参数表示待输出的队列)
    //(全局)友元函数
    friend ostream& operator<<(ostream& os, const Queue<Type>& q)
    {
        os << "< ";
        QueueItem<Type>* p;
        for (p = q.head; p; p = p -> next) 
        {
            os << p->item << " ";
        }
        os << ">";
        return os;
    }
    
    //访问头部和尾部的函数
    const QueueItem<Type>* Head() const
    {
        return head;
    }
    const QueueItem<Type>* End() const
    {
        return (tail == NULL) ? NULL : tail -> next;
    }
    
private:
    // 队列的头尾指针
    QueueItem<Type>* head;
    QueueItem<Type>* tail;
    // 释放队列空间
    void destroy();
    // 拷贝起始元素
    void copy_items(const Queue&);
    // 指定范围拷贝队列元素
    template<class It>
    void copy_items(It beg, It end);
};

//去除队列的头部数据
template<class Type>
void Queue<Type>::pop() 
{
    QueueItem<Type>* p = head;
    head = head -> next;
    // 释放空间
    delete p;
}

// 队列中插入数据,尾插法
template<class Type>
void Queue<Type>::push(const Type& val) 
{
    QueueItem<Type>* pt = new QueueItem<Type>(val);
    if (empty())
    {
        // 头部和尾部指针指向相同数据
        head = tail = pt;
    }
    else 
    {
        tail -> next = pt;
        //元素添加到队列后tail指针指向该元素
        tail = pt;
    }
}

template < >
void Queue<const char*>::push(const char* const& val);

template < >
void Queue<const char*>::pop();

// 将队列orig的所有元素插入其他队列,原队列元素保留
template <class Type>
void Queue<Type>::copy_items(const Queue& orig)
{
    for (QueueItem<Type>* pt = orig.head; pt; pt = pt -> next)
    {
        push(pt -> item);
    }
}

template <class Type>
Queue<Type>& Queue<Type>::operator=(const Queue& q)
{
    destroy();
    copy_items(q);
}

template<class Type> template<class It> void Queue<Type>::assign(It beg, It end)
{
    destroy();
    copy_items(beg, end);
}

//拷贝指定范围的队列元素插入原队列
template<class Type> template<class It> void Queue<Type>::copy_items(It beg, It end)
{
    while (beg != end)
    {
        push(*beg);
        ++beg;
    }
}

//销毁队列
template<class Type>
void Queue<Type>::destroy()
{
    while (!empty())
    {
        pop();
    }
}

主函数:

#include "queue.h"
#include<cstring>
#include<iostream>
void TestQueue()
{
    // 创建字符队列
    Queue <char>q;
    // 将数据插入队列
    q.push('A');
    q.push('B');
    q.push('C');
    // 将数据移出队列
    q.pop();
    // 输出队列首元素和队列
    cout << "队列首元素:" << q.front() << endl;
    cout << q ;
}
int main() {
    TestQueue();
    system("pause");
    return 0;
}

运行结果:

队列首元素:B

< B C >

2.2 成员模板函数

模板类的成员函数通常需要用到模板类型参数(成员模板函数)。

//拷贝指定范围的队列元素插入原队列
template<class Type> template<class It> void Queue<Type>::copy_items(It beg, It end)
{
    while (beg != end)
    {
        push(*beg);
        ++beg;
    }
}

2.3 模板成员函数的特化

// 模板类Queue中的成员函数push()和pop()
template < >
void Queue<const char*>::push(const char* const& val);

template < >
void Queue<const char*>::pop();

当我们的模板函数无法对所有数据类型生效的时候,我们就需要对部分函数进行特化,这里我们对模板类Queue中的成员函数push()和pop()进行特化,使其实现字符串的入队和出队操作。

template <>
void Queue<const char*>::push(const char* const& val)
{
    // 根据字符串长度创建字符数组
    char* new_item;
    // 拷贝字符内容
    new_item = new char[strlen(val) + 1];
    strncpy_s(new_item, strlen(new_item), val, strlen(val));
    QueueItem<const char*>* pt = new QueueItem<const char*>(new_item);
    if (empty())
    {
        head = tail = pt;
    }
    else 
    {
        tail->next = pt;
        tail = pt;
    }
}

template <>
void Queue<const char*>::pop() 
{
    // 特化模板类QueueItem
    QueueItem<const char*>* p = head;
    delete head->item;
    head = head->next;
    // 释放指针空间
    delete p;
}

3. 智能指针

JAVA可以自动释放申请内存,然而C++并不具备这个特点。因此在C++工程的编写过程中,经常会产生类似内存泄露的问题。(最近在用C++写界面,当写到通过键盘来浏览图片文件夹时,经常由于忘记释放内存导致浏览过程中程序崩溃)

为了避免繁琐的内存管理问题,同时更加安全的使用系统内存,c++引入了智能指针。智能指针与常规指针的区别在于,它能够自动的判断所指向的内存是否还指针指向它,如果这片内存已没有任何常规指针指向它,则认为这片内存的生命周期已经结束,自动的释放这片内存占用的区域,留给他人使用。

接下来我们通过代码分析智能指针的优势:

template <class T>
class AutoPtr
{
public:
    //构造函数
    AutoPtr(T* pData);
    //拷贝构造函数
    AutoPtr(const AutoPtr<T>& h);
    //声明周期结束时调用析构函数
    ~AutoPtr();
    //”=“重载
    AutoPtr<T>& operator=(const AutoPtr<T>& h);
    //用户数减1
    void decrUser();
    //”->“重载(返回的指针允许被改变)
    T* operator ->() {
        return m_pData;
    }
    //”*“重载(能使用成员运算符(".")来访问成员变量)
    T& operator*() {
        return *m_pData;
    }
    // ”*“重载(不能使用成员运算符(".")来访问成员变量)
    const T& operator *() const {
        return *m_pData;
    }
    //”->“重载(返回的指针不允许被改变)
    const T* operator -> () const {
        return m_pData;
    }
private:
    //存储数据
    T* m_pData;
    //存储用户数
    int* m_nUser;
};

// 构造函数
template < class T>
AutoPtr<T>::AutoPtr(T* pData)
{
    m_pData = pData;
    //初始化用户数为1
    m_nUser = new int(1);
}

// 析构函数
template < class T>
void AutoPtr<T>::decrUser()
{
    --(*m_nUser);
    if ((*m_nUser) == 0) {
        //删除数据
        delete m_pData;
        //地址赋为空
        m_pData = 0;
        delete m_nUser;
        m_nUser = 0;
    }
}

template < class T>
AutoPtr<T>::~AutoPtr()
{
    decrUser();
}

// 拷贝构造函数
template < class T>
AutoPtr<T>::AutoPtr(const AutoPtr<T>& h) {
    m_pData = h.m_pData;
    m_nUser = h.m_nUser;
    //用户数加1
    (*m_nUser)++;
}

// "="重载
template < class T>
AutoPtr<T>& AutoPtr<T>::operator=(const AutoPtr<T>& h)
{
    decrUser();
    m_pData = h.m_pData;
    m_nUser = h.m_nUser;
    (*m_nUser)++;
}

主函数调用智能指针:

void TestAutoPtr()
{
	// 创建一个类的智能指针
    AutoPtr<CMatrix> h1;
    // 定义数据
    double data[6] = {1,2,3,4,5,6};
    // 生成2行3列的数组(下标从0开始)
    h1 -> Create(2,3,data);
    cout << *h1 << endl;
    // h2是由拷贝函数创建的,和h1指向同一个地方
    AutoPtr<CMatrix> h2(h1);
    // 通过set()函数改变同一地址的值
    (*h2).Set(0,2,41);
    cout << *h1 << endl << *h2;
}

int main()
{
    TestAutoPtr();
    return 0;
}

运行结果:

2 3

1 2 3

4 5 6

2 3

1 2 41

4 5 6

2 3

1 2 41

4 5 6

4. 总结

  • 模板为工程代码的编写提供了极大的便利,需要注意的是一般模板函数的声明放在头文件中,而特化模板函数声明放在CPP文件中。
  • 函数模板、类模板均可进行特化。在工程搭建的过程中,不是所有的相似功能都是通过通用函数实现的,当我们需要对某一类型数据进行特殊处理时,特化就显得特别重要了。
  • 在做一个C++项目工程时,如果每次new出来的内存都要手动delete,这将显得特别的麻烦。而智能指针可以通过析构函数自动释放它管理的内存(在对象生命期即将结束时)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值