目录
一、类模板基本语法
//类模板
template<class T1 , class T2>
class Person
{
public:
Person(T1 name, T2 age)
{
this->m_name = name;
this->m_age = age;
}
void showPerson()
{
cout << "name:" << this->m_name << "age:" << this->m_age << endl;
}
T1 m_name;
T2 m_age;
};
void test01()
{
Person<string, int>p1("孙悟空", 999);
p1.showPerson();
}
二、类模板与函数模板区别
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
template<class T1, class T2 = int>
class Person
{
public:
Person(T1 name, T2 age)
{
this->m_name = name;
this->m_age = age;
}
T1 m_name;
T2 m_age;
}
void test01()
{
Person<string>p1("孙悟空", 999);
}
三、类模板中成员函数创建时机
- 普通类中的成员函数一开始就可以创建
- 类模板中成员函数在调用时才创建
#include <iostream>
#include <string>
using namespace std;
class Person1
{
public:
void showPerson1()
{
cout << "Person1 show" << endl;
}
};
class Person2
{
public:
void showPerson2()
{
cout << "Person2 show" << endl;
}
};
//类模板
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();
}
int main()
{
test01();
system("pause");
}
四、类模板对象做函数参数
类模板实例化出的对象,向函数传参的方式,一共有3种传入方式:
- 指定传入的类型 -- 直接显示对象的数据类型 (最常用)
- 参数模板化 -- 将对象中的参数变为模板进行传递
- 整个类模板化 -- 将这个对象类型 模板化进行传递
#include <iostream>
#include <string>
using namespace std;
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age)
{
m_name = name;
m_age = age;
}
void showperson()
{
cout << "姓名:" << m_name << "年龄:" << m_age << endl;
}
T1 m_name;
T2 m_age;
};
//1、指定传入的类型
void printPerson1(Person<string, int> &p)
{
p.showperson();
}
void test01()
{
Person<string, int> p1("孙悟空", 99);
printPerson1(p1);
}
//2、模板参数化
template<class T1 , class T2>
void printPerson2(Person<T1, T2>& p)
{
p.showperson();
cout << "T1的类型为:" << typeid(T1).name() << endl;
cout << "T2的类型为:" << typeid(T2).name() << endl;
}
void test02()
{
Person<string, int> p2("猪八戒", 88);
printPerson2(p2);
}
//3、整个类模板化
template<class T>
void printPerson3(T& p)
{
p.showperson();
cout << "T的类型为:" << typeid(T).name() << endl;
}
void test03()
{
Person<string, int>p3("唐僧", 77);
printPerson3(p3);
}
int main()
{
test03();
system("pause");
}
五、类模板与继承
当类模板碰到继承时,需要注意:
- 当子继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需要变为类模板
#include <iostream>
#include <string>
using namespace std;
template<class T>
class Base
{
public:
T m;
};
//class Son :public Base //错误,必须知道父类中T类型,才能继承给子类
class Son :public Base<int>
{
};
void test01()
{
Son s1;
}
//如果想灵活指定父类中T类型,子类也需要变类模板
template<class T1 ,class T2>
class Son2 :public Base<T2>
{
public:
T1 obj;
Son2()
{
cout << "T1的数据类型为:" << typeid(T1).name() << endl;
cout << "T2的数据类型为:" << typeid(T2).name() << endl;
}
};
void test02()
{
Son2<int ,char> s2;
}
int main()
{
test02();
system("pause");
}
六、类模板成员函数类外实现
//构造函数的类外实现
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 << "姓名:" << m_name << " 年龄:" << m_age << endl;
}
七、类模板分文件编写
问题:
- 类模板成员函数创建时机在调用阶段,导致分文件编写时候链接不到
解决:
- 直接包含.cpp源文件
- 将声明和实现写在同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
八、类模板与友元
- 全局函数类内实现 - 直接在类内声明友元即可
- 全局函数类外实现 - 需要提前让编译器知道全局函数的存在
#include <iostream>
#include <string>
using namespace std;
//提前让编译器知道Person类存在
template<class T1, class T2>
class Person;
//2、类外实现
template<class T1, class T2>
void printPerson2(Person<T1, T2> p)
{
cout << "类外实现 -- 姓名:" << p.m_name << " 类外实现 -- 年龄:" << p.m_age << endl;
}
template<class T1 ,class T2>
class Person
{
public:
Person(T1 name, T2 age)
{
this->m_name = name;
this->m_age = age;
}
1、全局函数 类内实现
// friend void printPerson(Person<T1,T2> p)
//{
// cout << "姓名:" << p.m_name << " 年龄:" << p.m_age << endl;
//}
//2、全局函数 类外实现
//加空模板参数列表
//如果全局函数(友元)是类外实现,需要让编译器提前知道这个函数的存在
friend void printPerson2<>(Person<T1, T2> p);
private:
T1 m_name;
T2 m_age;
};
1、全局函数 类内实现调用
//void test01()
//{
// Person<string, int> p1("TOM", 99);
// Person<string, int> p2("T", 989);
// printPerson(p2);
//}
//2、全局函数 类外实现调用
void test02()
{
Person<string, int> p3("类外1", 99);
Person<string, int> p4("类外2", 989);
printPerson2(p4);
}
int main()
{
test02();
system("pause");
}
九、类模板案例
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 将数组中的数据存储到堆区
- 构造函数中可以传入数组的容量
- 提供对应的拷贝构造函数以及operator= 防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标的方式访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
cpp文件:
#include <iostream>
#include <string>
#include "MyArray.hpp"
using namespace std;
void printIntArray(MyArray<int>& arr)
{
for (int i = 0; i < arr.getSize(); i++)
{
cout << arr[i] << endl;
}
}
void test01()
{
MyArray<int> arr1(5);
/*MyArray<int> arr2(arr1);
MyArray<int> arr3(100);
arr3 = arr1;*/
for (int i = 0; i < 5; i++)
{
arr1.Push_Back(i); //尾插法
}
cout << "arr1的打印输出:" << endl;
printIntArray(arr1);
}
//测试自定义数据类型
class Person
{
public:
Person()
{
}
Person(string name, int age)
{
m_name = name;
m_age = age;
}
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 test02()
{
MyArray<Person> arr(10);
Person p1("孙悟空", 999);
Person p2("韩信", 888);
Person p3("赵云", 777);
Person p4("妲己", 666);
//将数据插入到数组中
arr.Push_Back(p1);
arr.Push_Back(p2);
arr.Push_Back(p3);
arr.Push_Back(p4);
//打印数组
printPersonArray(arr);
//输出容量
cout << "arr容量为: " << arr.getCapacity() << endl;
//输出大小
cout << "arr大小为: " << arr.getSize() << endl;
}
int main()
{
test02();
system("pause");
}
hpp(头文件):
#pragma once
#include <iostream>
using namespace std;
template<class T>
class MyArray
{
private:
T* pAddress; //指针指向堆区开辟的真实数组
int m_Capacity; //数组容量
int m_Size; //数组大小
public:
//有参构造 参数 容量
MyArray(int capacity)
{
cout << "MyArray 的有参构造调用"<<endl;
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
}
//拷贝构造
MyArray(const MyArray& arr)
{
cout << "MyArray 的拷贝构造调用" << endl;
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//深拷贝
this->pAddress = new T[arr.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=调用" << endl;
//先判断原来堆区是否有数据,如果有则先释放
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[arr.m_Capacity];
//将arr中的数据都拷贝进来
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
//尾插法
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 == 0)
{
return;
}
this->m_Size--;
}
//通过下标方式访问数组中的元素 arr[0] =100
T & operator[](int index) //重载[]运算符
{
return this->pAddress[index];
}
//返回数组容量
int getCapacity()
{
return this->m_Capacity;
}
//返回数组大小
int getSize()
{
return this->m_Size;
}
//析构函数
~MyArray()
{
cout << "MyArray 的析构函数调用" << endl;
if (this->pAddress != NULL)
{
delete[] this->pAddress;
}
}
};