2020.9.8C++Primer学习笔记————模板函数

C++Primer学习笔记————模板函数

看C++Primer看到了第十章函数模板部分,其中提到了模板函数用法,帮助强类型语言减少简单方法的代码量。

C++是强类型语言,在调用方法时需要对传参有严格的判断,例如实现一个简单的大小判断方法时:

int min( int a, int b ) { return a < b ? a : b; 
} 
double min( double a, double b ) { return a < b ? a : b; 
}

想要对比不同类型的参数需要不同的方法,使得一个简单的方法需要重复定义好几次。
而想要一次编写代替多个重复方法,使用宏定义扩展在某些情况下可以实现,但同时也可能导致结果出错,例如:

#include <iostream> 
#define min(a,b) ((a) < (b) ? (a) : (b)) int main() { const int size = 10; int ia[size]; int elem_cnt = 0; int *p = &ia[0]; // 计数数组元素的个数while ( min(p++,&ia[size]) != &ia[size] ) ++elem_cnt; cout << "elem_cnt : " << elem_cnt << "\texpecting: " << size << endl; return 0; 
}

在这个demo中,因为实参操作p++的存在,最后输出的结果会变成
elem_cnt : 5 expecting: 10
实际count次数只有size的一半,因为应用在指针实参 p 上的后置递增操作随每次扩展而被应用了两次,一次是在 a 和 b 的测试中 另一次是在宏的返回值被计算期间。

模板函数:

函数模板提供了一种机制,通过它我们可以保留函数定义和函数调用的语义。在一个程序位置上封装了一段代码,确保在函数调用之前实参只被计算一次,而无需像宏方案那样绕过 C++的强类型检查。
函数模板提供一个种用来自动生成各种类型函数实例的算法,程序员对于函数接口参数和返回类型中的全部或者部分类型进行参数化 parameterize,而函数体保持不变。

上面是摘自C++Primer,总结下来就是将方法的参数类型(而非参数)进行参数化,将 int ,double 等参数类型也当成参数传入方法。
这种函数模板最适合要传入多个同参数类型的参数时,例如:

template <class Type> 
Type min( Type a, Type b ) { return a < b ? a : b; 
} int main() { // ok: int min( int, int ); min( 10, 20 ); // ok: double min( double, double ); min( 10.0, 20.0 );}

也可以有不同的参数类型传入,有几个参数类型就声明几个Type:

template <class TypeA, class TypeB> 
TypeB plus( TypeA a, TypeB b ) { return b + a;
} int main() { // ok: float plus( int, float ); plus( 10, 20.5 ); }

还可以在模板函数中声明模板非类型参数,其代表一个常量表达式,例如:

template <class Type, int size> 
Type min( const Type (&r_array)[size] ) 
{ /* 找到数组中元素最小值的参数化函数 */ Type min_val = r_array[0]; for ( int i = 1; i < size; ++i ) if ( r_array[i] < min_val ) min_val = r_array[i]; return min_val; 
}

其中 size 就是一个 int 类型的常量。

声明冲突:

如果在全局域中声明了与模板参数同名的对象,函数或类型,则该全局名将被隐藏。

typedef double Type; 
template <class Type> 
Type min( Type a, Type b ) 
{ // tmp 类型为模板参数 Type // 不是全局 typedef Type tmp = a < b ? a : b; return tmp; 
}

如上,即模板参数覆盖全局变量。

在函数模板定义中声明的对象或类型不能与模板参数同名,模板类型参数名可以被用来指定函数模板的返回类型。

引入关键字typename:

在函数模板声明中,如传入的参数是一个类的实体:

class PARM{
public:char *name;
...
}PARM Parm;template <class Parm, class U> Parm minus( Parm* array, U value ) 
{ Parm::name * p; // 这是一个指针声明还是乘法 乘法
}

类型Parm要在实例化时才知道他是一个类的实体,Parm::name * p模板函数声明时才是一个指针,对于模板函数内部的定义来说这个 * 代表乘法。
所以这个时候需要引入 typename 关键字来确定变量到底是指针还是一个类内属性:

template <class Parm, class U> Parm minus( Parm* array, U value ) 
{ typename Parm::name * p; // ok: 指针声明
}

函数模板也可以被声明为 inline 或 extern 形式,但是修饰符需要放在函数名前而不是 template 前。

// ok: 关键字跟在模板参数表之后
template <typename Type> inline Type min( Type, Type ); 
// 错误: inline 指示符放置的位置错误
inline 
template <typename Type> Type min( Array<Type>, int );

函数模板实例化:

模板函数的声明在被调用时会隐式的进行模板实例化,根据传入的参数生成独立的函数,过程是隐式的。
用函数实参的类型来决定模板实参的类型和值的过程被称为模板实参推演

template <typename Type, int size> Type min( Type (&p_array)[size] ) { /* ... */ } 
// pf 指向 int min( int (&)[10] ) 
int (*pf)(int (&)[10]) = &min;

指针 pf 被函数模板实例的地址初始化,编译器通过检查 pf 指向的函数的参数类型来决定模板实例的实参。
pf 的类型是指向函数的指针,该函数有一个类型为 int(&)[10]的参数,当 min()被实例化时,该参数的类型决定了 Type 的模板实参的类型和 size 的模板实参的值。Type 的模板实参为 int,size 的模板实参为 10,被实例化的函数是 min(int(&)[10]) ,指针 pf 指向这个模板实例。

在取函数模板实例的地址时,必须能够通过上下文环境为一个模板实参决定一个惟一的类型或值。如果不能决定出这个惟一的类型或值,就会产生编译时刻错误:

template <typename Type, int size> Type min( Type (&r_array)[size] ) { /* ... */ } 
typedef int (&rai)[10]; 
typedef double (&rad)[20]; 
void func( int (*)(rai) ); 
void func( double (*)(rad) ); 
int main() { // 错误: 哪一个 min() 的实例? func( &min ); 
}

这里func()不知道该取 int 型 min 函数的地址还是 double 型函数的地址,需要用一个强制类型转换显式地指出实参的类型则可以消除编译时刻错误:

int main() { // ok: 强制转换指定实参类型func( static_cast< double(*)(rad) >(&min) ); 
}

模板实参推演:

当函数模板被调用时,对函数实参类型的检查决定了模板实参的类型和值,这个过程被称为模板实参推演

函数模板 min()的函数参数是一个引用,它指向了一个 Type 类型的数组,为了匹配函数参数,函数实参必须也是一个表示数组类型的左值。下面的调用是个错误,因为 pval 是 int*类型而不是 int 数组类型的左值:

template <class Type, int size> Type min( Type (&r_array)[size] ) { /* ... */ }void f( int pval[9] ) { // 错误: Type (&)[] != int* int jval = min( pval ); 
}

想正确传入数组参数,我自己尝试的方法:

int f(int pval[9]) {int ia[9];memcpy(ia, pval, 9*(sizeof(int)));int jval = min(ia);return jval;
}

要想成功地进行模板实参推演,函数实参的类型不一定要严格匹配相应函数参数的类型。
下列三种类型转换是允许的:左值转换,限定转换和到一个基类(该基类根据一个类模板实例化而来)的转换。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/757485.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法体系-11 第十一节:二叉树基本算法(上)

一 两链表相交 1.1 题目描述 给定两个可能有环也可能无环的单链表&#xff0c;头节点head1和head2。请实现一个函数&#xff0c;如果两个链表相交&#xff0c;请返回相交的 第一个节点。如果不相交&#xff0c;返回null 【要求】 如果两个链表长度之和为N&#xff0c;时间复杂…

静电无处不在:揭秘液晶显示屏静电防护的“大师级“策略

静电&#xff0c;仿佛是电子产品制造过程中的隐形杀手&#xff0c;尤其对于液晶显示屏等精密电子元器件的影响更是不可小觑。然而&#xff0c;面对这一挑战&#xff0c;有些制造商采取了一系列超越寻常的静电防护措施。今天&#xff0c;我们将揭开他们的"大师级"策略…

利用Android studio 查看模拟器中数据文件

打开Android studio &#xff0c;然后按照下图选择 然后会在右侧打开一个这样子的管理弹窗 找到 data/data/your project file 你的缓存跟下载的文件就都在里面了

【C++】每日一题 219 最小栈

设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。 void push(int val) 将元素val推入堆栈。 void pop() 删除堆栈顶部的元素。 int top() 获取堆栈顶部的元素。 int…

BigDecimal保留两位小数失败问题

文章目录 背景问题解决如何测试代码 背景 测试时发现在线swagger测试会自动处理BigDecimal小数点后面的数字&#xff0c;就是有零的会都给你去掉&#xff0c;比如9.000与9.500到最后都会被swagger处理成9跟9.5。使用postman测是最准的&#xff0c;测出来的就是9.000跟9.500。 …

数据库基本内容与安装MySQL数据库

目录 一.数据库基本内容 1.数据 &#xff08;1&#xff09;描述事物的符号记录 &#xff08;2&#xff09;包括数字&#xff0c;文字、图形、图像、声音、档案记录等 &#xff08;3&#xff09;以“记录”形式按统一的格式进行存储 2.表 &#xff08;1&#xff09;将不同…

Java SE 22 新增特性

Java SE 22 新增特性 作者&#xff1a;Grey 原文地址&#xff1a; 博客园&#xff1a;Java SE 22 新增特性 CSDN&#xff1a;Java SE 22 新增特性 源码 源仓库: Github&#xff1a;java_new_features 使用未命名的变量和模式 我们经常需要定义一些我们根本不需要的变量…

【Linux】基础 IO(动静态库)-- 详解

一、前言 为什么要使用别人的代码&#xff1f; 主要是为了提高程序开发的效率和程序的健壮性。 当别人把功能都实现了&#xff0c;然后我们再基于别人的代码去做二次开发&#xff0c;那么效率当然就提高了。其次&#xff0c;这里基于的别人当然不是随便找的一个人&#xff0c;…

[Qt学习笔记]Qt鼠标事件mouseMoveEvent实时获取图像的坐标和像素值

目录 1、介绍2、效果展示3、实现过程3.1 图像的加载和显示3.2 设置鼠标跟踪事件激活3.3 实现代码 4、源码展示 1、介绍 上一篇介绍了使用OpenCV的setMouseCallback回调函数实现获取鼠标点击点的图像坐标和像素值&#xff0c;本篇使用鼠标事件mouseMoveEvent函数来实现实时获取…

OPPO 后端二面,凉凉。。。

美众议院通过 TikTok 法案 之前我们讲了 老美要求字节跳动在 165 天内剥离短视频应用 TikTok&#xff0c;当时的最新进度是 TikTok 给 1.7 亿美国用户发弹窗&#xff0c;发动用户群众给国会打电话进行抗议。 但显然这点力度的抗议并不会造成什么实质影响。 昨晚&#xff0c;美国…

精读《useRef 与 createRef 的区别》

1 引言 useRef 是常用的 API&#xff0c;但还有一个 createRef 的 API&#xff0c;你知道他们的区别吗&#xff1f;通过 React.useRef and React.createRef: The Difference 这篇文章&#xff0c;你可以了解到何时该使用它们。 2 概述 其实原文就阐述了这样一个事实&#xf…

【EDSR】《Enhanced Deep Residual Networks for Single Image Super-Resolution》

CVPR workshops-2017 首尔大学 code&#xff1a; https://github.com/limbee/NTIRE2017/tree/masterhttps://github.com/sanghyun-son/EDSR-PyTorch 文章目录 1 Background and Motivation2 Related Work3 Advantages / Contributions4 Method4.1 Residual blocks4.2 Single…

华为OD机试 - 单向链表中间节点(Java JS Python C C++)

题目描述 给定一个单链表 L&#xff0c;请编写程序输出 L 中间结点保存的数据。 如果有两个中间结点&#xff0c;则输出第二个中间结点保存的数据。例如&#xff1a; 给定 L 为 1→7→5&#xff0c;则输出应该为7&#xff1b; 给定 L 为 1→2→3→4&#xff0c;则输出应该为…

盘点国内IP地址服务的功能及提供商

随着互联网的快速发展和普及&#xff0c;IP地址服务提供商在中国市场扮演着越来越重要的角色。这些代理软件提供商不仅为用户提供稳定的网络连接&#xff0c;还可以帮助用户实现IP地址切换、绕过地理限制等功能。虎观代理接下来将详解国内IP地址服务的主要功能&#xff0c;并对…

王道c语言-chap13 栈实例

王道c语言-chap13 栈实例 #include <iostream>#define END -1 #define MaxSize 50typedef int ElemType; typedef struct {int data[MaxSize];int top; } SqStack;void InitStack(SqStack &s) {s.top-1; }void PrinfSqStack(SqStack s) {for (int i 0; i < s.to…

什么是布隆过滤器

布隆过滤器&#xff08;Bloom Filter&#xff09;是一种概率型数据结构&#xff0c;用于判断一个元素是否存在于一个集合中。它的主要优点是占用空间少、查询速度快&#xff0c;并且支持高效的插入和查询操作。 布隆过滤器由一个位数组和多个哈希函数构成。初始时&#xff0c;…

八股文三(Spring、Spring Cloud Alibaba)

Spring篇 什么是Spring Spring是个轻量级的框架&#xff0c;他有两大内核分别是IOC、AOPIOC是什么 * 依赖注入和控制反转&#xff0c;它是一种思想&#xff0c;创建对象不是直接new出来&#xff0c;而是交给IOC容器&#xff0c;由容器去创建进行管理&#xff0c;这就是控制反…

解决antd vue 的table列表过宅内容换行和过长有空白列的问题

主要有两个注意点 1、columns数组中保留一个对象不设置宽度&#xff0c;其余都要设置宽度&#xff1b; 2、HTML中scroll要设置为{x: 足够长的固定宽度}&#xff1b; 具体代码如下&#xff1a; <a-tableref"table"size"middle"rowKey"rowSerial&qu…

字符串函数---(1)

字符函数 文章目录 前言1.strlen 的使用和模拟实现2.strcpy 的使用和模拟实现3. strcat 的使用和模拟实现4. strcmp 的使用和模拟实现 前言 上一篇我们学习了字符函数&#xff0c;下来我们学习常见的字符串函数 1.strlen 的使用和模拟实现 size_t strlen(const char *str) 字…

(70min)字节暑假实习二面(已挂)

还没找到工作的小伙伴加油&#xff01; 字节看面评果然名不虚传。。 面试官&#xff1a; 看你一面基础也回答得不好&#xff0c;没事&#xff0c;那我就不问你八股&#xff0c;多问项目吧。 然后开始鞭打项目。 项目使用mq&#xff0c;从背景的&#xff0c;技术选型&#xff0…