Day_16
1-函数模版
C++可以通过 重载 写很多一样名字的函数,然后根据参数进行选择调用:
void swap1(char &a, char &b)
void swap1(int &a, int &b)
但是这样还不够简单!可以通过 函数模板 的方式,就只用写一个了!(和写函数差不多)。
函数模板的本质是类型的参数化。
template <typename T1> //写多少种T就要用多少种
void myswap(T1 &a, T&b)
{
T1 c = 0;
c = a;
a = b;
b = c;
}
template 关键字就是告诉编译器:我要进行泛型编程了。
1.1函数模板定义形式 :
template < 类型形式参数表 >
//类型形式参数的形式为:(写了多少个就要用多少个)
typename T1 , typename T2 , …… , typename Tn
或 class T1 , class T2 , …… , class Tn
//用 typename 或 class都可以
【注】在模板定义语法中关键字 class 与t ypename 的作用完全一样。
不过,ypename另外一个作用为:使用 **嵌套依赖类型 **。
1.2函数模板调用
//两种都可以
myswap<float>(a, b); //推荐!显示类型调用
myswap(a, b); //自动数据类型推导
2-函数模板 遇上 函数重载
- 函数模板不允许自动类型转化
- 普通函数能够进行自动类型转换
函数模板可以像普通函数一样被重载
C++编译器优先考虑普通函数
如果函数模板可以产生一个更好的匹配,那么选择模板
可以通过空模板实参列表的语法限定编译器只通过模板匹配
template <typename T>
void myswap(T &a, T &b)
{
T c = 0;
c = a;
a = b;
b = c;
cout << "hello ....我是模板函数 欢迎 calll 我" << endl;
}
void myswap(int a, char c)
{
cout << "a:" << a << "c:" << c << endl;
cout << "我是普通函数 欢迎来访" << endl;
}
当调用函数:
int main()
{
int a = 10;
char c = 'z';
myswap(a, c); // 普通函数的调用: 可以进行隐式的类型转换
myswap(c, a); //进行隐式的类型转换 ASCII码
myswap(a, a); // 函数模板函数的调用(本质:类型参数化): 将严格的按照类型进行匹配,不会进行自动类型转换
}
但是我要是写一个一样的函数一样的类型:
void myswap(int a, int c)
{
cout << "a:" << a << "c:" << c << endl;
cout << "我是普通函数 欢迎来访" << endl;
}
那么在调用时:
myswap<int>(a, c); // 函数模板的调用
myswap(c, a); //普通函数的调用
myswap<>(a, c); // 这样写也会优先选择 函数模板
myswap<int>(a, a); // 函数模板的调用
所以在用函数模板的时候最好还是 显示调用
3-函数模板机制探究
3.1-函数模板机制结论
两次编译原理:
编译器 并不是把 函数模板 处理成能够处理任意类的函数;
编译器 从 函数模板 通过具体类型产生不同的函数;
编译器会对函数模板进行 两次编译;
在声明的地方对 模板代码 本身进行 编译;在调用的地方对 参数替换后的代码 进行 编译 。
4-类模板
4.1-为什么需要类模板?
类模板 与 函数模板 的定义和使用类似。
有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同。
-
类模板用于实现类所需数据的 类型参数化 ;
-
类模板在表示如 数组、表、图 等数据结构显得特别重要,
这些数据结构的表示和算法不受所包含的元素类型的影响。
4.2-单个 类模板 语法
以往类的写法:
class A{
public:
A(int a, int b)
{
this->a = a;
this->b = b;
}
void Print()
{
cout << "a: " << a << " b: " << b << endl;
}
private:
int a;
int b;
};
用模板后:(这里我使用了a b 两个不同的类型)
template <class T1, class T2>
class A{
public:
A(T1 a, T2 b)
{
this->a = a;
this->b = b;
}
void Print()
{
cout << "a: " << a << " b: " << b << endl;
}
private:
T1 a;
T2 b;
};
int main()
{
A<int,char> aa(1,'c'); //调用的时候记得写上类型
}
4.3-类模板 做 函数参数
void UseA( A<int> &a ) //要写参数类型
{
a.printA();
}
main()
{
//模板类(本身就是类型化的)====具体的类=====>定义具体的变量
A<int> a1(11), a2(20), a3(30); //模板类是抽象的 ====>需要进行 类型具体
UseA(a1);
UseA(a2);
UseA(a3);
}
4.4-从模板类 派生出 类 的写法
template <class T1>
class A{
public:
A(T1 a, T1 b)
{
this->a = a;
this->b = b;
}
void Print1()
{
cout << "a: " << a << " b: " << b << endl;
}
public:
T1 a;
T1 b;
};
class B : public A<int> //记得写类型
{
public: //这里要对 A的类型说明
B(int c=0, int d=0, int a=0, int b=1) : A<int>(a,b)
{
this-> c = c;
this-> d = d;
}
void Print2()
{
cout << "c: " << c << " d " << d << " a: " << a << endl;
}
private:
int c;
int d;
};
int main()
{
B b1(1,2,3,4);
b1.Print2();
}
4.5-从 模板类 派生 模板类
template <class 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:
T c;
};
5- 类模板语法知识体系梳理
5.1-所有的类模板函数写在类的内部
写了一个 友元函数调用, 写了一个运算符重载
template <typename T>
class Complex
{
//写了一个友元函数
friend Complex MySub(Complex &c1, Complex &c2)
{
Complex tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
public:
Complex(T a, T b) //因为不知道 a b的数据类型,所以不给初始值
{
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;
}
private:
T a;
T b;
};
void main()
{
//需要把模板类 进行具体化以后 才能定义对象 C++编译器要分配内存
Complex<int> c1(1, 2);
Complex<int> c2(3, 4);
Complex<int> c3 = c1 + c2;
//c3.printCom();
Complex<int> c4 = MySub(c1, c2);
cout << c4 << endl;
}
5.2-所有的类模板函数写在类的外部,在一个cpp中
把函数实现 弄成 函数声明,这样就可以把函数写在外面了。
不要乱用 友元函数,容易出错。
在函数实现前要加上 模板声明:
template <typename T1>;
同理, 类的函数 要加上 类的域名,又因为它是模板类,所以还要加上参数列表:
template <typename T>
Complex<T>::Complex(T a, T b) //构造函数在类的外部实现
{
this->a = a;
this->b = b;
}
template <typename T>
void Complex<T>::printCom()
//要是返回Complex的话就这样写:Complex<T> Complex<T>::printCom()
{
cout << "a:" << a << " b: " << b << endl;
}
在类的内部声明就行:
template <typename T>
class Complex
{
public:
Complex(T a, T b);
void printCom();
private:
T a;
T b;
};
5.3-所有的类模板函数写在类的外部,在不同的.h和.cpp中
可以把类写到一个 .h 文件里,在里面记得声明 成员函数;
//文件名为 Others.h
#include <iostream>
using namespace std;
template <class T>
class Complex
{
public:
Complex(T a, T b)
{
this->a = a;
this->b = b;
}
void printCom(); //声明成员函数
private:
T a;
T b;
};
成员函数写在另一个 .h 文件里,记得要引用类的说明;
#include <iostream>
#include "Others.h" //引用类的 .h 头文件
using namespace std;
template <class T> //引用类说明
class Complex;
template <typename T>
void Complex<T>::printCom() //类中的printCom,作用域说明符
{
cout << "a:" << a << " b: " << b << endl;
}
6-类模板总结
归纳以上的介绍
类模板的用途 就是让我们的 数据 与 算法 分离。
可以这样声明和使用类模板:
1- 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
2- 将此类中准备改变的类型名( 如 int 要改变为 float 或 char )改用一个自己指定的虚拟类型名(如上例中的 T1 )。
3- 在类声明前面加入一行,格式为:
template <class 虚拟类型参数>
如:
template <class numtype> //注意本行末尾无分号
class Compare
{…}; //类体
4- 用类模板定义对象时用以下形式:
类模板名<实际类型名> 对象名;
类模板名<实际类型名> 对象名(实参表列);
如:
Compare<int> cmp;
Compare<int> cmp(3,7);
- 如果在类模板外定义成员函数,应写成类模板形式:
template <class 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
//eg.
template <typename T>
void Complex<T>::printCom()
{
cout << "a:" << a << " b: " << b << endl;
}
关于类模板的几点说明:
1- 类模板的类型参数可以有一个或多个,每个类型前面都必须加 class ,如:
template <class T1,class T2>
class someclass
{…};
在定义对象时分别代入实际的类型名,如: