什么是类模板?
类模板和函数模板总体上差不多,都是进行虚拟替换!
https://blog.csdn.net/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;
}
注意事项:
- 模板类定义类对象,必须显示指定类型
- 模板中如果使用了构造函数, 则遵守以前的类的构造函数的调用规则
- 使用类模板,函数参数必须显示指定类型
https://blog.csdn.net/cpp_learner/article/details/103335482
运行截图:
继承和派生中类模板的使用
https://blog.csdn.net/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-blog.csdnimg.cn/2020022113384279.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文件。
类模板与友元函数
https://blog.csdn.net/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静态变量value
cout << "*****************************" << endl;
Father<float> s1, s2, s3;
// 使用不同类型的对象去修改value
s1.value = 999;
cout << "f3:" << f3.value << endl;
// 由结果可以知道,不同类型的对象他们并没有共性static静态变量value
s2.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; -
和使用类一样,使用类模板时要注意其作用域,只有在它的有效作用域内用使用它定义对象。
-
模板类也可以有支持继承,有层次关系,一个类模板可以作为基类,派生出派生模板类。
来源:CSDN
作者:cpp_learner
链接:https://blog.csdn.net/cpp_learner/article/details/104424898