首页 > 编程语言 >C++ 模板的笔记2

C++ 模板的笔记2

时间:2024-02-20 17:13:14浏览次数:28  
标签:std 函数 list 笔记 参数 C++ print 模板

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;

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

标签:std,函数,list,笔记,参数,C++,print,模板
From: https://www.cnblogs.com/AndreaDO/p/18019389

相关文章

  • 解决方案 | 笔记本电脑能连上WIFI,但是无Internet显示地球图标,怎么回事?(win10)
    一、背景任务栏托盘区显示地球图标,但是实际上可以上网。   疑难诊断一般是这种情况: 二、可能的有效解决方案 0方案0:使用360断网急救箱傻瓜式修复个人制作|360断网急救箱新版-2.0版单文件绿色版分享(解决99%的电脑无法上网问题)https://www.cnblogs.com/issacne......
  • C++ 以指针(*)作为参数和以指针引用(*&)作为参数的区别
    首先说结论,传入指针只能更改指针所指向的那一块内存的数据,传入指针引用既能修改指针本身的地址也能修改指针所指向的内存。假设现在有这样一个功能:传入一个数组指针,并将另一个数组的地址赋值给被传入的指针,以完成数据更新功能。定义两个函数,分别以指针和指针引用为参数://数组......
  • 新版VSC++安装QuantLib量化工具包安装及其使用
    1.下载安装boosthttps://boostorg.jfrog.io/artifactory/main/release/建议安装与当前电脑VS版本年份差不多的boost自行设置安装路径2.去Github下载开源代码QuantLibhttps://github.com/lballabio/QuantLib/releases下载解压3.安装VS安装C++window桌面开发环境!4.打......
  • [学习笔记]哈希与哈希表
    1.定义我们定义一个把字符串映射到整数的函数\(f\),这个\(f\)称为是Hash函数。我们希望这个函数\(f\)可以方便地帮我们判断两个字符串是否相等。词汇“映射”映射意为将一个集合中的任意元素和另一个集合中的元素一一对应。(请大佬指正)2.思想Hash的核心思想在于,将输入......
  • C++函数用法
    1.getline函数的用法函数声明boolgetline(istream&in,string&s)功能说明从输入流读入一行到变量strings,即使是空格也可以读入。直到出现以下情况为止:读入了文件结束标志读到一个新行(有重载函数可以指定行分隔符,默认是"\n".)达到字符串的最大长度如果getline没有读......
  • Go语言精进之路读书笔记第30条——使用接口提高代码的可测试性
    Go语言有一个惯例是让单元测试代码时刻伴随着你编写的Go代码。单元测试是自包含和自运行的,运行时一般不会依赖外部资源(如外部数据库、外部邮件服务器等),并具备跨环境的可重复性(既可在开发人员的本地运行,也可以在持续集成的环境中运行)。30.1实现一个附加免责声明的电子邮件发送函......
  • 做题笔记 III
    \(1\sim100\)的题目在做题笔记II。\(\texttt{Le0**an}\):我写了四篇做题笔记、一篇生成函数详解和一篇模拟赛复盘了!\(\texttt{xl****13}\):我写了零篇做题笔记了!!!111\(101\sim125\)\(\color{blue}(101)\)ARC172ELast9Digits难度\(^*2400\)。数论抽象题。有一个结......
  • XSimGCL论文阅读笔记
    Abstract我们提出了一种非常简单的图对比学习方法作为推荐,该方法放弃了无效的图增强,而是使用一种简单而有效的基于噪声的嵌入增强来生成CL的视图Introduction这里介绍以下SimGCL的缺点:除了推荐任务的正向/反向传递外,每个小批内的对比任务还需要两个额外的正向和反向传递,也就是......
  • 用C++实现string类
    今天用C++实现了一个string类,包括构造函数、拷贝构造、赋值构造、流输出、移动构造、重载+号,发现很多细节都没有考虑到,细节都在注释中,贴在这里作为备忘吧。 1#include<iostream>2#include<cstring>34usingnamespacestd;567classMyString{......
  • C++(2)Big-Endian VS Littler-Endian
    1、概念大端:高字节存放在低地址,低字节存放在高地址。小端:低字节存放在低地址,高字节存放在高地址。简称“低低小”2、如何判别大端小端intIsSmallEnd2(){ inti=0x11223344; if(*(char*)(&i)==0x44) { return1; } else return0;}......