之前已学习了函数模板的相关知识,接下来学习类模板的相关知识。
类模板作用:
语法:
template<typename T,typename U,typename V,...> //类中需要几个数据类型就写几个
类的定义
//类的实例化
类名<数据类型1,数据类型2,数据类型3,...> 变量名(参数) //在实例化的时候需要将模板的数据类型参数列表依次指定。
解释:
示例:
#include
using namespace std;
template<class NameType,class AgeType>
class Person1
{
public:
Person1(NameType name, AgeType age)
{
this->name = name;
this->age = age;
}
NameType name;
AgeType age;
void showPerson()
{
cout << "姓名 : " << this->name << endl;
cout << "年龄 : " << this->age << endl;
}
};
void test1()
{
Person1<string, int> p("张三", 60);
p.showPerson();
}
int main()
{
test1();
system("pause");
return 0;
}
类模板与函数模板的区别主要有两点:
示例:
#include
#include
using namespace std;
template<class NameType,class AgeType>
class Person2
{
public:
Person2(NameType name,AgeType age)
{
this->name = name;
this->age = age;
}
void showPerson()
{
cout << "name : " << this->name << endl;
cout << "age : " << this->age << endl;
}
NameType name;
AgeType age;
};
template<class NameType, class AgeType = int >
class Person2_02
{
public:
Person2_02(NameType name, AgeType age)
{
this->name = name;
this->age = age;
}
void showPerson()
{
cout << "name : " << this->name << endl;
cout << "age : " << this->age << endl;
}
NameType name;
AgeType age;
};
void test2()
{
//Person2 p("孙悟空", 1000); 无法用自动类型推导
Person2<string, int> p("孙悟空", 1000); //显示指定是可以的
p.showPerson();
Person2_02<string> p2("猪八戒", 900.3); //有默认参数,是默认的
p2.showPerson();
//用法和函数的默认参数是相同的,也要遵循从右往左的规则
Person2_02<string,double> p3("沙僧", 800.5);
p3.showPerson();
}
int main()
{
test2();
system("pause");
return 0;
}
类模板中成员函数和普通类中成员函数创建时机是有区别的:
#include
using namespace std;
class Person3_1
{
public:
void showPerson1()
{
cout << "Person1 show" << endl;
}
};
class Person3_2
{
public:
void showPerson2()
{
cout << "Person2 show" << endl;
}
};
template<class T>
class MyClass3
{
public:
T obj;
//成员函数
void func1()
{
obj.showPerson1();
}
void func2()
{
obj.showPerson2();
}
//这里showPerson1()和showPerson2()是两个不同类下的成员函数,但是这里用一个变量调用,从逻辑上是说不过去的
//但是函数模板是没问题的,因为函数模板不会在一开始就创建这两个函数,只有在调用的时候才创建
};
void test3_01()
{
MyClass3<Person3_1> p;
p.func1(); //因为指明了T是Person3_1,所以可以调用
//p.func2(); //因为类型不对,所以不能调用
}
int main()
{
test3_01();
system("pause");
return 0;
}
学习目标:
一共有三种传入方式:
示例:
#include
#include
using namespace std;
template<class T1,class T2>
class Person4
{
public:
Person4(T1 name, T2 age)
{
this->name = name;
this->age = age;
}
void showPerson()
{
cout << "名字是: " << name << endl;
cout << "年龄是: " << age << endl;
}
T1 name;
T2 age;
};
//1.
void printPerson1(Person4<string,int>& p) //一般用这种,因为这样代码安全,不容易出错。
{
p.showPerson();
}
//2.
template<class T1,class T2>
void printPerson2(Person4<T1, T2>& p)
{
cout << "T1 : " << typeid(T1).name() << endl; //这是看数据类型的方法,可以查看类型或者变量的数据类型.
cout << "T2 : " << typeid(T2).name() << endl;
p.showPerson();
}
//3.
template<class T>
void printPerson3(T& p)
{
cout << typeid(T).name() << endl;
p.showPerson();
}
void test4_01()
{
//1.指定传入类型
Person4<string, int> p1("孙悟空", 100);
printPerson1(p1);
//2.参数模板化
Person4<string, int> p2("猪八戒", 90);
printPerson2(p2);
//3.整个类模板化
Person4<string, int> p3("沙僧", 30);
printPerson3(p3);
}
int main()
{
test4_01();
system("pause");
return 0;
}
查看数据类型语法:
typeid(变量/数据类型).name()
总结:
当类模板碰到继承时,需要注意以下几点:
class Son:继承方式 Base<数据类型>
示例:
#include
using namespace std;
template<class T>
class Base
{
public:
Base()
{
cout << "T类型:" << typeid(T).name() << endl;
}
T m;
};
class Son :public Base<int> //子类是普通函数,不指定数据类型,编译器无法分配内存.
{
public:
};
//如果想灵活指定父类T的类型,子类也需要变类模板
template<class T1, class T2>
class Son2 :public Base<T1> //根据传递规则可知,此时父类中的数据类型T就是现在的数据类型T1
{
public:
Son2()
{
cout << "T1类型:" << typeid(T1).name() << endl;
cout << "T2类型:" << typeid(T2).name() << endl;
}
T2 obj;
};
void test5_01()
{
Son s1;
Son2<double,char> s2; //T1为int ,T2为char
}
int main()
{
test5_01();
system("pause");
return 0;
}
语法:
类内定义
template<class T1,class T2,...>
//类内
函数声明;
//类外
template<class T1,class T2,...>
作用域<T1, T2,...>::函数名(){}
总结:
类模板中成员函数类外实现时,需要加上模板参数列表(无论成员函数是否使用了模板参数,都需要加,可以记为固定写法)。
示例:
#include
using namespace std;
template<class T1,class T2>
class Person6
{
public:
Person6(T1 name, T2 age); //类内声明
//{
// this->name = name;
// this->age = age;
//}
void showPerson();
//{
// cout << "姓名:" << name << endl;
// cout << "年龄:" << age << endl;
//}
T1 name;
T2 age;
};
template<class T1,class T2>
Person6<T1, T2>::Person6(T1 name, T2 age)
{
this->name = name;
this->age = age;
}
template<class T1, class T2>
void Person6<T1, T2>::showPerson()
{
cout << "姓名:" << name << endl;
cout << "年龄:" << age << endl;
}
void test6()
{
Person6<string,int> p1("孙悟空",20);
p1.showPerson();
}
int main()
{
test6();
system("pause");
return 0;
}
问题:
常规的分文件编写,是在.h头文件中编写类的成员函数声明,在.cpp源文件中编写类的成员函数定义。
这样写错在的问题就是——因为类中的成员函数不会在最开始就被编译器创建。当你的执行文件包含了头文件时,编译器在头文件中看到了成员函数的声明,编译不会出错,但是编译器不会去相应的.cpp文件中找这些成员函数的定义,就导致这些成员函数的定义没有被包含到执行文件中,所以调用的时候,编译器找不到声明对应的定义,就会无法解析命令。
解决:
示例(方式一很简单,这里只展示方式二):
创建person.hpp头文件,编写
#pragma once
#include
using namespace std;
template<class T1, class T2>
class Person7
{
public:
Person7(T1 name, T2 age);
void showPerson();
T1 name;
T2 age;
};
template<class T1, class T2>
Person7<T1, T2>::Person7(T1 name, T2 age)
{
this->name = name;
this->age = age;
}
template<class T1, class T2>
void Person7<T1, T2>::showPerson()
{
cout << "姓名: " << name << endl;
cout << "年龄: " << age << endl;
}
创建执行文件,编写
#include"person.hpp"
#include
//类模板分文件编写问题及解决
void test7()
{
Person7<string, int> p1("孙悟空", 1000);
p1.showPerson();
}
int main()
{
test7();
system("pause");
return 0;
}
#include
using namespace std;
//通过全局函数来打印Person8的信息
template<class T1,class T2>
class Person8;
//类外实现
//
//对于普通的函数和类都是一开始就由编译器创建,因为这里只声明了类而没有定义,所以会报错.
//因为函数模板同样不是一开始就被创建,而是在调用的时候才创建,所以这里不用担心类的声明和定义顺序
//因为在调用此函数模板的时候,类模板的相关定义已经被创建完毕.
template<class T1, class T2> //函数模板的实现
void printPerson2(Person8<T1, T2> p)
{
cout << "姓名 : " << p.name << endl;
cout << "年龄 : " << p.age << endl;
}
template<class T1,class T2>
class Person8
{
//全局函数 类内实现 据说只能在vs上通过
friend void printPerson(Person8<T1, T2> p)
{
cout << "姓名 : " << p.name << endl;
cout << "年龄 : " << p.age << endl;
}
//加空模板参数列表
//如果全局函数是类外实现,需要让编译器知道这个函数的存在
friend void printPerson2<>(Person8<T1, T2> p); //声明为函数模板
public:
Person8(T1 name, T2 age)
{
this->name = name;
this->age = age;
}
private:
T1 name;
T2 age;
};
void test01()
{
Person8<string, int> p1("汤姆", 20);
printPerson(p1);
printPerson2(p1);
}
int main()
{
test01();
system("pause");
return 0;
}
全局函数类外实现还是很复杂的。
注意:
printPerson2
函数中调用了类的成员,而此类在这个函数之前只有声明,没有定义。这对于普通的类来说是不能编译通过的,可以参考 类的友元以及类的定义和声明顺序问题。
但是对于类模板和函数模板是准许的,因为函数模板同样不是一开始就被创建,而是在调用的时候才创建,类的模板在调用此函数模板前已经被创建完毕,所以这里不用担心类的声明和定义顺序。
案例描述:实现一个通用的数组类,要求如下:
示例:
创建头文件: MyArray.hpp
#pragma once //防止重复包含
#include
using namespace std;
template<class T>
class MyArray
{
public:
//有参构造,参数是容量
MyArray(int capacity)
{
cout << "MyArray有参构造调用" << endl;
this->capacity = capacity;
this->size = 0;
this->pAddress = new T[this->capacity]; //capacity表示开辟的数组中的元素个数
}
//拷贝构造
MyArray(const MyArray& arr)
{
cout << "MyArray拷贝构造调用" << endl;
//浅拷贝
this->capacity = arr.capacity;
this->size = arr.size;
//深拷贝
this->pAddress = new T[arr.capacity];
for (int i = 0; i < this->size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
}
MyArray& operator=(const MyArray& arr)
{
cout << "MyArray的operator=构造调用" << endl;
//判断原来堆区是否有数据
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->capacity = 0;
this->size = 0;
}
//深拷贝
this->capacity = arr.capacity;
this->size = arr.size;
this->pAddress = new T[arr.capacity];
for (int i = 0; i < this->size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
//尾插法
void Push_Back(const T& value)
{
if (this->size == this->capacity)
{
return;
}
this->pAddress[this->size] = value;
this->size++;
}
//尾删法
void Pop_Back()
{
//让用户访问不到删除最后一个元素,即为尾删,逻辑删除
if (this->size == 0)
{
return;
}
this->size--;
}
//通过下标方式访问数组的元素 ,如果想让这个值作为左值,需要用引用类型,否则不会改变原数组元素. arr[0]=100
T& operator[](int index)
{
return this->pAddress[index];
}
//返回数组的容量
int getCapacity()
{
return this->capacity;
}
int getSize()
{
return this->size;
}
//析构函数
~MyArray()
{
if (this->pAddress != 0)
{
cout << "MyArray析构函数调用" << endl;
delete[] this->pAddress; //释放堆区数组
this->pAddress = 0;
}
}
private:
T* pAddress; //指向堆区开辟的真实数组
int capacity; //数组容量
int size; //数组大小
};
创建执行文件:
#include
using namespace std;
#include"MyArray.hpp"
void printArray(MyArray<int>& arr)
{
for (int i = 0; i < arr.getSize(); i++)
{
cout << arr[i] << endl; //索引
}
}
void test9_01()
{
MyArray<int> arr1(10);
MyArray<int> arr2(arr1);
MyArray<int> arr3(100);
arr3 = arr1;
}
void test9_02()
{
MyArray<int> arr1(10);
for (int i = 0; i < 6; i++)
{
//尾插法插入数据
arr1.Push_Back(i);
}
cout << "arr1的打印输出为: " << endl;
printArray(arr1);
cout << "arr1的容量为: " << arr1.getCapacity() << endl;
cout << "arr1的大小为: " << arr1.getSize() << endl;
MyArray<int> arr2(arr1);
cout << "arr2的打印输出为: " << endl;
printArray(arr2);
arr2.Pop_Back();
cout << "尾删后\narr2的打印输出为: " << endl;
printArray(arr2);
}
//测试自定义数据类型
class Person9
{
public:
Person9() {}; //必须写无参构造,向堆区开辟内存的时候是没有参数的,如果不写无参构造函数,会报错.
Person9(string name, int age)
{
this->name = name;
this->age = age;
}
string name;
int age;
};
void printArray(MyArray<Person9>& arr) //函数重载
{
for (int i = 0; i < arr.getSize(); i++)
{
cout << "姓名: " << arr[i].name << " 年龄: " << arr[i].age << endl;
}
}
void test9_03()
{
//在向堆区开辟内存的时候如果有有参构造函数,就不会调用无参构造函数,而这里又没有传入参数,那么就无法开辟内存而报错
MyArray<Person9> arr(10);
Person9 p1("孙悟空", 999);
Person9 p2("猪八戒", 900);
Person9 p3("沙僧", 850);
Person9 p4("赵云", 550);
Person9 p5("关羽", 650);
//数据插入
arr.Push_Back(p1);
arr.Push_Back(p2);
arr.Push_Back(p3);
arr.Push_Back(p4);
arr.Push_Back(p5);
printArray(arr);
//输出容量
cout << "arr的容量为: " << arr.getCapacity() << endl;
//输出大小
cout << "arr的大小为: " << arr.getSize() << endl;
}
int main()
{
test9_01();
test9_02();
test9_03();
system("pause");
return 0;
}