做视频网站的技能,韩国ps教程网站,赣州市城乡建设局网站,wordpress 商家定位更多精彩内容..... #x1f389;❤️播主の主页✨#x1f618; Stark、-CSDN博客 本文所在专栏#xff1a; C系列语法知识_Stark、的博客-CSDN博客 其它专栏#xff1a; 数据结构与算法_Stark、的博客-CSDN博客 C系列项目实战_Stark、的博客-CSDN博客 座右铭#xff1a;梦… 更多精彩内容..... ❤️播主の主页✨ Stark、-CSDN博客 本文所在专栏 C系列语法知识_Stark、的博客-CSDN博客 其它专栏 数据结构与算法_Stark、的博客-CSDN博客 C系列项目实战_Stark、的博客-CSDN博客 座右铭梦想是一盏明灯照亮我们前行的路无论风雨多大我们都要坚持不懈。 泛型的意思就是广泛的类型。泛型编程是C很强大的一个特性。它主要的一个目的是增加代码复用性增加程序的可扩展性。C的泛型编程主要靠模板来实现模板又被分为两类函数模板和类模板。在学习模板前我先提出一个问题请写出一个相加函数。
你可能下意识地就写出来了
int add(int a,int b){return ab;
}
现在我来实验一下
int main(){coutadd(3.0,4.5)endl;//预期7.5//实际输出7return 0;
}
你觉得我这是在挑刺但是事实就是这样客户就需要你写出来一个能做任何类型都能相加的一个函数。你就无奈的去写去改去增加。 为了解决反复更改增加这一问题我们应该使用C为我们提供的模板技术来应用到编程上。这时候我就可以写一个函数
//templateclass T
templatetypename T//用哪个关键字都一样
T add(const T a,const T b)
{return ab;
}
在使用时我们就可以指定类型了
int main()
{coutaddint(1,3)endl;coutadddouble(3.14,6.28)endl;coutaddstring(123,321)endl;return 0;
}
我们只需要写一段函数代码就可以实现之前需要定义多个函数需要干的事是不是很方便。
另外我们前面实现vector时就通篇使用了模板的泛型编程思想。包括我们使用的std::vector都离不开模板的支持。
类属类型参数化又称参数模板。使得程序算法可以从逻辑功能上抽象把被处理的对象数据类型作为参数传递。 模板把函数或类要处理的数据类型参数化表现为参数的多态性称为类属 模板用于表达逻辑结构相同但具体数据元素类型不同的数据对象的通用行为。 一、函数模板
C提供了函数模板(function template)。所谓函数模板实际上是建立一个通用函数其函数类型和形参类型不具体指定用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替不必定义多个函数只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型从而实现了不同函数的功能。
1.函数模板的定义
templatetypename _Tx,typename _Ty, ...... ,typename _Tn
返回值类型 函数名(参数列表){//函数体//return;
} Tips: ①template关键字告诉C编译器我要开始泛型了你不要随便报错 ②typename _Tx,_Ty,....._Tn 为模板的参数列表使用尖括号包围列表不能为空 ③typename只可以被class代替 ④_Tx,_Ty,......, _Tn等表示类型可以像函数的缺省参数一样给出缺省类型 templatetypename Tx,typename Ty //true
templateclass Tx,class Ty //true
templateclass Tx,typename Ty //truetemplateclass Tx,Ty,Tz //false
templatetypename Tx,Ty //falsetemplateclass Tx,class Tyint //true 以上语法就相当于一个修饰符作为函数的修饰使得函数认识修饰中提到的类型。
函数模板定义由模板说明和函数定义组成。模板说明的类属参数必须在函数定义中至少出现一次。函数参数列表中可以使用类属类型参数也可以使用一般类型参数。
2.函数模板的使用(实例化)
通过为函数模板的参数列表赋予具体类型变成模板函数的过程叫做实例化。模板函数包括显式调用与隐式调用。
自动数据类型推导(隐式调用)
int main(){int ia1,ib2;double da10.3,db20.5;//true,函数模板自动推算出Tintadd(ia,ib);//true函数模板自动推算出Tdoubleadd(da,db);//false函数模板没有理解你的T到底是什么意思add(ia,db);//true函数模板自动推算出Tintadd(ia,(int)da);return 0;
}
显式类型调用
//函数模板 是一个模板等待被实例化
templatetypename T
T add(const T a,const T b)
{return ab;
}int main(){int a3;double b10.5;//true,模板已经确定了T为int就会尝试将所有参数类型转化为intaddint(a,b);//模板函数是一个函数由函数模板实例化得到return 0;
}
3.模板函数遇上函数重载
函数模板和普通函数的区别函数模板不允许自动类型转化普通函数能够进行自动类型转换
模板函数和普通函数在一起时的调用规则 1.函数模板可以像普通函数一样被重载 2.C编译器优先考虑普通函数 3.如果函数模板可以产生更好的匹配那么选择模板 4.可以通过空模板实参列表的语法限定编译器只能通过模板匹配 #include iostream
using namespace std;template typename T
void myswap(T a, T b)
{T t;t a;a b;b t;coutmyswap 模板函数doendl;
}void myswap(char a, int b)
{int t;t a;a b;b t;coutmyswap 普通函数doendl;
}void main()
{char cData a;int iData 2;//myswapint(cData, iData); //结论 函数模板不提供隐式的数据类型转换 必须是严格的匹配myswap(cData, iData); //myswap(iData, cData);couthello...endl;system(pause);return ;
}
#include iostream
using namespace std;
int Max(int a, int b)
{coutint Max(int a, int b)endl;return a b ? a : b;
}templatetypename T
T Max(T a, T b)
{coutT Max(T a, T b)endl;return a b ? a : b;
}templatetypename T
T Max(T a, T b, T c)
{coutT Max(T a, T b, T c)endl;return Max(Max(a, b), c);
}void main()
{int a 1;int b 2;coutMax(a, b)endl; //当函数模板和普通函数都符合调用时,优先选择普通函数coutMax(a, b)endl; //若显示使用函数模板,则使用 类型列表coutMax(3.0, 4.0)endl; //如果 函数模板产生更好的匹配 使用函数模板coutMax(5.0, 6.0, 7.0)endl; //重载coutMax(a, 100)endl; //调用普通函数 可以隐式类型转换 system(pause);return ;
}
C编译器模板机制剖析
思考为什么函数模板可以和函数重载放在一起。C编译器是如何提供函数模板机制的
编译器编译原理
①什么是gcc gccGNU C Compiler编译器的作者是Richard Stallman也是GNU项目的奠基者。 什么是gccgcc是GNU Compiler Collection的缩写。最初是作为C语言的编译器GNU C Compiler现在已经支持多种语言了如C、C、Java、Pascal、Ada、COBOL语言等。 gcc支持多种硬件平台甚至对Don Knuth 设计的 MMIX 这类不常见的计算机都提供了完善的
②gcc主要特征 1gcc是一个可移植的编译器支持多种硬件平台 2gcc不仅仅是个本地编译器它还能跨平台交叉编译。 3gcc有多种语言前端用于解析不同的语言。 4gcc是按模块化设计的可以加入新语言和新CPU架构的支持 5gcc是自由软件 ③gcc编译过程 预处理Pre-Processing 编译Compiling 汇编Assembling 链接Linking Gcc *.c –o 1exe (总的编译步骤) Gcc –E 1.c –o 1.i //宏定义 宏展开 Gcc –S 1.i –o 1.s Gcc –c 1.s –o 1.o Gcc 1.o –o 1exe 结论gcc编译工具是一个工具链。。。。 hello程序是一个高级语言程序这种形式容易被人读懂。为了在系统上运行hello.c程序每条语句都必须转化为低级机器指令。然后将这些指令打包成可执行目标文件格式并以二进制形式存储器于磁盘中。
④gcc常用编译选项 选项 作用 -o 产生目标.i、.s、.o、可执行文件等 -c 通知gcc取消链接步骤即编译源码并在最后生成目标文件 -E 只运行C预编译器 -S 告诉编译器产生汇编语言文件后停止编译产生的汇编语言文件扩展名为.s -Wall 使gcc对源文件的代码有问题的地方发出警告 -Idir 将dir目录加入搜索头文件的目录路径 -Ldir 将dir目录加入搜索库的目录路径 -llib 链接lib库 -g 在目标文件中嵌入调试信息以便gdb之类的调试程序调试
练习 gcc -E hello.c -o hello.i预处理 gcc -S hello.i -o hello.s编译 gcc -c hello.s -o hello.o汇编 gcc hello.o -o hello链接 以上四个步骤可合成一个步骤 gcc hello.c -o hello直接编译链接成可执行目标文件 gcc -c hello.c或gcc -c hello.c -o hello.o编译生成可重定位目标文件 建议初学都加这个选项。下面这个例子如果不加-Wall选项编译器不报任何错误但是得到的结果却不是预期的。 #include stdio.h int main(void) { printf(21 is %f, 3); return 0; } Gcc编译多个.c hello_1.h hello_1.c main.c 一次性编译 gcc hello_1.c main.c –o newhello 独立编译 gcc -Wall -c main.c -o main.o gcc -Wall -c hello_1.c -o hello_fn.o gcc -Wall main.o hello_1.o -o newhello
模板函数反汇编观察
命令g -S 7.cpp -o 7.s
.file 7.cpp
.text
.def __ZL6printfPKcz; .scl 3; .type 32; .endef
__ZL6printfPKcz:
LFB264:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
pushl %ebx
subl $36, %esp
.cfi_offset 3, -12
leal 12(%ebp), %eax
movl %eax, -12(%ebp)
movl -12(%ebp), %eax
movl %eax, 4(%esp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call ___mingw_vprintf
movl %eax, %ebx
movl %ebx, %eax
addl $36, %esp
popl %ebx
.cfi_restore 3
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE264:
.lcomm __ZStL8__ioinit,1,1
.def ___main; .scl 2; .type 32; .endef
.section .rdata,dr
LC0:
.ascii a:%d b:%d \12\0
LC1:
.ascii c1:%c c2:%c \12\0
LC2:
.ascii pause\0
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB1023:
.cfi_startproc
.cfi_personality 0,___gxx_personality_v0
.cfi_lsda 0,LLSDA1023
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
call ___main
movl $0, 28(%esp)
movl $10, 24(%esp)
movb $97, 23(%esp)
movb $98, 22(%esp)
leal 24(%esp), %eax
movl %eax, 4(%esp)
leal 28(%esp), %eax
movl %eax, (%esp)
call __Z6myswapIiEvRT_S1_ //66 126
movl 24(%esp), %edx
movl 28(%esp), %eax
movl %edx, 8(%esp)
movl %eax, 4(%esp)
movl $LC0, (%esp)
call __ZL6printfPKcz
leal 22(%esp), %eax
movl %eax, 4(%esp)
leal 23(%esp), %eax
movl %eax, (%esp)
call __Z6myswapIcEvRT_S1_ //77 155
movzbl 22(%esp), %eax
movsbl %al, %edx
movzbl 23(%esp), %eax
movsbl %al, %eax
movl %edx, 8(%esp)
movl %eax, 4(%esp)
movl $LC1, (%esp)
call __ZL6printfPKcz
movl $LC2, (%esp)
LEHB0:
call _system
LEHE0:
movl $0, %eax
jmp L7
L6:
movl %eax, (%esp)
LEHB1:
call __Unwind_Resume
LEHE1:
L7:
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1023:
.def ___gxx_personality_v0; .scl 2; .type 32; .endef
.section .gcc_except_table,w
LLSDA1023:
.byte 0xff
.byte 0xff
.byte 0x1
.uleb128 LLSDACSE1023-LLSDACSB1023
LLSDACSB1023:
.uleb128 LEHB0-LFB1023
.uleb128 LEHE0-LEHB0
.uleb128 L6-LFB1023
.uleb128 0
.uleb128 LEHB1-LFB1023
.uleb128 LEHE1-LEHB1
.uleb128 0
.uleb128 0
LLSDACSE1023:
.text
.section .text$_Z6myswapIiEvRT_S1_,x
.linkonce discard
.globl __Z6myswapIiEvRT_S1_
.def __Z6myswapIiEvRT_S1_; .scl 2; .type 32; .endef
__Z6myswapIiEvRT_S1_: //126
LFB1024:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $16, %esp
movl 8(%ebp), %eax
movl (%eax), %eax
movl %eax, -4(%ebp)
movl 12(%ebp), %eax
movl (%eax), %edx
movl 8(%ebp), %eax
movl %edx, (%eax)
movl 12(%ebp), %eax
movl -4(%ebp), %edx
movl %edx, (%eax)
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1024:
.section .text$_Z6myswapIcEvRT_S1_,x
.linkonce discard
.globl __Z6myswapIcEvRT_S1_
.def __Z6myswapIcEvRT_S1_; .scl 2; .type 32; .endef
__Z6myswapIcEvRT_S1_: //155
LFB1025:
.cfi_startproc
pushl %eb
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $16, %esp
movl 8(%ebp), %eax
movzbl (%eax), %eax
movb %al, -1(%ebp)
movl 12(%ebp), %eax
movzbl (%eax), %edx
movl 8(%ebp), %eax
movb %dl, (%eax)
movl 12(%ebp), %eax
movzbl -1(%ebp), %edx
movb %dl, (%eax)
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1025:
.text
.def ___tcf_0; .scl 3; .type 32; .endef
___tcf_0:
LFB1027:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $8, %esp
movl $__ZStL8__ioinit, %ecx
call __ZNSt8ios_base4InitD1Ev
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1027:
.def __Z41__static_initialization_and_destruction_0ii; .scl 3; .type 32; .endef
__Z41__static_initialization_and_destruction_0ii:
LFB1026:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $24, %esp
cmpl $1, 8(%ebp)
jne L11
cmpl $65535, 12(%ebp)
jne L11
movl $__ZStL8__ioinit, %ecx
call __ZNSt8ios_base4InitC1Ev
movl $___tcf_0, (%esp)
call _atexit
L11:
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1026:
.def __GLOBAL__sub_I_main; .scl 3; .type 32; .endef
__GLOBAL__sub_I_main:
LFB1028:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $24, %esp
movl $65535, 4(%esp)
movl $1, (%esp)
call __Z41__static_initialization_and_destruction_0ii
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE1028:
.section .ctors,w
.align 4
.long __GLOBAL__sub_I_main
.ident GCC: (rev2, Built by MinGW-builds project) 4.8.0
.def ___mingw_vprintf; .scl 2; .type 32; .endef
.def _system; .scl 2; .type 32; .endef
.def __Unwind_Resume; .scl 2; .type 32; .endef
.def __ZNSt8ios_base4InitD1Ev; .scl 2; .type 32; .endef
.def __ZNSt8ios_base4InitC1Ev; .scl 2; .type 32; .endef
.def _atexit; .scl 2; .type 32; .endef
函数模板机制结论
1.编译器并不是把函数模板处理成能够处理任意类的函数
2.编译器从函数模板通过具体类型产生不同的函数
3.编译器会对函数模板进行两次编译
4.在声明的地方对模板代码本身进行编译在调用的地方对参数替换后的代码进行编译。
二、类模板
类模板用于实现类所需数据的类型参数化类模板在表示如数组、表、图等数据结构显得特比重要。这些数据结构不受所包含的元素类型的影响。最成功的案例也就是我们使用的STL的容器。
1.单个类模板语法
//类的类型参数化 抽象的类
//单个类模板
templatetypename T
class A
{
public:A(T t){this-t t;}T getT(){return t;}
protected:
public:T t;
};
void main()
{//模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则Aint a(100); a.getT();printAA(a);return ;
}
2.继承中的类模板语法 结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)Aint
class B : public Aint
{
public:B(int i) : Aint(i){}void printB(){coutA:tendl;}
protected:
private:
};//模板与上继承
//怎么样从基类继承
//若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数
void pintBB(B b)
{b.printB();
}
void printAA(Aint a) //类模板做函数参数
{//a.getT();
}void main()
{Aint a(100); //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 a.getT();printAA(a);B b(10);b.printB();couthello...endl;system(pause);return ;
}
3.类模板语法知识体系梳理
①类模板函数写在类的内部正常写
②类模板函数写在类的外部在一个.cpp中
//构造函数 没有问题
//普通函数 没有问题
//友元函数用友元函数重载
//friend ostream operator T (ostream out, ComplexT c3) ;
//友元函数友元函数不是实现函数重载非
//1需要在类前增加 类的前置声明 函数的前置声明
templatetypename T
class Complex; templatetypename T
ComplexT mySub(ComplexT c1, ComplexT c2);//2类的内部声明 必须写成:
friend ComplexT mySub T (ComplexT c1, ComplexT c2);
//3友元函数实现 必须写成
templatetypename T
ComplexT mySub(ComplexT c1, ComplexT c2)
{ComplexT tmp(c1.a - c2.a, c1.b-c2.b);return tmp;
}
//4友元函数调用 必须写成
Complexint c4 mySubint(c1, c2);
coutc4;
结论友元函数只用来进行 左移 友移操作符重载。
③类模板函数卸载类的外部在不同的.h和.cpp中
也就是类模板函数说明和类模板实现分开写。比如在头文件写模板类的声明定义成员函数只声明未实现在源程序文件中进行函数的实现。
此时如果我们像往常一样包含头文件在编译时的链接阶段会报错。解决方法有两种
1.将.cpp文件一同包含
2.将两个文件写到一个.hpp文件中(.hpp只是一个约定俗成的模板类头文件后缀名)
4.类模板中的static关键字 从类模板实例化的每个模板类有自己的类模板数据成员该模板类的所有对象共享一个static数据成员 和非模板类的static数据成员一样模板类的static数据成员也应该在文件范围定义和初始化 每个模板类有自己的类模板的static数据成员副本 #includebits/stdc.h
using namespace std;const double pi 3.14159;
templateclass T
class Circle {T radius;static int total;//类模板的静态数据成员
public:Circle(T r 0):radius(r){tatal;}void Set_Radius(T r) { radius r; }double Get_Radius() { return radius; }double Get_Girth() { return 2 * pi * radius; }double Get_Area() { return pi * radius * radius; }static int ShowTotal();//类模板的静态成员函数
};
templateclass T
int CircleT::total 0;
templateclass T
int CircleT::ShowTotal() { return total; }
void test241004_01() {Circleint A, B;A.Set_Radius(16);B.Set_Radius(105);cout who\tRadius\tGirth\tArea endl;cout A\t A.Get_Radius() \t A.Get_Girth() \t A.Get_Area() endl;cout B\t B.Get_Radius() \t B.Get_Girth() \t B.Get_Area() endl;cout int Total Circleint::ShowTotal() endl;//cout Total B.ShowTotal() endl;//cout Total A.ShowTotal() endl;
}
void test241004_02() {Circledouble X(6.23), Y(10.5), Z(25.6);cout who\tRadius\tGirth\tArea endl;cout X\t X.Get_Radius() \t X.Get_Girth() \t X.Get_Area() endl;cout Y\t Y.Get_Radius() \t Y.Get_Girth() \t Y.Get_Area() endl;cout Z\t Z.Get_Radius() \t Z.Get_Girth() \t Z.Get_Area() endl;cout int Total Circleint::ShowTotal() endl;cout double Total Circledouble::ShowTotal() endl;
} 我们明显看到Circleint和Circledouble属于两个类他们都有属于自己的static成员total互不干扰。 5.类模板在项目开发中的应用 模板是C类型参数化的多态工具。C提供函数模板和类模板。 模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。 同一个类属参数可以用于多个模板。 类属参数可用于函数的参数类型、返回类型和声明函数中的变量。 模板由编译器根据实际数据类型实例化生成可执行代码。实例化的函数。 模板称为模板函数实例化的类模板称为模板类。 函数模板可以用多种方式重载。 类模板可以在类层次中使用 。 *.模板特化与可变参数模板
模板特化
模板特化分为全特化与偏特化。偏特化又分为部分特化与限制特化
templateclass _Tx,class _Ty
class Data{_Tx m_dx;_Ty m_dy;
public:Data(){coutData_Tx , _Tyendl;}
};//全特化:将类属全部确定
template
class Dataint,char
{int m_dx;char m_dy;
public:Data(){coutDataint,charendl;}
};//偏特化确定类属中部分类型
//部分特化
templateclass T
class DataT,int
{T m_dx;int m_dy;
public:Data(){coutDataT,intendl;}
};
//限制特化
templateclass M,class N
class DataM* , N*
{/*略*/};
templateclass M,class N
class DataM ,N
{/*略*/};
可变参数模板 C可变参数模板Variadic Templates是C11引入的一种功能允许你定义接受可变数量的参数的模板。这种特性非常强大可以用来编写更灵活和通用的代码尤其是在处理函数、类等时。
templatetypename... Args
void func(Args... args) { // 函数体
}
这里Args是一个类型参数包可以接受任意数量的类型。args是一个参数包可以接收任意数量的参数。
递归展开参数包
#include iostream
#include string templatetypename T
void print(const T value) { std::cout value std::endl;
} templatetypename T, typename... Args
void print(const T first, const Args... rest) { std::cout first std::endl; print(rest...); // 递归调用
} int main() { print(1, 2.5, Hello, std::string(World)); // 可以输入多种类型 return 0;
} 在上面的例子中print函数可以接受任意数量和类型的参数并且能够逐个打印它们的值。在 C11 中由于没有逗号表达式的折叠表达式的特性我们只能通过递归方式来处理参数包。
逗号表达式展开参数包
#include iostream // 打印单个值的函数模板
templatetypename T
void print(const T value) { std::cout value std::endl;
} // 使用逗号表达式展开参数包的函数模板
templatetypename... Args
void printAll(const Args... args) { // 使用逗号表达式展开以执行打印 (print(args), ...); // 展开参数包并使用逗号表达式
} int main() { printAll(1, 2.5, Hello, std::string(World)); return 0;
}
在上述代码中printAll 函数接受一个可变数量的参数并使用(print(args), ...)的形式展开这些参数。这是一个**折叠表达式Fold Expression**的例子它是 C17 中引入的新特性。逗号运算符在这里允许我们对 print(args) 的每个调用依次执行从而打印出所有参数的内容。 重要特性 递归解包通过递归模板函数你可以将参数包逐个提取和处理。 类型推断编译器可以自动推导传入参数的类型。 使用标准库功能可以与标准库中的功能如std::tuplestd::index_sequence等结合使用以实现更复杂的功能。 感谢大家