C++模板的笔记2

关于可变参函数模板借鉴了一部分笔记,感谢大佬

类模板中的嵌套

类模板可以嵌套其他类模板,就像普通类可以嵌套其他普通类一样。嵌套的类模板可以访问外部类模板的成员,包括私有成员。

示例:

#include <iostream>
using namespace std; template <typename T>
class Outer {
public:
template <typename U>
class Inner
{
public:
void print()
{
std::cout << Outer::value << std::endl;
}
private:
T value;
};
private :
static int value;
}; template <class T>
int Outer<T>::value = 10;
int main()
{
Outer<int>::Inner<double> inner;
inner.print();
return 0;
}

类模板中的友元

类模板可以声明友元函数或类模板,就像普通类可以声明友元函数或类一样。友元函数或类模板可以访问类模板的私有成员。

注意非本类的友元函数必须定义在本类中,在类外编译器会找不到。

示例:

#include <iostream>
using namespace std; template <typename T>
class MyClass {
public:
// 友元函数
friend void print(const MyClass& obj)
{
// 访问类模板的私有成员
std::cout << obj.data << std::endl;
}
MyClass(T d):data(d){}; template <typename U>
friend class MyFriend; private:
T data;
}; // 友元类模板
template <typename T>
class MyFriend {
public:
void print(const MyClass<T>& obj) {
// 访问类模板的私有成员
std::cout << obj.data << std::endl;
}
};
int main()
{
MyClass<int> mc(10);
MyFriend<int> mf;
print(mc); //10
mf.print(mc); //10
return 0;
}

函数模板作为模板友元

函数模板可以作为类模板的友元,就像普通函数可以作为普通类的友元一样。

示例:

#include <iostream>
using namespace std; template <typename T>
class MyClass {
public:
// 将函数模板友元声明放在类模板的内部
template <typename U>
friend void p(const MyClass<T>& obj);
MyClass(T d):data(d)
{}
void print(){
std::cout<<data<<std::endl;
}
private:
T data;
}; template <typename U>
void p()
{
MyClass<int> obj(10);
obj.print();
} int main() {
// 调用函数模板友元
p<int>(); //10 return 0;
}
  • 类模板可以嵌套其他类模板,嵌套的类模板可以访问外部类模板的成员,包括私有成员。
  • 类模板可以声明友元函数或类模板,友元函数或类模板可以访问类模板的私有成员。
  • 函数模板可以作为类模板的友元。

可变参函数模板

可变参函数模板是C++11标准中新增的功能。在C++11之前,C++中没有可变参函数模板,只能使用宏或其他变通方法来实现类似的功能。

template <class... T>
void f(T... args);

上面的可变模版参数的定义当中,省略号的作用有两个:

1.声明一个参数包T... args,这个参数包中可以包含0到任意个模板参数;

2.在模板定义的右边,可以将参数包展开成一个一个独立的参数。

可变参函数模板允许函数接受数量可变的参数。这可以通过使用 ... 语法来实现。

template <class... T>
void f(T... args)
{
cout << sizeof...(args) << endl;
}
f();
f(1);
f(2,10);
/*
输出
0
1
2
*/

如果我们需要将参数包中的每个参数打印出来的话就需要通过一些方法了。展开可变模版参数函数的方法一般有两种:一种是通过递归函数来展开参数包,另外一种是通过逗号表达式来展开参数包。下面来看看如何用这两种方法来展开参数包。

递归函数来展开参数包

//可变参函数模板
void print();
template <class T,class ...Args>
void print(T head,Args... rest)
{
cout << "parameter "<<head<< endl;
print(rest...);
}
// 递归终止函数
void print()
{
cout<<"empty"<<endl;
}
int main() {
print(1,2,3,4);
return 0;
} /*
parameter 1
parameter 2
parameter 3
parameter 4
empty
*/

递归调用的过程是这样的:

print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
print();

上面的终止递归函数可以写这样

template <class T>
void print(T t)
{
cout<<t<<endl;
}
/*
输出结果
parameter 1
parameter 2
parameter 3
4
*/

递归调用的过程是这样的:

print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);

我们可以通过递归来实现参数求和

template <class T>
T sum(T); template <class T,class ...Args>
T sum(T head,Args... rest)
{
return head +sum<T>(rest...);
} // 递归终止函数
template <class T>
T sum(T t)
{
return t;
}

通过逗号表达式来展开参数包。

递归函数展开参数包是一种标准做法,也比较好理解,但也有一个缺点,就是必须要一个重载的递归终止函数,即必须要有一个同名的终止函数来终止递归,这样可能会感觉稍有不便。我们采取用逗号表达式来简化代码。

逗号表达式:逗号运算符只返回整个逗号表达式中最右边的操作数的值。

int a=1,b=2,c=6;
int d=0;
d = (a = b, c);
cout<<d<<endl; //返回6

表达式 d = (a = b, c); 的计算过程如下:

  • 首先,逗号运算符从左到右计算其操作数。
  • 第一个操作数 a = b 将 b 的值(即2)赋给 a。
  • 然后,逗号运算符继续计算第二个操作数 c,其值为6。
  • 最后,逗号表达式的结果是整个逗号表达式中最右边的操作数,即 c 的值6。
  • 这个值(6)被赋给 d。
  • 因此,d 的最终值是6,cout << d << endl; 会输出6。

运用逗号表达式把之前的print例子修改这样,终止函数不用再同名

template <class T>
void printarg(T t)
{
cout << t << endl;
} template <class ...Args>
void expand(Args... args)
{
int arr[] = {(printarg(args), 0)...};
} expand(1,2,3,4);
/*
1
2
3
4
*/

我们可以把上面的例子再进一步改进一下,将函数作为参数,就可以支持lambda表达式了,从而可以少写一个递归终止函数了,具体代码如下:

template<class F,class... Args>
void expand(const F&f,Args&&...args)
{
initializer_list<int>{ (f(std::forward<Args>(args) ),0)... } ;
}
int main()
{
expand([](auto i){cout<<i<<endl;},1,2.0,1.8,20,0,111,"test");
return 0;
}
/*
输出
1
2
1.8
20
0
111
test
*/

讲解一下上面的代码,利用了C++11引入的右值引用、完美转发、初始化列表和C++14 lambda函数等特性.

这段代码定义了一个名为 expand 的可变参函数模板,并在 main 函数中使用了一个 lambda 表达式来调用它。我们一步一步来分析这段代码。

    1. 可变参函数模板 expand
template<class F,class... Args>
void expand(const F& f,Args&&...args)
{
std::initializer_list<int>{ (f(std::forward<Args>(args) ),0)... } ;
}
  • 模板参数

F: 一个函数对象或可调用实体的类型。

Args...: 一个可变参数模板,表示可以接收任意数量和类型的参数。

  • 函数参数

const F& f: 一个对函数对象或可调用实体的常量引用。

Args&&...args: 使用右值引用的可变参数列表,这允许我们完美转发参数。

  • std::initializer_list

std::initializer_list 是 C++11 标准引入的一个模板类,它提供了一种方便的方式来初始化和访问一组同类型的对象。这个列表是由花括号 {} 包围的初始化器生成的,可以包含任意数量的元素,这些元素在列表中是常量引用。

主要特点

非常量对象:

尽管 std::initializer_list 的元素是常量引用,但你可以修改这些元素所引用的底层对象的值(如果这些对象是可修改的)。然而,你不能修改 std::initializer_list 本身的大小或内容。

不拥有其元素: std::initializer_list 并不拥有其包含的元素。

这意味着当 std::initializer_list 的生命周期结束时,其引用的底层对象不会被自动销毁。

构造和析构:

std::initializer_list 的构造函数接受一个指针和长度,或者另一个 std::initializer_list,或者一个花括号包围的初始化列表。它没有显式的析构函数,因为析构是自动处理的。

存储未指定:

std::initializer_list 的存储是未指定的,这意味着它可以在自动、临时或静态只读内存中存储。

不是容器:

尽管 std::initializer_list 提供了一种访问元素的方式,但它不是一个容器类。因此,你不应该用它来传递期望长期存储的值。

初始化器列表构造函数:

当一个类的构造函数接受一个 std::initializer_list 作为参数时,这个构造函数被称为“初始化器列表构造函数”。

下面是一个简单的例子,展示了如何使用 std::initializer_list:

#include <iostream>
#include <initializer_list> class MyClass {
public:
MyClass(std::initializer_list<int> list) {
for (const auto& element : list) {
std::cout << element << " ";
}
std::cout << std::endl;
}
}; int main() {
MyClass obj{1, 2, 3, 4, 5}; // 使用初始化器列表构造了一个 MyClass 对象
return 0;
}

解析这段代码 std::initializer_list{ ( f(std::forward(args) ),0)... } ;

  • 1,先使用了initializer_list 来调用一组参数,然后在这里面使用了逗号表达式,例如 f(1), 0,f(2.0), 0,f(1.8), 0 等,每个逗号表达式的结果(即 f 的返回值)都是 int 类型,所以std::initializer_list里面是int类型。
  • 2,在函数里面调用了f函数指针类型,在函数指针里面使用了完美转发来转发右值args
  • 3,由于逗号表达式f(args),0... 因此最后都只会返回0

折叠表达式

分为左折叠和右折叠表达式

左折叠的例子:

template <typename... Args>
constexpr auto sum(Args... args) {
return (args + ...); // 左折叠表达式,从左往右边 计算所有args的和
}

右折叠的例子:

template <typename... Args>
constexpr auto sum(Args... args) {
return (... + args); // 左折叠表达式,从左往右边 计算所有args的和
}

可变模版参数类

可变参数模板类是一个带可变模板参数的模板类,比如C++11中的元祖std::tuple就是一个可变模板类,它的定义如下:

template< class... Types >
class tuple;

这个可变参数模板类可以携带任意类型任意个数的模板参数:

std::tuple<int> tp1 = std::make_tuple(1);
std::tuple<int, double> tp2 = std::make_tuple(1, 2.5);
std::tuple<int, double, string> tp3 = std::make_tuple(1, 2.5, “”);

可变参数模板的模板参数个数可以为0个,所以下面的定义也是也是合法的:

std::tuple<> tp;

可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不同,可变参数模板类的参数包展开需要通过模板特化和继承方式去展开,展开方式比可变参数模板函数要复杂。下面我们来看一下展开可变模版参数类中的参数包的方法。

C++ 模板的笔记2的更多相关文章

  1. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  2. OpenERP QWeb模板标签笔记

    在OpenERP中,通过QWeb来对模板进行渲染后加载到浏览器中,而模板中有许多的标签来定制各种需求变化,在这里记录学习过程中碰到的标签定义,以方便查询. 模板中的标签统一都是以"t-&qu ...

  3. 初步C++类模板学习笔记

    类模板 实现:在上课时间的定义给它的一个或多个参数,这些参数代表了不同的数据类型.                              -->抽象的类. 在调用类模板时, 指定參数, 由编 ...

  4. handlebar JS模板使用笔记

    直接上代码: (定义模板) (编译注入) ***知识点*** //数据必须为Json数据(强调:jsonp数据不行,和json是两种数据,jsonp多了callback回调函数来包裹json数据) 遍 ...

  5. tornada模板学习笔记

    import tornado.web import tornado.httpserver import tornado.ioloop import tornado.options import os. ...

  6. 潭州课堂25班:Ph201805201 django 项目 第五课 静态页面转为模板 (课堂笔记)

    一.分析静态页面   1.静态vs动态 条目 静态页面 动态页面 网站内容 固定不变 经常变动 浏览器加载速度 更快(无需向服务器发起请求) 更慢 改变网站内容 很难(修改或者创建新的html页面) ...

  7. smarty详细使用教程(韩顺平smarty模板技术笔记)

    MVC是一种开发模式,强调数据的输入.处理.显示是强制分离的 Smarty使用教程1.如何配置我们的smarty解压后把libs文件夹放在网站第一级目录下,然后创建两个文件夹templates 存放模 ...

  8. C++模板学习笔记

    一个有趣的东西:实现一个函数print, 输入一个数组, 输出数组的各个维度长度. eg. ], b[][], c[][][]; print(a); //(2, 4) print(b); //(3, ...

  9. 《C++ Primer Plus》14.4 类模板 学习笔记

    14.4.1 定义类模板下面以第10章的Stack类为基础来建立模板.原来的类声明如下:typedef unsigned long Item; class Stack{private:    enum ...

  10. C++Array类模板编写笔记

    C++Array类模板 函数模板和类模板都属于泛型技术,利用函数模板和类模板来创建一个具有通用功能的函数和类,以支持多种不同的形参,从而进一步简化重载函数的函数体设计. 声明方法:template&l ...

随机推荐

  1. 【介绍一个工具】图形化界面查看一个 golang 二进制文件的汇编代码

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 通常,可以通过命令行工具,来查看一个代码文件对应的汇编代码 ...

  2. 源码阅读:VictoriaMetrics中的golang代码优化方法

    全文请移步:https://zhuanlan.zhihu.com/p/469239020 或关注我的公众号: 公众号:一本正经的瞎扯

  3. Mysql 为现有数据生成Guid

    GUID 全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符.GUID主要用于在拥有多个节点.多台计算机的网络或系统中. ...

  4. Unity Editor自定义菜单排序(MenuItem Order)

    扩展Unity的菜单MenuItem MenuItem 属性用于向主菜单和检视面板上下文菜单添加菜单项. 该 MenuItem 属性能够将任何静态函数转变为菜单命令,仅静态函数可使用 MenuItem ...

  5. win10家庭版禁用更新

    前言 2020年初因为疫情在家远程办公,而我老家没有电脑,先后向两位大学生借了两台电脑来办公,发现一个现象:他们的电脑系统都是家庭版,也就是刚买电脑时安装的win10家庭版.也问了其它几位计算机专业的 ...

  6. TienChin 活动管理-添加活动接口

    ActivityController @PreAuthorize("hasPermission('tienchin:activity:create')") @Log(title = ...

  7. Jmeter报错权限不够

    Jmeter报错权限不够 解决办法: chmod +x jmeter

  8. 新来的一个同事,把SpringBoot参数校验玩的那叫一个优雅

    介绍 在开发现代应用程序时,数据验证是确保用户输入的正确性和应用程序数据完整性的关键方面.Spring Boot 提供了强大的数据验证机制,使开发者能够轻松地执行验证操作.本文将深入介绍 Spring ...

  9. 强化学习基础篇[3]:DQN、Actor-Critic详细讲解

    强化学习基础篇[3]:DQN.Actor-Critic详细讲解 1.DQN详解 1.1 DQN网络概述及其创新点 在之前的内容中,我们讲解了Q-learning和Sarsa算法.在这两个算法中,需要用 ...

  10. 环境调试bug【二】无法加载源“<string>”: Source unavailable

    1.无法加载源"<string>": Source unavailable. 网上解决方法: 总结来说就两种: debugStdLib: true 添加到 launch ...