目录
类模板语法
类模板与函数模板的区别
类模板中的成员函数创建时机
类模板对象做函数参数
类模板与继承
类模板成员函数类外实现
类模板分文件编写
类模板与友元
类模板数组案例 —— 数组类封装
类模板语法
类模板作用:
- 建立一个通用类,类中的成员数据类型可以不具体指定,用一个虚拟的类型来代替
语法:
template
// 类
解释:
template --- 声明创建模板
-
class --- 表面其后的符号是一种数据类型,可以用 typename 代替
-
T --- 通用的数据类型,名称可以代替,通常为大写字母
类模板作用:
- 建立一个通用类,类中的成员数据类型可以不具体指定,用一个虚拟的类型来代替
语法:
template// 类
解释:
template --- 声明创建模板
-
class --- 表面其后的符号是一种数据类型,可以用 typename 代替
-
T --- 通用的数据类型,名称可以代替,通常为大写字母
测试代码:
#includeusing namespace std; #include // 类模板 template class Person{ public: Person(NameType name, AgeType age){ this->m_Name = name; this->m_Age = age; } void ShowPerson(){ cout << "name:" << this->m_Name << endl; cout << "age: " << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; void test(){ // 类模板声明带上<类型> Person p1("李华", 19); p1.ShowPerson(); } int main(){ test(); system("pause"); return 0; }
运行结果:
类模板与函数模板的区别
类模板与函数模板区别主要有两点:
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
类模板与函数模板区别主要有两点:
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
区别①测试:(将模板参数列表去掉)缺少 类模板的参数列表
区别②测试代码:
#includeusing namespace std; #include // 类模板 template // 默认参数列表 class Person{ public: Person(NameType name, AgeType age){ this->m_Name = name; this->m_Age = age; } void ShowPerson(){ cout << "name:" << this->m_Name << endl; cout << "age: " << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; void test(){ Person p1("李华", 'a');// 模板的参数列表 p1.ShowPerson(); } int main(){ test(); system("pause"); return 0; }
运行结果:
- 类模板使用只能用显示指定类型方式
- 类模板中的模板参数列表可以有默认参数
类模板中的成员函数创建时机
类模板成员函数和普通类成员函数有区别:
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
类模板成员函数和普通类成员函数有区别:
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
区别①代码测试:
#includeusing namespace std; class Person1{ public: void ShowPerson1(){ cout << "Person1 show" << endl; } }; class Person2{ public: void ShowPerson2(){ cout << "Person2 show" << endl; } }; template class myClass{ public: T obj; // 类模板中的成员函数 // 没有调用不会创建 void func1(){ obj.ShowPerson1(); } void func2(){ obj.ShowPerson2(); } }; void test(){ } int main(){ test(); system("pause"); return 0; }
不会报错正常运行
区别②测试代码:
#includeusing namespace std; class Person1{ public: void ShowPerson1(){ cout << "Person1 show" << endl; } }; class Person2{ public: void ShowPerson2(){ cout << "Person2 show" << endl; } }; template class myClass{ public: T obj; // 类模板中的成员函数 // 没有调用不会创建 void func1(){ obj.ShowPerson1(); } void func2(){ obj.ShowPerson2(); } }; void test(){ myClass m1; m1.func1(); //m1.func2(); // 错误,确认为Person1类型,Person1类里没有func2中调用的成员函数 myClass m2; //m2.func1(); // 错误,确认为Person2类型,Person2类里没有func1中调用的成员函数 m2.func2(); } int main(){ test(); system("pause"); return 0; }
运行结果:
类模板刚开始确定不了类型,直到给定类型才能确定 ,所以成员函数并不是一开始就创建的,在调用时才去创建。
类模板对象做函数参数
类模板实例化出的对象,向函数传参的方式
-
一共有三种方式:
- 指定传入类型 --- 直接显示对象的数据类型
- 参数模板化 --- 将对象中的参数变为模板进行传递
- 整个类模板化 --- 将这个对象类型模板化进行传值
类模板实例化出的对象,向函数传参的方式
-
一共有三种方式:
- 指定传入类型 --- 直接显示对象的数据类型
- 参数模板化 --- 将对象中的参数变为模板进行传递
- 整个类模板化 --- 将这个对象类型模板化进行传值
传入指定类型
测试代码:
#includeusing namespace std; #include // 类模板对象做函数参数 template class Person{ public: Person(T1 name, T2 age){ this->m_Name = name; this->m_Age = age; } T1 m_Name; T2 m_Age; }; // 指定传入类型 void printPerson1(Person & p){ cout << p.m_Name << endl; cout << p.m_Age << endl; } void test01(){ Person p1("李华", 18); printPerson1(p1); } int main(){ test01(); system("pause"); return 0; }
运行结果:
参数模板化
传入模板化参数,再在函数上面对参数进行模板化
测试代码:
#includeusing namespace std; #include // 类模板对象做函数参数 template class Person{ public: Person(T1 name, T2 age){ this->m_Name = name; this->m_Age = age; } T1 m_Name; T2 m_Age; }; // 参数模板化 template void printPerson1(Person & p){ cout << p.m_Name << endl; cout << p.m_Age << endl; } void test01(){ Person p1("张三", 18); printPerson1(p1); } int main(){ test01(); system("pause"); return 0; }
运行结果:
可以用 typeid(T1) 的方式查看推出来的属性
cout << typeid(T1).name() << endl; cout << typeid(T2).name() << endl;
整个类模板化
测试代码:
#includeusing namespace std; #include // 类模板对象做函数参数 template class Person{ public: Person(T1 name, T2 age){ this->m_Name = name; this->m_Age = age; } T1 m_Name; T2 m_Age; }; // 整个类模板化 template void printPerson1(T& p){ cout << p.m_Name << endl; cout << p.m_Age << endl; cout << typeid(T).name() << endl; } void test01(){ Person p1("张三", 18); printPerson1(p1); } int main(){ test01(); system("pause"); return 0; }
运行结果:
- 类模板创建的的对象,可以有三种方式向函数中进行传参
- 使用比较广泛是第一种:指定传入的类型
类模板与继承
类模板用到继承时需要注意:
- 当子类继承的父类是一个类模板时,子类在声明时,要指定父类中 T 的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定父类中 T 的类型,子类也需要变成模板
类模板用到继承时需要注意:
- 当子类继承的父类是一个类模板时,子类在声明时,要指定父类中 T 的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定父类中 T 的类型,子类也需要变成模板
注意①,父类是类模板,需要指定T
怎么指定?
在类名后面加
class Son :public Base
测试代码:
#includeusing namespace std; #include // 类模板与继承 template class Base{ public: T m; }; // 灵活指定父类中 T 类型,子类也需要变成类模板 template class Son :public Base { public: Son() { cout << typeid(T1).name() << endl; cout << typeid(T2).name() << endl; } T1 obj; }; void test(){ Son s2; } int main(){ test(); system("pause"); return 0; }
运行结果:
如果父类是类模板,子类需要指定出父类中 T 的数据类型
类模板成员函数类外实现
#include
using namespace std;
#include
// 类模板中的成员函数类外实现
template
class Person {
public:
Person(T1 name, T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
// 构造函数类外实现,类模板参数列表写在作用域后面
template
Person::Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
// 成员函数类外实现,类模板参数列表写在作用域后面
template
void Person::showPerson() {
cout << this->m_Name << endl;
cout << this->m_Age << endl;
}
void test() {
Personp1("李华", 18);
p1.showPerson();
}
int main(){
test();
system("pause");
return 0;
}
运行结果:
类外实现时,要加上作用域,要加上类模板参数列表
写在作用域后面(两个冒号的前面),template 告诉编译器这是一个类模板参数列表中的T1和T2是什么。
类外实现类模板的构造函数
// 构造函数类外实现 templatePerson ::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; }
类外实现类模板的成员函数
// 成员函数类外实现 templatevoid Person ::showPerson() { cout << this->m_Name << endl; cout << this->m_Age << endl; }
类模板中成员函数类外实现时,需要加上类模板参数列表
类模板分文件编写
问题:类模板中成员函数创建时机时在调用阶段,导致份文件编写时链接不到
-
解决:
- 方法1:直接包含 .cpp 文件
- 方法2:将声明和实现写在同一个文件中,并更改后缀名为 .hpp,hpp 是约定的名称,并不是强制
问题:类模板中成员函数创建时机时在调用阶段,导致份文件编写时链接不到
-
解决:
- 方法1:直接包含 .cpp 文件
- 方法2:将声明和实现写在同一个文件中,并更改后缀名为 .hpp,hpp 是约定的名称,并不是强制
方法1:
直接包含源码
测试代码:
Person.h
#pragma once #include#include using namespace std; // 类模板分文件编写问题 template class Person { public: Person(T1 name, T2 age); void showPerson(); T1 m_Name; T2 m_Age; };
Person.cpp
#include"Person.h" // 构造函数类外实现 templatePerson ::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } // 成员函数类外实现 template void Person ::showPerson() { cout << this->m_Name << endl; cout << this->m_Age << endl; }
main.cpp
#include"Person.h"
void test() {
Personp1("李华", 18);
p1.showPerson();
}
int main(){
test();
system("pause");
return 0;
}
原因:进行分文件,分为 Person.h 和 Person.cpp 两个文件,在 .h 文件里写声明,在 .cpp 文件里写实现,最后在 main.cpp 中去调用,结果会出现无法解析的外部命令,说明是写了函数实现,但没有声明,按道理说 .h 里都给了声明,但编译器为什么认为没有?其实在类模板中成员函数是不会一开始就创建(类模板中的成员函数创建时机是在调用阶段),编译器也就没有发现类模板成员函数的声明了。
解决办法:将 main.cpp 中包含的文件改为 Person.cpp
#include"Person.cpp"
void test() {
Personp1("李华", 18);
p1.showPerson();
}
int main(){
test();
system("pause");
return 0;
}
运行结果:
让编译器去看 Person.cpp 中的内容(直接包含源文件) (用的不多)
方法2:(常用)
将 .h 和 .cpp 文件中的内容写在一起,将文件后缀改为 .hpp
hpp 是约定的,看到这个就可以知道这里面是类模板
-
那这个文件属于源文件还是头文件?
答案是:头文件
测试代码:
Person.hpp
#pragma once #include#include using namespace std; template class Person { public: Person(T1 name, T2 age); void showPerson(); T1 m_Name; T2 m_Age; }; // 构造函数类外实现 template Person ::Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } // 成员函数类外实现 template void Person ::showPerson() { cout << this->m_Name << endl; cout << this->m_Age << endl; }
main.cpp
#include"Person.hpp"
void test() {
Personp1("李华", 18);
p1.showPerson();
}
int main(){
test();
system("pause");
return 0;
}
运行结果:
主流解决方法是第二种,将类模板写在一起,修改后缀名为 .hpp。 (便于阅读和理解)
类模板与友元
全局函数类内实现:直接在类内声明友元
全局函数类外实现:需要提前让编译器知道全局函数的存在,不用加作用域
全局函数类内实现:直接在类内声明友元
全局函数类外实现:需要提前让编译器知道全局函数的存在,不用加作用域
全局函数类内实现
测试代码:
#includeusing namespace std; // 通过全局函数打印类信息 template class Person { // 全局函数 类内实现 friend void printPerson(Person p) { cout << p.m_Name << endl; cout << p.m_Age << endl; } public: Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } private: T1 m_Name; T2 m_Age; }; void test() { Person p1("李华", 19); printPerson(p1); } int main(){ test(); system("pause"); return 0; }
运行结果:
全局函数类外实现
测试代码:
#includeusing namespace std; // 声明 Person 类,并告诉编译器这个 Person 是一个类模板 template class Person; // 让编译器提前知道这个全局函数 template void printPerson(Person p); template class Person { // 类内声明 // 函数名后加空模板参数列表 friend void printPerson<>(Person p); public: Person(T1 name, T2 age) { this->m_Name = name; this->m_Age = age; } private: T1 m_Name; T2 m_Age; }; // 类外实现 // 让函数参数内认识 T1 和 T2 template void printPerson(Person p) { cout << p.m_Name << endl; cout << p.m_Age << endl; } void test() { Person p1("李华", 19); printPerson(p1); } int main(){ test(); system("pause"); return 0; }
运行结果:
建议类内实现(简单,编译器可以直接识别)
类模板数组案例 —— 数组类封装
实现通用的数组类的要求:
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 数组数据存储到堆区
- 构造函数可以传入数组容量
- 提供对应的拷贝构造函数以及 operator= 防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
实现通用的数组类的要求:
- 可以对内置数据类型以及自定义数据类型的数据进行存储
- 数组数据存储到堆区
- 构造函数可以传入数组容量
- 提供对应的拷贝构造函数以及 operator= 防止浅拷贝问题
- 提供尾插法和尾删法对数组中的数据进行增加和删除
- 可以通过下标访问数组中的元素
- 可以获取数组中当前元素个数和数组的容量
测试代码:
.hpp
#include#include using namespace std; template class MyArray { public: // 有参拷贝,指定数组容量 MyArray(int capacity) { cout << "有参构造" << endl; this->m_Capacity = capacity; this->m_Size = 0; this->pAddress = new T[this->m_Capacity]; } // 释放堆区指针 ~MyArray() { cout << "析构" << endl; if (this->pAddress != NULL) { delete[] this->pAddress; this->pAddress = NULL; } } // 拷贝构造 MyArray(const MyArray& arr) { cout << "拷贝构造" << 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 << "运算符重载" << 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]; for (int i = 0; i < this->m_Size; i++) { this->pAddress[i] = arr.pAddress[i]; } // 返回自身 return *this; } private: T* pAddress; // 指向堆区开辟的真实数组 int m_Capacity; // 数组容量 int m_Size; // 数组大小 };
main.cpp
#include"myArray.hpp"
void test() {
MyArray arr(10);
MyArray arr2(arr);
MyArray arr3(20);
arr3 = arr;
}
int main()
{
test();
system("pause");
return 0;
}
测试结果:
完成代码:
myArray.hpp
#include#include using namespace std; template class MyArray { public: // 有参拷贝,指定数组容量 MyArray(int capacity) { this->m_Capacity = capacity; this->m_Size = 0; this->pAddress = new T[this->m_Capacity]; } // 释放堆区指针 ~MyArray() { if (this->pAddress != NULL) { delete[] this->pAddress; this->pAddress = NULL; } } // 拷贝构造 MyArray(const MyArray& arr) { // 更新容量 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) { // 先判断原来堆区是否有数据,如果有先释放 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]; 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() { // 不能为 0 if (this->m_Size == 0) { return; } // 访问不到最后一个元素 this->m_Size--; } // 下标方式访问,本身作为返回 T& operator[](int index) { return this->pAddress[index]; } // 返回数组容量 int getCapacity() { return this->m_Capacity; } // 返回数组长度 int getSize() { return this->m_Size; } private: T* pAddress; // 指向堆区开辟的真实数组 int m_Capacity; // 数组容量 int m_Size; // 数组大小 };
main.cpp
#include"myArray.hpp"
void test() {
MyArray arr(10);
// 重载 [] 和 = 的测试
for (int i = 0; i < 10; i++) {
arr[i] = 10;
}
for (int i = 0; i < 10; i++) {
cout << arr[i] << " ";
}
cout << endl;
// 尾删法测试
for (int i = 0; i < 10; i++) {
arr.Pop_Back();
}
cout << endl;
// 尾插法测试
for (int i = 0; i < 10; i++) {
arr.Push_Back(i);
}
for (int i = 0; i < 10; i++) {
cout << arr[i] << " ";
}
cout << endl;
//arr3 = arr;
}
int main()
{
test();
system("pause");
return 0;
}
运行结果:



