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. 【k哥爬虫普法】爬取数据是否一定构成不正当竞争?

    我国目前并未出台专门针对网络爬虫技术的法律规范,但在司法实践中,相关判决已屡见不鲜,K 哥特设了"K哥爬虫普法"专栏,本栏目通过对真实案例的分析,旨在提高广大爬虫工程师的法律意识, ...

  2. OCR文字检测与识别系统:融合文字检测、文字识别和方向分类器的综合解决方案

    1. OCR文字检测与识别系统:融合文字检测.文字识别和方向分类器的综合解决方案 前两章主要介绍了DBNet文字检测算法以及CRNN文字识别算法.然而对于我们实际场景中的一张图像,想要单独基于文字检测 ...

  3. C/C++ 通过CRC32实现反破解

    我们可以通过使用CRC32算法计算出程序的CRC字节,并将其写入到PE文件的空缺位置,这样当程序再次运行时,来检测这个标志,是否与计算出来的标志一致,来决定是否运行程序,一旦程序被打补丁,其crc32 ...

  4. PHP header的几种用法

    PHP header的几种用法 定义:header() 函数向客户端发送原始的 HTTP 报头. 1. 跳转页面 header('Location:'.$url); //Location和" ...

  5. 【Unity3D】UGUI概述

    1 UGUI 与 GUI 区别 ​ GUI控件 在编译时不能可视化,并且界面不太美观,在实际应用中使用的较少.UGUI 在编译时可视化,界面美观,实际应用较广泛. 2 Canvas 渲染模式(Rend ...

  6. 利用javax.validation实现对bean属性校验

    1.使用场景介绍 controller层对传入的bean的属性进行非空.属性长度等合法性的校验. 传统的方式是我们要自己写if-else-去判断,比较麻烦. 2.实现效果 3.代码介绍 代码结构: p ...

  7. Java I/O 教程(十 一) BufferedWriter和BufferedReader

    Java BufferedWriter 类 Java BufferedWriter class 继承了Writer类,为Writer实例提供缓冲. 提升了写字符和字符串性能. 类定义: public ...

  8. duilib 入坑

    记录 duilib 开发遇到的问题 当前最新的 duilib 版本更新是在  2019-4-28-2,从 vcpkg 查询得知 我的机器是 windows 10,vs2019 我是从 duilib 库 ...

  9. win32 - 控制台聊天

    仅适用于同一台电脑的两个进程聊天,对于不同电脑之前的聊天需要依靠tcp/ip协议. 两个进程是通过发送WM_COPYDATA 消息来传输字节的. 代码如下: Server.cpp #include & ...

  10. win32 - 创建子线程中的窗口

    跟创建普通的win32窗口一样,线程中的窗口也需要注册和窗口处理过程 // Test_WM_CLOSE.cpp : Defines the entry point for the applicatio ...