C++模板基础知识

文章目录

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

虽然用了多年的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.mzph.cn/news/732879.shtml

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

相关文章

linux 日志轮转

前言: 在Linux系统中&#xff0c;日志轮转是一种重要的管理机制&#xff0c;它可以帮助管理日志文件的大小、数量以及保持系统的性能稳定。通过日志轮转&#xff0c;可以定期对日志文件进行归档、压缩或清理&#xff0c;确保系统的日志记录不会无限增长而占用过多的磁盘空间…

动态SLAM论文阅读笔记

近期阅读了许多动态SLAM相关的论文&#xff0c;它们基本都是基于ORB-SLAM算法&#xff0c;下面简单记录一下它们的主要特点&#xff1a; 1.DynaSLAM 采用CNN网络进行分割多视图几何辅助的方式来判断动态点&#xff0c;并进行了背景修复工作。 2.Detect-SLAM 实时性问题&…

TQTT X310 软件无线电设备的FLASH固件更新方法--WIN和UBUNTU环境

TQTT X310 除了PCIE口全部兼容USRP 官方的X310&#xff0c;并配备两块UBX160射频子板以及GPSDO。TQTT X310可以直接使用官方的固件&#xff0c;但是不支持官方的固件升级命令。这篇BLOG提供烧写刷新FLASH的方法。 这里分别给出WIN下和UBUNTU下升级的软件和方法 WIN环境下烧写…

Rust 语言的 async 关键字

一、Rust 的 async 关键字 Rust 语言的 async 关键字&#xff0c;它是 Rust 语言异步编程模型的核心组成部分。async 关键字用于标记一个函数或方法为异步的&#xff0c;这意味着该函数或方法内部将使用 await 关键字来等待异步操作&#xff08;如 IO 操作、网络请求等&#x…

Java代码审计安全篇-常见Java SQL注入

前言&#xff1a; 堕落了三个月&#xff0c;现在因为被找实习而困扰&#xff0c;着实自己能力不足&#xff0c;从今天开始 每天沉淀一点点 &#xff0c;准备秋招 加油 注意&#xff1a; 本文章参考qax的网络安全java代码审计&#xff0c;记录自己的学习过程&#xff0c;还希望…

R语言系列1——R语言基础:入门篇

目录 写在开头&#xff1a;1. R语言的基本语法1.1 变量与数据类型1.2 基本操作符与表达式 2. 数据结构简介2.1 向量(Vector)2.2 矩阵(Matrix)2.3 数组(Array)2.4 数据框(Data Frame)2.5 列表(List) 3. 基础函数与包的使用3.1 常用内置函数3.2 安装与加载R包3.2.1 安装R包3.2.2 …

rust的 || 是什么,怎么使用?

在Rust中&#xff0c;|| 是闭包的语法。闭包是一种可以捕获作用域中变量的匿名函数。|| 用来定义一个没有参数的闭包。 你可以使用 || 来创建一个没有参数的闭包&#xff0c;例如&#xff1a; let my_closure || {println!("This is a closure with no parameters.&quo…

使用Git将代码上传至代码托管平台GitCode

使用像GitLbi、GitHub、Gitee等代码托管平台用于版本控制非常滴方便&#xff0c;能够跟踪代码的变化和历史记录&#xff0c;方便管理和回滚&#xff0c;还允许多个开发者同时在一个项目上进行开发和协作&#xff0c;提高团队协作效率。 这些平台的代码托管和上传方式都大同小异…

Ainx的消息封装

&#x1f4d5;作者简介&#xff1a; 过去日记&#xff0c;致力于Java、GoLang,Rust等多种编程语言&#xff0c;热爱技术&#xff0c;喜欢游戏的博主。 &#x1f4d7;本文收录于Ainx系列&#xff0c;大家有兴趣的可以看一看 &#x1f4d8;相关专栏Rust初阶教程、go语言基础系列…

186基于matlab的信号盲源分离算法

基于matlab的信号盲源分离算法&#xff0c;包括变步长盲源分离&#xff08;EASI&#xff09;,RLS(自然梯度和普通梯度)&#xff0c;并将三种方法分离结果进行对比。程序已调通&#xff0c;可直接运行。 186 信号盲源分离算法 变步长盲源分离 (xiaohongshu.com)

智能革新:2024年AI辅助研发的挑战、机遇与未来展望

引言 在进入2024年的门槛时&#xff0c;我们站在了一个科技飞速发展的新纪元&#xff0c;其中&#xff0c;人工智能&#xff08;AI&#xff09;的持续进步和应用扩展无疑是推动这一变革的强大动力。AI辅助研发&#xff0c;作为将人工智能技术应用于科研和产品开发过程的一种模…

第三百九十一回

文章目录 1. 概念介绍2. 方法与细节2.1 实现方法2.2 具体细节 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何混合选择多个图片和视频文件"相关的内容&#xff0c;本章回中将介绍如何通过相机获取视频文件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. …

CSS中字符串类的教程

在CSS中&#xff0c;我们经常需要对文本进行格式化和样式化。字符串类&#xff08;String Classes&#xff09;是一种在CSS中非常有用的技术&#xff0c;可以帮助我们对文本进行更加灵活和精细的控制。在本教程中&#xff0c;我将介绍如何使用字符串类来实现各种文本效果。 1.…

windows11编译FFmpeg源码完整步骤

1.安装MSYS2 下载并安装MSYS2 安装GCC GCC安装成功 克隆FFmpeg源码 打开MSYS2终端并进入ffmpeg文件夹,然后输入./configure回车开始生成makefile

通过 varForamtter 将Class 转换为 mermaid 快速的查看类结构

通过 varForamtter 快速的查看类结构 开源技术栏 varFormatter 库不仅仅可以用于 类到json xml 的转换 还可以转换为 mermaid 图 今日有趣的技术小分享&#xff0c;类 结构&#xff0c;是在 编程 中很重要的&#xff0c;直观的查看结构 将会有利于我们了解类中的各个属性。 目…

「蓝桥·算法双周赛」第七场分级赛——小白入门赛

题目列表 说明 好久没打蓝桥杯的比赛&#xff0c;回来试试水&#xff0c;就开了第1、2、3一共三个题&#xff0c;第4题可惜了。1.thanks,mom【算法赛】 思路&#xff1a; 没什么好说的&#xff0c;但是当时比赛刚开始服务器有问题&#xff0c;基本提交的全WA了。#include <…

线程有几种状态,状态之间的流转是怎样的?

Java中线程的状态分为6种&#xff1a; 1.初始(NEW)&#xff1a;新创建了一个线程对象&#xff0c;但还没有调用start()方法。 2.运行(RUNNABLE)&#xff1a;Java线程中将就绪&#xff08;READY&#xff09;和运行中&#xff08;RUNNING&#xff09;两种状态笼统的称为“运行”…

数据库基础——mysql知识体系(掌握mysql,看完这篇文章就够了)

1.关系型数据库 关系型数据库是一种基于关系模型的数据库系统&#xff0c;将数据组织成表格的形式&#xff0c;表格由行和列组成&#xff0c;每行代表一个记录&#xff0c;每列代表一个属性。它使用结构化查询语言SQL进行数据管理和操作。 特点&#xff1a;1.数据的组织&…

【Azure 架构师学习笔记】- Azure Private Endpoint

本文属于【Azure 架构师学习笔记】系列。 前言 公有云的其中一个特点是默认允许公网访问&#xff0c; 这就对企业环境带来风险&#xff0c;也是很多年前企业对公有云抵触的其中一个原因&#xff0c;现在这类问题已经很少&#xff0c;因为有了很多技术来确保云上的资源被安全地…

HTML5:七天学会基础动画网页10

继续介绍3D转换: 3D转换:rotate3d 方法与说明 rrotateX(angle)otate3d(x,y,z,angle[角度]) 3D转换&#xff0c;正常取值0/1&#xff0c;0代表当前轴线不进行旋转&#xff0c;1反之&#xff0c;例:rotate3d(1,1,1,30deg)&#xff0c;代表三个轴线都要旋转30度 rotate3d(0…