1.模板简介
- C++的一种变成思想称为泛型编程,主要利用技术为模板
- 模板就是建立通用的模板,大大提高复用性。
- 特点:1.模板不可以直接使用,只是一个框架;2.模板的通用并不是万能的。
- C++提供两种模板机制:函数模板和类模板。
2.函数模板
2.1 函数模板基本语法
- 模板作用:建一个通用函数,其函数返回值和形参类型可以不具体制定,用一个虚拟类型代表。
- 函数模板利用关键字
template
. template
:声明创建模板typename
:表明其后面的符号是一种数据类型,可以用class
代替T
:通用的数据类型,名称可以替换,通常为大写字母- 使用函数模板两种方式:自动类型推导、显示指定类型。
- 模板的目的是为了提高复用性,将类型也参数化。
# include<bits/stdc++.h>
using namespace std;
// 声明一个模板,告诉编译器后面代码中紧跟着的T不要报错,T是一个通用数据类型
template<typename T>
void mySwap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
void test01()
{
int a = 1, b = 2;
mySwap(a, b); // 根据a、b的类型为int,自动推断T为int
cout << "a = " << a << " b = " << b << endl;
double c = 1.1, d = 2.2;
mySwap<double>(c, d); // 指定T数据类型为double
cout << "c = " << c << " d = " << d << endl;
}
int main()
{
test01();
return 0;
}
a = 2 b = 1
c = 2.2 d = 1.1
2.2 函数模板案例
利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
排序规则从大到小,排序算法为选择排序
分别利用char
数组和int
数组进行测试
普通函数与函数模板的区别
- 普通函数调用可以发生隐式类型转换
- 函数模板 用自动类型推导,不可以发生隐式类型转换
- 函数模板 用显示指定类型,可以发生隐式类型转换
int myAdd01(int a, int b)
{
return a + b;
}
template<class T>
int myAdd02(T a, T b)
{
return a + b;
}
void test01()
{
cout << myAdd01(1, 2) << endl;
cout << myAdd01(1, 'a') << endl; // 强制将'a'转换为int即ASCII码
cout << myAdd02(1, 1) << endl;
cout << myAdd02(1, 'a') << endl; // 报错,自动类型推导不会隐式转换
cout << myAdd02<int>(1, 'a') << endl; // 指定类型可以进行隐式转换
}
3
98
2
98
普通函数和函数模板调用规则
- 如果函数模板和普通函数都可以调用,优先调用普通函数
- 可以通过空模板参数列表强制调用函数模板
- 函数模板也可以发生函数重载
- 如果函数模板可以产生更好匹配,优先调用函数模板
总结:既然提供了函数模板就不要提供同名的普通函数,容易出现二义性
void Myprint(int a, int b)
{
cout << "调用普通函数\n";
}
template<typename T>
void Myprint(T a, T b)
{
cout<< "调用模板函数\n";
}
template<typename T>
void Myprint(T a, T b, T c) // 模板函数重载
{
cout<< "调用重载的模板函数\n";
}
void test01()
{
Myprint(1, 1); // output:调用普通函数
// 通过空模板参数列表,强制调用函数模板
Myprint<>(1, 1); // output:调用模板函数
Myprint(1, 1, 1); // output:调用重载的模板函数
// 如果函数模板产生更好的匹配,优先调用函数模板
Myprint('a', 'b'); // output: 调用模板函数
}
类模板和函数模板的区别
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
template<class NameType, class AgeType = int>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Name = name;
this->m_Age = age;
}
void showPerson()
{
cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
// 1.类模板没有自动类型推导使用方式
void test01()
{
// Person p("孙悟空", 1000); // 报错,无法用自动类型推导
Person<string, int> p("孙悟空", 10000);
p.showPerson();
}
// 2.类模板在参数列表中可以有默认参数
void test02()
{
Person<string> p("猪八戒", 999); // line 4中AgeType的默认类型为int,尖括号里就可以不用声明int
p.showPerson();
}
类模板中成员创建时机
类模板中成员函数在调用时才去创建
class Person1
{
public:
void showPerson1()
{
cout << "Person1 show!\n";
}
};
class Person2
{
public:
void showPerson2()
{
cout << "Person2 show!\n";
}
};
template <class T>
class MyClass
{
public:
T obj;
void func1() // 类模板成员函数创建时机为调用的时候创建!
{
obj.showPerson1();
}
void func2()
{
obj.showPerson2();
}
};
void test01()
{
MyClass<Person1> m;
m.func1();
// m.func2(); // 报错
}
类模板对象做函数参数
类模板实例化的对象,向函数传参方式:
1.指定传入的类型 –直接显示对象的数据类型(使用较为广泛)
2.参数模板化 –将对象中的参数变为模板进行传递
3.整个类模板化 –将这个对象类型模板化进行传递
template <class T1, class T2>
class Person
{
public:
T1 m_name;
T2 m_age;
Person(T1 name, T2 age)
{
this->m_name = name;
this->m_age = age;
}
void showPerson()
{
cout << "名字为:" << this->m_name << ' ' << "年龄:" << this->m_age << endl;
}
};
// 1.指定传入的类型
void print01(Person<string, int>& p)
{
p.showPerson();
}
void test01()
{
Person<string, int> p("孙悟空", 22);
print01(p); // Output: 名字为:孙悟空 年龄:22
}
// 2.将参数模板化
template<class T1, class T2>
void print02(Person<T1, T2>& p)
{
p.showPerson();
cout << "T1的类型为:" << typeid(T1).name() << endl;
cout << "T2的类型为:" << typeid(T2).name() << endl;
}
void test02()
{
Person<string, int> p("猪八戒", 22);
print02(p);
/*
Output: 名字为:猪八戒 年龄:22
T1的类型为:Ss
T2的类型为:i
*/
}
// 3.将整个类模板化
template<class T>
void print03(T& p)
{
p.showPerson();
cout << "T的类型为:" << typeid(T).name() << endl;
}
void test03()
{
Person<string, int> p("沙和尚", 22);
print03(p); // output: 名字为:沙和尚 年龄:22
//T的类型为:6PersonISsiE
}
类模板与继承
当类模板遇到继承时,需注意:
当子类继承的父类是一个类模板时,子类在声明的时候要指定出父类中T
的类型
如果不指定,遍历器无法给子类分配内存
* 如果想灵活指定父类中T
的类型,子类也需变为类模板
template <class T>
class Base
{
public:
T m;
};
// class Son : public Base // 报错,必须知道父类中T类型,才能继承给子类
class Son : public Base<int>
{
};
void test01()
{
Son s;
}
//想灵活指定父类中T类型,子类也需要变类模板
template<class T1, class T2>
class Son2:public Base<T2> // 父类Base中m类型为T2,子类Son2中obj类型为T1
{
public:
T1 obj;
};
void test02()
{
Son2<int, char> S2;
}
类模板中成员函数类外实现
类模板中成员函数类外实现的时候,需要加上模板参数列表
template <class T1, class T2>
class Person
{
public:
T1 m_Name;
T2 m_Age;
Person(T1 name, T2 age);
// {
// this->m_Age = age;
// this->m_Name = name;
// }
void showPerson();
// {
// cout << "姓名:" << this->m_Name << ' ' << "年龄:" << this->m_Age << endl;
// }
};
// 构造函数类外实现
template <class T1, class T2>
Person<T1, T2> :: Person(T1 name, T2 age)
{
this->m_Age = age;
this->m_Name = name;
}
// 成员函数类外实现
template <class T1, class T2>
void Person<T1, T2> :: showPerson()
{
cout << "姓名:" << this->m_Name << ' ' << "年龄:" << this->m_Age << endl;
}
void test01()
{
Person<string, int> P("jack", 20);
P.showPerson(); // Output: 姓名:jack 年龄:20
}
类模板分文件编写
问题:类模板中成员函数创建时机在调用阶段,导致分文件编写时链接不到
解决:
方式1.直接包含.cpp
源文件
方式2.将声明和实现写到同一个文件中,并更改为.hpp
,.hpp
是约定的名称,并不是强制
person.hpp
#pragma once
# include<bits/stdc++.h>
using namespace std;
template <class T1, class T2>
class Person
{
public:
T1 m_Name;
T2 m_Age;
Person(T1 name, T2 age);
void showPerson();
};
// 构造函数类外实现
template <class T1, class T2>
Person<T1, T2> :: Person(T1 name, T2 age)
{
this->m_Age = age;
this->m_Name = name;
}
// 成员函数类外实现
template <class T1, class T2>
void Person<T1, T2> :: showPerson()
{
cout << "姓名:" << this->m_Name << ' ' << "年龄:" << this->m_Age << endl;
}
类模板分文件编写:
.cpp
:
# include<bits/stdc++.h>
using namespace std;
# include "person.cpp" // 方式1.包含.cpp源文件
# include "person.hpp" // 方式2.将声明和实现写到一起,文件后缀改为.hpp
void test01()
{
Person<string, int> P("jack", 20);
P.showPerson(); // Output: 姓名:jack 年龄:20
}
int main()
{
test01();
return 0;
}
类模板与友元
全局函数类内实现:直接在类内声明友元即可
全局函数类外实现:需提前让编译器知道全局函数的存在
类模板案例:
实现一个通用数组Vector
,要求如下:
可以对内置数据类型以及自定义数据类型的数据进行存储
将数组中的数据存储到堆区
构造函数中可以传入数组的容量
提供对应拷贝函数以及operator=
防止浅拷贝问题
提供尾插法和尾删法对数组中的数据进行增加和删除
可以通过下标的方式访问数组中的元素
* 可以获取数组中当前元素个数和数组容量
MyArray.hpp
:包含模板类的定义与类内函数实现
// 自己通用的数组类
# pragma once
#include <iostream>
using namespace std;
template <class T>
class MyArray
{
public:
//有参构造,参数--容量
MyArray(int capacity)
{
cout << "MyArray的有参构造调用\n";
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity]; // 在堆区开辟一块空间并返回地址
}
// 拷贝构造
MyArray(const MyArray& arr)
{
cout << "MyArray的拷贝构造调用\n";
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
// 深拷贝
this->pAddress = new T[this->m_Capacity];
//将arr中的元素都拷贝过来
for(int i = 0; i < this->m_Size; i ++)
this->pAddress[i] = arr.pAddress[i];
}
// 重载operator=,防止浅拷贝
MyArray& operator=(const MyArray& arr)
{
cout << "MyArray的operator=构造调用\n";
//先判断原来堆区是否数据,若有先释放
if(this->pAddress != NULL)
{
delete [] this->pAddress;
this->pAddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
// 深拷贝
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->pAddress = new T[this->m_Capacity];
//将arr中的元素都拷贝过来
for(int i = 0; i < this->m_Size; i ++)
this->pAddress[i] = arr.pAddress[i];
}
// 尾插法
void Push_back(const T& val)
{
//判断是否有空间
if(this->m_Capacity == this->m_Size)
{
return;
}
this->pAddress[this->m_Size] = val;
this->m_Size ++;
}
void Pop_back()
{
if(this->m_Size) this->m_Size --;
}
// 通过下标访问数组
T& operator[](int index)
{
return this->pAddress[index];
}
// 返回数组容量
int Get_Capacity()
{
return this->m_Capacity;
}
// 返回数组大小
int Get_Size()
{
return this->m_Size;
}
// 析构函数(堆区开辟的空间需要手动释放)
~MyArray()
{
if(this->pAddress != NULL)
{
cout << "MyArray的析构调用\n";
delete [] this->pAddress; // 释放数组
this->pAddress = NULL;
}
}
private:
T * pAddress; // 指针指向堆区开辟的真是数组
int m_Capacity; // 数组容量
int m_Size; // 数组大小
};
MyArray.cpp
:数组的使用
# include <iostream>
using namespace std;
# include "MyArray.hpp"
void test01()
{
MyArray<int> arr1(5);
for(int i = 0; i < 5; i ++) arr1.Push_back(i); // 尾插法插入
for(int i = 0; i < 5; i ++) cout << arr1[i] << ' '; // 下标访问
cout << "arr1的容量为:" << arr1.Get_Capacity() << endl;
cout << "arr1的大小为:" << arr1.Get_Size() << endl;
arr1.Pop_back(); // 尾删
cout << "arr1的大小为:" << arr1.Get_Size() << endl; // 尾删后查看arr1大小
}
int main()
{
test01();
// cout << "你好";
return 0;
}