C++——模板
一、模板的概念
模板就是建立通用的模具,大大提高复用性 。是c++泛型编程思想和STL技术的主要实现。
二、函数模板
(一)概念:
函数模板是一种通用函数,用到关键字template和typename(或class),我们可以用类型参数代替具体的数据类型,从而使函数可以处理不同的数据,大大提高了代码复用性。例如:
template <typename T>
void myCompare(T &a, T &b) {
if (a == b) {
cout << "a == b" << endl;
}
else if (a > b) {
cout << "a > b" << endl;
}
else {
cout << "a < b" << endl;
}
}
void test() {
int a = 5, b = 10;
myCompare(a, b);
double c = 0.9, d = 0.5;
myCompare(c, d);
}
代码中模板的定义从template开始,<typename T>声明了参数列表,void myCompare(T &a, T &b){……}就是函数定义,T代替了参数类型。该函数模板可以用来比较两个int、double、long等内置类型的大小。和函数重载相比,函数模板更简洁,减少了代码量。
(二)用法
函数模板用法有两种:1、自动类型推导;2、显示指定类型
template<typename T>
T myAdd(T a, T b) {
return a + b;
}
void test() {
int a = 5, b = 10;
double c = 3.14, d = 6.28;
cout << myAdd(a, b) << endl; //自动类型推导
cout << myAdd<double>(c, d) << endl; //显示指定类型
}
函数模板的参数列表可以指定默认参数类型,和函数默认参数类型一样,需要从后向前指定。
(三)注意事项
函数模板的使用中,传入的参数,需要与函数定义中参数列表一致。例如:
template <typename T,typename K=int>
void myFunc(T &a, T &b,K &c) {
cout << "重载模板调用" << endl;
}
void test() {
int a = 5;
int b = 10;
char c;
myFunc(a, b, c); //正确
//myFunc(a, c, b); //错误
}
(四)函数模板实例
实现一个排序函数模板,能够满足整形、浮点型、字符型等多种内置类型数据数组的排序
template<typename T>
void mySort(T arr[], int len) {
for (int i = 0; i < len; i++) {
int maxID = i;
for (int j = i + 1; j < len; j++) {
if (arr[maxID] < arr[j]) {
maxID = j;
}
}
if (maxID != i) {
T temp = arr[maxID];
arr[maxID] = arr[i];
arr[i] = temp;
}
}
}
template<typename T>
void printArr(T* arr, int len) {
for (int i = 0; i < len; i++) {
cout << setw(2) << setiosflags(ios::left) << arr[i];
}
cout << endl;
}
void test01() {
int intArr[] = { 1,3,5,6,4,7,2,9,8 };
int len = sizeof(intArr) / sizeof(int);
mySort(intArr, len);
printArr(intArr, len);
cout << "…………………………………………" << endl;
char charArr[] = "asdfghjklqwertyuiopzxcvbnm";
len = sizeof(charArr) / sizeof(char);
mySort(charArr, len);
printArr(charArr, len);
}
(五)普通函数和函数模板对比
1、普通函数和函数模板的区别
普通函数支持隐式类型转换;函数模板采用自动类型推导时,不支持参数的隐式类型转换;函数模板使用显示指定类型的方式,可以发生隐式类型转换
template <typename T>
T myAdd(T a, T b) {
return a + b;
}
void test() {
int a = 5, b = 10;
char c = 'a';
cout << myAdd(a, b) << endl;
//cout << myAdd(a, c) << endl;//错误,采用自动类型推导,不能发生隐式类型转换
cout << myAdd<int>(a, c) << endl;//正确,采用显示指定类型,能够发生隐式类型转换
}
2、普通函数和函数模板的调用规则
(1)当普通函数和函数模板同时存在时,优先调用普通函数;
(2)可以通过空模板参数列表来强制调用函数模板;
(3)函数模板可发生重载;
(4)如果函数模板可以产生更好的匹配,优先调用函数模板。
template <typename T>
T myAdd(T a, T b) {
cout << "函数模板调用" << endl;
return a + b;
}
int myAdd(int a, int b) {
cout << "普通函数调用" << endl;
return a + b;
}
template <typename T>
T myAdd(T a, T b) {
cout << "函数模板调用" << endl;
return a + b;
}
template <typename T>
T myAdd(T a, T b, T c) {
cout << "函数模板的重载调用" << endl;
return a + b + c;
}
void test() {
int a = 5, b = 10;
cout << myAdd(a, b) << endl; //优先调用普通函数
cout << myAdd<>(a, b) << endl; //通过空模板参数列表强制调用函数模板
cout << myAdd(a, b, 10) << endl;//函数模板的重载
char c = 'c', d = 'd';
cout << (int)myAdd(c, d) << endl; //函数模板比普通函数更加适配,调用函数模板
}
输出结果:普通函数调用
函数模板调用
函数模板的重载调用
函数模板调用
例1:当普通函数和函数模板同时存在,且函数原型完全相同,优先调用普通函数。
例2:通过空模板参数列表,强制调用函数模板。
例3:函数模板发生了重载,通过对传入参数的数量控制调用模板
例4:例子中普通函数调用会发生隐式类型转换,函数模板更加适配,则调用函数模板。
TIP:为了避免普通函数和普板函数出现二义性,一般提供了函数模板就不再提供普通函数
三、模板的局限性
函数模板并不是万能的,当参数为数组类型,或者自定义类型(类)时,无法进行部分运算。例如:
template <typename T>
void myfunc(T &a, T &b) {
a = b;
}
c++提供了函数模板的重载,为特殊类型参数提供具体化模板。语法如下:
template <typename T>
void myfunc(T &a, T &b) {
a = b;
}
class Person {
public:
string m_name;
int m_age;
};
template<> void myfunc(Person &a, Person &b) {
a.m_name = b.m_name;
a.m_age = b.m_age;
}
特殊类型参数的函数模板具体化后,传入该类型参数会直接调用具体化类型参数的函数模板,不再调用通用函数模板。
四、类模板
类模板和函数模板相似,在template<class T>后面紧跟类定义
template <class NameType,class AgeType>
class Person {
public:
Person(){}
Person(NameType name, AgeType age) {
m_name = name;
m_age = age;
}
NameType m_name;
AgeType m_age;
};
(一)类模板和函数模板的比较:
1、类模板没有自动类型推导的使用方式,只有显式指定类型实例化对象。
2、类模板和函数模板的参数列表中均可以指定默认类型。
class Person {
public:
Person(){}
Person(NameType name=string, AgeType age=int) {
m_name = name;
m_age = age;
}
NameType m_name;
AgeType m_age;
};
//Person p("孙悟空", 999); //错误,缺少类模板Person的参数列表
Person <string, int>p("猪八戒", 888);//正确,类模板只能使用显示指定类型实例化对象
(二)类模板中成员函数创建时机
1、普通类中成员函数在声明时即可创建
2、类模板中成员函数在调用时创建,当模板中T类型不确定时,func1()函数和func2()函数无法创建,只有当实例化Myclass p,确定了T的类型为Person1后调用p.func1()时才会创建。
class Person1 {
public:
void show1() {
cout << "Person1 show" << endl;
}
};
class Person2 {
public:
void show2() {
cout << "Person2 show" << endl;
}
};
template<class T>
class MyClass {
public:
T obj;
void func1() {
obj.show1();
}
void func2() {
obj.show2();
}
};
void test() {
MyClass<Person1> p;
p.func1();
//p.func2();
}
int main(int argc, char const *argv[]) {
test();
return 0;
}
(三)类模板作为参数传入函数的三种方式
1、指定参数类型传参(常用)
2、利用函数模板,声明类模板中参数
3、利用函数模板,声明函数参数后自动推导
template<class T,class K>
class Person {
public:
Person(T name, K age) {
this->m_name = name;
this->m_age = age;
}
T m_name;
K m_age;
void show() {
cout << "姓名:" << this->m_name << endl;
cout << "年龄:" << this->m_age << endl;
}
};
//第一种传参方式:指定参数类型传参
void printPerson1(Person<string, int> &p) {
p.show();
cout << typeid(p).name() << endl;
}
//第二种传参方式:利用函数模板,声明类中参数类型
template<class T,class K>
void printPerson2(Person<T, K> &p) {
p.show();
cout << typeid(p).name() << endl;
}
//第三种传参方式:利用函数模板,声明函数参数类型后自动推导
template<class T>
void printPerson3(T &p) {
p.show();
cout << typeid(p).name() << endl;
}
void test() {
Person<string, int> p("唐僧", 30);
printPerson1(p);
printPerson2(p);
printPerson3(p);
}
(四)类模板与继承
当子类继承的父类为模板时,声明子类时需要指定父类中的参数类型;
或者子类也作为模板去继承父类模板,用子类模板中的参数类型来指定父类模板参数类型。
template<class T>
class Base {
public:
T m;
};
//子类继承类模板,需指定父类参数类型
class Son1 :public Base<char> {
};
//子类继承类模板也可声明为类模板
//此时相当于指定了父类模板中参数类型为T
template<class T,class K>
class Son2 :public Base<T> {
public:
K obj;
};
void test01() {
Base<char> b; //类模板实例化对象时,需指定参数类型
Son1 s1; //子类声明时已指定父类参数类型为字符型
Son2<int,char> s2; //子类为模板时,先指定了父类参数类型为T,也就是实例化时指定的int
}
(五)类模板成员函数的类外实现和分文件编写
1、类模板中成员函数的类外实现,每次要重申模板和模板的参数列表
template<class TypeName,class TypeAge>
class Person {
public:
TypeName m_name;
TypeAge m_age;
Person(TypeName name,TypeAge age);
void show();
};
template<class TypeName,class TypeAge>
Person<TypeName, TypeAge>::Person(TypeName name, TypeAge age) {
this->m_name = name;
this->m_age = age;
}
template<class TypeName,class TypeAge>
void Person<TypeName, TypeAge>::show() {
cout << this->m_name << endl;
cout << this->m_age << endl;
}
2、分文件编写:因类模板函数是在调用阶段创建,并不是在编译阶段创建,则编译器进行到调用语句时发生错误,无法识别类模板函数。解决办法:在编译阶段将函数体提供给编译器,代替函数原型。可以在编译阶段将函数体所在的cpp文件包含进去,也可以将函数体写进头文件,命名为.hpp文件(约定俗成,不强制),一般用第二种处理方法。
Person.hpp
#pragma once
template<class TypeName,class TypeAge>
class Person
{
public:
TypeName m_name;
TypeAge m_age;
Person(TypeName name, TypeAge age);
void show();
};
template<class TypeName, class TypeAge>
Person<TypeName, TypeAge>::Person(TypeName name, TypeAge age) {
this->m_name = name;
this->m_age = age;
}
template<class TypeName, class TypeAge>
void Person<TypeName, TypeAge>::show() {
cout << this->m_name << endl;
cout << this->m_age << endl;
}
main.cpp
#include<iostream>
using namespace std;
#include<string>
#include"Person.hpp"
void test() {
Person<string, int> p("张三", 20);
p.show();
}
int main(int argc, char const *argv[]) {
test();
return 0;
}
(六)类模板——友元
类模板的全局友元函数可以分为内部实现和外部实现两种方式
1、内部实现:直接在类内部实现函数体,函数前加上friend友元关键字即可。
2、外部实现:因传入参数不确定类型,则外部全局函数也需要用模板对应类模板
(1)声明类模板
(2)声明全局函数模板(模板参数列表是类模板参数列表)
(3)定义类模板
(4)类内部将全局函数模板声明为友元函数模板
(5)实现函数模板
#pragma once
#include<iostream>
using namespace std;
#include<string>
//声明类模板,目的是声明全局函数模板
template<class TypeName,class TypeAge>
class Person;
//声明全局函数模板
template<class TypeName,class TypeAge>
void printPerson(Person<TypeName, TypeAge> &p);
//定义类模板
template<class TypeName,class TypeAge>
class Person {
private:
TypeName m_Name;
TypeAge m_Age;
public:
Person(TypeName name,TypeAge age);
//外部实现全局友元函数模板
friend void printPerson<>(Person<TypeName, TypeAge> &p);
//内部实现全局友元函数
friend void printPerson1(Person<TypeName, TypeAge> &p) {
cout << p.m_Name << endl;
cout << p.m_Age << endl;
}
};
//构造函数的外部实现
template<class TypeName,class TypeAge>
Person<TypeName, TypeAge>::Person(TypeName name, TypeAge age) {
this->m_Name = name;
this->m_Age = age;
}
//全局友元函数模板的外部实现
template<class TypeName,class TypeAge>
void printPerson(Person<TypeName, TypeAge> &p) {
cout << p.m_Name << endl;
cout << p.m_Age << endl;
}
五、模板实例
用模板实现数组,能够实现对内置数据类型和自定义数据类型的储存和基本操作。通过独立写代码的过程对类的三种权限复习一下;对运算符的重载复习一下;对new的用法理解加深了一些。
MyArray.hpp
#pragma once
#include<iostream>
using namespace std;
//定义数组类模板
template<class T>
class MyArray {
private:
int m_size = 0; //元素数量
int m_capacity ; //数组容量
T *m_array = NULL; //数组指针
public:
MyArray(int capacity);
MyArray(const MyArray& array);
void add(const T &value); //添加元素
void del(const T &value); //删除元素
int search(const T &value); //搜索元素
void print(); //打印数组
T& operator[](int index); //[]重载
MyArray<T>& operator=(const MyArray& array);//=重载
bool isfull(); //判定数组是否已满
~MyArray();
};
//普通构造函数
template<class T>
MyArray<T>::MyArray(int capacity) {
this->m_size = 0;
this->m_capacity = capacity;
this->m_array = new T[capacity];
}
//拷贝构造函数,利用=重载避免浅拷贝
template<class T>
MyArray<T>::MyArray(const MyArray& array) {
*this = array;
}
//=号重载,返回对象本身实现连续复制
template<class T>
MyArray<T>& MyArray<T>::operator=(const MyArray& array) {
this->m_capacity = array.m_capacity;
this->m_size = array.m_size;
if (this->m_array) {
delete this->m_array;
}
this->m_array = new T[m_capacity];
for (int i = 0; i < this->m_size; i++) {
this->m_array[i] = array.m_array[i];
}
return *this;
}
//[]重载,取当前index下标元素
template<class T>
T& MyArray<T>::operator[](int index) {
return m_array[index];
}
//判定数组是否已满
template<class T>
bool MyArray<T>::isfull() {
return this->m_size == this->m_capacity;
}
//添加元素
template<class T>
void MyArray<T>::add(const T &value) {
if (!this->isfull()) {
this->m_array[this->m_size++] = value;
}
}
//删除元素
template<class T>
void MyArray<T>::del(const T &value) {
int index = search(value);
while (index != -1) {
for (int i = index; i < m_size - 1; i++) {
m_array[i] = m_array[i + 1];
}
m_size--;
index = search(value);
}
}
//查找元素
template<class T>
int MyArray<T>::search(const T &value) {
int index = -1;
for (int i = 0; i < m_size; i++) {
if (this->m_array[i] == value) {
index = i;
break;
}
}
return index;
}
//打印元素
template<class T>
void MyArray<T>::print() {
for (int i = 0; i < m_size; i++) {
cout << m_array[i] ;
if (i != m_size - 1) {
cout << " ";
}else{
cout << endl;
}
}
cout << endl;
}
template<class T>
MyArray<T>::~MyArray() {
if (m_array) {
delete m_array;
m_array = NULL;
}
}
main.cpp
#include<iostream>
using namespace std;
#include<string>
#include"myArray.hpp"
void test() {
MyArray<int> *pA_int = new MyArray<int>(10);
pA_int->add(3);
pA_int->add(5);
pA_int->add(7);
pA_int->add(8);
pA_int->add(8);
cout << (*pA_int)[1] << endl;
pA_int->print();
MyArray<int> pA_int2(*pA_int);
pA_int2.print();
pA_int2.del(8);
pA_int2.print();
}
int main(int argc, char const *argv[]) {
test();
return 0;
}