5.1C++模板
5.1.1 模板概论
c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。凡是函数体相同的函数都可以用这个模板代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现不同函数的功能。
- c++提供两种模板机制:函数模板和类模板
- 类属 - 类型参数化,又称参数模板
总结:
1.模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。
2.模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
5.1.2 函数模板
5.1.2.1 什么是函数模板?
#include <iostream>
using namespace std;
template<class T>
void MySwap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
void test01()
{
int a = 10;
int b = 20;
cout << "交换前" << a << " " << b << endl;
MySwap(a, b);
cout << "交换后" << a << " " << b << endl;
char c = 'a';
char d = 'b';
MySwap(c, d);
cout << "交换前" << c << " " << d << endl;
MySwap(c, d);
cout << "交换后" << c << " " << d << endl;
char e = 'x';
char f = 'y';
cout << "交换前" << e << " " << f << endl;
MySwap<char>(e, f); //函数模板可以自动类型推导,也可以显式指定类型
cout << "交换后" << e << " " << f << endl;
}
int main()
{
test01();
return 0;
}
输出结果
交换前10 20
交换后20 10
交换前b a
交换后a b
交换前x y
交换后y x
用模板是为了实现泛型,可以减轻编程的工作量,增强函数的重用性。
5.1.2.2 使用函数模板对数组进行排序
template <class T1>
void PrintArray(T1 arr[],int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
template <class T2>
void MySort(T2 arr[], int len)
{
for (int i = 0; i < len; i++)
{
for (int j = len - 1; j > i; j--)
{
if (arr[j] > arr[j - 1])
{
T2 temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
}
}
}
}
void test()
{
//char数组
char tempChar[] = "abcdefg";
int charLen = strlen(tempChar);
cout << "排序前" << endl;
PrintArray(tempChar, charLen);
//排序
MySort(tempChar, charLen);
cout << "排序后" << endl;
PrintArray(tempChar, charLen);
//int数组
int tempInt[] = { 1,2,3,4,5,6,7 };
int intLen = sizeof(tempInt) / sizeof(tempInt[0]);
cout << "排序前" << endl;
PrintArray(tempInt, intLen);
//排序
MySort(tempInt, intLen);
cout << "排序后" << endl;
PrintArray(tempInt, intLen);
}
int main()
{
test();
return 0;
}
输出结果
排序前
a b c d e f g
排序后
g f e d c b a
排序前
1 2 3 4 5 6 7
排序后
7 6 5 4 3 2 1
5.1.3 函数模板和普通函数区别
- 函数模板不允许自动类型转化
- 普通函数能够自动进行类型转化
template <class T>
void MyPlus(T a, T b)
{
cout << "调用模板函数" << endl;
}
void MyPlus(int a, int b)
{
cout << "调用普通函数" << endl;
}
void test()
{
int a = 0;
char b = 'a';
//调用函数模板要严格匹配类型
MyPlus(a, a);
MyPlus(b, b);
//调用普通函数,普通函数可以隐式类型转换
MyPlus(a, b);
MyPlus<int>(a, b);
}
int main()
{
test();
return 0;
}
输出结果
调用普通函数
调用模板函数
调用普通函数
调用模板函数
5.1.4 函数模板和普通函数在一起调用规则
- c++编译器优先考虑普通函数
- 可以通过空模板实参列表的语法限定编译器只能通过模板匹配
- 函数模板可以像普通函数那样可以被重载
- 如果函数模板可以产生一个更好的匹配,那么选择模板
template <class T>
void MyPlus(T a, T b)
{
cout << "调用模板函数" << endl;
}
void MyPlus(int a, int b)
{
cout << "调用普通函数" << endl;
}
template <class T>
void MyPlus(T a, T b, T c)
{
cout << "调用重载的模板函数" << endl;
}
void test()
{
int a = 10;
int b = 20;
char c = 'x';
char d = 'y';
//1.如果函数模板和普通函数都能匹配,c++编译器优先考虑普通函数
MyPlus(a, b);
//2.如果我必须要调用函数模板,则加上实参列表强制调用函数模板
MyPlus<>(a, b);
//3.相对于普通函数不需要类型转换,那么选择模板函数
MyPlus(c, d);
//4.函数模板可以重载
MyPlus<int>(a, b, c);
}
int main()
{
test();
return 0;
}
输出结果
调用普通函数
调用模板函数
调用模板函数
调用重载的模板函数
5.1.5 模板机制剖析
思考:为什么函数模板可以和普通函数放在一起?c++编译器是如何实现函数模板机制的?
5.1.5.1编译过程
hello.cpp程序是高级c语言程序,这种程序易于被人读懂。为了在系统上运行hello.c程序,每一条c语句都必须转化为低级的机器指令。然后将这些机器指令打包成可执行目标文件格式,并以二进制形式存储于磁盘中。
预处理(Pre-processing) -> 编译(Compiling) ->汇编(Assembling) -> 链接(Linking)
5.1.5.2 模板实现机制
函数模板机制结论:
- 编译器并不是把函数模板处理成能够处理任何类型的函数
- 函数模板通过具体类型产生不同的函数
- 编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
5.1.6模板的局限性
假设有如下模板函数:
template<class T>
void f(T a, T b)
{
…
}
如果代码实现时定义了赋值操作 a = b,但是T为数组,这种假设就不成立了同样,如果里面的语句为判断语句 if(a>b),但T如果是结构体,该假设也不成立,另外如果是传入的数组,数组名为地址,因此它比较的是地址,而这也不是我们所希望的操作。
总之,编写的模板函数很可能无法处理某些类型,另一方面,有时候通用化是有意义的,但C++语法不允许这样做。为了解决这种问题,可以提供模板的重载,为这些特定的类型提供具体化的模板。
class Person
{
public:
Person(const string name, int age)
{
this->Name = name;
this->Age = age;
}
string Name;
int Age;
};
template<class T> void mySwap(T& p1, T& p2)
{
string nameTemp;
int ageTemp;
nameTemp = p1.Name;
p1.Name = p2.Name;
p2.Name = nameTemp;
ageTemp = p1.Age;
p1.Age = p2.Age;
p2.Age = ageTemp;
}
void test()
{
Person p1("孙悟空", 999);
Person p2("猪八戒", 888);
cout << p1.Name << " " << p1.Age << endl;
cout << p2.Name << " " << p2.Age << endl;
mySwap(p1, p2);
cout << p1.Name << " " << p1.Age << endl;
cout << p2.Name << " " << p2.Age << endl;
}
int main()
{
test();
return 0;
}
输出结果
孙悟空 999
猪八戒 888
猪八戒 888
孙悟空 999
5.1.7 类模板
5.1.7.1 类模板基本概念
类模板和函数模板的定义和使用类似,我们已经进行了介绍。有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同。
类模板用于实现类所需数据的类型参数化
template <class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->Name = name;
this->Age = age;
}
void ShowPerson()
{
cout << this->Name << " " << this->Age << endl;
}
public:
NameType Name;
AgeType Age;
};
void test()
{
//Person p1("孙悟空",999); //类模板不能进行类型自动堆导
Person<string, int> p1("孙悟空", 999); //需要指定类型
p1.ShowPerson();
}
int main()
{
test();
return 0;
}
输出结果
孙悟空 999
5.1.7.2 类模板做函数参数
//类模板
template <class NameType,class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->Name = name;
this->Age = age;
}
void PrintPerson()
{
cout << this->Name << " " << this->Age << endl;
}
public:
NameType Name;
AgeType Age;
};
//类模板做函数参数
void DoBussiness(Person<string, int>& p)
{
p.Age = p.Age + 20;
p.Name = p.Name + " vip";
p.PrintPerson();
}
void test()
{
Person<string, int>p("Tom", 30);
p.PrintPerson();
DoBussiness(p);
}
int main()
{
test();
return 0;
}
输出结果
Tom 30
Tom vip 50
5.1.7.3 类模板派生普通类
//类模板
template<class T>
class Myclass
{
public:
Myclass(T property)
{
this->Property = property;
}
public:
T Property;
};
//子类实例化的时候需要具体化的父类,子类需要知道父类的具体类型是什么样的
//这样c++编译器才能知道给子类分配多少内存
//普通派生类
class Subclass :public Myclass<int>
{
public:
Subclass(int b) :Myclass<int>(20) //给父类初始化
{
this->B = b;
}
void Print()
{
cout << this->B << " " << this->Property << endl;
}
public:
int B;
};
void test()
{
Subclass b(10);
b.Print();
}
int main()
{
test();
return 0;
}
输出结果
10 20
5.1.7.4 类模板派生类模板
//父类模板
template <class T1>
class Base
{
public:
Base(T1 m)
{
this->M = m;
}
public:
T1 M;
};
//继承类模板的时候,必须要确定父类的大小
template<class T2>
class Child :public Base<double>
{
public:
Child(T2 param) :Base<double>(20.00)
{
this->Param = param;
}
void Print()
{
cout << this->Param << " " << this->M << endl;
}
public:
T2 Param;
};
void test()
{
Child<int> d1(10);
d1.Print();
}
int main()
{
test();
return 0;
}
输出结果
10 20
5.1.7.5 类模板类内实现
template <class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->Name = name;
this->Age = age;
}
void ShowPerson()
{
cout << this->Name << " " << this->Age << endl;
}
public:
NameType Name;
AgeType Age;
};
void test()
{
Person<string, int>p1("孙悟空", 999);
p1.ShowPerson();
}
int main()
{
test();
return 0;
}
输出结果
孙悟空 999
5.1.7.6 类模板类外实现
template <class T1,class T2>
class Person
{
public:
Person(T1 name, T2 age);
void Print();
public:
T1 Name;
T2 Age;
};
template <class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->Name = name;
this->Age = age;
}
template<class T1, class T2>
void Person<T1, T2>::Print()
{
cout << this->Name << " " << this->Age << endl;
}
void test()
{
Person<string, int>p1("孙悟空", 999);
p1.Print();
}
int main()
{
test();
return 0;
}
输出结果
孙悟空 999
5.1.7.7 类模板头文件和源文件分离问题
//1.Person.hpp 部分
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include <stdbool.h>
#include <iostream>
#include <string>
using namespace std;
template <class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
void Print();
public:
T1 Name;
T2 Age;
};
template <class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->Name = name;
this->Age = age;
}
template <class T1, class T2>
void Person<T1, T2>::Print()
{
cout << this->Name << " " << this->Age << endl;
}
//2. main.cpp 部分
#include"Person.hpp"
int main()
{
Person<string, int>p1("孙悟空", 999);
p1.Print();
return 0;
}
输出结果
孙悟空 999
结论: 案例代码在qt编译器顺利通过编译并执行,但是在Linux和vs编辑器下如果只包含头文件,那么会报错链接错误,需要包含cpp文件,但是如果类模板中有友元类,那么编译失败!
解决方案: 类模板的声明和实现放到一个文件中,我们把这个文件命名为.hpp(这个是个约定的规则,并不是标准,必须这么写).
原因:
- 类模板需要二次编译,在出现模板的地方编译一次,在调用模板的地方再次编译。
- C++编译规则为独立编译。
5.1.7.8模板类碰到友元函数
//告诉编译器Person这个类模板是存在的
template <class T1, class T2>class Person;
//告诉编译器Print这个函数模板是存在的
template <class T1, class T2>void Print(Person<T1, T2>& p);
//友元函数在类内实现
template <class T1,class T2>
class Person
{
public:
Person(T1 name, T2 age)
{
this->Name = name;
this->Age = age;
}
//1.友元函数在类内实现
friend void Print1(Person<T1, T2>& p)
{
cout << p.Name << " " << p.Age << endl;
}
//2.友元函数在类外实现
//友元函数类外实现 加上<>空参数列表,告诉编译去匹配函数模板
friend void Print2(Person<T1, T2>& p);
//3.类模板碰到友元函数模板
template <class T3, class T4>
friend void Print3(Person<T3, T4>& p)
{
cout << p.Name << " " << p.Age << endl;
}
public:
T1 Name;
T2 Age;
};
template <class T1, class T2>
void Print2(Person<T1, T2>& p)
{
cout << p.Name << " " << p.Age << endl;
}
void test01()
{
Person<string, int>p1("孙悟空", 999);
Print1(p1);
Print2<string, int>(p1);
Print3(p1);
}
int main()
{
test01();
return 0;
}
输出结果
孙悟空 999
孙悟空 999
孙悟空 999
5.1.8类模板的应用
设计一个数组模板类(MyArray),完成对不同类型元素的管理
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include <stdbool.h>
#include <iostream>
#include <string>
using namespace std;
template <class T>
class MyArray
{
public:
//有参构造
MyArray(int capacity)
{
this->Capacity = capacity;
this->Size = 0;
this->pAddress = new T[this->Capacity];
}
//拷贝构造
MyArray(const MyArray& arr)
{
this->Capacity = arr.Capacity;
this->Size = arr.Size;
this->pAddress = arr.pAddress;
for (int i = 0; i < this->Size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
}
//重载 [] 操作符
T operator[](int index)
{
return this->pAddress[index];
}
//尾插
void Push_Back(const T& val)
{
if (this->Capacity == this->Size)
{
return;
}
this->pAddress[this->Size] = val;
this->Size++;
}
//尾删
void Pop_back()
{
if(this->Size==0)
{
return;
}
this->Size--;
}
int getSize()
{
return this->Size;
}
~MyArray()
{
if (this->pAddress != NULL)
{
delete[]this->pAddress;
this->pAddress = NULL;
this->Capacity = 0;
this->Size = 0;
}
}
private:
T* pAddress;
int Capacity;
int Size;
};
class Person
{
public:
Person() {};
Person(string name,int age)
{
this->Name = name;
this->Age = age;
}
public:
string Name;
int Age;
};
void PrintMyArrayInt(MyArray<int>& arr)
{
for (int i = 0; i < arr.getSize(); i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
void PrintMyArrayPerson(MyArray<Person>& personArr)
{
for (int i = 0; i < personArr.getSize(); i++)
{
cout << personArr[i].Name << " " << personArr[i].Age << endl;
}
cout << endl;
}
int main()
{
MyArray<int>MyArrayInt(10);
for (int i = 0; i < 9; i++)
{
MyArrayInt.Push_Back(i);
}
PrintMyArrayInt(MyArrayInt);
MyArray<Person>MyArrayPerson(10);
Person p1("德玛西亚", 30);
Person p2("孙悟空", 20);
Person p3("猪八戒", 10);
MyArrayPerson.Push_Back(p1);
MyArrayPerson.Push_Back(p2);
MyArrayPerson.Push_Back(p3);
PrintMyArrayPerson(MyArrayPerson);
return 0;
}
输出结果
0 1 2 3 4 5 6 7 8
德玛西亚 30
孙悟空 20
猪八戒 10