C++——函数模板与类模板

这篇具有很好参考价值的文章主要介绍了C++——函数模板与类模板。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

0.关注博主有更多知识

C++知识合集

目录

1.泛型编程

2.函数模板

2.1函数模板实例化

2.2函数模板参数的匹配原则

3.类模板

4.模板的分离编译

1.泛型编程

实际上泛型编程的难度是比较高的,但我们泛型编程的初学者,当然要从简单的地方开始入手。

我们可以写出很多份交换函数,这些函数之间构成函数重载,这样在调用的时候就能自动调用不同参数类型的函数:

void Swap(int &left, int &right)
{
	int tmp = left;
	left = right;
	right = tmp;
}

void Swap(char &left, char &right)
{
	char tmp = left;
	left = right;
	right = tmp;
}

void Swap(double &left, double &right)
{
	double tmp = left;
	left = right;
	right = tmp;
}

/*......*/

那么像上面这样写能不能解决我的需求呢?能!但是太挫了,原因有两个

  1.重载的函数仅仅是类型不同,函数体内容都是一样的,代码复用的率比较低;只要有新的类型出现,就需要增加对应的函数

  2.代码的可维护性比较低,一个函数出错可能所有的重载都会出错

那么我们可以试着摸一摸泛型编程的门道,向上面的Swap()函数一样,这几个Swap()函数除了参数类型不一样,其他的大部分内容都是一样,那么是不是可以将这些相同的地方集合起来,组成一个模板,然后让编译器根据不同的类型来利用该模板自动生成代码呢?

事实上在C++当中,确实存在上述这样的模板,通过给这个模板提供不同的参数类型,就可以获得不同的具体类型代码。那么泛型编程就是编写与类型无关的通用代码,是代码复用的一种手段,模板是泛型编程的基础。

2.函数模板

函数模板代表了一个函数家族,该函数模板与类型无关,在是使用时被参数化,根据实参类型产生函数的特定类型版本。也就是说函数模板不是一个具体的函数,在调用函数模板时会根据调用函数的实参生成一份特定的类型版本代码。我们以上面的Swap()函数为例:

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

int main()
{
	int x = 3, y = 5;
	Swap(x, y);

	char chl = 'a', chr = 'b';
	Swap(chl, chr);

	double d = 1.1, b = 4.3;
	Swap(d, b);
	return 0;
}

C++——函数模板与类模板

函数模板的格式为:

template <class T1,class T2,......,class Tn>
返回类型 函数名(参数列表)
{}

其中,"template"是定义模板时的关键字,其后跟上一堆尖括号,尖括号里面的内容就是模板的参数,需要注意,模板的参数与函数的参数不一样,模板的参数是定义类型,而函数的参数是定义指定类型的变量。其中模板参数的定义可以使用关键字class或者typename。函数模板的有效范围在"template"关键字之后碰到的第一个函数内有效

函数模板本身并不是一个函数。在编译器编译阶段,编译器需要根据调用函数模板时传入的实参来推演并实例化生成对应类型的函数以供正确调用。

C++——函数模板与类模板

例如在外部调用Swap()的两个实参都是double类型,在编译阶段,编译器会根据实参的类型,将函数模板的参数类型由T替换为double,然后再实例化出一份真正的函数代码:

C++——函数模板与类模板

2.1函数模板实例化

上面我们介绍了编译器是如何通过函数模板实例化出对应类型函数的大致流程,接下来介绍我们如何调用函数模板以及一些细节:

  1.隐式实例化:让编译器根据实参推演出函数模板的参数类型,然后再生成具体的函数

template <typename T>
T Add(const T &x, const T &y)
{
	return x + y;
}

int main()
{
	/*让编译器根据实参的类型推演出函数模板的参数类型
	 *然后再生成具体的真实函数*/
	int a = 1, b = 3;
	cout << Add(a , b) << endl;

	double c = 3.1, d = 4.6;
	cout << Add(c , d) << endl;

	return 0;
}

C++——函数模板与类模板

  用法是非常简单的,但是我们需要注意,上面程序的模板参数只有一个,即T,那么这就意味着只能确定一种类型,所以在调用函数模板时需要保证两个实参的类型相同。那么我们也知道,函数调用的时候,实参与形参之间可能发生隐式类型转换,但是我们也需要知道,这种隐式类型转换发生在函数调用当中而不在函数模板调用当中

template <typename T>
T Add(const T &x, const T &y)
{
	return x + y;
}

int main()
{
	int a = 1, b = 3;
	double c = 3.1, d = 4.6;

	/*错误调用,a的类型为int,c的类型为double
	 *函数模板只有一个参数,即只能确定一种类型
	 *而函数模板的调用不会发生隐式类型转化*/
	cout << Add(a , c) << endl;
	return 0;
}

C++——函数模板与类模板

  所以解决方案有两种,要么自己手动进行强制类型转换,要么使用显式实例化模板:

template <typename T>
T Add(const T &x, const T &y)
{
	return x + y;
}

int main()
{
	int a = 1, b = 3;
	double c = 3.1, d = 4.6;

	/*手动强制类型转换,使得实参类型统一*/
	cout << Add(a , (int)c) << endl;
	return 0;
}

C++——函数模板与类模板

  2.显式实例化:在函数模板名称后使用<>指定模板参数的类型,这时就已经实例化出了真实的函数

template <typename T>
T Add(const T &x, const T &y)
{
	return x + y;
}

int main()
{
	int a = 1, b = 3;
	double c = 3.1, d = 4.6;

	/*显式实例化,Add<int>确定了函数模板的参数类型
	 *所以即可生成对应的真实函数
	 *所以此时可以发生隐式类型转换,因为此时就是在调用真实的函数*/
	cout << Add<int>(a, c) << endl;
	return 0;
}

C++——函数模板与类模板

  此时实参与形参时间可以发生隐式类型转换,因为调用的时候不再是调用函数模板,而是调用真实的函数。如果不能发生隐式类型转化,那么将会报错。

2.2函数模板参数的匹配原则

如果函数与函数模板同时存在,那么在调用的时候优先选择函数而非函数模板

int Add(int x, int y)
{
	return x + y;
}

template <class T>
T Add(T x, T y)
{
	return x + y;
}
int main()
{
	/*此时函数模板与函数同时存在,但是这里调用例子中
	 *实参直接与函数的参数匹配,所以直接调用函数而非调用函数模板*/
	cout << Add(1, 2) << endl;
	return 0;
}

这个时候需要注意了,如果我们显式地实例化函数模板生成一份与已存在函数相同的函数,不会发生报错,并且正常调用:

int Add(int x, int y)
{
	return x + y;
}

template <class T>
T Add(T x, T y)
{
	return x + y;
}

int main()
{
	/*此时函数模板与函数同时存在,但是这里调用例子中
	*实参直接与函数的参数匹配,所以直接调用函数而非调用函数模板*/
	cout << Add(1, 2) << endl;

	/*显式实例化生成一份真实的函数
	 *看起来实例化的函数与已经存在的函数冲突
	 *实际上并没有发生冲突,还可以正常调用*/
	cout << Add<int>(1, 2) << endl;
	return 0;
}

在这个例子当中,已经存在了一份int Add(int , int),而我们显式实例化函数模板又生成了一份int Add(int , int),但是编译器没有报错,这就说明了在符号表当中,他们的函数名修饰规则一定不一样。我们在Linux环境下使用g++观察上面这个程序的汇编代码:

 

C++——函数模板与类模板

虽然说调用函数时有限使用普通函数,但如果函数模板更加匹配,那么最后调用的时候会选择函数模板

int Add(int x, int y)
{
	return x + y;
}

template <class T1,class T2>
T1 Add(T1 x, T2 y)
{
	return x + y;
}

int main()
{
	/*这两个调用都可以调用普通函数,但是要发生隐式类型转换
	 *但是上面的函数模板有两个参数,所以对应两个类型
	 *所以调用函数模板是最合适的*/
	cout << Add(1.1, 5) << endl;
	cout << Add(5, 3.4) << endl;
	return 0;
}

C++——函数模板与类模板

3.类模板

类模板的定义与函数模板的定义差不多,并且类模板不是真正的类,只有实例化出来的类才是真正的类。

我们以一个Stack类为例:

template <class T>
class Stack
{
public:
	Stack(int capacity = 4)
		:_top(0), _capacity(capacity)
	{
		_elem = new T[_capacity];
	}

	void push(const T &in)
	{
		/*不考虑扩容......*/
		_elem[_top++] = in;
	}
private:
	T *_elem;
	int _top;
	int _capacity;
};

我们Stack类作为栈,那么栈就需要存储数据,那么既然要存储数据就必然涉及到不同类型的数据,所以使用一个模板是非常有必要的。那么类模板的使用不能像函数模板那样隐式实例化,类模板的使用必须显式实例化

int main()
{
	/*实例化出不同的Stack类,用来存储不同的数据类型
	 *这几个实例化出来的类的类型是不相同的!*/
	Stack<int> st1;
	Stack<double> st2;
	Stack<char> st3;

	/*错误!他们不是相同的类型!*/
	//st1 = st2;
	return 0;
}

需要注意的是,使用不同的类型实例化出来的类,他们之间是不同的类型

4.模板的分离编译

对于函数模板来说,如果想要在一个文件下声明和定义分离是可以的,那么格式就得像下面这样:

/*函数模板的声明*/
template<class T>

T Add(const T &x, const T &y);
int main()
{
	cout << Add(1, 2) << endl;
	return 0;
}

/*函数模板的定义*/
template <class T>
T Add(const T &x, const T &y)
{
	return x + y;
}

C++——函数模板与类模板

对于类模板当中的成员函数来说,他们也是函数模板,如果这些函数模板在类模板当中声明,在类模板外定义也是可以的:

template <class T>
class Stack
{
public:
	/*类模板中只有声明*/
	Stack(int capacity = 4);
	void push(const T &in);
private:
	T *_elem;
	int _top;
	int _capacity;
};

/*声明与定义分离*/
template <class T>
Stack<T>::Stack(int capacity = 4)
:_top(0), _capacity(capacity)
{
	_elem = new T[_capacity];
}

template <class T>
void Stack<T>::push(const T &in)
{
	/*不考虑扩容......*/
	_elem[_top++] = in;
}

我们说过在类中如果只有成员函数的声明,在类外定义该成员函数时需要指明类域。上面代码当中的定义部分看起来非常奇怪,但实际上我正现在遵守刚才所说的原则,原因就在于类模板不是一个真正的类,类模板必须实例化之后才生成一个真正的类。所以在选择在类模板外部定义函数时,需要显式实例化类模板。

C++——函数模板与类模板

虽然函数模板可以在一个文件当中声明与定义分离,但是如果在多文件当中声明与定义分离编译,那么又会触发隐藏奖励:链接错误。类模板当中的成员函数也是如此:

// test.cpp
/*只有声明,定义在另一个源文件当中*/
template<class T>
T Add(const T &x, const T &y);

int main()
{
	cout << Add(1, 2) << endl;
	return 0;
}
// func.cpp
/*函数模板的定义*/
template <class T>
T Add(const T &x, const T &y)
{
	return x + y;
}

C++——函数模板与类模板

这个问题我们再熟悉不过了,原因就在于test.cpp文件当中没有Add()函数的定义,只有声明,所以编译器会认为Add()函数在其他文件当中存在,所以编译可以过;那么对于func.cpp文件来说,它也没有Add()函数的定义,因为它只有一个函数模板,并且没有人调用该模板实例化出一个函数,所以func.cpp被编译之后生成的符号表当中就没有Add()这个函数。那么test.cpp文件需要调用Add()函数但是本文件没有啊,就要发动链接器去func.cpp文件生成的符号表当中去找,但是因为func.cpp生成的符号表当中没有Add()函数,所以产生链接错误。

C++——函数模板与类模板

那么解决方法有两种,一种是在多文件分离编译中负责定义函数的文件当中显式实例化函数:

/*函数模板的定义*/
template <class T>
T Add(const T &x, const T &y)
{
	return x + y;
}

/*显式实例化*/
template int Add(const int &x, const int &y);
template double Add(const double &x, const double &y);

第二种便是模板不要在多文件当中声明和定义分离编译。第一种解决方案虽然可以正常调用函数,但是这不是一种明智的做法,因为这直接违背了泛型编程的初衷。对于类模板也一样,类模板当中的成员函数模板也不要多文件声明和定义分离编译。所以无论如何,在使用模板时,无论是函数模板还是类模板,都不要声明和定义在多文件当中分离编译

下面仅仅是演示一下类模板分离多文件分离编译也会产生链接错误:

// func.h
template <class T>
class Stack
{
public:
	/*类模板中只有声明*/
	Stack(int capacity = 4);
	void push(const T &in);
private:
	T *_elem;
	int _top;
	int _capacity;
};
// func.cpp

#include "func.h"

/*声明与定义分离*/
template <class T>
Stack<T>::Stack(int capacity = 4)
:_top(0), _capacity(capacity)
{
	_elem = new T[_capacity];
}

template <class T>
void Stack<T>::push(const T &in)
{
	/*不考虑扩容......*/
	_elem[_top++] = in;
}
// test.cpp

#include "func.h"
int main()
{
	Stack<int> st;
	st.push(1);
	return 0;
}

C++——函数模板与类模板

解决方案之一便是在定义的文件当中显式实例化:文章来源地址https://www.toymoban.com/news/detail-475174.html

/*声明与定义分离*/
template <class T>
Stack<T>::Stack(int capacity = 4)
:_top(0), _capacity(capacity)
{
	_elem = new T[_capacity];
}

template <class T>
void Stack<T>::push(const T &in)
{
	/*不考虑扩容......*/
	_elem[_top++] = in;
}

/*显式实例化*/
template class Stack<int>;

到了这里,关于C++——函数模板与类模板的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】C++模板基础知识篇

    个人主页 : zxctscl 文章封面来自:艺术家–贤海林 如有转载请先通知 实现一个通用的交换函数: 在实现不同类型的参数Swap就得写很多个, 用起来太麻烦了。 使用函数重载虽然可以实现,但是有一下几个不好的地方: 重载的函数仅仅是类型不同,代码复用率比较低,只要

    2024年03月28日
    浏览(49)
  • 浅述C++模板——函数模板及类模板

    模板作为 C++ 的一大特色,对于泛型编程有着重要的作用。同时,对于大规模类似的函数或是类型不确定的类,模板都起了至关重要的作用。 在开始学习模板之前,我们首先需要了解模板。先看下面一个例子: 我们可以轻易发现,对于函数 func_one、func_two,两者实现的功能基

    2024年02月09日
    浏览(45)
  • 【C++】——模板(泛型编程+函数模板+类模板)

    之前我们学习了函数重载,让我们在写相似函数的时候非常方便,但函数重载还有很多不足的地方,比如,每次写相似函数的时候,都要我们重新重载一个逻辑、代码几乎一样的函数,这就导致了我们的效率变低,所以我们今天来学习C++模板的相关知识点,学习完模板之后,

    2024年02月05日
    浏览(48)
  • 【C++】什么是函数模板/类模板?

    函数模板简单来说就是一个模板,与函数参数的类型无关,是一个模子,不是真正的函数,实例化的函数会根据实参的类型 自动 推导类型。 函数模板是一个模板,并不是真正的函数,它是根据传递过来的实参的类型实例化一个具体的函数,相当于我们将重复的事情交给了编

    2024年02月07日
    浏览(59)
  • C++函数模板和类模板

    C++另一种编程思想称为泛型编程,主要利用的技术是模板 C++提供两种模板机制:函数模板和类模板 即:我们提供一个抽象的函数或类,并不具体指定其中数据的类型,而是某个虚拟类型代替。只提供基本的功能。其具体的数据类型,只在其被调用时视具体情况实例化。 举个

    2024年02月12日
    浏览(43)
  • 【C++】泛型编程 ① ( 函数模板 | 函数模板概念 | 函数模板意义 | 函数模板定义语法 | 函数模板调用语法 | 显式类型调用 | 自动类型推导 )

    在 C++ 语言中 , 泛型编程 的 核心就是 函数模板 和 类模板 ; 函数模板 Function Template 是 C++ 语言 中的 重要特性 ; 函数模板概念 : 建立一个 \\\" 通用函数 \\\" , 不指定该函数的 函数返回值类型 和 函数参数类型 , 仅使用 \\\" 虚拟类型 \\\" 代表 上述 两种类型 , 该 \\\" 通用函数 \\\" 就是 \\\" 函数模

    2024年02月19日
    浏览(39)
  • 【C++】模板初阶——函数模板和类模板

    🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。 🚁 个人主页:不 良 🔥 系列专栏:🛸C++  🛹Linux 📕 学习格言:博观而约取,厚积而薄发 🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同

    2024年02月10日
    浏览(44)
  • 【C++从入门到放弃】模板介绍(函数模板、类模板)

    🧑‍💻作者: @情话0.0 📝专栏:《C++从入门到放弃》 👦个人简介:一名双非编程菜鸟,在这里分享自己的编程学习笔记,欢迎大家的指正与点赞,谢谢!   以我们之前所学的知识,假如要实现一个通用的加法函数,那么可以通过函数重载的方式来实现。 使用函数重载虽

    2023年04月14日
    浏览(45)
  • C++基础(10)——函数模板和类模板

    本文主要介绍了C++中函数模板和类模板基本知识 模板函数的定义 templatetypename T+函数的定义或声明 模板函数的两种使用方法(编译器自动推测、显示指定T的类型) 注意事项: 模板一定要确定指出T的数据类型才可以使用;T由编译器自动推导出来,如果类型不一致也不可以使

    2024年02月09日
    浏览(40)
  • 【C++初阶】八、初识模板(泛型编程、函数模板、类模板)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【C++初阶】七、内存管理 (C/C++内存分布、C++内存管理方式、operator new / delete 函数、定位new表

    2024年02月04日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包