8. 模板
函数模板也叫泛型编程,就是建立一个通用函数,函数类型和参数类型不具体指定,而是用虚拟类型代表。这个通用函数就叫做函数模板。
模
板
(
函
数
模
板
和
类
模
板
)
{
模
板
函
数
模
板
类
−
−
>
对
象
模板(函数模板和类模板) \begin{cases} 模板函数\\ 模板类-->对象 \end{cases}
模板(函数模板和类模板){模板函数模板类−−>对象
template <typename T1,typename T2>
关键字可以告诉c++编译器开始泛型编程。
#include<iostream>
using namespace std;
template <typename T>
void myswap(T &a, T &b)
{
T c;
c = a;
a = b;
b = c;
}
int main(int argc, char *argv[])
{
char a = 'a',b = 'b';
myswap<char>(a,b); // <>格式对应于template声明
//myswap(a,b) 可以自动类型推导
cout<<a<<" "<<b<<endl;
cin.get();
return 0;
}
/*
b a
*/
如果涉及指针操作,则这样声明:
template <typename T,typename T2>
void func(T *p, T2 n);
8.1 模板机制原理
gcc -S
生成的.s
文件中的汇编代码展示了函数调用过程。其实是编译器根据函数调用帮我们生成了函数代码。
具体过程是两次编译过程:
- 根据代码简易编译模板
- 根据调用,替换参数后再次编译
8.2 模板函数与普通函数的区别
函数模板将严格按照类型匹配,不会自动类型转换;普通函数可以隐式类型转换。
#include<iostream>
using namespace std;
template <typename T>
void myswap(T &a, T &b)
{
cout<<"template"<<endl;
}
void myswap(int a, char b)
{
cout<<"normal"<<endl;
}
int main(int argc, char *argv[])
{
int a = 1;
char b = 'a';
myswap(a,b);
myswap(b,a);
myswap(a,a);
cin.get();
return 0;
}
/*
normal
normal
template
*/
注意几点:
- 函数模板可以被重载;
- c++编译器优先考虑普通函数;
- 如果函数可以产生一个更好的匹配,则选择模板;
- 可以通过
<>
只选择模板。
#include<iostream>
using namespace std;
template <typename T>
void myswap(T &a)
{
cout<<"template"<<endl;
}
void myswap(int a)
{
cout<<"normal"<<endl;
}
int main(int argc, char *argv[])
{
int a = 1;
float b = 1.0;
myswap(a);
myswap(b);
cin.get();
return 0;
}
/*
normal
template
template
*/
8.3 类模板
在表示数组、表、图等数据结构时,可以将数据类型和算法分离。
模板类本身是抽象的,使用时要用尖括号<>
提供数据类型。
类模板的使用实际上是将类模板实例化成一个具体的模板类。
#include<iostream>
using namespace std;
template <typename T>
class MyClass{
private:
T a;
public:
MyClass(T a)
{
this->a = a;
}
void printA()
{
cout<<a<<endl;
}
};
void useMyClass(MyClass<int> &c)
{
c.printA();
}
int main(int argc, char *argv[])
{
MyClass<int> c(1);
useMyClass(c);
cin.get();
return 0;
}
类模板函数有3种实现方法:
- 在类的内部
- 类的外部,在同一cpp中
- 类的外部,在不同头文件和
hpp
中
下面是第二种实现方式。
template <typename T>
class A{
protected:
T a;
public:
A(T a)
{
this->a = a;
}
void printA();
};
template <typename T>
void A<T>::printA()
{
cout<<a<<endl;
}
因为模板的原理是二次编译,所以涉及友元函数时,要再次注明<T>
#include<iostream>
using namespace std;
template <typename T>
class A {
public:
T a;
public:
A(T a)
{
this->a = a;
}
friend ostream& operator<< <T>(ostream &out, A &c);
};
template <typename T>
ostream & operator<< (ostream &out, A<T> &c)
{
out << c.a << endl;
return out;
}
切记,友元函数在这里这能重载重定向操作符`<<,>>!!!!!
第三种方式:
//头文件
#pragma once
template <typename T>
class A {
public:
T a;
public:
A(T a)
{
this->a = a;
}
friend ostream& operator<< <T>(ostream &out, A &c);
};
//hpp
#include <iostream>
#include "myclass.h"
template <typename T>
ostream & operator<< (ostream &out, A<T> &c)
{
out << c.a << endl;
return out;
}
//main
#include<iostream>
using namespace std;
#include "myclass.hpp"
int main(int argc, char *argv[])
{
A<int> c(1);
cin.get();
return 0;
}
8.3.1 类模板派生
继承时要知道父类所占内存大小,所以仍然要用尖括号<>
提供数据类型。
template <typename T>
class A{
protected:
T a;
public:
A(T a)
{
this->a = a;
}
void printA()
{
cout<<a<<endl;
}
};
class B : public A<int>
{
private:
int b;
public:
B(int a, int b) : A<int>(a)
{
this->b = b;
}
void printB()
{
cout<<b<<endl;
}
};
如果要从模板类派生模板类,则要这样做:
template <typename T>
class A{
protected:
T a;
public:
A(T a)
{
this->a = a;
}
void printA()
{
cout<<a<<endl;
}
};
template <typename T>
class B : public A<T>
{
private:
T b;
public:
B(T a, T b) : A<T>(a)
{
this->b = b;
}
void printB()
{
cout<<b<<endl;
}
};
8.3.2 类模板中的static关键字
每一种<T>
,都拥有自己的一个静态变量。