草庐IT

C++——模板初阶与泛型编程

花想云(西安第一深情) 2023-05-25 原文


文章目录

💐专栏导读

🌸作者简介:花想云,在读本科生一枚,致力于 C/C++、Linux 学习。

🌸本文收录于 C++系列,本专栏主要内容为 C++ 初阶、C++ 进阶、STL 详解等,专为大学生打造全套 C++ 学习教程,持续更新!

🌸相关专栏推荐:C语言初阶系列C语言进阶系列 数据结构与算法

💐文章导读

本章我们将学习模板,与泛型编程初次见面。C++相比于C语言有这么多丰富的接口与类型,都源于泛型编程。本章的内容为模板初阶知识,为接下来的STL学习打下坚实的基础。

🌷引例

如何实现一个通用的交换函数(swap)呢?我们已经学过函数重载的使用,函数重载在这种场景下貌似作用很大。

void Swap(int& a, int& b)
{
	auto tmp = a;
	a = b;
	b = tmp;
}

void Swap(double& a, double& b)
{
	auto tmp = a;
	a = b;
	b = tmp;
}

void Swap(char& a, char& b)
{
	auto tmp = a;
	a = b;
	b = tmp;
}
//...

虽然函数重载可以实现,但是有点吃力。最糟心的事儿是明明这些函数几乎都长一个样子,仅仅是参数类型不同而已,但是每多一个类型就得写一个对应的重载函数。

那么有什么办法能做到一劳永逸呢?能否做到告诉编译器:我给你一个模板,你来帮我实现每个函数,只是换一下参数类型就OK了?

这就是我们今天要讲到的模板——模板是泛型编程的基础。模板不仅适用于函数,也适用于

🌷函数模板

🌺函数模板的概念

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

🌺函数模板的格式

template<typename T1, typename T2,......,typename TN>
返回值类型 函数名(参数列表)
{
	//...函数体
}

🌼示例1

template<typename T>
void Swap(T& a,T& b)
{
	auto tmp = a;
	a = b;
	b = tmp;
}

🍁注意事项

  1. typename后面的内容不一定是T,可自由指定;
  2. typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class);

🌼示例2

template<class N>
void Swap(N& a, N& b)
{
	auto tmp = a;
	a = b;
	b = tmp;
}

🌷函数模板的原理

函数模板是一个描述的过程一幅设计图一个模板,并不是真正的函数。编译器会根据模板使用特定方式产生具体类型函数。所以其实模板就是将本来应该我们做的重复的事情交给了编译器去做(想想印刷术的原理)。

  • 在编译器编译阶段,编译器需要根据传入的实参类型推演生成对应类型的函数以供调用。

比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。

🌷函数模板的实例化

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

🌺隐式实例化

编译器根据实参推演模板参数的实际类型。

🌼示例1

template<class T>
T Add(T a, T b)
{
	return  a + b;
}
void Test()
{
	int a = 10;
	int b = 100;
	cout << Add(a, b) << endl;
}

🌺显式实例化

在函数名后的<>中指定模板参数的实际类型

🌼错误示例

template<class T>
T Add(T a, T b)
{
	return  a + b;
}

void Test()
{
	int a = 10;
	double d = 3.14;

	cout << Add(a, d) << endl;
}

🍁错误原因
因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参aT推演为int,通过实参dT推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错。

  • 在模板中,编译器一般不会进行类型转换操作。


🌼正确的做法1

显式实例化:就是在告诉编译器:不用你推演了,我已经指定这个类型了。

template<class T>
T Add(T a, T b)
{
	return  a + b;
}

void Test()
{
	int a = 10;
	double d = 3.14;
	cout << Add<int>(a, d) << endl;
}

🌼正确的做法2

使用多个模板参数

template<class T,class Y>
Y Add(T a, Y b)
{
	return  a + b;
}

void Test()
{
	int a = 10;
	double d = 3.14;
	cout << Add<int>(a, d) << endl;
}

🌺一个疑问

很多小伙伴初次理解模板都会有一个疑问——请问如下情况中,两次调用的swap函数是一个函数吗?或者说,两次调用的函数在同一个地址空间吗?

template<class N>
void Swap(N& a, N& b)
{
	auto tmp = a;
	a = b;
	b = tmp;
}
void Test()
{
	int a = 10;
	int b = 100;
	Swap(a,b);

	char c1 = 'a';
	char c2 = 'b';
	Swap(c1, c2);
}

我们不妨先来看看汇编代码吧。如下图所示,很显然两次调用的函数并不是一个函数(一个类型为Swap< int >,一个为Swap< char >)。事实上,虽然我们看不见这两份函数的代码,但是它们实实在在的产生了。

🌷模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

🌼示例1

template<class T>
T Add(T a, T b)
{
	return  a + b;
}

int Add(int a, int b)
{
	return a + b;
}

void Test()
{
	int a = 10;
	int b = 100;
	cout << Add<int>(a, b) << endl;
}
  1. 对于非模板函数同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板

🌼示例2

template<class T>
T Add(T a, T b)
{
	cout << "模板" << endl;
	return  a + b;
}
int Add(int a, int b)
{
	cout << "非模板" << endl;
	return a + b;
}
void Test()
{
	int a = 10;
	int b = 100;
	double d = 3.14;
	cout << Add(a, b) << endl;//使用非模板函数
	cout << Add(a, d) << endl;//使用模板
}

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

🌷类模板

同样的,对于也有同样的套路。

🌺类模板的定义格式

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

🌼示例

template<class T>
class Vector
{
public:
	//...
private:
	T* _a;
	size_t _size;
	size_t _capacity;
};

🌺类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

🌼示例

// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2

点击下方个人名片,可添加博主的个人QQ,交流会更方便哦~
↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

有关C++——模板初阶与泛型编程的更多相关文章

  1. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  2. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  3. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  4. ruby-on-rails - Mandrill API 模板 - 2

    我正在使用Mandrill的RubyAPIGem并使用以下简单的测试模板:testastic按照Heroku指南中的示例,我有以下Ruby代码:require'mandrill'm=Mandrill::API.newrendered=m.templates.render'test-template',[{:header=>'someheadertext',:main_section=>'Themaincontentblock',:footer=>'asdf'}]mail(:to=>"JaysonLane",:subject=>"TestEmail")do|format|format.h

  5. ruby - Chef Ruby 遍历 .erb 模板文件中的属性 - 2

    所以这可能有点令人困惑,但请耐心等待。简而言之,我想遍历具有特定键值的所有属性,然后如果值不为空,则将它们插入到模板中。这是我的代码:属性:#===DefaultfileConfigurations#default['elasticsearch']['default']['ES_USER']=''default['elasticsearch']['default']['ES_GROUP']=''default['elasticsearch']['default']['ES_HEAP_SIZE']=''default['elasticsearch']['default']['MAX_OP

  6. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  7. ruby - 如何以编程方式删除实例上的 "singleton information"以使其编码(marshal)? - 2

    我创建了一个由于“在运行时执行的单例元类定义”而无法编码的对象(这段代码的描述是否正确?)。这是通过以下代码执行的:#defineclassXthatmyusesingletonclassmetaprogrammingfeatures#throughcallofmethod:break_marshalling!classXdefbreak_marshalling!meta_class=class我该怎么做才能使对象编码正确?是否可以从对象instance_of_x的classX中“移除”单例组件?我真的需要一个建议,因为我们的一些对象需要通过Marshal.dump序列化机制进行缓存。

  8. Ruby 元编程问题 - 2

    我正在查看Ruby日志记录库Logging.logger方法并从sourceatgithub提出问题与这段代码有关:logger=::Logging::Logger.new(name)logger.add_appendersappenderlogger.additive=falseclass我知道类 最佳答案 这实际上删除了方法(当它实际被执行时)。这是确保close不会被调用两次的保障措施。看起来好像有嵌套的“class 关于Ruby元编程问题,我们在StackOverflow上找到一

  9. ruby - Paperclip:以编程方式分配图像并设置其名称 - 2

    使用Paperclip,我想从这样的URL抓取图像:require'open-uri'user.photo=open(url)问题是我最后得到一个像“open-uri20110915-4852-1o7k5uw”这样的文件名。有什么方法可以更改user.photo上的文件名?作为一个额外的变化,Paperclip将我的文件存储在S3上,所以如果我可以在初始分配中设置我想要的文件名就更好了,这样图像就会上传到正确的S3key。像这样:user.photo=open(url),:filename=>URI.parse(url).path 最佳答案

  10. ruby - 如何以编程方式检查证书是否已被吊销? - 2

    我正在开发一个xcode自动构建系统。在执行一些预构建验证时,我想检查指定的证书文件是否已被撤销。我了解securityverify-cert验证其他证书属性但不验证吊销。我如何检查撤销?我正在用Ruby编写构建系统,但我对任何语言的想法都持开放态度。我阅读了这个答案(Openssl-Howtocheckifacertificateisrevokedornot),但指向底部的链接(DoesOpenSSLautomaticallyhandleCRLs(CertificateRevocationLists)now?)进入的Material对我的目的来说有点过于复杂(用户上传已撤销的证书是一

随机推荐