C++的模板详细讲解

目录

前言

函数模板

语法

函数模板的定义

函数模板的使用

函数模板特化

函数模板与普通模板的区别

类模板

语法

类模板的定义

类模板的使用

类模板对象的创建

类模板对象作为函数参数

1. 指定传入类型

2.参数模板化

类模板有默认参数

类模板与友元

全局变量类模板内实现

全局变量类外实现


前言

C++除了类与对象的思想外,还有泛型编程的思想,其本质是复用,利用一个虚拟的类型先代替可能传入的变量的类型,编译器再通过确定传入的类型编译函数,来减少代码量。C++的模板分为两类:函数模板类模板,下面依次来讲解这两种模板。

STL是C++的精华,而STL的核心是类与对象以及泛型编程,学习了这两部分内容对STL的理解可以更加深刻。

使用编译环境:VS2022

函数模板

在实现代码的时候,可能参数的类型不同,导致要多写一个一样的函数,只是修改了其中冲突的类型,这样会导致代码冗长,模板的使用对函数是很方便的。

语法

函数模板的定义

先看函数的定义,这里需要关键字template以及<>,再使用class关键字或者typename关键字(这两个关键字没差别)跟上一个虚拟类型的参数名,注意:模板声明后只有下一行才在这个模板的作用域下,下面的function2没办法在不再声明模板的情况下使用上一个函数的虚拟类型参数。

template <class T>
void function(T a/*想要使用模板的参数*/, int b)
{
    // 实现的代码
}

template <typename T2>
void function2(T2 a)
{
    // 实现的代码
}

函数模板的使用

实现了函数的定义,接下来就要使用函数了。

与普通的函数不一样,在使用函数模板时需要<>来区分,例如下面的代码

template <typename T>
void print(T a, T b)
{
    cout << "a = " << a << " b = " << b << endl;
}

void test()
{
    int a = 10;
    int b = 20;
    print<>(a, b);
}

上述的函数模板的使用是利用了自动类型推导机制,这个机制允许程序员不用指定类型就能使用函数模板,即<>中不写类型

除了自动类型推导外,还有指定传入类型,如下就是使用指定传入类型机制的代码

template <typename T>
void print(T a, T b)
{
    cout << "a = " << a << " b = " << b << endl;
}

void test()
{
    int a = 10;
    int b = 20;
    print<int>(a, b);
}

在类中也可以使用函数模板。

函数模板特化

下面的代码比较特殊,是为了在一些特殊情况使用的

#include <iostream>
using namespace std;
//#include <string>

template<typename T>
void print()
{
	cout << "普通的函数模板的调用" << endl;
}

template<> // 显式特化
void print<float>()
{
	cout << "针对float特化类型print函数的调用" << endl;
}

template<>
void print<int>()
{
	cout << "针对int特化类型print函数的调用" << endl;
}

template<>
void print<string>()
{
	cout << "针对string特化类型print函数的调用" << endl;
}

void test()
{
	print<string>();
	print<float>();
	print<int>();
	print<char>();
}

int main()
{
	test();

	system("pause");
	return 0;
}

 运行结果

 

首先,定义一个函数模板,里面可以实现代码。这个函数模板下面再写特殊类型需要特殊的实现逻辑的同名函数,在前面添加"template<>"(显式特化),并在实现的函数名后面添加<>确定针对的类型。这样就能实现针对一些特殊情况,在之前写的函数模板不能对特定类型使用通用的实现处理逻辑时,单独为其实现处理逻辑

在类中实现时,可以类内实现(和上面一样,不同的是作用域在类中)和类外实现。

注意:类外实现时,需要将template<typename 虚拟类型>一起写上去。如下:

class Print
{
public:
	template<typename T>
	void print();
};

template<typename T>
void Print::print()
{
	cout << "普通的函数模板的调用" << endl;
}

template<>
void Print::print<float>()
{
	cout << "针对float特化类型print函数的调用" << endl;
}

template<>
void Print::print<int>()
{
	cout << "针对int特化类型print函数的调用" << endl;
}

template<>
void Print::print<string>()
{
	cout << "针对string特化类型print函数的调用" << endl;
}

函数模板与普通模板的区别

看看以下代码,首先,函数模板也能发生函数重载

#include <iostream>
using namespace std;

void print(int a, int b)
{
	cout << "普通函数调用" << endl;
}

template <class T>
void print(T a, T b)
{
	cout << "函数模板1的调用" << endl;
}

template <class T>
void print(T a, T b, T c)
{
	cout << "函数模板2的调用" << endl;
}

void test02()
{
	int a = 10;
	int b = 20;
	int c = 30;
	print(a, b); // 优先调用普通函数,没有函数定义的普通函数也会调用并报错
	print<>(a, b); // 空模板强制调用函数模板
	print<>(a, b, c); // 函数模板发生函数重载
	print('a', 'b'); // 由于参数具体为char类型的且普通函数没有,所以调用函数模板
}

int main()
{
	//test01();
	test02();

	system("pause");
	return 0;
}

这段代码的最主要的意义在于研究普通函数与函数模板在同一个项目中同时存在时,编译器如何调用函数,方便我们在实际开发中提高使用函数模板提高效率,减少调试

普通函数和函数模板在使用上最大的区别是在使用了<>(甚至是空模板)后,调用的都是函数模板。在没有使用<>调用,且编译器发现普通函数没有匹配的参数列表时,将调用函数模板。

这段代码的运行结果如下:

类模板

接下来看类模板

语法

类模板的定义

和函数模板大同小异,顺便提一嘴,模板的定义可以使用列表,也就是可以一次性定义多个虚拟类型参数名。

template <class NameType, class AgeType>
class Person
{
public:
	Person(NameType Name, AgeType age)
	{
		this->m_Name = Name;
		this->m_Age = age;
	}
	void show()
	{
		cout << this->m_Name << " " << this->m_Age << endl;
	}
	
	NameType m_Name;
	AgeType m_Age;
};

类模板的使用

类模板对象的创建

形如函数模板的使用,在实例化时,需要使用<>

Person<string, int> p("张三", 666);
Person<string, char> c("李四", 555);
// Person<string> pp("王五", 999); error

这样就创建了Person类的对象, 类模板根据指定的类型创建对象。这个还是比较好理解的。

由于类模板的变量有两个,没达到两个就会报错。

类模板对象作为函数参数

类模板作为函数参数的使用大概分为有三种,都是对参数列表操作的:指定传入类型、参数模板化、整个类模板化

1. 指定传入类型

即明确传入的对象类型对应的类模板的类型参数是什么,用语言描述还是太过于复杂,上代码

void print(Person<string, int>& p)
{
	p.show();
}

前面创建的对象p可以传入这个函数中,因为明确了函数的参数类型就是Person<string, int>类模板,也是因为这种方式的传参比较简单、可读性强,所以在类模板对象作为函数参数的使用中比较常用。

2.参数模板化

与其一个一个指定类型,不如再一次使用模板。这个方法的巧妙之处在于,它利用自动类型推导机制,区分传入的类模板类型中的类型参数的不同,区分了不同的类型。

template <class NameType, class AgeType>
void print2(Person<NameType, AgeType> &p)
{
	p.show();
	// 查看编译器是否自动推导出类型了
	cout << "NameType的类型为 " << typeid(NameType).name() << endl;
	cout << "AgeType的类型为 " << typeid(AgeType).name() << endl;
}

3.整个类模板化

这个方法比上一个还要简单暴力,直接将传入的类模板类型当作一整个类型,缺点也很明显,就是泛化太严重了,很容易被调用,且可读性更差了,在多人大型项目中,别人可能看不出来参数是类模板对象

template <class T>
void print3(T &p)
{
	p.show();
	cout << "自动推导的类型为 " << typeid(T).name() << endl;
}

类模板有默认参数

template <class NameType, class AgeType = int>
class Person
{
public:
	Person(NameType Name, AgeType age)
	{
		this->m_Name = Name;
		this->m_Age = age;
	}
	void show()
	{
		cout << this->m_Name << " " << this->m_Age << endl;
	}
	
	NameType m_Name;
	AgeType m_Age;
};

void print(Person<string, int>& p)
{
	p.show();
}

template <class NameType, class AgeType = int>
void print2(Person<NameType, AgeType> &p)
{
	p.show();
	cout << "NameType的类型为 " << typeid(NameType).name() << endl;
	cout << "AgeType的类型为 " << typeid(AgeType).name() << endl;
}

template <class T>
void print3(T &p)
{
	p.show();
	cout << "自动推导的类型为 " << typeid(T).name() << endl;
}

void test01()
{
	Person<string, int> p("张三", 666);
	Person<string> pp("李四", 555);
	// Person p("张三", 666); error
	Person<string> ppp("王五", 454);

	print(p);
	print2(pp);
	print3(ppp);
}


int main()
{
	test01();

	system("pause");
	return 0;
}

从最右边起,使用“=”号赋值在类模板的变量中,如上面的代码。

在上面的代码中,<>中至少要有一个类型,因为不能利用自动类型推导机制创建对象。

当类模板对象作为函数参数时,也可以在类型变量中设置默认类型。

注意:函数模板没有默认参数

类模板与友元

当全局函数要与类构建友元关系时,需要特殊处理才能正常使用。

全局变量类模板内实现

在类模板内的变量加上friend关键字后,就变成了全局变量,可以直接访问,同时使用指定传入类型的方法将类模板对象作为函数参数。

#include <iostream>
using namespace std;
#include <string>

template <class Name, class Age>
class Person
{
	// 全局函数 类内实现
	friend void show(Person<Name, Age> p)
	{
		cout << "姓名:" << p.m_Name << "\t年龄:" << p.m_Age << endl;
	}

public:
	Person(Name name, Age age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
private:
	Name m_Name;
	Age m_Age;
};

void test01()
{
	Person<string, int> p("张三", 18);
	show(p);
}

int main()
{
	test01();

	system("pause");
	return 0;
}

全局变量类外实现

需要提前声明类模板,并在类模板定义前实现全局变量

如果使用的是参数模板化方法,在类中添加友元friend并加上<>表示这个全局变量是函数模板;指定传入类型不用在函数名后面加<>,也要在类中添加friend声明是友元(注意:类模板做函数参数使用的方法一定要统一,使用整个类模板化方法很难实现),

满足上面的条件才能用这个全局变量访问类模板中的私有权限的成员,否则编译器会报错。

#include <iostream>
using namespace std;
#include <string>

template <class Name, class Age> class Person; // 要提前声明

// 全局函数实现要在类之前
template <class Name, class Age>
void printshow(Person<Name, Age> p)
{
	cout << "姓名:" << p.m_Name << "\t年龄:" << p.m_Age << endl;
}

template <class Name, class Age>
class Person
{
	// 全局函数 类外实现
	// 需要加个空模板列表
	friend void printshow<>(Person<Name, Age> p);

public:
	Person(Name name, Age age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}
private:
	Name m_Name;
	Age m_Age;
};

void test02()
{
	Person<string, int> p("李四", 19);
	printshow(p);
}

int main()
{
	test02();

	system("pause");
	return 0;
}

学到这里,C++的模板这块内容基本讲完了,想必你对C++的泛型编程有了一定的了解,在写代码的时候多多使用,相信你会用得很熟练。