什么是类模板?
类模板和函数模板总体上差不多,都是进行虚拟替换!
/cpp_learner/article/details/104390433
为什么要使用类模板?
类模板能够为类的数据成员、成员函数的参数、返回值提供动态参数化的机制,即可以构造不同数据类型的实例。
类模板的定义
类模板由模板说明和类说明构成
模板说明同函数模板,如下:
template <类型形式参数表>
类声明
例如:
template <typename T>class Father {public:Father(T name = "无名");//Father 的成员函数private :T name;};
单个类模板的使用
其实就是说明了模板,然后在对类的成员类型使用模板的参数替换!
代码示例:
#include <iostream>#include <Windows.h>using namespace std;// 模板说明template <typename T>class test {public:// 构造函数的参数列表使用虚拟类型test(T k = 10) {this->k = k; }// 成员函数返回值使用虚拟类型T getK() const {return k; }private:// 数据类型使用虚拟类型T k;};// 使用类模板,函数参数必须显示指定类型void print(test<int>& test) {cout << test.getK() << endl;}int main(void) {// 1.模板类定义类对象,必须显示指定类型// 2.模板中如果使用了构造函数, 则遵守以前的类的构造函数的调用规则test<int> k(100);cout << k.getK() << endl;print(k);system("pause");return 0;}
注意事项:
模板类定义类对象,必须显示指定类型模板中如果使用了构造函数, 则遵守以前的类的构造函数的调用规则使用类模板,函数参数必须显示指定类型
/cpp_learner/article/details/103335482
运行截图:
继承和派生中类模板的使用
/cpp_learner/article/details/104076682
一共有三种情况:
父类是普通类,子类是模板类;父类是模板类,子类是普通类;父类和子类都是模板类。
第一种情况:父类是普通类,子类是模板类
和普通继承的用法一模一样,子类的模板类型可以给继承于父类的数据成员使用
例:
定义一个普通类Father,有私有成员char *name; int age;
定义一个模板类Son, 有私有成员int weight;
定义对象Father 和 Son ,并输出里面的值。
代码:
#include <iostream>#include <Windows.h>using namespace std;// 第一种情况:父类是普通类,子类是模板类:// 和普通继承的用法一模一样,子类的模板类型可以给继承于父类的数据成员使用class Father {public:Father(const char* name=NULL, int age=0) {if (!name) {name = "无名";}this->name = new char[strlen(name) + 1];strcpy_s(this->name, strlen(name) + 1, name);this->age = age;}~Father() {if (name) {delete name;}}char* getName() const {return name; }int getAge() const {return age; }private:char* name;int age;};template <typename T>class Son : public Father {public:// 子类的模板类型可以给继承与父类的数据成员使用Son(const char* name = NULL, T age = 0, T weight = 0) : Father(name, age) {this->weigth = weight;}~Son() {}T getWeigth() const {return weigth; }private:T weigth;// 体重};int main(void) {Father father("父亲", 38);// 定义儿子对象时,需要指定类型Son<int> son("儿子", 18, 55);cout << "姓名:" << father.getName() << ", 年龄:"<< father.getAge() << endl;cout << "姓名:" << son.getName() << ", 年龄:" << son.getAge() << ", 体重:" << son.getWeigth() << endl;system("pause");return 0;}
运行截图:
和普通继承的用法一模一样,子类的模板类型可以给继承于父类的数据成员使用
定义模板类对象时,需要指定类型:
例如代码中的Son:
// 定义儿子对象时,需要指定类型Son<int> son("儿子", 18, 55);
子类的模板类型可以给继承于父类的数据成员使用:
Son(const char* name = NULL, T age = 0, T weight = 0) : Father(name, age) {this->weigth = weight;}
第二种情况:父类是模板类,子类是普通类
继承时必须在子类里实例化父类的类型参数,子类无法使用父类的模板类型
代码示例:
#include <iostream>#include <Windows.h>using namespace std;// 第二种情况:父类是模板类,子类是普通类:// 继承时必须在子类里实例化父类的类型参数,子类无法使用父类的模板类型template <typename T, typename Y>class Father {public:Father(const T* name = NULL, Y age = 0) {if (!name) {name = "无名";}this->name = new char[strlen(name) + 1];strcpy_s(this->name, strlen(name) + 1, name);this->age = age;}~Father() {if (name) {delete name;}}T* getName() const {return name; }Y getAge() const {return age; }private:T* name;Y age;};// 继承时必须在子类里实例化父类的类型参数class Son : public Father<char, int> {public:// 子类中, 不能使用T,Y表示类型// 这里显示类型可写可不写,因为父类的构造函数赋了默认值Son(const char* name = NULL, int age = 0, int weight = 0) : Father<char, int>(name, age) {this->weigth = weight;}~Son() {}int getWeigth() const {return weigth; }private:int weigth;// 体重};int main(void) {// 定义父亲对象时,需要指定类型Father<char, int> father("父亲", 40);Son son("儿子", 20, 60);cout << "姓名:" << father.getName() << ", 年龄:"<< father.getAge() << endl;cout << "姓名:" << son.getName() << ", 年龄:"<< son.getAge() << ", 体重:" << son.getWeigth() << endl;system("pause");return 0;}
运行截图:
继承时必须在子类里实例化父类的类型参数,子类无法使用父类的模板类型
定义模板类对象时,需要指定类型!
例如代码中的Father:
// 定义父亲对象时,需要指定类型Father<char, int> father("父亲", 40);
继承时必须在子类里实例化父类的类型参数:
// 继承时必须在子类里实例化父类的类型参数class Son : public Father<char, int> {};
第三种情况,父类和子类都是模板类时
继承时必须在子类里实例化父类的类型参数,子类的虚拟类型可以传递到父类中使用
代码示例:
#include <iostream>#include <Windows.h>using namespace std;// 第三种情况,父类和子类都是模板类时// 继承时必须在子类里实例化父类的类型参数,子类的虚拟类型可以传递到父类中使用template <typename T, typename Y>class Father {public:Father(const T* name = NULL, Y age = 0) {if (!name) {name = "无名";}this->name = new char[strlen(name) + 1];strcpy_s(this->name, strlen(name) + 1, name);this->age = age;}~Father() {if (name) {delete name;}}T* getName() const {return name; }Y getAge() const {return age; }private:T* name;Y age;};// 继承时必须在子类里实例化父类的类型参数template <typename Z>class Son : public Father<char, int> {public:// 子类中, 不可以使用父类的T,Y表示类型// 这里显示类型可写可不写,// 子类的模板类型可以给继承与父类的数据成员使用// 因为父类的构造函数赋了默认值Son(const char* name = NULL, Z age = 0, Z weight = 0) : Father<char, int>(name, age) {this->weigth = weight;}~Son() {}Z getWeigth() const {return weigth; }private:Z weigth;// 体重};int main(void) {// 定义父亲对象时,需要指定类型Father<char, int> father("父亲", 42);// 定义儿子对象时,需要指定类型Son<int> son("儿子", 22, 62);cout << "姓名:" << father.getName() << ", 年龄:"<< father.getAge() << endl;cout << "姓名:" << son.getName() << ", 年龄:"<< son.getAge() << ", 体重:" << son.getWeigth() << endl;system("pause");return 0;}
运行截图:
继承时必须在子类里实例化父类的类型参数,子类的虚拟类型可以传递到父类中使用
定义模板类对象时,需要指定类型!
例如代码中的Father 和 Son:
// 定义父亲对象时,需要指定类型Father<char, int> father("父亲", 42);// 定义儿子对象时,需要指定类型Son<int> son("儿子", 22, 62);
继承时必须在子类里实例化父类的类型参数:
// 继承时必须在子类里实例化父类的类型参数template <typename Z>class Son : public Father<char, int> {};
子类的虚拟类型可以传递到父类中使用:
// 子类中, 不可以使用父类的T,Y表示类型// 这里显示类型可写可不写,// 子类的模板类型可以给继承与父类的数据成员使用// 因为父类的构造函数赋了默认值Son(const char* name = NULL, Z age = 0, Z weight = 0) : Father<char, int>(name, age) {this->weigth = weight;}
结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么
父类一般类,子类是模板类, 和普通继承的玩法类似子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中
所有的类模板函数写在类的内部;类模板函数的三种表达描述方式
(上面 继承和派生中类模板的使用 中就是这种用法)所有的类模板函数写在类的外部,在一个cpp中所有的类模板函数写在类的外部,在不同的.h和.cpp中
第二种:所有的类模板函数写在类的外部,在一个cpp中
需求:
定义一个Father类(使用模板类),有私有成员float weigth; int age;
;重载加号运算符和赋值运算符;定义对象:
Father<float, int> f1(50, 30);Father<float, int> f2(60, 32);Father<float, int> f3(0, 0);
计算:f3 = f1 + f2;
;输出f3.
根据需求,我们得先说明类模板:template <typename T, typename Y>;
然后实现类,在类的外部再实现函数方法,但是每个函数前都得再说明一次类模板;
返回值类型、类声明也必须加上<T, Y>,且类型说明也必须使用T,Y替换。
代码示例:
#include <iostream>#include <Windows.h>using namespace std;// 所有的类模板函数写在类的外部实现,在一个cpp中template <typename T, typename Y>class Father {public:Father(T weigth = 0, Y age = 0);T getWeigth() const;Y getAge() const;Father& operator+(const Father& father);Father& operator=(const Father& father);private:T weigth;Y age;};// 必须重新说明类模板template <typename T, typename Y>// 类声明也必须加上<T, Y>,且类型说明也必须使用T,Y替换Father<T, Y>::Father(T weigth, Y age) {this->weigth = weigth;this->age = age;}// 必须重新说明类模板template <typename T, typename Y>// 返回值类型也必须使用类模板中的参数替换T Father<T, Y>::getWeigth() const {return weigth;}template <typename T, typename Y>Y Father<T, Y>::getAge() const {return age;}template<typename T, typename Y>// 类的返回值也必须使用类模板中的参数替换Father<T, Y>& Father<T, Y>::operator+(const Father& father){static Father f;// 可写可不写类模板中的参数:Father<T, Y> f;f.weigth = this->weigth + father.weigth;f.age = this->age + father.age;return f;}template<typename T, typename Y>// 赋值运算符重载Father<T, Y>& Father<T, Y>::operator=(const Father& father) {this->weigth = father.weigth;this->age = father.age;return *this;}![在这里插入图片描述](https://img-/022113384279.png)int main(void) {Father<float, int> f1(50, 30);Father<float, int> f2(60, 32);Father<float, int> f3(0, 0);f3 = f1 + f2;cout << "f3的体重为:" << f3.getWeigth() << ", 年龄为:" << f3.getAge() << endl;system("pause");return 0;}
运行截图:
总结:
在同一个cpp 文件中把模板类的成员函数放到类的外部,需要注意以下几点:
函数前声明 template <类型形式参数表>类的成员函数前的类限定域说明必须要带上虚拟参数列表返回的变量是模板类的对象时必须带上虚拟参数列表成员函数参数中出现模板类的对象时必须带上虚拟参数列表成员函数内部没有限定
第三种:所有的类模板函数写在类的外部,在不同的.h和.cpp中
用法和第二种类似,只是要记得,模板类实现的文件后缀一般用 .hpp ; 且mian函数里面需包含的头文件不是类声明的.h文件,而且类实现的.hpp文件。
代码实例:
Fatehr.h
#pragma once// 所有的类模板函数分开写,写在不同的文件中template <typename T, typename Y>class Father {public:Father(T weigth = 0, Y age = 0);T getWeigth() const;Y getAge() const;Father& operator+(const Father& father);Father& operator=(const Father& father);private:T weigth;Y age;};
Father.hpp
#include "Father.h"// 必须重新说明类模板template <typename T, typename Y>// 类声明也必须加上<T, Y>,且类型说明也必须使用T,Y替换Father<T, Y>::Father(T weigth, Y age) {this->weigth = weigth;this->age = age;}// 必须重新说明类模板template <typename T, typename Y>// 返回值类型也必须使用类模板中的参数替换T Father<T, Y>::getWeigth() const {return weigth;}template <typename T, typename Y>Y Father<T, Y>::getAge() const {return age;}template<typename T, typename Y>// 类的返回值也必须使用类模板中的参数替换Father<T, Y>& Father<T, Y>::operator+(const Father& father){static Father f;// 可写可不写类模板中的参数:Father<T, Y> f;f.weigth = this->weigth + father.weigth;f.age = this->age + father.age;return f;}template<typename T, typename Y>// 赋值运算符重载Father<T, Y>& Father<T, Y>::operator=(const Father& father) {this->weigth = father.weigth;this->age = father.age;return *this;}
mian函数
#include <iostream>#include <Windows.h>#include "Father.hpp"using namespace std;int main(void) {Father<float, int> f1(50, 30);Father<float, int> f2(60, 32);Father<float, int> f3(0, 0);f3 = f1 + f2;cout << "f3的体重为:" << f3.getWeigth() << ", 年龄为:" << f3.getAge() << endl;system("pause");return 0;}
运行截图:
注意:当类模板的声明(.h文件)和实现(.cpp 或.hpp文件)完全分离,因为类模板的特殊实现,我们应在使用类模板时使用#include 包含 实现部分的.cpp 或.hpp文件。
类模板与友元函数
/cpp_learner/article/details/104193181
这两者合在一起使用属于特殊情况
需求:
使用友元函数实现两类相加
代码示例:
#include <iostream>#include <Windows.h>using namespace std;// 所有的类模板函数写在类的外部实现,在一个cpp中template <typename T, typename Y>class Father {public:Father(T weigth = 0, Y age = 0);void print() const;//Father& operator+(const Father& father);Father& operator=(const Father& father);private:T weigth;Y age;// 必须说明类模板template <typename T, typename Y>friend Father<T, Y> add(const Father<T, Y> &f1, const Father<T, Y> &f2);};template <typename T, typename Y>Father<T, Y>::Father(T weigth, Y age) {this->weigth = weigth;this->age = age;}//template<typename T, typename Y> 类的返回值也必须使用类模板中的参数替换//Father<T, Y>& Father<T, Y>::operator+(const Father& father)//{//static Father f;// 可写可不写类模板中的参数:Father<T, Y> f;////f.weigth = this->weigth + father.weigth;//f.age = this->age + father.age;////return f;//}template<typename T, typename Y>void Father<T, Y>::print() const {cout << "体重为:" << this->weigth << ", 年龄为:" << this->age << endl;}template<typename T, typename Y>// 赋值运算符重载Father<T, Y>& Father<T, Y>::operator=(const Father& father) {this->weigth = father.weigth;this->age = father.age;return *this;}// 友元函数实现:两数相加template<typename T, typename Y>//返回值类型,类类型,函数体里面定义对象都需要添加对应的类型Father<T, Y> add(const Father<T, Y>& f1, const Father<T, Y>& f2) {Father<T, Y> f;f.weigth = f1.weigth + f2.weigth;f.age= f1.age + f2.age;return f;}int main(void) {Father<float, int> f1(500, 30);Father<float, int> f2(60, 32);Father<float, int> f3(0, 0);// 调用友元函数时也必须添加类型f3 = add<float, int>(f1, f2);f3.print();system("pause");return 0;}
运行截图:
上面那种情况是 “所有的类模板函数写在类的外部实现,在一个cpp中”,
现在我们把它分成三个文件来实现,
其实都是一样的。
Father.h
#pragma once// 所有的类模板函数分开写,写在不同的文件中template <typename T, typename Y>class Father {public:Father(T weigth = 0, Y age = 0);void print() const;//Father& operator+(const Father& father);Father& operator=(const Father& father);private:T weigth;Y age;template <typename T, typename Y>friend Father<T, Y> add(const Father<T, Y>& f1, const Father<T, Y>& f2);};
Father.hpp
#include <iostream>#include "Father.h"using namespace std;// 必须重新说明类模板template <typename T, typename Y>// 类声明也必须加上<T, Y>,且类型说明也必须使用T,Y替换Father<T, Y>::Father(T weigth, Y age) {this->weigth = weigth;this->age = age;}template <typename T, typename Y>void Father<T, Y>::print() const {cout << "体重为:" << this->weigth << ", 年龄为:" << this->age << endl;}//template<typename T, typename Y> 类的返回值也必须使用类模板中的参数替换//Father<T, Y>& Father<T, Y>::operator+(const Father& father)//{//static Father f;// 可写可不写类模板中的参数:Father<T, Y> f;////f.weigth = this->weigth + father.weigth;//f.age = this->age + father.age;////return f;//}template<typename T, typename Y>// 赋值运算符重载Father<T, Y>& Father<T, Y>::operator=(const Father& father) {this->weigth = father.weigth;this->age = father.age;return *this;}// 友元函数实现:两数相加template<typename T, typename Y>Father<T, Y> add(const Father<T, Y>& f1, const Father<T, Y>& f2) {Father<T, Y> f;f.weigth = f1.weigth + f2.weigth;f.age = f1.age + f2.age;return f;}
mian函数
#include <Windows.h>#include "Father.hpp"int main(void) {Father<float, int> f1(50, 30);Father<float, int> f2(60, 32);Father<float, int> f3(0, 0);//f3 = f1 + f2;f3 = add(f1, f2);f3.print();system("pause");return 0;}
运行截图:
结论:
(1) 类内部声明友元函数,必须写成一下形式
template<typename T>friend Father<T> add (Father<T> &a, Father<T> &b);
(2) 友元函数实现 必须写成
template<typename T>Father<T> add(Father<T> &a, Father<T> &b) {// 如果有定义新的对象那么也必须写成:Father<T> c;}
(3) 友元函数调用 必须写成
Father<int> c1, c2;Father<int> c = add<int>(c1, c2);
类模板与静态变量
代码注释中有详细的用法和知识点。
代码示例:
#include <iostream>#include <Windows.h>using namespace std;template <typename T>class Father {public:Father(T socre = 0);void getScore() const;public:static T value;private:T score;};// 静态成员也可以使用类模板参数实例化template <typename T> T Father<T>::value = 666;template <typename T>Father<T>::Father(T cosre) {this->score = score;}template <typename T>void Father<T>::getScore() const {return score;}int main(void) {Father<int> f1, f2, f3;cout << "f1:" << f1.value << endl;// 使用f2去修改静态变量value的值f2.value = 888;cout << "f3:" << f3.value << endl;// 由结果可以看出,f1, f2, f3 共享static静态变量valuecout << "*****************************" << endl;Father<float> s1, s2, s3;// 使用不同类型的对象去修改values1.value = 999;cout << "f3:" << f3.value << endl;// 由结果可以知道,不同类型的对象他们并没有共性static静态变量values2.value = 111;cout << "s3:" << s3.value << endl;// 最后得出总结:// 类模板中,同一类型的对象共享同一个静态变量// 不同类型的对象并没有共享同一个静态变量,而是有各自的静态变量return 0;}
运行截图:
分开三个文件来实现用法都是一样的,这里就不介绍了。
总结:
从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员;和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化;static 数据成员也可以使用虚拟类型参数T。
类模板使用总结
归纳以上的介绍,可以这样声明和使用类模板:
先写出一个实际的类。
将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的T)。
在类声明前面加入一行,格式为:
template <typename 虚拟类型参数>
如:
template < typename numtype >
class A
{…}; //类体
用类模板定义对象时用以下形式:
类模板名<实际类型名> 对象名;
或 类模板名<实际类型名> 对象名(实参表列);
如:
A cmp;
A cmp(3,7);
如果在类模板外定义成员函数,应写成类模板形式:
template <typename 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
关于类模板的几点补充:
类模板的类型参数可以有一个或多个,每个类型前面都必须加typename 或class,如:
template <typename T1,typename T2>
class someclass
{…};
在定义对象时分别代入实际的类型名,如:
someclass<int, char> object;
和使用类一样,使用类模板时要注意其作用域,只有在它的有效作用域内用使用它定义对象。
模板类也可以有支持继承,有层次关系,一个类模板可以作为基类,派生出派生模板类。