前言
C++提供了函数模板(function template)。所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
1)C++提供两种模板机制:函数模板、类模板
2)类属 —— 类型参数化,又称参数模板
使得程序(算法)可以从逻辑功能上抽象,把被处理的对象(数据)类型作为参数传递。
总结:
- 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。
- 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为
1、函数模板
1.1 为什么要有函数模板
需求:写n个函数,交换char类型、int类型、double类型变量的值。
#include "iostream"
using namespace std;
//函数业务逻辑相同, 类型不一样
void myswap01(int &a, int &b)
{
int c = 0;
c = a;
a = b;
b = c;
}
void myswap02(char &a, char &b)
{
char c = 0;
c = a;
a = b;
b = c;
}
//类型参数化 泛型编程
//template 告诉C++我要开始泛型编程
template <typename T>
void myswap03(T &a, T &b)
{
T c=0;
c = a;
a = b;
b = c;
cout << "hello...我是模板函数"<<endl;
}
//函数模板的调用
//显示类型 调用
//自动类型推导
void main()
{
int x = 10;
int y = 20;
myswap03<int>(x, y); //函数模板 现KC实类型 调用
myswap03(x, y);
cout << x << "," << y << endl;
char a = 'a';
char b = 'b';
myswap03<char>(a, b);
cout << a << "," << b << endl;
system("pause");
return;
}
void main01()
{
int x = 10;
int y = 20;
myswap01(x, y);
cout << x << "," << y << endl;
char a = 'a';
char b = 'b';
myswap02(a, b);
cout << a << "," << b << endl;
cout<<"hello..."<<endl;
system("pause");
return ;
}
1.2函数模板语法
函数模板定义形式
template < 类型形式参数表 >
类型形式参数的形式为:
typename T1 , typename T2 , …… , typename Tn
或 class T1 , class T2 , …… , class Tn
函数模板调用
myswap<float>(a, b); //显示类型调用
myswap(a, b); //自动数据类型推导
1.3函数模板和模板函数
1.4函数模板做函数参数
#include "iostream"
using namespace std;
//让你对字符数组 int数组排序
template <typename T, typename T2>
int mySort(T *array, T2 size)
{
int i, j;
T tmp;
if (array == NULL)
{
return -1;
}
for (i=0; i<size; i++)
{
for (j = i+1; j < size; j++)
{
if (array[i] < array[j])
{
tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
}
return 0;
}
template <typename T, typename T2>
void myPrint(T *array, T2 size)
{
int i = 0;
for (i=0; i<size; i++)
{
cout << array[i] << " ";
}
cout << endl;
}
void main()
{
//int myarray[] = { 11,22,33,44,55,7,89,2 };
char buf[] = "asdrgsdfdtgge";
int len = strlen(buf);
int size = sizeof(buf) / sizeof(*buf);
mySort(buf, size);
myPrint(buf, size);
cout<<"hello..."<<endl;
system("pause");
return ;
}
1.5 函数模板遇上函数重载
函数模板和普通函数区别结论:
- 函数模板不允许自动类型转化
- 普通函数能够进行自动类型转换
函数模板和普通函数在一起,调用规则: - 1 函数模板可以像普通函数一样被重载
- 2 C++编译器优先考虑普通函数
- 3 如果函数模板可以产生一个更好的匹配,那么选择模板
- 4 可以通过空模板实参列表的语法限定编译器只通过模板匹配
#include "iostream"
using namespace std;
template <typename T>
void myswap(T &a, T &b)
{
T c = 0;
c = a;
a = b;
b = c;
cout << "hello...我是模板杉树" << endl;
}
void myswap(int a, char c)
{
cout << "a:" << a << "c:" << c << endl;
cout << "我是普通函数" << endl;
}
void main()
{
int a = 10;
char c = 'z';
myswap(a, c);//普通调用函数:可以进行隐式的类型转换
myswap(c, a);
myswap(a, a);//调用函数模板(本质:类型参数化)严格按照类型进行匹配,不会进行类型转换
cout<<"hello..."<<endl;
system("pause");
return ;
}
#include "iostream"
using namespace std;
/*
1. 函数模板可以像普通函数一样被重载
2. C++编译器优先考虑普通函数
3. 若函数模板可以产生一个更好的匹配,那么选择模板
4. 可通过空模板实参列表的语法限定编译器只通过模板匹配
*/
int Max(int a, int b)
{
cout << "int Max(int a, int b)" << endl;
return a > b ? a : b;
}
template<typename T>
T Max(T a, T b)
{
cout << "T Max(T a, T b)" << endl;
return a > b ? a : b;
}
template<typename T>
T Max(T a, T b, T c)
{
cout << "T Max(T a, T b, T c)" << endl;
return Max(Max(a, b), c);
}
void main()
{
int a = 1;
int b = 2;
cout << Max(a, b) << endl;
cout << Max<>(a, b) << endl; //若显示的使用函数模板,使用<>类型列表
cout << Max(3.0, 4.0) << endl; //若函数模板可以产生一个更好的匹配,那么选择模板
cout << Max(5.0, 6.0, 7.0) << endl;
cout << Max('a', 100) << endl; //调用普通函数 可以隐式类型转换
cout<<"hello..."<<endl;
system("pause");
return ;
}
2. 类模板
2.1为什么需要类模板
类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:
- 类模板用于实现类所需数据的类型参数化
- 类模板在表示如数组、表、图等数据结构显得特别重要,
这些数据结构的表示和算法不受所包含的元素类型的影响
2.2单个类模板语法
//类的类型参数化 抽象的类
//单个类模板
template<typename T>
class A
{
public:
A(T t)
{
this->t = t;
}
T &getT()
{
return t;
}
protected:
public:
T t;
};
void main()
{
//模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
A<int> a(100);
a.getT();
printAA(a);
return ;
}
2.3继承中的类模板语法
- 子模板类派生时,需要具体化模板类,C++编译器需要知道父类的具体数据类型,
- 要知道父类占多大的内存 只有数据类型固定下来,才知道如何分配内存
#include "iostream"
using namespace std;
//A 编程模板 类
//模板类 类型参数化
template <typename T>
class A
{
public:
A(T a = 0)
{
this->a = a;
}
public:
void printA()
{
cout << "a: " << a << endl;
}
protected:
public:
T a;
};
//子模板类派生时,需要具体化模板类,C++编译器需要知道父类的具体数据类型,
//要知道父类占多大的内存 只有数据类型固定下来,才知道如何分配内存
class B : public A<int>
{
public:
B(int a=10, int b=20) : A<int>(a)
{
this->b = b;
}
void printB()
{
cout << "a: " << a << " b: " << b << endl;
}
protected:
private:
int b;
};
template <typename T>
class C : public A<T>
{
public:
C(T c, T a) : A<T>(a)
{
this->c = c;
}
void printC()
{
cout << "c: " << c << endl;
}
protected:
private:
T c;
};
void main()
{
C<int> c1(1, 2);
c1.printC();
system("pause");
}
void main62()
{
B b1(1, 2);
b1.printB();
cout << "hello..." << endl;
system("pause");
return;
}
void UsedA(A<int> &a)
{
a.printA();
}
void main61()
{
//类模板 本身就是类型化的====具体的类=====》定义具体的变量
A<int> a1(11), a2(20), a3(30); //需要进行 类型具体化
//a1.printA();
UsedA(a1);
UsedA(a2);
UsedA(a3);
cout<<"hello..."<<endl;
system("pause");
return ;
}
2.4类模板语法知识体系梳理
2.4.1 复数类------所有函数在类内部
#include "iostream"
using namespace std;
template <typename T>
class Complex
{
friend Complex MySub(Complex &c1, Complex &c2)
{
Complex tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
friend ostream &operator<<(ostream &out, Complex &c3)
{
out << c3.a << " + " << c3.b <<"i"<< endl;
return out;
}
public:
Complex(T a = 0, T b = 0)
{
this->a = a;
this->b = b;
}
Complex operator+ (Complex &c2)
{
Complex tmp(a + c2.a, b + c2.b);
return tmp;
}
void printCom()
{
cout << "a:" << a << " b:" << b << endl;
}
protected:
private:
T a;
T b;
};
//运算符重载<< >>只能用友元函数,其它的运算符重载都要写成成员函数不要滥用友元函数
/*
ostream &operator<<(ostream &out, Complex &c3)
{
out << "a:" << c3.a << " b:" << c3.b << endl;
return out;
}*/
void main()
{
//需要将模板类具体化之后才能定义对象 C++编译器要分配内存
Complex<int> c1(1, 2);
Complex<int> c2(3, 4);
Complex<int> c3 = c1 + c2;
//c3.printCom();
cout << c3 << endl;
//滥用友元函数
{
Complex<int> c4 = MySub(c1, c2);
cout << c4 << endl;
}
cout<<"hello..."<<endl;
system("pause");
return ;
}
2.4.2所有的类模板函数写在类的外部,在一个cpp中
构造函数
template <typename T>
Complex<T>::Complex(T a, T b)
普通函数
template <typename T>
void Complex<T>::printCom()
友元函数:用友元函数重载 << >>
friend ostream& operator<< <T> (ostream &out, Complex<T> &c3) ;
友元函数:友元函数不是实现函数重载(非 << >>)
- 1)需要在类前增加 类的前置声明 函数的前置声明
template<typename T>
class Complex;
template<typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
- 2)类的内部声明 必须写成:
friend Complex<T> mySub <T> (Complex<T> &c1, Complex<T> &c2);
- 3)友元函数实现 必须写成:
template<typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b-c2.b);
return tmp;
}
- 4)友元函数调用 必须写成
Complex<int> c4 = mySub<int>(c1, c2);
cout<<c4;
#include "iostream"
using namespace std;
template <typename T>
class Complex;//类的前置声明
template <typename T>
Complex<T> MySub(Complex<T> &c1, Complex<T> &c2);
template <typename T>
class Complex
{
friend ostream &operator<< <T> (ostream &out, Complex<T> &c3);
friend Complex<T> MySub<T>(Complex<T> &c1, Complex<T> &c2);
public:
Complex(T a = 0, T b = 0);
void printCom();
Complex operator+ (Complex &c2);
protected:
private:
T a;
T b;
};
//运算符重载<< >>只能用友元函数,其它的运算符重载都要写成成员函数不要滥用友元函数
template <typename T>
ostream &operator<<(ostream &out, Complex<T> &c3)
{
out << c3.a << " + " << c3.b <<"i"<< endl;
return out;
}
template <typename T>
Complex<T> MySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
//构造函数写在了类的外部
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
template <typename T>
void Complex<T>::printCom()
{
cout << "a:" << a << " b:" << b << endl;
}
//成员函数实现+运算符重载
template <typename T>
Complex<T> Complex<T>::operator+ (Complex<T> &c2)
{
Complex tmp(a + c2.a, b + c2.b);
return tmp;
}
void main()
{
//需要将模板类具体化之后才能定义对象 C++编译器要分配内存
Complex<int> c1(1, 2);
Complex<int> c2(3, 4);
Complex<int> c3 = c1 + c2;
//c3.printCom();
cout << c3 << endl;
{
Complex<int> c4 = MySub<int>(c1, c2);
cout << c4 << endl;
}
cout << "hello..." << endl;
system("pause");
return;
}
2.4.3所有的类模板函数写在类的外部,在不同的.h和.cpp中
dm_complex.h
#include <iostream>
using namespace std;
template <typename T>
class Complex
{
friend ostream &operator<<<T>(ostream &out, Complex &c3);
public:
Complex(T a = 0, T b = 0);
void printCom();
Complex operator+ (Complex &c2);
protected:
private:
T a;
T b;
};
dm_complex.cpp
#include <iostream>
using namespace std;
#include "dm_09_complex.h"
template <typename T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
template <typename T>
void Complex<T>::printCom()
{
cout << "a:" << a << " b:" << b << endl;
}
//成员函数实现+运算符重载
template <typename T>
Complex<T> Complex<T>::operator+ (Complex<T> &c2)
{
Complex tmp(a + c2.a, b + c2.b);
return tmp;
}
//运算符重载<< >>只能用友元函数,其它的运算符重载都要写成成员函数不要滥用友元函数
template <typename T>
ostream &operator<<(ostream &out, Complex<T> &c3)
{
out << c3.a << " + " << c3.b << "i" << endl;
return out;
}
dm_main.cpp
#include <iostream>
using namespace std;
#include "dm_09_complex.cpp"
void main()
{
//需要将模板类具体化之后才能定义对象 C++编译器要分配内存
Complex<int> c1(1, 2);
Complex<int> c2(3, 4);
Complex<int> c3 = c1 + c2;
//c3.printCom();
cout << c3 << endl;
cout << "hello..." << endl;
system("pause");
return;
}
2.4.4总结
归纳以上的介绍,可以这样声明和使用类模板:
- 1 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
- 2 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
- 3在类声明前面加入一行,格式为:
template <class 虚拟类型参数>
如:
template <class numtype> //注意本行末尾无分号
class Compare
{…}; //类体
- 4 用类模板定义对象时用以下形式:
类模板名<实际类型名> 对象名;
类模板名<实际类型名> 对象名(实参表列);
如:
Compare<int> cmp;
Compare<int> cmp(3,7);
- 5 如果在类模板外定义成员函数,应写成类模板形式:
template <class 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
关于类模板的几点说明:
- 1 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:
template <class T1,class T2>
class someclass
{…};
在定义对象时分别代入实际的类型名,如:
someclass<int,double> obj; - 2 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
- 3 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。
2.5类模板中的static关键字
- 从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员
- 和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
- 每个模板类有自己的类模板的static数据成员副本
原理图
#include <iostream>
using namespace std;
template <typename T>
class AA
{
public:
static T m_a;
protected:
private:
};
template <typename T>
T AA<T>::m_a = 0;
void main()
{
AA<int> a1, a2, a3;
a1.m_a = 10;
a2.m_a++;
a3.m_a++;
cout << AA<int>::m_a << endl;
AA<char> b1, b2, b3;
b1.m_a = 'a';
b2.m_a++;
b3.m_a++;
cout << AA<char>::m_a << endl;
//m_a应该是 每一种类型的类 使用自己的m_a
cout<<"Hello..."<<endl;
system("pause");
return;
}