C++ 类模板 详解
什么是类模板?
类模板和函数模板总体上差不多,都是进行虚拟替换!
https://blog.csdn.net/cpp_learner/article/details/104390433
为什么要使用类模板?
类模板能够为类的数据成员、成员函数的参数、返回值提供动态参数化的机制,即可以构造不同数据类型的实例。
类模板的定义
类模板由模板说明和类说明构成
模板说明同函数模板,如下:
template <类型形式参数表>
类声明
例如:
template <typename T>
class Father {
public:Father(T name = "无名");//Father 的成员函数private :T name;
};
单个类模板的使用
其实就是说明了模板,然后在对类的成员类型使用模板的参数替换!
代码示例:
#include
#include 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
#include 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
#include 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
#include 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
然后实现类,在类的外部再实现函数方法,但是每个函数前都得再说明一次类模板;
返回值类型、类声明也必须加上
代码示例:
#include
#include 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替换
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 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;
}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替换
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 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
#include
#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
#include 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
类的返回值也必须使用类模板中的参数替换
//Father& Father::operator+(const Father& father)
//{
// static Father f; // 可写可不写类模板中的参数:Father 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
#include "Father.h"using namespace std;// 必须重新说明类模板
template <typename T, typename 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
类的返回值也必须使用类模板中的参数替换
//Father& Father::operator+(const Father& father)
//{
// static Father f; // 可写可不写类模板中的参数:Father 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
#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
#include 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
如:
template < typename numtype >
class A
{…}; //类体 -
用类模板定义对象时用以下形式:
类模板名<实际类型名> 对象名;
或 类模板名<实际类型名> 对象名(实参表列);
如:
A cmp;
A cmp(3,7); -
如果在类模板外定义成员函数,应写成类模板形式:
template
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
关于类模板的几点补充:
-
类模板的类型参数可以有一个或多个,每个类型前面都必须加typename 或class,如:
template
class someclass
{…};
在定义对象时分别代入实际的类型名,如:
someclassobject; -
和使用类一样,使用类模板时要注意其作用域,只有在它的有效作用域内用使用它定义对象。
-
模板类也可以有支持继承,有层次关系,一个类模板可以作为基类,派生出派生模板类。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
