当前位置: 首页 > news >正文

C++——模板

在C语言阶段,当遇到功能类似的函数时,我们只能定义多个名称不同的函数。

int Add1(int x, int y)
{return x + y;
}double Add2(double x, double y)
{return x + y;
}

到了C++我们首先学了函数重载,这使得我们不必再为功能相似的函数起不同的名字。

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

虽然函数重载解决了C语言中命名的问题,但是依然要自己实现多个函数,麻烦,而且函数重载也有几个不好的地方:

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

那有没有一种方法,我们只需要像活字印刷术那样,将会重复用的字刻成一个一个的模板,等到在需要使用该字的时候,直接用该模板即可,不必再用人工手写。 

C++就提供了一种方法来解决C语言以及函数重载对功能相同函数的不足——模板。

一.模板

在日常生活中,处处有模板。我们做PPt时就会使用相对应的模板来使自己的ppt更加高大上。

而在C++中,模板是一个具有某种功能的函数或者类的统称。但是该模板不能够直接被调用,我们需要对其进行实例化,成为某个特定的函数或者类。

C++模板分为函数模板和类模板

二.函数模板 

函数模板,实际上是建立一个通用函数,其函数类型和形参的类型不具体指定,用一个虚拟额类型来代表。

一个函数模板就是一个公式,可用来生成针对特定类型的函数版本。上面的Add函数的模板版本像下面这样:

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

模板定义以关键字template开始,后面跟一个模板参数列表(template parameter list),这是一个逗号分隔的一个或多个模板参数的列表,用<>包围起来。

模板参数列表的作用很像函数参数列表。函数参数列表定义了若干特定类型的局部变量,但并未指出如何初始化它们。在运行时,调用者提供实参来初始化它们。

类似的,模板参数表示在类或者函数在定义时需要用到的类型。当我们使用模板时,我们(隐式或者显式地)指定模板实参,将其绑定到模板参数上,进行初始化操作。

我们对Add函数指定了一个模板参数T。在Add中,T表示一个类型。而T表示的实际类型则在编译时根据Add的使用情况来确定。

总的来说,模板参数列表中的参数就是未来函数或者是类的参数或者函数的类型

注意:在模板定义中,模板参数列表不能为空

2.1模板参数

函数模板参数的定义很像函数参数的定义。函数参数定义时:类型+变量名。

而模板参数定义时要加上关键字typename/class+模板参数名。

typename/clss 变量名

typename和class都可以定义模板参数,两者功能相同,且可以同时出现。

template <typename T1, class T2>
void func(T1 x, T2 y)
{//……
}

2.2实例化函数模板

当我们调用一个函数模板时,编译器通常会根据我们传的实参来推断模板的类型。这种实例化方式为隐式实例化。

例如:对于Add函数,我们用下面的方式传参时:

std::cout << Add(1, 2) << std::endl;

我们传入实参(1,2)为整型,编译器就会推断模板的参数为int,并用来初始化T。此时我们的Add函数就变成了:

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

但是当我们传实参时,传入了两个不同类型的实参,此时编译器就会报错:

	std::cout << Add(1, 2.0) << std::endl;

因为我们只有一个模板参数,但是却传了两个不同类型的实参,此时编译器不知道该用哪一个实参的类型作为该模板的类型。 

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

为了解决上面这种问题,我们有两种解决方案:
1.对实参进行强制类型转换

std::cout << Add(1, (int)2.0) << std::endl;
//或
std::cout << Add((double)1, 2.0) << std::endl;

2.显式实例化

	std::cout << Add<int>(1, 2.0) << std::endl;

我们可以先显式的实例化改模板的参数,即该模板此时已经是显式实例化后的模板,此时在传入类型不同的实参时,就会发生隐式类型转换。

2.3显式实例化

显式实例化即在函数名后用<>指定该模板参数的类型。

int a = 1;
double b = 19.2;std::cout << Add<int>(a, b) << std::endl;//显式实例化

如果类型不匹配,编译器会尝试进行隐式类型转换,如果不能转换则编译器报错。

2.4模板的调用

当我们实例化模板之后,调用的是这个模板还是由该模板生成的新函数?

答案是新函数!

我们通过汇编代码可以看出,如果调用的都是函数模板的话,这两次调用应该是同一个地址。但是这两次调用的函数在不同的地址上,由此可以看出调用模板时调用的是由模板生成的新函数。 

2.5模板参数的匹配原则 

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

template <typename T>
T Add(T x, T y)
{return x + y;
}int Add(int x, int y)
{return x + y;
}int main()
{Add(1, 2);//调用非模板函数Add(1.0, 2.5);//调用模板函数return 0;
}

2.5.2对于非模板函数和同名的模板函数而言,如果其他条件都相同,编译器会优先调用非模板函数,如果模板可以产生一个更合适的函数,则编译器调用模板函数

template <typename T1, typename T2>
T1 Add(T1 x, T2 y)
{return x + y;
}int Add(int x, int y)
{return x + y;
}int main()
{Add(1, 2);//调用非模板函数//调用模板函数//此时如果调用非模板函数会发生隐式类型转换,会丢失精度//而对于模板来说,可以生成一个针对于该实参的专属的函数,不会丢失精度//所以对编译器而言此时调用模板函数是一个更好的选择Add(1, 2.5);return 0;
}

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

三.类模板

类模板定义方式和函数模板的定义方式类似:

template <typename T1, typename T2>
class A
{
public:A(){std::cout << "A()" << std::endl;}
private:T1 x;T2 y;
};

其中类中定义的成员函数也可以使用模板参数。

注意:类模板不建议声明和定义分别存放在.h和.cpp文件中,可能导致链接错误

类模板的实例化必须显式实例化

A<int, int> a1;
A<int, double> a2;

实例化方式为:类模板后+<里面放需要的类型>+对象名

相关文章:

  • Oracle(136)什么是UNDO表空间?
  • 2024 Snap 新款ar眼镜介绍
  • 初写MySQL四张表:(4/4)
  • 2000-2023年中国气候政策不确定性指数(全国、省、市三个层面)
  • uni-app+vue3开发微信小程序使用本地图片渲染不出来报错[渲染层网络层错误]Failed to load local image resource
  • django项目添加测试数据的三种方式
  • 开发者“SmilingWolf”的标签标注模型
  • K8S精进之路-控制器StatefulSet有状态控制 -(2)
  • 基于springboot vue 大学生竞赛管理系统设计与实现
  • 速盾:高防cdn防御的时候会封ip吗?
  • 【线程】线程安全的单例模式
  • C\C++内存管理详解
  • 基于Jeecg-boot开发系统--后端篇
  • Linux-df命令使用方法
  • Vue3 路由传参:玩转 params,让页面交互更流畅!
  • 【划重点】MySQL技术内幕:InnoDB存储引擎
  • Android 控件背景颜色处理
  • C++类的相互关联
  • Docker入门(二) - Dockerfile
  • ES6 ...操作符
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • Redis 懒删除(lazy free)简史
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • vue-router 实现分析
  • 闭包--闭包之tab栏切换(四)
  • 闭包--闭包作用之保存(一)
  • 构造函数(constructor)与原型链(prototype)关系
  • 详解NodeJs流之一
  • C# - 为值类型重定义相等性
  • 积累各种好的链接
  • # .NET Framework中使用命名管道进行进程间通信
  • # 利刃出鞘_Tomcat 核心原理解析(二)
  • #include
  • #window11设置系统变量#
  • $refs 、$nextTic、动态组件、name的使用
  • (1)常见O(n^2)排序算法解析
  • (2)空速传感器
  • (读书笔记)Javascript高级程序设计---ECMAScript基础
  • (二)十分简易快速 自己训练样本 opencv级联lbp分类器 车牌识别
  • (分布式缓存)Redis持久化
  • (十六)一篇文章学会Java的常用API
  • *** 2003
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .net core 连接数据库,通过数据库生成Modell
  • .Net Core 微服务之Consul(二)-集群搭建
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .net MVC中使用angularJs刷新页面数据列表
  • .NET 设计模式—简单工厂(Simple Factory Pattern)
  • .NET关于 跳过SSL中遇到的问题
  • .w文件怎么转成html文件,使用pandoc进行Word与Markdown文件转化
  • @Async注解的坑,小心
  • [1127]图形打印 sdutOJ
  • [Android Pro] AndroidX重构和映射
  • [Assignment] C++1