C++模板基础知识

news/2024/7/27 8:26:58/文章来源:https://blog.csdn.net/mo4776/article/details/136577555

文章目录

    • 模板
      • 模板的声明与定义
      • 函数模板
      • 非类型模板参数
      • 类模板
        • 类的成员函数定义
        • 构造函数的定义
        • 类的静态成员的定义
        • 类模板的实例化
        • 使用模板类型中的类型成员
      • 默认模板参数
      • 指定显示模板实参(函数模板显示实参)
      • 引用折叠和右值引用参数
      • 可变参数模板
        • 对参数包的扩展
        • 对参数包的转发
        • 可变参数模板的示例
        • 模板参数传入可调用对象

虽然用了多年的C++,但是C++泛型编程在开发工作中接触的很少,就连需要使用模板的场景都很少。因为用的少,模板相关的知识就是看了忘,忘了看。

但是在阅读一些开源的C++项目,模板还是经常遇到的,为了理解代码,模板的基础知识还是得了然于心。这篇文章是记录了C++模板基础知识 ,内容来自 C++ Primer,做了归纳方便以后查看。

模板

模板是泛型编程的基础。分别有模板函数和模板类,声明一个模板函数或模板类与定义一个普通函数和类一样,只是并不指定明确的类型了。

模板包含了模板实例的步骤,对不同类型相同模板的函数或类,编译器在编译阶段生成对应的代码。不同类型的模板实例化是不同的类型。

模板的声明与定义

为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常即包括声明也包括定义。

函数模板和类模板成员函数的定义通常放在头文件中。像常规的头文件和源文件分离编译的情况,在模板情况下行不通。

如下模板类Blob ,在blob_test.h文件中

#include <string>
template <typename T,typename U> 
class Blob {
public:Blob(T a,U b);
public:T getA();U getB();
private:T _a;U _b;
};

如果将Blob类中的构造函数及getA()getB()函数,定义在文件blob_test.cpp中,如下:

#include "blob_test.h"
template <typename T,typename U>
Blob<T,U>::Blob(T v,U v1):_a(v),_b(v1) {}template <typename T,typename U> 
T Blob<T,U>::getA() {return _a;
}template <typename T,typename U>
U Blob<T,U>::getB() {return _b;
}

那么在main.cpp文件中

#include "blob_test.h"
#include <iostream>
int main() {//Blob<int,std::string>是实例化的类型Blob<int,std::string> b(18,"123");std::cout<<"A:"<<b.getA()<<std::endl;std::cout<<"B:"<<b.getB()<<std::endl;
}

编译blob_test.h,blob_test.cpp,main.cpp将会报错,因为在main.cpp中只包含了blob_test.h只有声明,没有定义,编译器无法实例化该模板。

所以对模板,一般将声明与定义都写在头文件,即声明也包含定义。那么blob_test.h修改如下:

#include <string>
template <typename T,typename U> 
class Blob {
public:Blob(T a,U b);
public:T getA();U getB();
private:T _a;U _b;
};//Blob<T,U>就表示一个类
template <typename T,typename U> 
T Blob<T,U>::getA() {return _a;
}template <typename T,typename U>
U Blob<T,U>::getB() {return _b;
}template <typename T,typename U>
Blob<T,U>::Blob(T v,U v1):_a(v),_b(v1) {}

模板的设计者应该提供一个头文件,包含模板定义及在类模板或成员定义中用到的所有名字的声明。模板用户必须包含模板的头文件,以及用来实例化模板的任何类型的头问题。

模板的定义与声明,可以看看这篇文章。

函数模板

template <typename T>
int compare(const T& v1,const T& v2) {if (v1 < v2) return -1;if (v2 < v1) return 1;return 0;
}std::cout<<compare(1,0)<<std::endl;

模板定义以关键字template开始,后跟一个模板参数列表这是一个逗号分隔的一个或多个模板参数的列表

函数模板不必显示写出类型实参,编译器可以根据实参推导。比如compare(1,0)在编译期间就被编译器实例化为一个intcompare版本。

一般来说,我们可以将类型参数看作类型说明符,就像内置类型或类型说明符一样使用。

特别是,类型参数可以用来指定返回类型或参数的参数类型,以及在函数体内用于变量声明或类型转换。

template <typename T>
T foo(T *p) {//tmp的类型将是指针p指向的类型T tmp = *p;//...return tmp;
}

非类型模板参数

template<unsigned N,unsigned M>
int compare(const char(&p1)[N],const char(&p2)[M]);

类模板

如下是一个模板类Bolb的声明。

template <typename T> class Blob {
public:T& back();T& operator[](size_type i);
private:std::shared_ptr<std::vector<T>> _data;static size_t _v;
};

Blob<T>就当一个正常的类使用**,template <typename T> Blob<T> 就是它的完整形式。

类的成员函数定义

当我们定义一个成员函数时,应如下定义:

template <typename T>
ret-type Blob<T>::member-name(parm-list)

Blob成员函数定义如下:

template <typename T>
void Blob<T>::check(size_t i,const std::string& msg) const {....;
}
template <typename T>
T& Blob<T>::back() {....
}
构造函数的定义
template <typename T>
Blob<T>::Blob():data(...) {...
}
类的静态成员的定义
template <typename T>
size_t Foo<T>::_v = 0;
类模板的实例化

与函数模板不同之处是,编译器不能为类模板推断模板参数类型,实例化时必须指定类型,如Blob<int>Blob<std::string>

使用模板类型中的类型成员

当编译器遇到这样的语句 T::size_type *p; 时,它需要指定我们是正在定义一个名为p的变量,还是将一个名为size_typestatic数据成员与名为p的变量相乘。默认情况下,C++语言假定通过作用域运算符访问的名字不是类型。

因此,如果我们希望使用一个模板类型参数的类型成员,就必须显示告诉编译器该名字是一个类型。我们通过使用关键字typename来实现这一点:

template <typename T>
typename T::value_type top(const T& c) {if (!c.empty()) {return c.back();} else {return typename T::value_type();}}

默认模板参数

template <typename T, typename F = less<T>> 
int compare(const T& v1,const T& v2, F f= F()) {...
}
template <class T = int> 
class Numbers {....
};

指定显示模板实参(函数模板显示实参)

我们可以定义表示返回类型的第三个模板参数,从而允许用户控制返回类型:

template <typename T1,typename T2,typename T3>
T1 sum(T2,T3);

T1指定返回值的类型。

实例化时,返回值的类型被显示指定

// T1是显示指定,T2和T3是从函数实参类型推断而来
auto val3 = sum<long long>(i,lng);

引用折叠和右值引用参数

如下一个模板函数f(T&&),注意参数类型的推导。

template <typename T> void f(T &&);
f3(42);//42是一个右值,那么模板参数T被推导为int
int i = 118;
f3(i);//i是个左值,那么模板参数T被推导为int&

如下例子,函数f将改变量i的值,因为类型被推导为int&

#include <iostream>
template <typename T> void f(T&& v) {v = 18;
}int main() {int i = 0;f(i);std::cout<<"i:"<<i<<std::endl;
}

当我们将一个左值传递给函数的右值引用参数,且此右值引用指向模板类型参数(如T&&)时,编译器推断模板类型参数为实参的左值引用类型。

因此,当我们调用f3(i)时,编译器推断T的类型为int&,而非int

那么对f3(i),编译器实际推导为:

void f3<int&>(int& &&);

T的类型为int&,触发了C++中的引用折叠。
引用折叠的规则如下:

  • X& &X& &&X&& &都折叠成类型X&
  • 类型X&& &&折叠成X&&

那么void f3<T>(T &&)的模板参数可以称为万能引用,因为实参既可以传入左值,也可以传入右值:
f(i);
f(18);

可变参数模板

可以传入多个实参,像printf一样。

template <typename T,typename ...Args>
void foo(const T &t,const Args& ...rest);
对参数包的扩展
template <typename... Args>
ostream &errorMsg(ostream &os,const Args&... rest) {return print(os,debug_rep(rest)...);
}

对参数包rest进行了扩展,将对每个参数都调用debug_rep

对参数包的转发
//...Args表示模板参数的类型
//Args&&... 表示以Args声明了形参args
template<typename ...Args>
void fun(Args&&... args) {work(std::forward<Args>(args)...);
}
可变参数模板的示例
#include <iostream>
void work(int a,int b) {std::cout<<"===> enter work(int a,int b)"<<std::endl;std::cout<<"a:"<<a<<",b:"<<b<<std::endl;std::cout<<"===> exit work(int a,int b)"<<std::endl;
}void work(int& a,int& b) {std::cout<<"===> enter work(int& a,int& b)"<<std::endl;a = 1;b = 2;std::cout<<"===> exit work(int& a,int& b)"<<std::endl;
}void work(const int&a,const int& b) {std::cout<<"===> enter work(const int&a,const int& b)"<<std::endl;std::cout<<"===> exit work(const int&a,const int& b)"<<std::endl;
}template <typename ...Args>
void foo(Args&& ...v) {work(std::forward<Args>(v)...);
}int main() {//模板参数的实参类型//类型被推断为int//此时会报错,因为选择const int&和int,int&都可以foo(18,118);int a=16,b=116;/*模板参数会被推断为int&,此时会有引用折叠,但是通过forward*将类型保留转发到了work。*此时 int&,const int&都适合,但是int&更加合适,会选择int&。*/foo(a,b);std::cout<<"a:"<<a<<",b:"<<b<<std::endl;
}
模板参数传入可调用对象
#include <iostream>template<typename Fun,typename ...Args>
void Test(Fun _fun,Args&&... args) {_fun(std::forward<Args>(args)...);
}//函数对象
struct SWork {void operator()(int a,int b) {std::cout<<"SWork,a:"<<a<<",b:"<<b<<std::endl;}
};//函数
void work(int a,int b) {std::cout<<"work,a:"<<a<<",b:"<<b<<std::endl;
}int main() {Test(work,18,18);Test(SWork(),19,19);
}

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

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

相关文章

代理模式以及静态代理、JDK代理、Cglib代理的实现

代理模式&#xff08;Proxy&#xff09; 介绍 1、代理模式&#xff1a;为对象提供一个替身&#xff0c;以控制对这个对象的访问&#xff0c;即通过代理对象访问目标对象&#xff0c;这样做的好处是&#xff1a;可以在目标对象实现的基础上&#xff0c;增强额外的功能操作 &…

第十篇 - 如何利用人工智能技术做好营销流量整形管理?(Traffic Shaping)- 我为什么要翻译介绍美国人工智能科技巨头IAB公司

IAB平台&#xff0c;使命和功能 IAB成立于1996年&#xff0c;总部位于纽约市​​​​​​​。 作为美国的人工智能科技巨头社会媒体和营销专业平台公司&#xff0c;互动广告局&#xff08;IAB- the Interactive Advertising Bureau&#xff09;自1996年成立以来&#xff0c;先…

数据结构 - 栈和队列

本篇博客将介绍栈和队列的定义以及实现。 1.栈的定义 栈是一种特殊的线性表&#xff0c;只允许在固定的一端进行插入和删除数据&#xff0c;插入数据的一端叫做栈顶&#xff0c;另一端叫做栈底。栈中的数据遵守后进先出的原则 LIFO (Last In First Out)。 插入数据的操作称为压…

【AI视野·今日Robot 机器人论文速览 第八十四期】Thu, 7 Mar 2024

AI视野今日CS.Robotics 机器人学论文速览 Thu, 7 Mar 2024 Totally 23 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers 3D Diffusion Policy Authors Yanjie Ze, Gu Zhang, Kangning Zhang, Chenyuan Hu, Muhan Wang, Huazhe Xu模仿学习提供了一种教…

js【详解】Promise

为什么需要使用 Promise &#xff1f; 传统回调函数的代码层层嵌套&#xff0c;形成回调地狱&#xff0c;难以阅读和维护&#xff0c;为了解决回调地狱的问题&#xff0c;诞生了 Promise 什么是 Promise &#xff1f; Promise 是一种异步编程的解决方案&#xff0c;本身是一个构…

防御保护--IPSEC VPPN实验

实验拓扑图 实验背景&#xff1a;FW1和FW2是双机热备的状态。 实验要求&#xff1a;在FW5和FW3之间建立一条IPSEC通道&#xff0c;保证10.0.2.0/24网段可以正常访问到192.168.1.0/24 IPSEC VPPN实验配置&#xff08;由于是双机热备状态&#xff0c;所以FW1和FW2只需要配置FW1…

解决方案TypeError: string indices must be integers

文章目录 一、现象&#xff1a;二、解决方案 一、现象&#xff1a; PyTorch深度学习框架&#xff0c;运行bert-mini&#xff0c;本地环境是torch1.4-gpu&#xff0c;发现报错显示&#xff1a;TypeError: string indices must be integers 后面报字符问题&#xff0c;百度过找…

阿里云服务器安装EMQX避坑指南

首先我们在EMQX官网按照教程安装了EMQX&#xff0c;博主采用的是Ubantu20.04 然后用netstat -lntu指令检测emqx是否启动 从上面这张图片可以看到我们的端口已经在本地运行了&#xff0c;18083是后台管理系统的端口。随后我们在阿里云服务器配置安全组规则。 但是即便做到这样仍…

数据结构之栈详解(C语言手撕)

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生 &#x1f648;个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE &#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN&…

如何保证消息不丢之MQ重试机制消息队列

1. 简介 死信队列&#xff0c;简称&#xff1a;DLX&#xff0c;Dead Letter Exchange&#xff08;死信交换机&#xff09;&#xff0c;当消息成为Dead message后&#xff0c;可以被重新发送到另外一个交换机&#xff0c;这个交换机就是DLX 那么什么情况下会成为Dead message&a…

智慧城市如何助力疫情防控:科技赋能城市安全

目录 一、引言 二、智慧城市与疫情防控的紧密结合 三、智慧城市在疫情防控中的具体应用 1、智能监测与预警系统 2、智慧医疗与健康管理 3、智能交通与物流管理 4、智慧社区与基层防控 四、科技赋能城市安全的未来展望 五、结论 一、引言 近年来&#xff0c;全球范围内…

鼠标右键没有git bash here,右键添加git bash here并增加图标

突然发现自己鼠标右键没有git bash here&#xff0c;或者安装之后就没有git bash here。后面那种情况多半是没有默认装在C盘。我们装在其他盘的时候就需要自己去配置。git gui目前用不上&#xff0c;这里只讲git bash here。网上一堆教程&#xff0c;说法不同大多不能用要么就很…

Docker部署SimpleMindMap结合内网穿透实现公网访问本地思维导图

文章目录 1. Docker一键部署思维导图2. 本地访问测试3. Linux安装Cpolar4. 配置公网地址5. 远程访问思维导图6. 固定Cpolar公网地址7. 固定地址访问 SimpleMindMap 是一个可私有部署的web思维导图工具。它提供了丰富的功能和特性&#xff0c;包含插件化架构、多种结构类型&…

1-Git-基础

版本控制 为什么要进行版本控制&#xff1f; **个人角度&#xff1a;**代码的修改繁杂&#xff0c;如果一点点修改代码&#xff0c;不利于开发。进行版本控制&#xff0c;可以在特定历史状态下进行修改。即可以进行回退和撤销操作 **团队角度&#xff1a;**每个人负责各自的…

垃圾收集器底层算法

垃圾收集器底层算法 三色标记 在并发标记的过程中&#xff0c;因为标记期间应用线程还在继续跑&#xff0c;对象间的引用可能发生变化&#xff0c;多标和漏标的情况就有可能发生&#xff0c;这里我们引入“三色标记”来给大家解释下把Gcroots可达性分析遍历对象过程中遇到对象…

灯塔:CSS笔记(2)

一 选择器进阶 后代选择器&#xff1a;空格 作用&#xff1a;根据HTML标签的嵌套关系&#xff0c;&#xff0c;选择父元素 后代中满足条件的元素 选择器语法&#xff1a;选择器1 选择器2{ css } 结果&#xff1a; *在选择器1所找到标签的后代&#xff08;儿子 孙子 重孙子…

opencv dnn模块 示例(24) 目标检测 object_detection 之 yolov8-pose 和 yolov8-obb

前面博文【opencv dnn模块 示例(23) 目标检测 object_detection 之 yolov8】 已经已经详细介绍了yolov8网络和测试。本文继续说明使用yolov8 进行 人体姿态估计 pose 和 旋转目标检测 OBB 。 文章目录 1、Yolov8-pose 简单使用2、Yolov8-OBB2.1、python 命令行测试2.2、opencv…

iOS-系统弹窗调用

代码&#xff1a; UIAlertController *alertViewController [UIAlertController alertControllerWithTitle:"请选择方式" message:nil preferredStyle:UIAlertControllerStyleActionSheet];// style 为 sheet UIAlertAction *cancle [UIAlertAction actionWithTit…

Unity性能优化篇(七) UI优化注意事项以及使用Sprite Atlas打包精灵图集

UI优化注意事项 1.尽量避免使用IMGUI(OnGUI)来做游戏时的UI&#xff0c;因为IMGUI的开销比较大。 2.如果一个UGUI的控件不需要进行射线检测&#xff0c;则可以取消勾选Raycast Target 3.尽量避免使用完全透明的图片和UI控件。因为即使完全透明&#xff0c;我们看不见它&#xf…

C#,老鼠迷宫问题的回溯法求解(Rat in a Maze)算法与源代码

1 老鼠迷宫问题 迷宫中的老鼠&#xff0c;作为另一个可以使用回溯解决的示例问题。 迷宫以块的NN二进制矩阵给出&#xff0c;其中源块是最左上方的块&#xff0c;即迷宫[0][0]&#xff0c;目标块是最右下方的块&#xff0c;即迷宫[N-1][N-1]。老鼠从源头开始&#xff0c;必须…