目录
类模板的作用:建立一个通用类,类中的成员数据类型可以不具体指定,用一个虚拟的类型来代表。
1,类模板基本语法
template<typename T>
类
template --- 声明创建模板
typename --- 表明其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母
示例:
#include <iostream>
using namespace std;
#include <string>
template<class NameType,class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
m_Name = name;
m_Age = age;
}
NameType m_Name;
AgeType m_Age;
void showPerson()
{
cout << "name:" << m_Name << " age:" << m_Age << endl;
}
};
void test()
{
Person<string, int> p("张三",18);
p.showPerson();
}
int main()
{
test();
system("pause");
return 0;
}
类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板。
2,类模板和函数模板区别
类模板与函数模板区别主要有两点:
1,类模板没有自动类型推导的使用方式;
2,类模板在模板参数列表中可以有默认参数。
如下代码中,在定义类对象时,如不显示指定类型,因类模板没有自动类型推导,会报错。
#include <iostream>
using namespace std;
#include <string>
template<class NameType,class AgeType>
class Person
{
public:
Person(NameType name, AgeType age)
{
m_Name = name;
m_Age = age;
}
NameType m_Name;
AgeType m_Age;
void showPerson()
{
cout << "name:" << m_Name << " age:" << m_Age << endl;
}
};
void test()
{
Person p("张三",18);
p.showPerson();
}
int main()
{
test();
system("pause");
return 0;
}
如下代码中,类模板参数列表中可以有默认参数:
#include <iostream>
using namespace std;
#include <string>
template<class NameType,class AgeType = int> //类模板参数列表有默认参数
class Person
{
public:
Person(NameType name, AgeType age)
{
m_Name = name;
m_Age = age;
}
NameType m_Name;
AgeType m_Age;
void showPerson()
{
cout << "name:" << m_Name << " age:" << m_Age << endl;
}
};
void test()
{
//类模板在参数列表中有默认参数
Person<string> p("张三",18);
p.showPerson();
}
int main()
{
test();
system("pause");
return 0;
}
3,类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的:
普通类中的成员函数一开始就可以创建;
类模板中的成员函数在调用时才创建。
如下代码中类模板中的成员函数,只有
#include <iostream>
using namespace std;
#include <string>
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 test()
{
MyClass<Person1> m;
m.func1();
//m.func2();
}
int main()
{
test();
system("pause");
return 0;
}
4,类模板参数做函数参数
类模板实例化出的对象,作为实参传入函数时,有三种传入方式:
1,指定传入的类型 --- 直接显示对象的数据类型;
2,参数模板化 --- 将对象中的参数变为模板进行传递;
3,整个类模板化 --- 将对象类型 模板化进行传递。
第一种方式: 直接显示对象的数据类型;
#include <iostream>
using namespace std;
#include <string>
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;
};
void printPerson(Person<string, int>& p)
{
p.showPerson();
}
void test()
{
Person<string, int> p("张三",18);
printPerson(p);
}
int main()
{
test();
system("pause");
return 0;
}
第二种方式: 将对象中的参数变为模板进行传递;
#include <iostream>
using namespace std;
#include <string>
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;
};
template<class T1,class T2>
void printPerson(Person<T1, T2>& p)
{
p.showPerson();
cout << "T1的类型:" << typeid(T1).name() << endl;
cout << "T2的类型:" << typeid(T2).name() << endl;
}
void test()
{
Person<string, int> p("张三",18);
printPerson(p);
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
姓名:张三 年龄:18
T1的类型:class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
T2的类型:int
请按任意键继续. . .
第三种方式: 整个类模板化;
#include <iostream>
using namespace std;
#include <string>
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;
};
template<class T>
void printPerson(T & p)
{
p.showPerson();
cout << "T的类型:" << typeid(T).name() << endl;
}
void test()
{
Person<string, int> p("张三",18);
printPerson(p);
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:
姓名:张三 年龄:18
T的类型:class Person<class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,int>
请按任意键继续. . .
类模板对象传入函数时,函数形参的类型使用比较广泛的是第一种:指定传入的类型。
5,类模板与继承
当类模板碰到继承时,需要注意以下几点:
当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型,如果不指定,编译器无法给子类分配内存,如果想灵活指定出父类中T的类型,子类也需变为类模板。
如下代码中,因为父类是类模板,子类在继承时需指定父类中T的类型:
#include <iostream>
using namespace std;
#include <string>
template<class T>
class Base
{
};
class Son :public Base<int>
{
};
void test()
{
Son s1;
}
int main()
{
test();
system("pause");
return 0;
}
如下代码中,可以将子类也设定为类模板,可以灵活指定父类中T的类型:
#include <iostream>
using namespace std;
#include <string>
template<class T>
class Base
{
};
template<class T1,class T2>
class Son :public Base<T2>
{
public:
T1 obj;
Son()
{
cout << "T1的类型:" << typeid(T1).name() << endl;
cout << "T2的类型:" << typeid(T2).name() << endl;
}
};
void test()
{
Son<int,char> s1;
}
int main()
{
test();
system("pause");
return 0;
}
注意:如果父类是类模板,子类在继承时需要指定父类T的类型,如果要灵活指定父类T的类型,子类也要写成类模板的形式。
6,类模板成员函数类外实现
如下代码为类模板成员函数在类外实现:
#include <iostream>
using namespace std;
#include <string>
template<class T1,class T2>
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
template<class T1,class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
m_Name = name;
m_Age = age;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "姓名:" << m_Name << " 年龄:" << m_Age << endl;
}
void test()
{
Person<string, int> p("张三", 18);
p.showPerson();
}
int main()
{
test();
system("pause");
return 0;
}
注意:类模板中成员函数在类外实现时,需要加上模板参数列表。
7,类模板分文件编写
类模板分文件编写时,因为类模板成员函数创建时机是在调用阶段,导致程序编译时链接不到。
有两种解决办法:
1,直接包含.cpp文件;
2,将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制的(如果看到hpp文件,可以理解为类模板的分文件编写)。
以下代码,主要介绍第二种方法:
主函数中包含hpp文件:
#include <iostream>
using namespace std;
#include <string>
#include "Person.hpp"
void test()
{
Person<string, int> p("张三", 18);
p.showPerson();
}
int main()
{
test();
system("pause");
return 0;
}
hpp文件定义类模板及成员函数:
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
m_Name = name;
m_Age = age;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "姓名:" << m_Name << " 年龄:" << m_Age << endl;
}
主流的解决方式是第二种,将类模板成员函数写到一起,并将文件后缀名改为.hpp。
8,类模板与友元
全局函数做友元两种实现方法:
全局函数在类内实现 --- 直接在类内声明友元即可;
全局函数在类外实现 --- 需要提前让编译器知道全局函数的存在。
以下代码为友元全局函数在类内实现:
#include <iostream>
using namespace std;
#include <string>
template<class T1, class T2>
class Person
{
friend void printPerson(Person<T1, T2> p)
{
cout << "姓名:" <<p.m_Name << " 年龄:" << p.m_Age << endl;
}
public:
Person(T1 name, T2 age);
private:
T1 m_Name;
T2 m_Age;
};
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
m_Name = name;
m_Age = age;
}
void test()
{
Person<string, int> p("张三", 18);
printPerson(p);
}
int main()
{
test();
system("pause");
return 0;
}
以下代码为友元全局函数在类外实现:
#include <iostream>
using namespace std;
#include <string>
//首先要声明类模板,让友元全局函数知道存在这个类模板
template<class T1, class T2>
class Person;
template<class T1,class T2>
void printPerson(Person<T1, T2> p)
{
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}
template<class T1, class T2>
class Person
{
friend void printPerson<>(Person<T1, T2> p); //需要加空模板参数列表
public:
Person(T1 name, T2 age);
private:
T1 m_Name;
T2 m_Age;
};
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
m_Name = name;
m_Age = age;
}
void test()
{
Person<string, int> p("张三", 18);
printPerson(p);
}
int main()
{
test();
system("pause");
return 0;
}
注意:友元全局函数在类外实现时,在类内声明友元时,需要加空模板参数列表。
需要提前让编译器知道友元全局函数的存在。
建议全局函数做类内实现,用法简单,而且编译器可以直接识别。
9,类模板案例
案例描述:实现一个通用的数组类,要求如下:
可以对内置数据类型以及自定义数据类型的数据进行存储;
将数组中的数据存储到堆区;
构造函数中可以传入数组的容量;
提供对应的拷贝构造函数以及operator=防止浅拷贝问题;
提供尾插法对数组中的数据进行增加和删除;
可以通过下标的方式访问数组中的元素;
可以获取数组中当前元素个数和数组的容量;
通过案例描述可知,要建立一个类模板,对类模板进行分 文件编写时,使用.hpp后缀文件保存类模板的声明和定义。
后缀为.hpp文件中的代码:
#pragma once
#include <iostream>
using namespace std;
template<class T>
class myArray
{
public:
myArray(int capacity)
{
//cout << "myArray构造函数" << endl;
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[capacity];
}
myArray(const myArray& arr)
{
//cout << "myArray拷贝构造函数" << endl;
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
if (this->pAddress != NULL)
{
delete[] this->pAddress;
}
this->pAddress = new T[arr.m_Capacity];
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
}
myArray& operator=(const myArray& arr)
{
//cout << "myArray operator=函数" << endl;
if (this->pAddress != NULL)
{
delete[] pAddress;
pAddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
pAddress = new T[arr.m_Capacity];
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
void Push_Back(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--;
}
T& operator[](int index)
{
return this->pAddress[index];
}
int get_Capacity()
{
return this->m_Capacity;
}
int get_Size()
{
return this->m_Size;
}
~myArray()
{
//cout << "myArray析构函数" << endl;
if (pAddress != NULL)
{
delete[] pAddress;
pAddress = NULL;
}
}
private:
T* pAddress;
int m_Capacity;
int m_Size;
};
.cpp后缀文件中的代码:(需要包含上边后缀为.hpp的文件)
#include <iostream>
using namespace std;
#include <string>
#include "myArray.hpp"
class Person
{
public:
Person()
{
}
Person(string name, int age)
{
this->m_Name = name;
this->age = age;
}
string m_Name;
int age;
};
void printPerson(myArray<Person>& arr)
{
for (int i = 0; i < arr.get_Size(); i++)
{
cout << "姓名:" << arr[i].m_Name << " 年龄:" << arr[i].age << endl;
}
}
void test()
{
myArray<Person> arr(5);
Person p1("孙悟空",1000);
Person p2("唐僧", 20);
Person p3("猪八戒",100);
Person p4("沙僧", 30);
arr.Push_Back(p1);
arr.Push_Back(p2);
arr.Push_Back(p3);
arr.Push_Back(p4);
printPerson(arr);
myArray<Person> arr1(arr);
printPerson(arr1);
myArray<Person> arr2(10);
arr2 = arr1;
printPerson(arr2);
cout << arr2.get_Size() << endl;
arr2.Pop_Back();
printPerson(arr2);
}
int main()
{
test();
system("pause");
return 0;
}