突破编程_C++_C++14新特性(变量模板)

1 变量模板在 C++14 中的引入与扩展

在 C++14 中,变量模板的引入与扩展为编程带来了许多便利,特别是在泛型编程方面。这一特性允许我们直接定义模板变量,而不需要将其包装在模板类或模板函数中,从而使得代码更加直观和简洁。

首先,我们来详细了解一下 C++14 之前模板的使用限制。在 C++14 之前,虽然模板已经可以用于定义类型和函数,但对于变量,我们只能在模板类或模板函数中定义它们。这导致在某些情况下,代码可能会变得冗余和复杂。例如,如果我们想为每种类型定义一个常量,可能需要定义一个模板类,并在其中定义这个常量。这样的做法不仅增加了代码的复杂性,也使得使用这个常量变得不那么直观。

比如,假设想要在 C++11 中为每种类型定义一个模板常量 PI:

// 假设我们想要在C++11中为每种类型定义一个模板常量PI  // 定义一个模板类来包含常量PI  
template<typename T>  
struct PiValue {  static constexpr T value = T(3.14159265358979323846);  
};  // 使用这个模板类来获取特定类型的PI值  
int main() {  double piDouble = PiValue<double>::value;  float piFloat = PiValue<float>::value;  // ...  return 0;  
}

上面的代码定义了一个模板类 PiValue,它包含一个静态常量成员 value,这个常量成员的类型由模板参数 T 决定。然后,在 main 函数中,通过 PiValue<double>::value 和 PiValue<float>::value 来获取 double 和 float 类型的 PI 值。这种方法虽然可行,但确实增加了代码的复杂性,并且每次想要使用 PI 值时都需要通过类来访问,不够直观。

然而,C++14 引入了变量模板,这一特性极大地改变了这种状况。变量模板允许我们直接定义模板变量,使得我们可以更加直观和简洁地定义和使用模板变量。这种直接的定义方式不仅提高了代码的可读性,也降低了代码的复杂性。

针对上面的问题,可以更简洁地实现同样的功能:

// C++14 引入变量模板后,我们可以直接定义PI变量模板  
template<typename T>  
constexpr T pi = T(3.14159265358979323846);  // 使用变量模板PI,代码更简洁直观  
int main() {  double piDouble = pi<double>;  float piFloat = pi<float>; return 0;  
}

在上面 C++14 的示例中,直接定义了一个名为 pi 的变量模板,它可以为任何类型生成一个 PI 常量。在 main 函数中,可以直接通过 pi<double> 和 pi< 来获取对应类型的 PI 值,或者使用类型推导让编译器自动推断 pi 的类型。这种方法不仅减少了代码的复杂性,也使得使用常量变得更加直观。

除了上面的优势,C++14 对变量模板的扩展也使得其应用更加广泛。通过变量模板,我们可以定义一系列变量或静态数据成员,使得模板的返回值进一步扩大。这使得变量模板在编写泛型代码时,能够提供更加灵活和强大的功能。

在实际使用中,变量模板的实例化也非常方便。例如,我们可以为不同类型的变量模板提供不同的实例化,每个实例化都有自己独立的地址。这种特性使得变量模板在处理不同类型的数据时,能够保持高度的灵活性和独立性。

2 变量模板的语法与声明

(1)语法与声明

变量模板的声明使用 template 关键字,后面紧跟一个模板参数列表(可以是类型参数或值参数),然后是变量的声明。下面是一个简单的例子:

template<typename T>  
constexpr T pi = T(3.14159265358979323846);

在这个例子中,pi 是一个变量模板,它的类型由模板参数 T 决定。constexpr 表示这个变量是一个常量表达式,可以在编译时求值。

(2)实例化

double radius = 1.0;
double circle_area = pi<double> * radius * radius; // 指定类型  

在上面的代码中,pi<double> 显式地指定了 pi 变量的类型为 double。

(3)默认值与多个模板参数

变量模板也可以有默认模板参数,这使得使用更加灵活:

template<typename T = double>  
constexpr T pi = T(3.14159265358979323846);double circle_area = pi<> * radius * radius; // 没有明确指定类型  

在这个版本中,如果没有明确指定T的类型,它将默认为double。

(4)此外,变量模板也可以有多个模板参数:

template<typename T, int N>  
constexpr T factorial = N * factorial<T, N - 1>;  template<typename T>  
constexpr T factorial<T, 0> = 1; // 特化以结束递归

在这个例子中,factorial 是一个递归定义的变量模板,用于计算阶乘。它有两个模板参数:类型 T 和整数 N。递归的基准情况是当 N 为 0 时,factorial 的值为 1。

(5)注意事项

  • 变量模板必须在所有使用它的翻译单元中可见,否则链接器可能会报错。
  • 变量模板的定义必须出现在所有使用它的地方之前,或者通过包含头文件的方式确保其可见性。
  • 由于变量模板在编译时实例化,因此如果模板参数是复杂的类型,可能会导致编译时间增加。

3 变量模板的使用示例

3.1 基础示例

(1)定义和使用简单的变量模板

#include <iostream>  // 定义一个变量模板  
template<typename T = double>
constexpr T pi = T(3.14159265358979323846);int main() {// 使用默认类型double circleArea = pi<> * 5.0 * 5.0;std::cout << "Circle area: " << circleArea << std::endl;// 也可以显式指定类型  float piFloat = pi<float>;std::cout << "Pi as float: " << piFloat << std::endl;return 0;
}

上面代码的输出为:

Circle area: 78.5398
Pi as float: 3.14159

这个例子定义了一个变量模板 pi,在 circleArea 的计算中,编译器使用默认类型 double,而在 piFloat 的定义中,显式地指定了 pi 的类型为 float。

(2)使用变量模板定义类型无关的常量数组

#include <iostream>  
#include <array>  // 定义一个变量模板,用于创建固定大小的数组  
template<typename T, std::size_t N>
constexpr std::array<T, N> zeros = {};int main() {// 使用变量模板创建整数数组  auto intArray = zeros<int, 5>;for (int value : intArray) {std::cout << value << ' ';}std::cout << std::endl;// 使用变量模板创建浮点数数组  auto floatArray = zeros<float, 3>;for (float value : floatArray) {std::cout << value << ' ';}std::cout << std::endl;return 0;
}

上面代码的输出为:

0 0 0 0 0
0 0 0

这个例子定义了一个变量模板 zeros,它用于创建包含指定类型和大小的零值数组。在 main 函数中,分别用它创建了整数数组和浮点数数组。

(3)使用变量模板和模板元编程创建类型相关的常量

#include <iostream>  
#include <type_traits>  // 定义一个变量模板,用于创建基于类型大小的常量  
template<typename T>
constexpr std::size_t typeSize = sizeof(T);int main() {// 输出int类型的大小  std::cout << "Size of int: " << typeSize<int> << std::endl;// 输出double类型的大小  std::cout << "Size of double: " << typeSize<double> << std::endl;// 使用条件编译检查类型是否为指针  if (std::is_pointer<decltype(typeSize<int*>)>::value) {std::cout << "typeSize<int*> is a pointer type." << std::endl;}else {std::cout << "typeSize<int*> is not a pointer type." << std::endl;}return 0;
}

上面代码的输出为:

Size of int: 4
Size of double: 8
typeSize<int*> is not a pointer type.

这个例子定义了一个变量模板 typeSize,它返回给定类型 T 的大小(以字节为单位)。然后在 main 函数中用它来获取 int 和 double 类型的大小,并使用 std::is_pointer 来检查 typeSize<int*> 是否是指针类型。

3.2 在函数中使用变量模板

在函数中使用 C++14 的变量模板时,可以像使用普通变量一样使用它们。可以使用默认类型,或者也可以显式地指定类型。下面是一个示例,展示了如何在函数内部使用变量模板:

#include <iostream>  
#include <vector>  // 定义一个变量模板  
template<typename T>  
constexpr T pi = T(3.14159265358979323846);  // 一个函数,它接受一个半径并使用pi变量模板来计算圆的面积  
template<typename T>  
T calculateCircleArea(T radius) {  return pi<T> * radius * radius;  
}  int main() {  // 使用double类型的半径调用函数  double areaDouble = calculateCircleArea(5.0);  std::cout << "Circle area with double: " << areaDouble << std::endl;  // 使用float类型的半径调用函数  float areaFloat = calculateCircleArea(5.0f);  std::cout << "Circle area with float: " << areaFloat << std::endl;  // 使用显式推导的pi类型  auto areaAuto = calculateCircleArea(5); // 这里的5是int类型,所以pi的类型也会被推导为int  std::cout << "Circle area with int: " << static_cast<double>(areaAuto) << std::endl; // 需要显式转换为double来打印,因为int乘以int得到int  return 0;  
}

上面代码的输出为:

Circle area with double: 78.5398
Circle area with float: 78.5398
Circle area with int: 75

上面的代码定义了一个函数 calculateCircleArea,它接受一个模板参数 T,表示半径的类型。在函数内部,使用 pi<T>来计算圆的面积。由于 pi 是一个变量模板,它会根据传入的半径类型 T 进行编译。

在 main 函数中,分别使用 double、float 和 int 类型的半径调用 calculateCircleArea 函数,并打印出计算得到的面积。注意,当使用 int 类型的半径时,由于整数乘法会丢失小数部分,因此打印结果前需要将其转换为 double 类型。

此外,还可以在函数内部直接使用变量模板,而不需要通过模板参数传递类型。例如:

#include <iostream>  // 变量模板定义  
template<typename T>  
constexpr T pi = T(3.14159265358979323846);  // 一个非模板函数,它直接使用pi变量模板  
void printCircleArea(double radius) {  double area = pi<double> * radius * radius;  std::cout << "Circle area: " << area << std::endl;  
}  int main() {  printCircleArea(5.0); // 输出使用double类型的pi计算得到的面积  return 0;  
}

上面代码的输出为:

Circle area: 78.5398

在这个例子中,printCircleArea 是一个非模板函数,它直接在函数内部使用 pi<double> 来计算面积。由于显式指定了 pi 的类型为 double,因此不需要通过模板参数来传递类型信息。这样可以使代码更加简洁和直观。

3.3 变量模板与静态数据成员的结合使用

在 C++14 中,变量模板可以与类的静态数据成员结合使用,从而允许我们定义与类类型相关的全局常量或变量。通过将变量模板与静态成员结合,我们可以创建与类类型紧密相关的全局状态或配置,同时保持类型安全和代码的灵活性。

下面是一个示例,展示了如何将变量模板与类的静态数据成员结合使用:

#include <iostream>  // 定义一个变量模板,它使用类的静态成员作为类型参数  
template<typename T>
constexpr auto MyVarTemplate = T::Value;// 定义一个类模板,它包含一个静态数据成员  
template<typename T>
class MyClass {
public:// 静态数据成员,它的值将在类定义时确定  static constexpr T Value = T(42);
};// 使用MyClass的特化来定义不同的值  
template<>
class MyClass<double> {
public:static constexpr double Value = 3.14159;
};int main() {// 使用变量模板与MyClass<int>的静态成员结合  std::cout << "MyVarTemplate for MyClass<int>: " << MyVarTemplate<MyClass<int>> << std::endl;// 使用变量模板与MyClass<double>的特化静态成员结合  std::cout << "MyVarTemplate for MyClass<double>: " << MyVarTemplate<MyClass<double>> << std::endl;return 0;
}

上面代码的输出为:

MyVarTemplate for MyClass<int>: 42
MyVarTemplate for MyClass<double>: 3.14159

这个例子定义了一个变量模板 MyVarTemplate,它接受一个类型参数 T,并引用了 T::Value 这个静态成员。然后,定义了一个类模板 MyClass,它有一个静态数据成员 Value,其值在类定义时确定。对于 MyClass<double>,这里提供了一个特化版本,其 Value 成员具有不同的值。

在 main 函数中,通过将 MyClass<int> 和 MyClass<double> 作为类型参数传递给 MyVarTemplate,来访问这些静态数据成员的值。由于 MyVarTemplate 是一个变量模板,它会根据提供的类型自动推导并引用相应的静态成员。

需要注意的是,静态数据成员必须在类定义中初始化,并且它们的值必须在编译时常量表达式中确定。上面的例子使用 constexpr 来确保这些条件得到满足。

通过将变量模板与类的静态数据成员结合使用,可以创建灵活且类型安全的全局配置或状态,这些配置或状态与特定的类类型紧密相关。这种方法允许在不增加代码复杂性的情况下,为不同的类类型定义不同的常量值或行为。

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

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

相关文章

vue nextTick的简化版

以下内容来源于通义千问 的回答 在 Vue.js 中&#xff0c;$nextTick 的核心功能是在下次 DOM 更新循环结束之后执行延迟回调。这是因为 Vue 使用异步更新队列来优化渲染性能&#xff0c;当数据发生变化时&#xff0c;并不会立即更新 DOM&#xff0c;而是在同一事件循环结束时批…

2024年 前端JavaScript 进阶 第2天 笔记

2.1-内容和创建对象方式 2.2-164-构造函数 2.3-new实例化执行过程 2.4-实例成员和静态成员 2.5-基本包装类型 2.6-0bject静态方法 2.7-数组reduce累计方法 对象数组 加0 2.7-数组find、every和转换为真 --说明手册文档 MDN Web Docs 2.8-字符串常见方法 2.3 String 1.常见实例…

【微服务框架】微服务简介

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

论文复现2: YOLOv5 DeepSORT视频追踪

YOLOv5 DeepSORT是视频检测跟踪算法,结合了YOLOv5的目标检测和DeepSORT的目标跟踪能力。它具备实时性能、高准确性以及稳定的目标跟踪能力,为行人和车辆等实时监测和分析提供了有效的解决方案。 直接在github找的综合代码,没有做分别训练,因为我要实现的就一个类,所以直接…

2010-2021年各省碳排放测算数据(含原始数据+计算过程+结果)

2010-2021年各省碳排放测算数据&#xff08;含原始数据计算过程结果&#xff09; 1、时间&#xff1a;2010-2021年 2、指标&#xff1a;原煤(万吨)、原煤(万吨CO2)、焦炭(万吨)、焦炭(万吨CO2)、汽油(万吨)、汽油(万吨CO2)、煤油(万吨)、煤油(万吨CO2)、柴油(万吨)、柴油(万吨…

比较转录组分析揭示了116种山茶属(Camellia)植物的深层系统发育和次生代谢物演化-文献精读分享1

Comparative transcriptomic analysis unveils the deep phylogeny and secondary metabolite evolution of 116 Camellia plants &#xff0c;比较转录组分析的一篇文献。 SUMMARY 茶花植物包括200多个种类&#xff0c;具有极大的多样性和巨大的经济、观赏和文化价值。我们对…

Git版本管理使用手册--目录

理解几个重要的概念 1安装Git、TortoiseGit 2为群组成员分配角色 3创建项目仓库 3Git与其他产品继承 4克隆仓库到本地 5使用.ignore文件 8将本地项目提交到空白仓库 9管理者创建开发分支 10开发者从仓库获取开分支&#xff08;检出分支以及分支切换&#xff09; 11开发者提交代…

微信小程序 - 用promise封装wx.request

简介 Promise 对象表示异步操作最终的完成&#xff08;或失败&#xff09;以及其结果值。在javascript中&#xff0c;当发起异步方法调用时候&#xff0c;要想在调用方线程获取异步结果&#xff0c;可以借助promise对象实现。 promise同时支持all,any等api。本文将会对wx.req…

[机器学习]练习切比雪夫距离

切比雪夫距离(Chebyshev distance) 切比雪夫距离是指在几何空间中两点之间的最大差值&#xff0c;或者说是两点在各个坐标轴上差值的最大绝对值。它以数学家彼得切比雪夫&#xff08;Peter Chebyshev&#xff09;的名字命名。 在二维空间中&#xff0c;切比雪夫距离可以表示为…

黄金涨是商品牛市的领先信号

自2022年11月以来&#xff0c;黄金价格持续上涨&#xff0c;目前已经突破历史新高&#xff0c;历史上黄金上涨&#xff0c;大多是商品全面牛市的领先信号。在2008年Q4、2019年也出现过&#xff0c;黄金比其他商品更强&#xff0c;但随后的2009年和2020年均是商品的全面牛市。同…

spring注解@EventListener实现监听原理

文章目录 EventListener使用方式EventListener实现原理1.引入时机2 初始化时机3 作用时机->将加了EventListener注解的方法识别出来&#xff0c;并封装为监听器&#xff0c;加载spring容器中 总结 EventListener使用方式 package com.cyl.listener;import org.springframew…

【EasyExcel】多sheet、追加列

业务-EasyExcel多sheet、追加列 背景 最近接到一个导出Excel的业务&#xff0c;需求就是多sheet&#xff0c;每个sheet导出不同结构&#xff0c;第一个sheet里面能够根据最后一列动态的追加列。原本使用的 pig4cloud 架子&#xff0c;使用 ResponseExcel注解方式组装返回数据…

设计模式深度解析:AI如何影响装饰器模式与组合模式的选择与应用

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 AI如何影响装饰器模式与组合模式的选择与应用 在今天这个快速发展的技术时代&#…

Segger Embedded Studio IDE使用体验——默认的Section和Linker的设置

Segger Embedded Studio IDE使用体验之一——默认的Section和Linker的设置 一、简介二、操作2.1 编译后代码分析2.1.1 符号浏览器2.1.2 读取elf文件和map文件 2.2 调试2.2.1 查看变量2.2.2 设置供电 2.3 运行环境设置2.3.1 编译器2.3.2 汇编器2.3.3 包含其他文件2.3.4 .bss和.d…

Java SE入门及基础(45)

目录 嵌套类&#xff08;上&#xff09; 4. 内部类 内部类对象创建语法 示例 5. 局部内部类 示例 6. 匿名内部类 示例 Java SE文章参考:Java SE入门及基础知识合集-CSDN博客 嵌套类&#xff08;上&#xff09; 4. 内部类 As with instance methods and variables, an…

【Java】【设计模式】动态代理

因为需要看spring源代码&#xff0c;看到代理这个词看不懂&#xff0c;后面顺着线索发现需要学习一下设计模式的动态代理&#xff0c;然后疯狂补课&#xff0c;结果发现&#xff0c;都不知道在说什么例子给的也比较抽象&#xff0c;好在自己付出快4小时还是有一些微弱的感悟的。…

Spring Cloud: openFegin使用

文章目录 一、OpenFeign简介二、Springboot集成OpenFeign1、引入依赖2、EnableFeignClients注解&#xff08;1&#xff09;应用&#xff08;2&#xff09;属性解析 3、 FeignClient&#xff08;1&#xff09;应用&#xff08;2&#xff09;属性解析&#xff08;3&#xff09;向…

基于百度地图实现Android定位功能实现(一)

Android集成百度地图 文章目录 Android集成百度地图前言准备工作创建工程申请密钥 在项目中集成BaiduMap SDK创建地图 前言 本案例使用百度地图实现在Android中集成地图&#xff0c;并且实现了普通地图/卫星地图&#xff0c;以及路况图和热状图功能&#xff1b; 参考技术文档&…

Android SQLite的使用

前言 本文用于介绍SQLite&#xff0c;SQLite是Android内置的数据库&#xff0c;是一款轻量级的关系型数据库。它具有运算速度快、占用资源少等优点。支持SQL语法同时遵循数据库的ACID事务。 创建数据库 Android为我们提供了一个SQLiteOpenHelper帮助类&#xff0c;我们可以在…

MTU/TCPMSS/VLAN/ACCESS/TRUNK/HYBRID

MTU RFC标准定义以太网的默认MTU值为1500 最小64字节是为了保证最极端的冲突能被检测到&#xff0c;64字节是能被检测到的最小值&#xff1b;最大不超过1518字节是为了防止过长的帧传输时间过长而占用共享链路太长时间导致其他业务阻塞。所以规定以太网帧大小为64~1518字节&am…