【C++】函数模板与类模板

💗个人主页💗

⭐个人专栏------C++学习

💫点击关注🤩一起学习C语言💯💫

目录

[1. 泛型编程](#1. 泛型编程)

[2. 模板概念](#2. 模板概念)

[3. 函数模板](#3. 函数模板)

[3.1 函数模板概念](#3.1 函数模板概念)

[3.2 函数模板定义格式](#3.2 函数模板定义格式)

[3.3 示例](#3.3 示例)

[3.4 函数模板的实例化](#3.4 函数模板的实例化)

[3.5 函数模板重载](#3.5 函数模板重载)

[3.6 模板参数的匹配原则](#3.6 模板参数的匹配原则)

[4. 类模板](#4. 类模板)

[4.1 类模板定义格式](#4.1 类模板定义格式)

[4.2 示例](#4.2 示例)

[4.3 类模板的实例化](#4.3 类模板的实例化)

[5. 模板的特化](#5. 模板的特化)

[5.1 概念](#5.1 概念)

[5.2 函数模板特化](#5.2 函数模板特化)

[5.3 类模板特化](#5.3 类模板特化)

[5.3.1 全特化](#5.3.1 全特化)

[5.3.2 偏特化](#5.3.2 偏特化)


1. 泛型编程

如何实现一个通用的交换函数呢?

cpp 复制代码
#include <iostream>
using namespace std;

void Swap(int& left, int& right)
{
	int temp = left;
	left = right;
	right = temp;
}
void Swap(double& left, double& right)
{
	double temp = left;
	left = right;
	right = temp;
}
void Swap(char& left, char& right)
{
	char temp = left;
	left = right;
	right = temp;
}

int main()
{
	int a = 2, b = 3;
	Swap(a, b);
	cout << a << ' ' << b << endl;

	double c = 1.1, d = 2.2;
	Swap(c, d);
	cout << c << ' ' << d << endl;

	char x = 'a', y = 'b';
	Swap(x, y);
	cout << x << ' ' << y << endl;

	return 0;
}

使用函数重载虽然可以实现,但是有一下几个不好的地方:

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数。
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错。

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件 (即生成具体类型的代码),那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础。

2. 模板概念

C++模板是一种编程技术,它允许我们编写通用的代码来适应不同的数据类型。使用模板可以将类型参数化,从而实现代码的复用。

简单来说,模板是一种将代码与数据类型解耦的方法。通过模板,我们可以定义能够处理多种数据类型的函数或类,并且在编译时根据具体的数据类型生成相应的代码。

通过使用模板,我们可以避免编写重复的代码来处理不同的数据类型。只需编写一次模板代码,即可在需要时根据不同的数据类型进行实例化,生成相应的函数或类。

模板分为函数模板和类模板两种类型。

  • 函数模板允许我们编写一个通用的函数,可以接受不同类型的参数。函数模板使用类型参数作为函数的参数或返回值类型,使得函数可以适用于多种数据类型。
  • 类模板允许我们编写一个通用的类,可以使用不同类型的成员变量和成员函数。类模板使用类型参数来定义类的成员变量类型和函数返回值类型,从而实现类的通用性。

C++模板是一项强大的功能,它可以提高代码的重用性和灵活性。通过模板,我们可以编写更加通用和灵活的代码,使得我们的程序具有更好的可扩展性和可维护性。

3. 函数模板

3.1 函数模板概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

3.2 函数模板定义格式

函数模板的定义以关键字 template 开始,后跟类型参数列表和函数定义。类型参数可以是任意有效的C++类型。函数模板允许在函数的参数列表、返回值类型或函数体中使用类型参数。

cpp 复制代码
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表)
{
    函数体
}

3.3 示例

用模板来实现上面的不同类型交换问题:

注意:

typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class )

cpp 复制代码
#include <iostream>
using namespace std;

template<typename T>
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

int main()
{
	int a = 2, b = 3;
	Swap(a, b);
	cout << a << ' ' << b << endl;

	double c = 1.1, d = 2.2;
	Swap(c, d);
	cout << c << ' ' << d << endl;

	char x = 'a', y = 'b';
	Swap(x, y);
	cout << x << ' ' << y << endl;
	return 0;
}

3.4 函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。

模板参数实例化分为:隐式实例化和显式实例化。

隐式实例化:

在函数调用时,编译器会根据参数类型自动推断出函数模板的类型参数,并生成对应的函数实例。

cpp 复制代码
#include <iostream>
using namespace std;

template <typename T>
void print(T value) 
{
	cout << value << endl;
}

int main()
{
	print(10);  // 隐式实例化为 print<int>(10)
	print(3.14);  // 隐式实例化为 print<double>(3.14)
	return 0;
}

显式实例化:

我们可以显式地指定函数模板的类型参数,从而生成指定类型的函数实例。在函数模板的定义前加上 template 关键字和具体的类型参数,然后进行函数调用。

cpp 复制代码
#include <iostream>
using namespace std;

template <typename T>
void print(T value) 
{
	cout << value << endl;
}
int main()
{
	print<int>(10);  // 显式实例化为 print<int>(10)
	print<double>(3.14);  // 显式实例化为 print<double>(3.14)
	return 0;
}

需要注意的是,对于函数模板的每个实例化都会生成独立的函数代码,因此多个不同的实例化会占用额外的存储空间。因此,如果某个函数模板只有在特定类型参数下才会被使用,可以考虑使用显式实例化的方式,以避免生成无用的函数代码。

另外,C++也提供了一种手动显式实例化所有可能的函数模板实例化的方式,即使用 template 关键字和具体的类型参数,然后用关键字 extern 声明该函数模板的实例化。

cpp 复制代码
template <typename T>
void print(T value) 
{
	cout << value << endl;
}

extern template void print<int>(int);  // 手动显式实例化为 print<int>(int)
extern template void print<double>(double);  // 手动显式实例化为 print<double>(double)

函数模板的实例化是在编译时进行的,因此会带来一些额外的编译时间。但它能够提供更高效的代码,因为编译器可以针对具体类型进行优化,并减少运行时的类型检查。

3.5 函数模板重载

函数模板的重载是指在同一个作用域内,根据不同的参数类型或参数个数,定义多个函数模板的情况。

注意以下几点:

  1. 函数模板重载必须在同一个作用域内,可以在全局作用域或命名空间中定义。

  2. 参数类型或参数个数必须不同,可以通过添加或删除参数来区分。

  3. 函数模板重载的返回类型可以相同或不同。

  4. 调用函数模板时,编译器会根据实参的类型或个数来匹配最合适的函数模板。

cpp 复制代码
template<typename T>
void print(T value) 
{
  cout << value << endl;
}

template<typename T1, typename T2>
void print(T1 value1, T2 value2) 
{
  cout << value1 << ", " << value2 << endl;
}

int main() 
{
  print(10); // 调用第一个函数模板
  print(10, 20); // 调用第二个函数模板
}

函数模板的重载能够提供更灵活的参数处理和代码复用,通过适当的选取不同的函数模板,可以满足不同的使用需求。

3.6 模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
cpp 复制代码
#include <iostream>
using namespace std;

// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
}
int main()
{
	Add(1, 2); // 与非模板函数匹配,编译器不需要特化
	Add<int>(1, 2); // 调用编译器特化的Add版本

	return 0;
}
  1. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模 板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
cpp 复制代码
#include <iostream>
using namespace std;


// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
}
int main()
{
	Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
	Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数

	return 0;
}
  1. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

4. 类模板

4.1 类模板定义格式

C++中的类模板是一种用来生成类的蓝图或模板。类模板允许我们定义通用的类,其中的成员和成员函数的类型可以根据用户的需求进行参数化。

类模板的定义通常以template<typename T>开头,其中T是类型参数,可以是任何合法的C++类型。然后,在类的定义中,我们可以使用T来表示成员变量的类型、函数的返回类型和参数类型。

cpp 复制代码
template<class T1, class T2, ..., class Tn>
class 类模板名
{
 // 类内成员定义
}; 

4.2 示例

cpp 复制代码
#include <iostream>
using namespace std;

template<typename T>
class Array 
{
private:
    T* elements;
    int size;

public:
    Array(int size) 
    {
        this->size = size;
        elements = new T[size];
    }

    ~Array() 
    {
        delete[] elements;
    }

    T& operator[](int index) 
    {
        return elements[index];
    }
};

int main()
{
    Array<int> intArray(5); // 创建一个存储int类型元素的数组模板实例
    Array<double> doubleArray(10); // 创建一个存储double类型元素的数组模板实例

    return 0;
}

在上面的示例中,我们定义了一个名为Array的类模板,其中T是类型参数。类模板拥有一个私有成员变量elements,用于存储元素,以及一个size变量表示数组的大小。我们还定义了构造函数、析构函数和[]运算符重载函数,它们都使用了类型参数T

通过实例化类模板,我们可以创建不同类型的数组对象,而且这些对象可以使用类模板中定义的成员函数和操作符。

需要注意的是,类模板的成员函数通常需要在类模板的定义体外进行定义,例如:

cpp 复制代码
template<typename T>
void Array<T>::print() 
{
  for (int i = 0; i < size; i++) 
{
    cout << elements[i] << " ";
  }
  cout << endl;
}

这里的Array<T>::print()是一个类模板Array的成员函数,我们需要在类模板外部使用Array<T>::来指定成员函数所属的类模板,并且需要在函数名后面加上类型参数<T>

4.3 类模板的实例化

类模板的实例化是指根据模板定义创建具体的类的过程。在实例化时,需要为模板参数提供具体的类型,从而根据模板生成一个真正的类。

  1. 隐式实例化:当我们在代码中使用具体类型来创建类的对象时,编译器会自动进行类模板的实例化。例如,

    cpp 复制代码
    #include <iostream>
    using namespace std;
    
    template<typename T>
    class Array 
    {
    	// 类模板定义
    };
    int main()
    {
    	Array<int> intArray;  // 隐式实例化为Array<int>
    	Array<double> doubleArray;  // 隐式实例化为Array<double>
    	return 0;
    }
  2. 显式实例化:除了隐式实例化,我们还可以显式地要求编译器实例化模板。通过在代码中显式实例化特定的模板类型,我们可以确保生成模板类的实例。例如,

    cpp 复制代码
    template<typename T>
    class Array 
    {
      // 类模板定义
    };
    
    // 显式实例化Array<int>类
    template class Array<int>;
    
    // 显式实例化Array<double>类
    template class Array<double>;

5. 模板的特化

5.1 概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结 果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板

cpp 复制代码
#include <iostream>
using namespace std;

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}
class Date
{
public:
	friend ostream& operator<<(ostream& _cout, const Date& d);

	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}

	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

int main()
{
	cout << Less(1, 2) << endl; // 可以比较,结果正确
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl; // 可以比较,结果正确
	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; // 可以比较,结果错误
	return 0;
}

可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。

此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。

5.2 函数模板特化

函数模板特化是指为模板函数的特定类型参数提供自定义的实现。与类模板特化类似,函数模板特化可以根据特定类型的需求,提供更具体的实现。

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
cpp 复制代码
class Date
{
public:
	friend ostream& operator<<(ostream& _cout, const Date& d);

	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}

	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}

// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
	return *left < *right;
}

int main()
{
	cout << Less(1, 2) << endl; 
	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl; 
	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了

	return 0;
}

注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。

cpp 复制代码
bool Less(Date* left, Date* right)
{
 return *left < *right;
}

5.3 类模板特化

5.3.1 全特化

全特化即是将模板参数列表中所有的参数都确定化。

cpp 复制代码
#include <iostream>
using namespace std;

template <typename T>
class MyTemplate 
{
public:
    MyTemplate(T value) 
    {
        cout << "Generic Template: " << value << endl;
    }
};

template <>
class MyTemplate<int> 
{
public:
    MyTemplate(int value) 
    {
        cout << "Specialized Template for int: " << value + 1 << endl;
    }
};

int main() 
{
    MyTemplate<float> obj1(3.14);    // 使用通用模板,输出:Generic Template: 3.14
    MyTemplate<int> obj2(10);        // 使用特化模板,输出:Specialized Template for int: 11

    return 0;
}

在上面的示例中,我们定义了一个通用的类模板MyTemplate,它使用模板参数T来表示数据类型,并在构造函数中打印出传入的值。

然后,我们通过特化语法,为类型int提供了一个全特化的实现。在全特化实现中,我们将完全重新定义并实现了新的类。

main函数中,我们创建了两个对象obj1obj2,分别使用了通用模板和全特化模板进行实例化。根据对象的类型,编译器会选择相应的模板进行实例化。

5.3.2 偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。

cpp 复制代码
#include <iostream>
using namespace std;

template <typename T, typename U>
class MyTemplate 
{
public:
    MyTemplate(T t, U u) 
    {
        cout << "Generic Template: " << t << ", " << u << endl;
    }
};

template <typename T>
class MyTemplate<T, int> 
{
public:
    MyTemplate(T t, int u) 
    {
        cout << "Partial Specialization for int: " << t << ", " << u + 1 << endl;
    }
};

int main() 
{
    MyTemplate<float, float> obj1(3.14, 2.5);    // 使用通用模板,输出:Generic Template: 3.14, 2.5
    MyTemplate<int, int> obj2(10, 20);           // 使用通用模板,输出:Generic Template: 10, 20
    MyTemplate<double, int> obj3(4.5, 5);        // 使用偏特化模板,输出:Partial Specialization for int: 4.5, 6

    return 0;
}

在上面的示例中,我们定义了一个通用的类模板MyTemplate,它接受两个模板参数TU,并在构造函数中打印出传入的值。

然后,我们通过偏特化语法,为类型int提供了一个偏特化的实现。在偏特化实现中,我们将重新定义并实现了新的类,并对第二个模板参数固定为int。这意味着只有第二个参数是int时,才会选择偏特化模板进行实例化。

main函数中,我们创建了三个对象obj1obj2obj3,分别使用了通用模板和偏特化模板进行实例化。根据对象的类型,编译器会选择相应的模板进行实例化。在obj3的情况下,由于第二个参数是int,因此偏特化模板会被选择。

相关推荐
knoci1 小时前
【Go】-基于Gin框架的IM通信项目
开发语言·后端·学习·golang·gin
luluvx1 小时前
LeetCode[简单] 136. 只出现一次的数字
算法·leetcode·职场和发展
RaidenQ1 小时前
2024.9.27 Python面试八股文
linux·开发语言·python
fhvyxyci2 小时前
【数据结构初阶】排序算法(上)插入排序与选择排序
数据结构·算法·排序算法
予早3 小时前
LeetCode 69. x 的平方根
算法·leetcode
Thomas_YXQ3 小时前
Unity3D PostLateUpdate为何突然占用大量时间详解
开发语言·数码相机·游戏·unity·架构·unity3d
戊子仲秋3 小时前
【LeetCode】每日一题 2024_9_27 每种字符至少取 K 个(双指针)
算法·leetcode·职场和发展
学步_技术3 小时前
Python编码系列—Python模板方法模式:定义算法骨架,让子类实现细节
python·算法·模板方法模式
勇敢滴勇4 小时前
【排序算法】选择排序、堆排序
数据结构·算法·排序算法
MrBlackmzq4 小时前
Datawhale Leecode基础算法篇 task04:贪心算法
python·算法·贪心算法