C++模板

1.模板函数

C++中的模板分为函数模板和类模板,我们先来看看函数模板,看如下程序:

cpp 复制代码
template <class T>        //模板函数,T可以是任何类型包括int,float,double,string等,具体类型取决于传入的参数
T add(T a, T b)
{
    return a + b;
}

该段程序就是定义的一个函数模板,函数模板在编译时编译器不会将其生成可执行代码其不是一个实在的函数,当具体要执行时就会根据可以传入的参数决定其具体功能。template是定义模板的关键字,<>里面的是模板参数,T可以是任何类型的参数,包括自定义类型,class是类型参数等同于typename关键字。

当有和模板函数同名的函数时就构成了模板函数的重载如下:

cpp 复制代码
template <class T>        //模板函数,T可以是任何类型包括int,float,double,string等,具体类型取决于传入的参数
T add(T a, T b)
{
    cout << "T add(T a, T b)" << endl;
    return a + b;
}

int add(int a, int b)     //模板函数的重载,当调用同名函数时若有匹配参数类型的的函数就调用匹配类型的参数,若没有则调用模板函数兼容
{
    cout << "int add(int a, int b)" << endl;
    return a + b;
}

float add(float a, float b)
{
    cout << "float add(float a, float b)" << endl;
    return a + b;
}

当调用同名函数时,会优先调用类型匹配的函数没有类型匹配的则会调用模板函数。

2.模板类

我们下面来看看模板类是怎么定义的呢?看下面的程序:

cpp 复制代码
template <class T>
class point
{
private:
    T x, y;

public:
    point(T xx, T yy);
    T getx();
    T gety();
    void showpoint();
};

可见模板类的也要在类的前面写上template关键字和<>带上参数,对于模板类中的成员可以是任意类型的。注意模板类的成员函数要是在类外定义的话和普通类的成员函数的定义存在区别如下:

cpp 复制代码
template <class T>           //模板类的成员函数在类外定义时,一定要加上这句话
point<T>::point(T xx, T yy)  //不同于普通的类成员函数在类外定义时,模板类的成员函数定义要在类名后加上<T>,T表示该模板类的类型,可以是任意类型
{
    x = xx;
    y = yy;
}

template <class T>
T point<T>::getx()           //对于有返回值的返回值也要写成T
{
    return x;
}

template <class T>
T point<T>::gety()
{
    return y;
}

template <class T>
void point<T>::showpoint()
{
    cout << "(" << x << "," << y << ")" << endl;
}

在每个成员函数定义前都要加上template <class T>这句话,作用域限定符前的类名后也要带上<T>表示该成员函数是模板类的成员函数。

cpp 复制代码
int main(int argc, char const *argv[])
{
    point<int> p1(1,2);       //在创建对象时要将point<T>中的T用具体的类型替换
    p1.showpoint();
    point<float> p2(1.1,2.2);
    p2.showpoint();
    point<string> p3("5","7");
    p3.showpoint();
    return 0;
}

在创建对象时,要将T用具体类型替换,该类型可以是任意类型。

在模板类中也可以对T设置一个默认的数据类型如下:

cpp 复制代码
template <class T1,class T2 = float>   //带默认参数的模板类,T1,T2都可以时任意类型,在不指定T2类型时会使用默认float类型
class point
{
private:
    T1 x;
    T2 y;

public:
    point(T1 xx, T2 yy);
    T1 getx();
    T1 gety();
    void showpoint();
};

可见该类我们第二个T我们就让其带上了默认类型float,当没有具体类型参数传入时就会使用默认类型。

cpp 复制代码
int main(int argc, char const *argv[])
{
    point<int , int> p1(1,2);    //在创建对象时要将T1用具体的类型替换,但是对于有默认类型参数的T2来说可以用具体参数也可以不是使用
    p1.showpoint();              //在没有定义时就是使用默认的float类型
    point<float> p2(1.1,2.2);
    p2.showpoint();
    point<string,string> p3("5","7");
    p3.showpoint();
    return 0;
}

可见在创建对象时可以给带默认参数类型的参数传入指定类型也可以不传。

cpp 复制代码
template <class T1, class T2 = float, int N = 10> // 带默认参数的模板类,T1,T2都可以时任意类型,在不指定T2类型时会使用默认float类型
class point                                       // 且在定义模板时还可以为默认的数据类型声明一个变量,并且给变量赋值
{
private:
    T1 x;
    T2 y;

public:
    point(T1 xx, T2 yy);
    T1 getx();
    T1 gety();
    void showpoint();
};

在模板类中我们还可以在定义模板类时用数据类型声明一个变量并且给变量赋值来,通过该值对模板类进行一些特殊的用法。

cpp 复制代码
template <class T1, class T2, int N>  // 当模板类参数有多个时,<>里面也要写入多个参数
point<T1, T2, N>::point(T1 xx, T2 yy) // 不同于普通的类成员函数在类外定义时,模板类的成员函数定义要在类名后加上<T>,T表示该模板类的类型,可以是任意类型
{
    x = xx + N;
    y = yy + N;
}

如上我们可以使用我们的变量来对该类的私有成员变量进行赋初始值,当然也可以有很多别的用法可以自己来实现。

在使用模板类时以为创建对象时可以传入任何类型的参数当然也就包含了类型,所以当要传入类类型时我们就需要注意一个运算符操作的问题了,如下:

cpp 复制代码
class point
{
private:
    int x, y;

public:
    point(int x=0, int y=0 )
    {
        this->x = x;
        this->y = y;
    }
    friend ostream& operator<<(ostream& os , const point& p)   //ostream& 是类类型引用,os是cout的引用,即cout
    {                                                          //因为<<不能直接对类进行操作,所以我们对该运算符进行重载
        os << "(" << p.x << "," << p.y << ")" << endl;       //cout<<x 重载为了(operator<<)(cout ,p)
        return os;
    }
};

template <class T>
class set
{
private:
    T x;

public:
    set(T x)
    {
        this->x = x;
    }
    void display();
};

template <class T>
void set<T>::display()
{
    cout << x << endl;
}

int main(int argc, char const *argv[])
{
    set<int> s1(10);
    s1.display();
    set<float> s2(10.1);
    s2.display();
    set<point> s3(point(10, 20)); // 当该模板类的类型是类类型时,要调用显示函数时就会出错,因为cout不能直接输出类
    //注意:在set<point>的模板实例化过程中实际传入的是一个point类的临时对象该对象没有参数,所以需要调用point类的无参构造函数
    //但是类中一旦声明了有参构造之后系统就不会默认生成无参构造了,在编译时就可能会报错
    s3.display();
    return 0;
}

可见我们想要的功能是将创建对象时直接赋值的参数输出显示出来,但是我们知道<<运算符并不支持对类类型的操作,我们我们就要用我们前面说到的运算符重载来解决这个问题,其实对于运算符重载实际上就是你想要将该运算符重载成实现出你想要的功能。

下面我们来讲讲模板类中的链表类模板,创建链表类模板就是创建一个对象的容器,在容器内可以对不同类型的对象进行插入、删除和排序等操作。

cpp 复制代码
#include <iostream>
using namespace std;


template <class T>           //链表类模板
class list                   //链表类
{
private:
    T data;
    list *next;
public:
    list();
    void insert(T value);                   //增
    void del(T value);                      //删
    void modify(T value , T newvalue);    //改
    int search(T value);                    //查
    void reverse();                           //逆置
    void show();                              //显示
};

template <class T>
list<T>::list()
{
    next = NULL;
}

template <class T>
void list<T>::insert(T value)
{
   list *p = new list;       //创建一个新节点
   p -> next = this ->next;  //将新节点的下一个节点指向当前节点的下一个节点
   this -> next = p;         //将当前节点的下一个节点指向新节点
   p -> data = value;         //将新节点的数据赋值为data
}

template <class T>
void list<T>::del(T value)
{
    list *p = this;  //从头节点开始遍历
    list *q = NULL;          //保存要删除的节点
    while(p->next != NULL)
    {
        if( p -> next -> data == value)
        {
            q = p -> next;
            p ->next = q -> next;
            delete q;
            return;
        }
        p = p -> next;
    }
}

template <class T>
void list<T>::modify(T value , T newvalue)
{
    list *p = this -> next ; //从头节点的下一个节点开始遍历
    while(p != NULL)
    {
        if(p -> data == value)
        {
            p -> data = newvalue;
            return;
        }
        p = p -> next;
    }
}

template <class T>
int list<T>::search(T value)
{
    list *p = this -> next;  //从头节点的下一个节点开始遍历
    int count = 0;
    while(p != NULL)
    {
        if(p -> data == value)
        {
            return count;
        }
        count++;
        p = p -> next;
    }
    return -1;
} 

template <class T>
void list<T>::reverse()
{
    list *p = this -> next;  //从头节点的下一个节点开始遍历
    list *q = NULL;          //保存当前节点的下一个节点
    while(p!= NULL)
    {
        list *r = p -> next;
        p -> next = q;
        q = p;
        p = r;
    }
    this -> next = q;
}

template <class T>
void list<T>::show()
{
    cout << "链表的元素顺序为:";
    list *p = this -> next;
    while(p != NULL)
    {
        cout << p -> data << " ";
        p = p -> next;
    }
}

int main(int argc, char const *argv[])
{
    list<int> l1;
    cout << "链表的插入顺序为:";
    for(int i = 0 ; i < 10 ; i++)
    {
        l1.insert(i + 1);
        cout << i + 1 << " ";
    }
    cout << endl;
    l1.show();
    cout << endl;
    l1.del(1);
    l1.del(10);
    l1.del(5);
    l1.show();
    cout << endl;
    l1.modify(9,99);
    l1.modify(6,66);
    l1.show();
    cout << endl;
    int index = l1.search(99);
    cout << "99的下标为:" << index << endl;
    index = l1.search(7);
    cout << "7的下标为:" << index << endl;
    l1.reverse();
    l1.show();
    cout << endl;
    cout << "*****************************************************************" << endl;
    list<double> l2;
    cout << "链表的插入顺序为:";
    for(int i = 0 ; i < 10 ; i++)
    {
        l2.insert(i + 1.1);
        cout << i + 1.1 << " ";
    }
    cout << endl;
    l2.show();
    cout << endl;
    l2.del(1.1);
    l2.del(10.1);
    l2.del(5.1);
    l2.show();
    cout << endl;
    l2.modify(9.1,99.1);
    l2.modify(6.1,66.1);
    l2.show();
    cout << endl;
    index = l2.search(99.1);
    cout << "99.1的下标为:" << index << endl;
    index = l2.search(7.1);
    cout << "7.1的下标为:" << index << endl;
    l2.reverse();
    l2.show();
    cout << endl;
    cout << "*****************************************************************" << endl;
    list<string> l3;
    string citys[10] = {"北京","上海","广州","深圳","成都","重庆","武汉","长沙","南京","苏州"};
    cout << "链表的插入顺序为:";
    for(int i = 0 ; i < 10 ; i++)
    {
        l3.insert(citys[i]);
        cout << citys[i] << " ";
    }
    cout << endl;
    l3.show();
    cout << endl;
    l3.del("北京");
    l3.del("成都");
    l3.del("苏州");
    l3.show();
    cout << endl;
    l3.modify("上海","上新");
    l3.modify("广州","广新");
    l3.show();
    cout << endl;
    index = l3.search("上新");
    cout << "上海新的下标为:" << index << endl;
    index = l3.search("广新");
    cout << "广州新的下标为:" << index << endl;
    l3.reverse();
    l3.show();
    cout << endl;
    return 0;
}

可见链表模板类的优势就是我们可以通过模板类实现数据的增删改查反转显示后,我们就可以对int,float,string等类型直接使用模板类实现链表类的功能啦,对于这个链表模板类我们实现了基本数据类型的增删改查反转显示功能,下面我们实现一个自定义类型作为传入参数的模板类。如下:

cpp 复制代码
class student
{
private:
    int num;
    string name;
    int age;
    float score;

public:
    student(int n=0, string na="", int a=0, float s=0.0)
    {
        num = n;
        name = na;
        age = a;
        score = s;
    }
};

首先我们定义一个学生类,以该类作为模板类的传入参数类型,对于模板类的定义和成员函数的实现和上段程序一样。主要注意的就是操作符对自定义类型不支持的问题,所以我们只需要在该学生类中进行运算符的重载以实现操作符的自定义类型的处理即可,程序如下:

cpp 复制代码
friend ostream &operator<<(ostream &out, student &s)
    {
        out << "|" << "num:" << s.num << "|" << "name:" << s.name << "|" << "age:" << s.age << "|" << "score:"  << "|" << s.score;
        return out;
    }
    bool operator==(student &s)
    {
        return (this->num == s.num);
    }
    //因为原本的运算符不能用来运算自定义类,所以我们要对运算符进行重载以达到我们的效果
相关推荐
flashlight_hi2 小时前
LeetCode 分类刷题:987. 二叉树的垂序遍历
数据结构·算法·leetcode
仰泳的熊猫2 小时前
1120 Friend Numbers
数据结构·c++·算法·pat考试
BestOrNothing_20152 小时前
C++ 成员函数运算符重载深度解析
c++·八股·运算符重载·operator·this指针·const成员函数·const引用
ALex_zry2 小时前
C++中经典的定时器库与实现方式
开发语言·c++
槿花Hibiscus2 小时前
C++基础:session实现和http server类最终组装
服务器·c++·http·muduo
仰泳的熊猫2 小时前
1116 Come on! Let‘s C
数据结构·c++·算法·pat考试
BTU_YC2 小时前
python 内网部署
开发语言·python
Bear on Toilet2 小时前
17 . 爬楼梯
算法·深度优先
ACERT3332 小时前
03矩阵理论复习-内积空间和正规矩阵
算法·矩阵