C++ 之函数模板
C++ 之函数模板
函数的参数类型不确定,这样就可以使用泛型。
// 类型参数化 C++中称之为泛型编程--模板技术
template <class T > // 告诉编译器,下面如果出现T不要报错,T是一个通用类型 这里将class替换成typename是一样的
void mySwap(T &a, T &b) {
T tmp = a;
a = b;
b = tmp;
}
自动类型推导,函数必须有参数才可以推导
// 2.显示调用
mySwap<int>(a, b); // 这里指定T是int类型
cout << "a=" << a << "," << "b=" << b << endl;
// 模板必须可以推导出T才可以使用 ,要么直接显示指定T
template < typename T>
void mySwap1() {
}
mySwap1 < double >(); // 如果不能根据参数列表推出来,就得显示指定类型
mySwap(a,b) // 根据a,b的类型自动推导T的类型。
mySwap<int> // (a,b)直接指定T的类型是int
2 使用函数模板实现通用数组排序
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
// 使用通用类型 对int char类型的数组进行排序
// 排序的规则 降序 使用选择排序
// 选择排序 每1趟中从待排序中选择一个最小的放在已排序的右端
template<class T>
void mySort(T arr[],int len) {
for (int i = 0; i < len-1;i++) {
int max = i;
for (int j = i + 1; j < len;j++) {
if (arr[max] < arr[j]) {
// 交换下标
max = j;
}
}
if (max != i) {
// 交换数据
swap(arr[max],arr[i]);
}
}
}
//输出数组元素的模板
template<class T>
void printArray(T arr[],int len) {
for (int i = 0; i < len;i++) {
cout << arr[i] << "\t";
}
}
void test01() {
char arr[] = "chengluofei";
//int len = sizeof(arr) / sizeof(char);
mySort(arr,strlen(arr));
printArray(arr, strlen(arr));
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
3 普通函数与函数模板的区别以及调用规则
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
//1. 普通函数与函数模板的区别
template<class T>
T myPlus(T a,T b) {
return a + b;
}
int myPlus2(int a , int b) {
return a + b;
}
void test01() {
int a = 10;
int b = 20;
cout << myPlus(a, b) << endl;
cout << myPlus2(a, b) << endl;
int c = 'c';
//cout << myPlus(a, c) << endl; // 类型推导出错
// char类型自动转int 类型 1. 普通的函数可以隐式类型转换 函数模板不可以进行隐式类型转换
cout << myPlus2(a, c) << endl;
}
// 2. 普通函数和函数模板的调用规则
template<class T>
void myPrint(T a, T b)
{
cout << "模板调用的myPrint" << endl;
}
template<class T>
void myPrint(T a, T b,T c)
{
cout << "模板调用的myPrint(a,b,c)" << endl;
}
void myPrint(int a, int b)
{
cout << "函数调用的myPrint" << endl;
}
void test02() {
int a = 10;
int b = 20;
// 1.如果出现重载(普通函数和函数模板函数名与参数个数相同),优先调用普通的函数 如果没有实现普通函数 会出错
//myPrint(10,20);
// 2.如果想强制调用函数模板 可以使用空菱形列表
myPrint<>(10,20);
// 3.函数模板可以发生重载
myPrint<>(10, 20,30);
// 4. 如果函数模板可以优先产生更好的匹配,那么优先调用函数模板
char c = 'c', d = 'd';
myPrint(c,d);
}
int main()
{
test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
4 模板机制
函数模板通过不同的类型产生不同的函数。 编译器会对函数模板进行两次编译。在声明的地方对模板本身进行编译, 在调用的时候对参数进行替换后再进行一次编译。 STL 90%以上都是使用模板来做的。 通过模板产生的函数就叫模板函数。(为函数模板传值时,会重新生成对应类型的函数。) 模板并不是万能的,不能通用所有的数据类型。 模板不能直接调用, 生成后的模板函数才可以调用。 二次编译,第一次对模板进行编译,第二次对替换T类型后的函数进行编译。5 函数模板的局限性
template < class T > void f(T a, T b){ …. } 如果T是数组的话, 就没办法使得a = b语句成立。if(a > b) 语句也不能成立。#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <string>
#include <vector>
class Person {
public:
Person(string name, int age) :m_Name(name), m_Age(age) {
}
string m_Name;
int m_Age;
};
template <class T>
bool myCompare(T &a ,T &b) {
if (a == b) {
return true;
}
else return false;
}
// 通过具体化自定义数据类型,解决上述问题
// 如果具体化能够优先匹配,那么就选择具体化
// 语法 template <> 函数的声明和实现 <具体类型>
template<> bool myCompare<Person>(Person &a, Person &b) {
if (a.m_Age == b.m_Age) {
return true;
}
else return false;
}
void test01() {
int a = 10;
int b = 20;
bool res = myCompare(a,b);
cout << res << endl;
Person p1("Tom",3);
Person p2("Jerry",4);
bool res2 = myCompare(p1, p2); // 匹配具体化的模板
cout << res2 << endl;
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
6 类模板的基本使用
// 类模板
template <class NameType,class AgeType>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_Name = name;
this->m_Age = age;
}
void showPerson() {
cout <<"姓名:"<<this->m_Name <<" 年龄:"<< this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
类模板支持默认参数的(默认的类型),函数模板不可以。
template <class NameType = string ,class AgeType= int>
如果这里声明了默认的类型,那么创建对象的时候,不需要在菱形中显示声明类型。
Person <> p("张三",12);
7 成员函数的创建时机
class Person1 {
public:
void showPerson1() {
cout << "Person1的调用" << endl;
}
};
class Person2 {
public:
void showPerson1() {
cout << "Person2的调用" << endl;
}
};
template <class T>
class Myclass {
public:
T obj;
void func1() {
obj.showPerson1();
}
void func2() {
obj.showPerson2();
}
};
// 成员函数一开始不会创建出来, 而是在运行时才去创建
void test02() {
Myclass<Person1> mc;
mc.func1();
mc.func2(); // 报错
}
8 类模板做函数的参数
// 1. 指定传入类型
void doWark(Person<string,int> &p) { // 指定传入类型
p.showPerson();
}
// 2.参数模板化
template<class T1,class T2>
void doWork2(Person<T1,T2> & p) {
// 如何查看类型
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
p.showPerson();
}
void test01() {
Person<string,int> p("张三",23);
doWark(p);
doWork2(p);
}
// 3 整体类型化
template <class T>
void doWork3(T &p) {
p.showPerson();
}
void test02() {
Person<> p("孙悟空",500);
doWork3(p);
}
9 类模板遇到继承问题以及解决办法
子类继承父类(此时父类是模板类)的时候,需要指定父类模板中的类型,不然创建子类对象的时候, 会默认调用父类的构造器,导致父类无法给未知类型的变量分配内存。
template <class T>
class Base {
public:
T m_A;
};
// 让child继承 必须告知base中的T类型 , 否则T无法分配内存
class Child : public Base<int> {
};
template <class T1, class T2>
class Child2 : public Base<T2> {
public:
Child2() {
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
}
T1 m_B;
};
void test01() {
Child2<int, double> child; // 由用户指定类型
}
10 类模板类外实现成员函数
template <class T1,class T2>
class Person {
public:
Person(T1 name, T2 age);
void showPerson();
private:
T1 m_name;
T2 m_age;
};
template <class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->m_name = name;
this->m_age = age;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "姓名:" << this->m_name << ", 年龄:" << this->m_age << endl;
}
11 类模板分文件编写以及问题解决
运行后,保持解决方法: 将Person.h 改为 Person.cpp
分文件写的话,因为是模板函数,代码一开始并不成成模板函数,因为确定不了类型。(C++是采用单元编译的。)
Person.h文件:
#pragma once
#include <iostream>
using namespace std;
#include <string>
template <class T1, class T2>
class Person {
public:
Person(T1 name, T2 age);
void showPerson();
private:
T1 m_name;
T2 m_age;
};
编译的时候, 编译检查Person.h文件,语法没有任何问题。 接着,检查Person.cpp文件。
#include "Person.h"
template <class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->m_name = name;
this->m_age = age;
}
template <class T1, class T2>
void::showPerson() {
cout << "姓名:" << this->m_name << ", 年龄:" << this->m_age << endl;
}
因为Person.cpp文件里面全是函数目版,编译器不生成模板函数,所以对应的Person.h中的具体实现也就实现不了。所以 在主函数里面,直接导入对应的cpp文件就可解决上面问题。
建议模板不要做分文件编写,一般写到一个类即可,全写到一个文件Person.h文件中,类内声明,类内实现。再把.h后缀改为.hpp.
main.cpp文件:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include "Person.hpp"
void test() {
Person<string, int> p1("张三",12);
p1.showPerson();
}
int main() {
test();
system("pause");
return EXIT_SUCCESS;
}
.hpp文件一般是写模板用的。
12 友元函数碰到类模板-友元函数类内实现
如果函数和普通函数都能匹配到的话,优先调用普通函数。
template <class T1, class T2>
class Person {
// 友元函数类内实现 默认是全局函数
friend void printPerson(Person<T1, T2> &p); // 普通函数的声明
public:
Person(T1 name, T2 age) {
this->name = name;
this->age = age;
}
private:
T1 name;
T2 age;
};
template <class T1, class T2>
void printPerson(Person<T1, T2> &p) {
cout << "姓名:" << p.name << "年龄" << p.age << endl;
}
void test() {
Person<string, int> p("张三", 12);
printPerson(p); // 报错 无法解析外部命令
}
解决方案: 在友元函数函数名处加上菱形括号,表示模板函数的声明。
// 让编译器提前看到printPerson的声明
// 让编译器看到Person类的声明
template <class T1, class T2> class Person;
template <class T1, class T2> void printPerson(Person<T1, T2> &p);
template <class T1, class T2>
class Person {
// 友元函数类内实现 默认是全局函数
//friend void printPerson(Person<T1, T2> &p); // 普通函数的声明
friend void printPerson<>(Person<T1, T2> &p); // 模板函数的声明
public:
Person(T1 name, T2 age) {
this->name = name;
this->age = age;
}
private:
T1 name;
T2 age;
};
template <class T1, class T2>
void printPerson(Person<T1, T2> &p) {
cout << "姓名:" << p.name << "年龄" << p.age << endl;
}
void test() {
Person<string, int> p("张三", 12);
printPerson(p);
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
13 类模板的应用-数组类封装
MyArray.hpp文件
template <class T>
class MyArray {
public:
explicit MyArray(int capacity) { // 防止隐式类型转换 MyArray arr = 10;
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
}
MyArray(const MyArray & arry) {
this->m_Capacity = arry.m_Capacity;
this->m_Size = arry.m_Size;
this->pAddress = new T[this->m_Capacity];
for (int i = 0; i < this->m_Size;i++) {
this->pAddress[i] = arry[i];
}
}
~MyArray() {
if (this->pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;
}
}
// 赋值操作符重载
void operator=(MyArray & arry) {
// 先判断原始数据 有 就清空
if (this->pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;
}
this->m_Capacity = arry.m_Capacity;
this->m_Size = arry.m_Size;
this->pAddress = new T[this->m_Capacity];
for (int i = 0; i < this->m_Size; i++) {
this->pAddress[i] = arry[i];
}
}
// [] 重载
T & operator[](int index){
return this->pAddress[index];
}
// 尾插法
void pushBack(T val) {
this->pAddress[this->m_Size] = val;
this->m_Size++;
}
// 获取大小
int getSize() {
return this->m_Size;
}
// 输出自身数据的方法
void printArry() {
for (int i = 0; i < this->m_Size;i++) {
cout << this->pAddress[i] << " ";
}
cout << endl;
}
private:
T * pAddress;
int m_Capacity;
int m_Size;
};
主函数.cpp文件:
class Person {
public:
Person() {
cout << "调用默认的构造函数" << endl;
}
Person(string name,int age) {
this->m_Name = name;
this->m_age = age;
}
~Person() {
cout << "调用析构函数" << endl;
}
string m_Name;
int m_age;
};
void printPersonArray(MyArray<Person> &arr) {
for (int i = 0; i < arr.getSize();i++) {
cout << "姓名:" << arr[i].m_Name << " 年龄" << arr[i].m_age << endl;
}
}
void test() {
//MyArray<int> arr(10);
//for (int i = 0; i < 10;i++) {
// arr.pushBack(i+100);
//}
//arr.printArry();
/*MyArray<float> arr(10);
for (int i = 0; i < 10;i++) {
arr.pushBack(0.1f+i);
}
arr.printArry();
cout << arr[5] << endl;*/
Person p1("张三",12);
Person p2("李四", 13);
Person p3("王五", 14);
Person p4("赵六", 15);
MyArray<Person> pArray(10);
pArray.pushBack(p1);
pArray.pushBack(p2);
pArray.pushBack(p3);
pArray.pushBack(p4);
printPersonArray(pArray);
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}