C++类模板核心原理深度解析

发表时间: 2024-07-30 15:39

在现代C++编程中,类模板是一种强大的工具,能够编写通用的类,以处理各种不同类型的数据。本文将深入探讨C++类模板的核心概念和用法,从基础定义到实际应用,帮助读者全面理解如何利用类模板提升代码的灵活性和复用性。无论是如何定义和实例化类模板,还是如何处理类模板中的成员函数和友元,都将在本文中得到详细讲解。

01

类模板的概述

C++ 类模板是一种用于创建通用类的机制,它可以让程序员编写一次类,然后让它适用于多种类型,在实际编程中非常实用。

类模板和函数模板的定义和使用类似。有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,类模板用于实现类所需数据的类型参数化。

类模板的定义方式类似于普通类的定义,只是需要在类名后面添加一对尖括号,其中包含类型参数列表。

02

类模板的定义方式

类模板定义:

template <class T>class MyStack {public:    // 构造函数、析构函数、入栈、出栈等函数private:    T* data;    int top;};

其中,template 关键字用于声明类模板,typename 则是用于指定类模板参数的类型,T 是类模板参数的名称,可以根据需要进行替换。类模板的实现和普通类类似,可以包含成员变量和成员函数,其中成员函数的实现可以放在头文件中或者单独的源文件中。需要注意的是,类模板的定义通常需要放在头文件中,以便在使用时能够正确地进行编译和链接。

在实际使用类模板时,需要通过指定类模板参数的实际类型来实例化类模板。类模板的实例化方式与函数模板类似,可以通过显式实例化、隐式实例化和模板特化三种方式来实现。其中,显式实例化是指通过在代码中显式地指定模板参数类型来实例化类模板,隐式实例化是指通过创建类实例时传递的实参类型来自动确定模板参数类型,模板特化则是指为特定的模板参数类型提供特定的实现方式。

需要注意的是,类模板可以嵌套在其他类中,也可以作为其他类的成员。此外,类模板可以使用类型别名、模板参数类型的默认值、模板特化等特性来增强其灵活性和可读性。

特别注意:类模板实例化对象时,不能自动类型推导。

示例:

#include <iostream>using namespace std;// 类模板template <class T1,class T2>class Data {private:	T1 a;	T2 b;public:	Data()	{		cout << "Data的无参构造" << endl;	}	Data(T1 a, T2 b)	{		this->a = a;		this->b = b;		cout << "Data的有参构造" << endl;	}	void showData()	{		cout << a << " " << b <<endl;	}};int main(){	// 类模板实例化对象	// Data ob(100,200);// error,必须指定类型	// 类模板实例化对象,必须指明T的类型	Data<int, int> ob(300, 400);	ob.showData();	Data<int, char> ob2(100, 'A');	ob2.showData();		return 0;}

输出:

Data的有参构造300 400Data的有参构造100 A

类模板的成员函数在类外实现:

  • 在每个成员函数前必须添加template<>
  • 作用域需要添加<>修饰。

示例:

#include <iostream>using namespace std;// 类模板template <class T1,class T2>class Data {private:	T1 a;	T2 b;public:	Data();	Data(T1 a, T2 b);	void showData();};template <class T1, class T2>Data<T1,T2>::Data(){	cout << "Data的无参构造" << endl;}template <class T1, class T2>Data<T1, T2>::Data(T1 a, T2 b){	this->a = a;	this->b = b;	cout << "Data的有参构造" << endl;}template<class T1,class T2>void Data<T1,T2>::showData(){	cout << a << " " << b << endl;}int main(){	// 类模板实例化对象	// Data ob(100,200);// error,必须指定类型	// 类模板实例化对象,必须指明T的类型	Data<int, int> ob(300, 400);	ob.showData();	Data<int, char> ob2(100, 'A');	ob2.showData();		return 0;}

03

函数模板作为类模板的友元

需要有template修饰。
示例:

#include <iostream>using namespace std;// 类模板template <class T1,class T2>class Data {	template <typename T3, typename T4>	friend void MyPrint(Data<T3, T4> &ob);private:	T1 a;	T2 b;public:	Data();	Data(T1 a, T2 b);	void showData();};template <class T1, class T2>Data<T1,T2>::Data(){	cout << "Data的无参构造" << endl;}template <class T1, class T2>Data<T1, T2>::Data(T1 a, T2 b){	this->a = a;	this->b = b;	cout << "Data的有参构造" << endl;}template<class T1,class T2>void Data<T1,T2>::showData(){	cout << a << " " << b << endl;}// 函数模板template <typename T3,typename T4>void MyPrint(Data<T3, T4> &ob){	cout << "函数模板友元:" << ob.a << " " << ob.b << endl;}int main(){	// 类模板实例化对象	// Data ob(100,200);// error,必须指定类型	// 类模板实例化对象,必须指明T的类型	Data<int, int> ob(300, 400);	MyPrint(ob);		Data<int, char> ob2(100,'A');	MyPrint(ob2);	return 0;}

输出:

Data的有参构造函数模板友元:300 400Data的有参构造函数模板友元:100 A

04

普通函数作为类模板的友元

普通函数的形参名必须是一个类模板确定的(即具体化类型)。

示例:

#include <iostream>using namespace std;// 类模板template <class T1,class T2>class Data {	friend void MyPrint(Data<int, char> &ob);private:	T1 a;	T2 b;public:	Data();	Data(T1 a, T2 b);	void showData();};template <class T1, class T2>Data<T1,T2>::Data(){	cout << "Data的无参构造" << endl;}template <class T1, class T2>Data<T1, T2>::Data(T1 a, T2 b){	this->a = a;	this->b = b;	cout << "Data的有参构造" << endl;}template<class T1,class T2>void Data<T1,T2>::showData(){	cout << a << " " << b << endl;}// 普通函数void MyPrint(Data<int, char> &ob){	cout << "普通函数友元:" << ob.a << " " << ob.b << endl;}int main(){	// 类模板实例化对象	//Data<int, int> ob(300, 400);	//MyPrint(ob);// error,不识别		Data<int, char> ob2(100,'A');	MyPrint(ob2);	return 0;}

输出:

Data的有参构造普通函数友元:100 A

05

模板头文件和源文件分离的问题

5.1、问题复现

首先,建立两个文件,分别是类模板的头文件(.h)和源文件(.cpp)。它们的内容如下:
Data.h

#pragma once#ifndef _DATA_H_#include <iostream>using namespace std;// 类模板template <class T1, class T2>class Data {private:	T1 a;	T2 b;public:	Data();	Data(T1 a, T2 b);	void showData();};#endif // !_DATA_H_

Data.cpp

#include "Data.h"template <class T1, class T2>Data<T1, T2>::Data(){	cout << "Data的无参构造" << endl;}template <class T1, class T2>Data<T1, T2>::Data(T1 a, T2 b){	this->a = a;	this->b = b;	cout << "Data的有参构造" << endl;}template<class T1, class T2>void Data<T1, T2>::showData(){	cout << a << " " << b << endl;}

main.cpp

#include <iostream>using namespace std;#include "Data.h"int main(){	// 类模板实例化对象	//Data<int, int> ob(300, 400);	//MyPrint(ob);// error,不识别		Data<int, char> ob2(100,'A');	ob2.showData();	return 0;}

编译报错:


如果没有实例化对象,则可以编译成功。

5.2、本质原因

本质原因是模板需要经过两次编译,第一次在预处理阶段,对类模板本身的编译;第二次是在实例化对象的时候,这个时候由于头文件和源文件分离,导致无法找到找到函数的实现位置,因为第二次编译的时候不会再去编译.cpp的源文件,没有重新编译就会导致无法确定T1和T2的类型。从而出现编译报错。

5.3、解决方案

既然是在第二次编译没有对.cpp编译过来,那么我们可以在使用的地方把cpp也包含过来。
例如:

#include <iostream>using namespace std;#include "Data.h"#include "Data.cpp"int main(){	// 类模板实例化对象		Data<int, char> ob2(100,'A');	ob2.showData();	return 0;}

这时就可以编译通过了。但是,一般是包含头文件,不包含源文件,既包含头文件又包含源文件这种方式还不如直接放入一个文件里面,比如把代码全部放在头文件里面。这里又有一些习惯问题,头文件一般不包含具体实现的内容,所以为了区分,C++使用了一种特殊的头文件名称.hpp(.h和.cpp的结合体)。

示例:
Data.hpp

#pragma once#ifndef _DATA_H_#include <iostream>using namespace std;// 类模板template <class T1, class T2>class Data {private:	T1 a;	T2 b;public:	Data();	Data(T1 a, T2 b);	void showData();};template <class T1, class T2>Data<T1, T2>::Data(){	cout << "Data的无参构造" << endl;}template <class T1, class T2>Data<T1, T2>::Data(T1 a, T2 b){	this->a = a;	this->b = b;	cout << "Data的有参构造" << endl;}template<class T1, class T2>void Data<T1, T2>::showData(){	cout << a << " " << b << endl;}#endif // !_DATA_H_

main.cpp

#include <iostream>using namespace std;#include "Data.hpp"int main(){	// 类模板实例化对象		Data<int, char> ob2(100,'A');	ob2.showData();	return 0;}


06

总结

C++ 类模板是一种用于创建通用类的机制,可以让程序员编写一次类,然后让它适用于多种类型,在实际编程中非常实用。

类模板的定义方式类似于普通类的定义,只是需要在类名后面添加一对尖括号,其中包含类型参数列表。类模板的实现和普通类类似,可以包含成员变量和成员函数,其中成员函数的实现可以放在头文件中或者单独的源文件中。需要注意的是,类模板的定义通常需要放在头文件中,以便在使用时能够正确地进行编译和链接。

在实际使用类模板时,需要通过指定类模板参数的实际类型来实例化类模板。类模板的实例化方式与函数模板类似,可以通过显式实例化、隐式实例化和模板特化三种方式来实现。

需要注意以下几点:

  1. 类模板的使用需要提供实际类型,可以在实例化类模板时显式指定类型,也可以通过传入实参来隐式推断类型。
  2. 类模板可以嵌套在其他类中,也可以作为其他类的成员。
  3. 类模板可以使用类型别名、模板参数类型的默认值、模板特化等特性来增强其灵活性和可读性。
  4. 类模板的定义通常需要放在头文件中,以便在使用时能够正确地进行编译和链接。