模板

  • C++的泛型编程利用的主要就是模板

  • 模板主要分为函数模板类模板

一、函数模板

函数模板的作用

建立一个通用的函数,其返回值和形参类型可以不具体确定,用一个虚拟的类型来代表。

1.语法:
1
2
3
4
5
6
7
8
template<typename T>//声明一个模板,告诉编译器后边的T是一个通用数据类型
//函数的声明/定义
void swap(T &a,T &b)
{
T temp = a;
a=b;
b=temp;
}
  • template:关键字,声明创建模板

  • typename:关键字,可以用class替代,表面其后面的符号是一种数据类型

  • T:通用的数据类型,也可换成其他的符号

2.注意:
  • 如果把函数的声明和实现分别放到.h和.cpp中,则要写两次template

  • template< >仅对紧跟的一个函数的声明或者定义有效,如果要写多个函数模板,则要写多次template< >

3.使用方法
1
2
3
4
5
6
//1.自动推导数据类型
int a,b;
swap(a,b);

//2.显式指定数据类型
swap<int>(a,b);//< >称为模板的参数列表

模板必须确定出T的数据类型,函数才能正常运行

4.函数模板和普通函数的区别
  • 普通函数可以发生自动类型转换(隐式类型转换)

  • 自动类型推导的函数模板不能发生隐式类型转换

  • 显示指定数据类型的函数模板,可以发生隐式类型转换

5.函数模板和普通函数重载时的调用规则
  • 如果函数模板和普通函数都能实现,优先调用函数模板

  • 可以通过空模板参数列表< >来强制调用函数模板

  • 函数模板本身也可以重载

  • 如果函数模板可以产生更好的匹配,则优先调用函数模板

 

6.模板可以针对特定的数据类型重载

语法:

template< > 返回值类型 函数名(特定数据类型 a , 特定数据类型 b….)

1
2
3
4
5
6
7
8
template<typename T>
void func(T &a, T &b)
{
if (a == b)
cout << "a==b" << endl;
else
cout << "a!=b" << endl;
}

如果传入数据类型T为一个用户自定义的类,则该模板函数就不能用了,用户需要针对该类重载一个模板函数

1
2
3
4
5
6
7
template<> void func(Person &a, Person &b)
{
if(a.age==b.age)
cout << "a==b" << endl;
else
cout << "a!=b" << endl;
}

二、类模板

类模板的作用:

创建一个通用类,类中成员的数据类型可以不具体指定,而用一个虚拟的类型代替。

1.语法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<class T1,class T2 ....>
//类的声明
//如:

template<typename Type_name, typename Type_height>
class Pen
{
public:
Pen(Type_name m_name, Type_height m_height)
{
this->m_name = m_name;
this->m_height = m_height;
}
Type_name m_name;
Type_height m_height;

};

注意:

  • 用类模板实例对象时,必须要在模板参数列表< >中指定数据类型

  • 类不能重载,因此类模板不能和已有的类重名

2.类模板和函数模板的区别
  • 类模板不能自动推导数据类型,只能显式指定

  • 类模板可以在模板参数列表中有默认的参数,如:

1
template<typename Type_name, typename Type_height = int>
3.类模板中成员函数的创建时机
  • 普通类的成员函数在类被定义好后就会被创建

  • 模板类的成员函数只有在具体的一个实例调用该函数时才会被创建

4.类模板作为函数的参数

类模板有三种方法可以作为函数的参数:

1.传入指定的数据类型—直接在形参列表显式指定数据类型

1
2
3
4
void test01(Pen<string, int> &p)
{
p.PenPrint();
}

2.参数模板化—将对象中的参数变成模板进行传递

1
2
3
4
5
template <class T1, class T2>
void test02(Pen<T1, T2>& p)
{
p.PenPrint();
}

3.整个类模板化—将这个对象类型 模板化进行传递

1
2
3
4
5
template <class T1>
void test03(T1& p)
{
p.PenPrint();
}
1
2
3
4
5
6
7
8
9
int main()
{
Pen<string, int> p1("钢笔", 12);
Pen<string, int> p2("铅笔", 12);
Pen<string, int> p3("蜡笔", 12);
test01(p1);
test02(p2);
test03(p3);
}
5.类模板和继承
  • 当子类继承一个类模板的时候,如果想让这个子类不是类模板而是一个确定的类,则需要在继承时在模板参数列表中写清楚数据类型

    1
    2
    3
    4
    5
    6
    7
    8
    template<class T1,class T2>
    class Base
    {
    T1 m_age;
    T2 m_weight;
    };

    class Son : public Base<int,float>
  • 如果想让子类的数据类型也是灵活可变的,则子类也要写成类模板的形式

    1
    2
    3
    4
    5
    template<class T3,class T4>
    class Son1 : public Base<T3,T4>
    {

    };
6.类模板成员函数的类外实现

如果要把类外成员函数的实现和声明分开写的话:

  • 每个成员函数上一行都得写一次template< >

  • 作用域也要加上模板参数列表

如:

1
2
3
4
5
6
7
8
9
template<class T1,class T2>
class Book
{
public:
Book(T1 pages, T2 size);
void fun1(void);
T1 pages;
T2 size;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
template<class T1, class T2>
Book<T1, T2>:: Book(T1 pages, T2 size)
{
this->pages = pages;
this->size = size;
}

template<class T1, class T2>
void Book<T1, T2>::fun1(void)
{
cout << this->pages << endl;
cout << this->size << ednl;
}
7.类模板的分文件编写

由于类模板成员函数只有在被实例调用的时候才会被创建,因此像写普通类那样将声明和实现分别放到.h 和 .cpp文件就会在编译时出现链接错误。

解决方法:

  • 直接 #include “xxx.cpp”,不推荐这样

  • 将类模板的声明和实现写到一个文件内,并把后缀改成.hpp(不改写成.h也可以,不过约定俗成是.hpp),然后再#inlcude “xxx.hpp”就行了