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;
}

注意事项:

  1. 模板类定义类对象,必须显示指定类型
  2. 模板中如果使用了构造函数, 则遵守以前的类的构造函数的调用规则
  3. 使用类模板,函数参数必须显示指定类型

https://blog.csdn.net/cpp_learner/article/details/103335482

运行截图:

在这里插入图片描述


继承和派生中类模板的使用

https://blog.csdn.net/cpp_learner/article/details/104076682

一共有三种情况:

  1. 父类是普通类,子类是模板类;
  2. 父类是模板类,子类是普通类;
  3. 父类和子类都是模板类。

第一种情况:父类是普通类,子类是模板类

和普通继承的用法一模一样,子类的模板类型可以给继承于父类的数据成员使用

例:

  1. 定义一个普通类Father,有私有成员 char *name; int age;
  2. 定义一个模板类Son, 有私有成员 int weight;
  3. 定义对象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;
}

结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么

  1. 父类一般类,子类是模板类, 和普通继承的玩法类似
  2. 子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数
  3. 父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中

类模板函数的三种表达描述方式

  1. 所有的类模板函数写在类的内部;
    (上面 继承和派生中类模板的使用 中就是这种用法)
  2. 所有的类模板函数写在类的外部,在一个cpp中
  3. 所有的类模板函数写在类的外部,在不同的.h和.cpp中

第二种:所有的类模板函数写在类的外部,在一个cpp中

需求:

  1. 定义一个Father类(使用模板类),有私有成员float weigth; int age;;
  2. 重载加号运算符和赋值运算符;
  3. 定义对象:
	Father<float, int> f1(50, 30);Father<float, int> f2(60, 32);Father<float, int> f3(0, 0);
  1. 计算:f3 = f1 + f2;;
  2. 输出f3.

根据需求,我们得先说明类模板:template
然后实现类,在类的外部再实现函数方法,但是每个函数前都得再说明一次类模板;
返回值类型、类声明也必须加上,且类型说明也必须使用T,Y替换。

代码示例:

#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;
}![在这里插入图片描述](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 文件中把模板类的成员函数放到类的外部,需要注意以下几点:

  1. 函数前声明 template <类型形式参数表>
  2. 类的成员函数前的类限定域说明必须要带上虚拟参数列表
  3. 返回的变量是模板类的对象时必须带上虚拟参数列表
  4. 成员函数参数中出现模板类的对象时必须带上虚拟参数列表
  5. 成员函数内部没有限定

第三种:所有的类模板函数写在类的外部,在不同的.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;
}

运行截图:

在这里插入图片描述

分开三个文件来实现用法都是一样的,这里就不介绍了。

总结:

  1. 从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员;
  2. 和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化;
  3. static 数据成员也可以使用虚拟类型参数T。

类模板使用总结

归纳以上的介绍,可以这样声明和使用类模板:

  1. 先写出一个实际的类。

  2. 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的T)。

  3. 在类声明前面加入一行,格式为:
    template
    如:
    template < typename numtype >
    class A
    {…}; //类体

  4. 用类模板定义对象时用以下形式:
    类模板名<实际类型名> 对象名;
    或 类模板名<实际类型名> 对象名(实参表列);
    如:
    A cmp;
    A cmp(3,7);

  5. 如果在类模板外定义成员函数,应写成类模板形式:
    template
    函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}

关于类模板的几点补充:

  1. 类模板的类型参数可以有一个或多个,每个类型前面都必须加typename 或class,如:
    template
    class someclass
    {…};
    在定义对象时分别代入实际的类型名,如:
    someclass object;

  2. 和使用类一样,使用类模板时要注意其作用域,只有在它的有效作用域内用使用它定义对象。

  3. 模板类也可以有支持继承,有层次关系,一个类模板可以作为基类,派生出派生模板类。


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部