IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++实验_3:模板 -> 正文阅读

[C++知识库]C++实验_3:模板

实验背景

本次实验基于Qt Creator下的c++环境,实现了关于C++特有的模板功能,C++最重要的特性之一就是代码重用,为了实现代码的通用性,需要让代码不受数据类型的影响,并且可以自动适应数据类型的变化。这就是参数化程序设计,模板是参数化程序设计的工具之一,可以实现参数多态性。所谓参数多态性,就是将程序所处理的对象的类型参数化,使得一段程序可以用于处理多种不同类型的对象。

1.模板函数

模板函数定义形式:
template<模板参数表>
类型名 函数名(参数表)
{
函数体的定义
}
所有函数模板的定义都是用关键字template开始的。函数参数表由用逗号分隔的模板参数构成,可以包括以下内容
1.class(或typename)标识符,指明可以接受一个类型参数。这些类型参数代表的是类型,可以是预定义类型或者自定义类型。
2.类型说明符 标识符,指明可以接受一个由"类型说明符"所规定类型的常量作为参数。
3.template<参数表>class 标识符,指明可以接收一个类模板名作为参数。
看起来有点蒙,我们举个具体例子

1.1.一般模板函数

如果我们要比较数据的大小,我们一般会使用compare函数,而如果我们要比较int和char两种数据类型,我们就需要定义两种输入参数的函数。

//int类型比较
int compare(const int v1,const int v2)
{
    if(v1>v2)
        return 1;
    else if(v1<v2)
        return -1;
    else
        return 0;
}
//char类型比较
int compare(const char v1,const char v2)
{
    if(v1>v2)
        return 1;
    else if(v1<v2)
        return -1;
    else
        return 0;
}
#endif // COMPARE_H

主函数:

int main()
{
    int a=20,b=10;
    char m='m',x='x';
    cout<<compare(a,b)<<endl;
    cout<<compare(m,x)<<endl;
    return 0;
}

通过函数重载来进行不同数据类型的比较,但如果需要比较的数据类型增多,这样会很麻烦,模板函数只需要一个函数即可解决所有的函数类型。
模板函数:

template <typename T>
//compare前面的int是因为返回值是1,-1,0,如果要返回和输入类型相同的值,将int改为T
int compare(const T v1,const T v2)
{
    if(v1>v2)
        return 1;
    else if(v1<v2)
        return -1;
    else
        return 0;
}

这样不管是什么类型的数据,都可以用这个函数进行比较。
总结:
1.函数模板中的类型参数T,表示一种抽象的类型,T没有任何含义,只是一个代号。当编译器检测到调用compare函数时,会用第一个实参的类型替换掉整个模板定义中的T,重新构造成一个完整的函数后编译这个新建的函数。
2.函数模板和重载时密切相关的。从函数模板产生的相关函数都是同名的,编译器用重载的解决的方法调用相应的函数。

1.2.特化模板函数

尽管模板有这么多好处,但我们要明确的认识到,世界上不可能存在一种方法可以解决所有的问题。所以就要引入特化模板,专门针对某种类型做出调整。

//头文件内声明
template <>
int compare<char*>(char*v1,char*v2);
//cpp文件实现
template<>
int compare<char*>(char*v1,char*v2)
{
    return strcmp(v1,v2);
}

这样就实现了关于语句的比较,而特化声明要放在模板函数的下面。
函数模板注意事项:
1.函数模板本身在编译时不会生成任何目标代码,只有由模板生成的实例会生成目标代码。
2.被多个源文件引用的函数模板,应当连同函数体一同放在头文件中,而不能像普通函数那样只将声明放在头文件中。
3.函数指针也只能指向模板的实例,而不能指向模板本身。

2.模板类Queue

类模板声明的语法形式是:
template<模板参数表>
class 类名
{
类成员声明
}
典型的例子就是vector,这个容器就是一个类模板,可以存入或读取任意类型的变量。这就是我们使用类模板的目的,可以让任意类型的对象使用相同的方法,而不需要单独设置。

2.1.模板类

//队列元素
template <class Type>
class QueueItem 
{
    Type item;//存储数据
    QueueItem * next;//队列下一个元素的地址
    QueueItem(const Type & data):item(data),next(0){};//参数列表构造
    friend class Queue<Type>;//友元授权
    friend ostream& operator<<(ostream& os, const Queue<Type> & q); //重载输出元素操作符
public:
    QueueItem<Type>* operator++()//返回队列下一个元素的地址
    {
        return next;
    }
    Type & operator*() //取出存储的元素
    {
        return item; 
    }
};
//队列
template <class Type> class Queue
{
private:
    void copy_items(const Queue &orig); //拷贝起始元素
    QueueItem<Type>* head;//队列头指针
    QueueItem<Type>* tail;//队列尾指针
    void destroy(); //释放队列空间
    template<class It> void copy_items(It beg, It end); //拷贝指定范围内的队列元素
public:
    Queue():head(0),tail(0){}; //参数列表构造,初始化head指针,tail指针
    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;} //返回队头
    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;}
};

2.2.成员模板函数

和模板函数一样,成员模板函数提供了任意类型均可使用的方法

//出队
template <class Type> void Queue<Type>::pop(){
    QueueItem<Type> * p =head;
    head = head->next;
    delete p; //释放空间
}
//删除队列
template <class Type> void Queue<Type>::destroy()
{
    while(!empty()){
        pop();
    }
}
//入队
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=pt; //尾部指针需要始终指向最后一个元素
    }
}
//将队列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;
    }
}

2.3.模板特化

模板成员函数特化和模板函数特化一样,头文件声明,cpp文件实现,特化类型在<>内声明

template<>
void Queue<const char*>::push(const char * const &val){
    char* new_item = new char[strlen(val)+1];
    strncpy(new_item,val,strlen(val)+1);
    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<const char*> * p = head;
    delete head->item;
    head = head->next;
    delete p;
}

模板类特化

template <> class Queue<const char *>
{

private:
    void copy_items(const Queue &orig); //拷贝起始元素
    QueueItem<const char *>* head;//队列跟需要头和尾两个指针
    QueueItem<const char *>* tail;//使用含有模板类组成的QueueItem类需要使用模板声明
    void destroy(); //释放队列空间
    template<class It> void copy_items(It beg, It end); //指定范围拷贝队列元素
public:
    Queue():head(0),tail(0){}; //参数列表构造器,初始化head指针,tail指针
    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();} //析构函数
    const char *& front(){return head->item;} //返回队列最前头的
    void push(const char *&val){ //对const char *模板函数进行特化
        char* new_item = new char[strlen(val)+1]; //根据字符串长度进行创建字符数组
            strncpy(new_item,val,strlen(val)+1); //拷贝字符内容
            QueueItem<const char* >*pt = new QueueItem<const char *>(new_item); //声明模板特化char *类
            if(empty()){
                head = tail = pt;
            }
            else{
                tail->next = pt;
                tail = pt;
            }
    }; //将元素放入队列
    void pop(){
        QueueItem<const char *> *p = head; //特化模板类QueueItem
            delete head->item;//char *数据需要自己管理,所以自己释放
            head = head->next;
            delete p; //释放指针空间
    }; //去除队列头部元素
    bool empty() const{return head==0;} //判断队列元素是否为空
    friend ostream& operator<<(ostream& os, const Queue<const char *> &q)
    {
        os<<"< ";
        QueueItem<const char *> * p;
        for(p=q.head;p;p=p->next)
        {
            os<<p->item<<" ";
        }
        os<<">";
        return os;
    }
    //访问头部和尾部的函数
    const QueueItem<const char *>* Head() const{return head;}
    const QueueItem<const char *>* End() const {return(tail==NULL)?NULL:tail;}
};

3.模板类AutoPtr

对于C和C++来说,指针的使用即是特色,也是弱点,不合适的指针使用可能会导致一系列问题,比如内存溢出,数据泄露等。因此如何更方便的使用指针呢,这就是AutoPtr(智能指针)的目标。

3.1.构造函数

template<class T>
AutoPtr<T>::AutoPtr(T* pData)
{
    m_pData = pData;
    m_nUser = new int(1);
}

3.2.析构函数

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;
    }
}

3.3.拷贝构造函数

template<class T>
AutoPtr<T>::AutoPtr(const AutoPtr<T>& h)
{
    m_pData = h.m_pData;
    m_nUser = h.m_nUser;
    (*m_nUser)++;
}

3.4.等号、->、*等运算符重载

=重载

AutoPtr<T>& AutoPtr<T>::operator=(const AutoPtr<T>& h)
{
    decrUser();
    m_pData = h.m_pData;
    m_nUser = h.m_nUser;
    (*m_nUser)++;
}

->重载

T* operator->()
{
    return m_pData;
}
const T* operator ->() const{
    return m_pData;
}

*重载

T& operator*()
{
    return *m_pData;
}
const T& operator *() const{
     return *m_pData;
}

3.5.主函数调用AutoPtr

void TestAutoPtr();
int main()
{
    TestAutoPtr();
    return 0;
}
void TestAutoPtr()
{
    AutoPtr<CMatrix> h1;
    double data[6] = {1,2,3,4,5,6};
    h1->Create(2,3,data);
    cout << *h1 << endl;
    AutoPtr<CMatrix> h2(h1);
    (*h2).Set(0,1,10);
    cout << *h1 << endl << *h2;
}

在这里插入图片描述
h2通过拷贝构造函数创建,h2调用Set方法后改变的是同一个地址的值,所以最后h1和h2输出结果相同。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-18 11:01:39  更:2021-11-18 11:04:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:27:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码