当前位置: 首页 > news >正文

泛型编程--模板【C++提升】(特化、类属、参数包的展开、static、模板机制、重载......你想知道的全都有)

 更多精彩内容.....

🎉❤️播主の主页✨😘

Stark、-CSDN博客

本文所在专栏:

C系列语法知识_Stark、的博客-CSDN博客

其它专栏:

数据结构与算法_Stark、的博客-CSDN博客

C系列项目实战_Stark、的博客-CSDN博客

座右铭:梦想是一盏明灯,照亮我们前行的路,无论风雨多大,我们都要坚持不懈。


泛型的意思就是广泛的类型。泛型编程是C++很强大的一个特性。它主要的一个目的是增加代码复用性,增加程序的可扩展性。C++的泛型编程主要靠模板来实现,模板又被分为两类:函数模板和类模板。在学习模板前,我先提出一个问题:请写出一个相加函数。

你可能下意识地就写出来了:

int add(int a,int b){return a+b;
}

现在我来实验一下:

int main(){cout<<add(3.0,4.5)<<endl;//预期7.5//实际输出7return 0;
}

你觉得我这是在挑刺,但是事实就是这样,客户就需要你写出来一个能做任何类型都能相加的一个函数。你就无奈的去写去改去增加。 为了解决反复更改增加这一问题,我们应该使用C++为我们提供的模板技术来应用到编程上。这时候我就可以写一个函数:

//template<class T>
template<typename T>//用哪个关键字都一样
T& add(const T& a,const T& b)
{return a+b;
}

在使用时我们就可以指定类型了:

int main()
{cout<<add<int>(1,3)<<endl;cout<<add<double>(3.14,6.28)<<endl;cout<<add<string>("123","321")<<endl;return 0;
}

我们只需要写一段函数代码,就可以实现之前需要定义多个函数需要干的事,是不是很方便。

另外,我们前面实现vector时就通篇使用了模板的泛型编程思想。包括我们使用的std::vector都离不开模板的支持。

类属:类型参数化,又称参数模板。使得程序(算法)可以从逻辑功能上抽象,把被处理的对象(数据)类型作为参数传递。

模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属

模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。


一、函数模板

C++提供了函数模板(function template)。所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。

1.函数模板的定义

template<typename _Tx,typename _Ty, ...... ,typename _Tn>
返回值类型 函数名(参数列表){//函数体//return;
}

 Tips:

①template关键字告诉C++编译器:我要开始泛型了,你不要随便报错

②typename _Tx,_Ty,....._Tn 为模板的参数列表,使用尖括号<>包围,列表不能为空

③typename只可以被class代替

④_Tx,_Ty,......, _Tn等表示类型,可以像函数的缺省参数一样给出缺省类型

template<typename Tx,typename Ty> //true
template<class Tx,class Ty> //true
template<class Tx,typename Ty> //truetemplate<class Tx,Ty,Tz> //false
template<typename Tx,Ty> //falsetemplate<class Tx,class Ty=int> //true

 以上语法就相当于一个修饰符,作为函数的修饰,使得函数认识修饰中提到的类型。

函数模板定义由模板说明和函数定义组成。模板说明的类属参数必须在函数定义中至少出现一次。函数参数列表中可以使用类属类型参数,也可以使用一般类型参数。

2.函数模板的使用(实例化)

通过为函数模板的参数列表赋予具体类型变成模板函数的过程叫做实例化。模板函数包括显式调用与隐式调用。

自动数据类型推导(隐式调用)

int main(){int ia=1,ib=2;double da=10.3,db=20.5;//true,函数模板自动推算出T=intadd(ia,ib);//true,函数模板自动推算出T=doubleadd(da,db);//false,函数模板没有理解你的T到底是什么意思add(ia,db);//true,函数模板自动推算出T=intadd(ia,(int)da);return 0;
}

显式类型调用

//函数模板 是一个模板,等待被实例化
template<typename T>
T& add(const T& a,const T& b)
{return a+b;
}int main(){int a=3;double b=10.5;//true,模板已经确定了T为int,就会尝试将所有参数类型转化为intadd<int>(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;cout<<"myswap 模板函数do"<<endl;
}void myswap(char &a, int &b)
{int t;t = a;a = b;b = t;cout<<"myswap 普通函数do"<<endl;
}void main()
{char cData = 'a';int  iData = 2;//myswap<int>(cData, iData);  //结论 函数模板不提供隐式的数据类型转换  必须是严格的匹配myswap(cData, iData); //myswap(iData, cData);cout<<"hello..."<<endl;system("pause");return ;
}
#include "iostream"
using namespace std;
int Max(int a, int b)
{cout<<"int Max(int a, int b)"<<endl;return a > b ? a : b;
}template<typename T>
T Max(T a, T b)
{cout<<"T Max(T a, T b)"<<endl;return a > b ? a : b;
}template<typename T>
T Max(T a, T b, T c)
{cout<<"T Max(T a, T b, T c)"<<endl;return Max(Max(a, b), c);
}void main()
{int a = 1;int b = 2;cout<<Max(a, b)<<endl; //当函数模板和普通函数都符合调用时,优先选择普通函数cout<<Max<>(a, b)<<endl; //若显示使用函数模板,则使用<> 类型列表cout<<Max(3.0, 4.0)<<endl; //如果 函数模板产生更好的匹配 使用函数模板cout<<Max(5.0, 6.0, 7.0)<<endl; //重载cout<<Max('a', 100)<<endl;  //调用普通函数 可以隐式类型转换 system("pause");return ;
}

C++编译器模板机制剖析

思考:为什么函数模板可以和函数重载放在一起。C++编译器是如何提供函数模板机制的?

编译器编译原理

①什么是gcc

gcc(GNU C Compiler)编译器的作者是Richard Stallman,也是GNU项目的奠基者。

什么是gcc:gcc是GNU Compiler Collection的缩写。最初是作为C语言的编译器(GNU C Compiler),现在已经支持多种语言了,如C、C++、Java、Pascal、Ada、COBOL语言等

gcc支持多种硬件平台,甚至对Don Knuth 设计的 MMIX 这类不常见的计算机都提供了完善的

②gcc主要特征

1)gcc是一个可移植的编译器,支持多种硬件平台

2)gcc不仅仅是个本地编译器,它还能跨平台交叉编译。

3)gcc有多种语言前端,用于解析不同的语言。

4)gcc是按模块化设计的,可以加入新语言和新CPU架构的支持

5)gcc是自由软件

③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程序是一个高级C语言程序,这种形式容易被人读懂。为了在系统上运行hello.c程序,每条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("2+1 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.单个类模板语法

//类的类型参数化 抽象的类
//单个类模板
template<typename T>
class A 
{
public:A(T t){this->t = t;}T& getT(){return t;}
protected:
public:T t;
};
void main()
{//模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则A<int>  a(100); a.getT();printAA(a);return ;
}

2.继承中的类模板语法

结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)A<int>

class B : public A<int>
{
public:B(int i) : A<int>(i){}void printB(){cout<<"A:"<<t<<endl;}
protected:
private:
};//模板与上继承
//怎么样从基类继承  
//若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数
void pintBB(B &b)
{b.printB();
}
void printAA(A<int> &a)  //类模板做函数参数 
{//a.getT();
}void main()
{A<int>  a(100); //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 a.getT();printAA(a);B b(10);b.printB();cout<<"hello..."<<endl;system("pause");return ;
}

3.类模板语法知识体系梳理

①类模板函数写在类的内部:正常写

②类模板函数写在类的外部(在一个.cpp中)

//构造函数 没有问题
//普通函数 没有问题
//友元函数:用友元函数重载 << >>
//friend ostream& operator<< <T> (ostream &out, Complex<T> &c3) ;
//友元函数:友元函数不是实现函数重载(非 << >>)
//1)需要在类前增加 类的前置声明 函数的前置声明 
template<typename T>
class Complex;  template<typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);//2)类的内部声明 必须写成: 
friend Complex<T> mySub <T> (Complex<T> &c1, Complex<T> &c2);
//3)友元函数实现 必须写成:
template<typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
{Complex<T> tmp(c1.a - c2.a, c1.b-c2.b);return tmp;
}
//4)友元函数调用 必须写成
Complex<int> c4 = mySub<int>(c1, c2);
cout<<c4;

结论:友元函数只用来进行 左移 友移操作符重载。

③类模板函数卸载类的外部(在不同的.h和.cpp中)

也就是类模板函数说明和类模板实现分开写。比如在头文件写模板类的声明定义,成员函数只声明未实现,在源程序文件中进行函数的实现。

此时如果我们像往常一样包含头文件,在编译时的链接阶段会报错。解决方法有两种:

1.将.cpp文件一同包含

2.将两个文件写到一个.hpp文件中(.hpp只是一个约定俗成的模板类头文件后缀名)

4.类模板中的static关键字

  • 从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员
  •  和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
  •  每个模板类有自己的类模板的static数据成员副本

 

#include<bits/stdc++.h>
using namespace std;const double pi = 3.14159;
template<class 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();//类模板的静态成员函数
};
template<class T>
int Circle<T>::total = 0;
template<class T>
int Circle<T>::ShowTotal() { return total; }
void test241004_01() {Circle<int> 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=" << Circle<int>::ShowTotal() << endl;//cout << "Total=" << B.ShowTotal() << endl;//cout << "Total=" << A.ShowTotal() << endl;
}
void test241004_02() {Circle<double> 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=" << Circle<int>::ShowTotal() << endl;cout << "double Total=" << Circle<double>::ShowTotal() << endl;
}

 

我们明显看到Circle<int>和Circle<double>属于两个类,他们都有属于自己的static成员total,互不干扰。 

5.类模板在项目开发中的应用

  • 模板是C++类型参数化的多态工具。C++提供函数模板和类模板。
  •  模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。
  •  同一个类属参数可以用于多个模板。
  •  类属参数可用于函数的参数类型、返回类型和声明函数中的变量。
  •  模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。

    模板称为模板函数;实例化的类模板称为模板类。

  •  函数模板可以用多种方式重载。
  •  类模板可以在类层次中使用 。

*.模板特化与可变参数模板

模板特化

模板特化分为全特化偏特化。偏特化又分为:部分特化限制特化

template<class _Tx,class _Ty>
class Data{_Tx m_dx;_Ty m_dy;
public:Data(){cout<<"Data<_Tx , _Ty>"<<endl;}
};//全特化:将类属全部确定
template<>
class Data<int,char>
{int m_dx;char m_dy;
public:Data(){cout<<"Data<int,char>"<<endl;}
};//偏特化:确定类属中部分类型
//部分特化
template<class T>
class Data<T,int>
{T m_dx;int m_dy;
public:Data(){cout<<"Data<T,int>"<<endl;}
};
//限制特化
template<class M,class N>
class Data<M* , N*>
{/*略*/};
template<class M,class N>
class Data<M& ,N&>
{/*略*/};

可变参数模板 

 C++可变参数模板(Variadic Templates)是C++11引入的一种功能,允许你定义接受可变数量的参数的模板。这种特性非常强大,可以用来编写更灵活和通用的代码,尤其是在处理函数、类等时。

template<typename... Args>  
void func(Args... args) {  // 函数体  
}

这里,Args是一个类型参数包,可以接受任意数量的类型。args是一个参数包,可以接收任意数量的参数。

递归展开参数包:

#include <iostream>  
#include <string>  template<typename T>  
void print(const T& value) {  std::cout << value << std::endl;  
}  template<typename 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函数可以接受任意数量和类型的参数,并且能够逐个打印它们的值。在 C++11 中,由于没有逗号表达式的折叠表达式的特性,我们只能通过递归方式来处理参数包。

逗号表达式展开参数包 :

#include <iostream>  // 打印单个值的函数模板  
template<typename T>  
void print(const T& value) {  std::cout << value << std::endl;  
}  // 使用逗号表达式展开参数包的函数模板  
template<typename... 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)**的例子,它是 C++17 中引入的新特性。逗号运算符在这里允许我们对 print(args) 的每个调用依次执行,从而打印出所有参数的内容。 

重要特性

  1. 递归解包:通过递归模板函数,你可以将参数包逐个提取和处理。

  2. 类型推断:编译器可以自动推导传入参数的类型。

  3. 使用标准库功能:可以与标准库中的功能(如std::tuplestd::index_sequence等)结合使用,以实现更复杂的功能。


感谢大家!

相关文章:

泛型编程--模板【C++提升】(特化、类属、参数包的展开、static、模板机制、重载......你想知道的全都有)

更多精彩内容..... &#x1f389;❤️播主の主页✨&#x1f618; Stark、-CSDN博客 本文所在专栏&#xff1a; C系列语法知识_Stark、的博客-CSDN博客 其它专栏&#xff1a; 数据结构与算法_Stark、的博客-CSDN博客 C系列项目实战_Stark、的博客-CSDN博客 座右铭&#xff1a;梦…...

安卓使用memtester进行内存压力测试

memteser简介 memtester 是一个用于测试内存可靠性的工具。 它可以对计算机的内存进行压力测试&#xff0c;以检测内存中的错误&#xff0c;例如位翻转、随机存取错误等。memtester 可以在不同的操作系统上运行&#xff0c;并且可以针对不同大小的内存进行测试。 下载源码 m…...

Dave Cheney: Go语言之禅

本篇内容是根据2020年3月份The Zen of Go音频录制内容的整理与翻译, Dave Cheney 讲述了 Go 之禅&#xff08;编写简单、可读、可维护的 Go 代码的十个工程价值&#xff09;。是什么让 Go 代码变得优秀&#xff1f;编写 Go 代码时&#xff0c;我们应该牢记哪些指导原则&#x…...

SpringMVC源码-AbstractUrlHandlerMapping处理器映射器将实现Controller接口的方式定义的路径存储进去

DispatcherServlet的initStrategies方法用来初始化SpringMVC的九大内置组件 initStrategies protected void initStrategies(ApplicationContext context) {// 初始化 MultipartResolver:主要用来处理文件上传.如果定义过当前类型的bean对象&#xff0c;那么直接获取&#xff0…...

满填充透明背景二维码生成

前几天项目上线的时候发现一个问题&#xff1a;通过Hutool工具包生成的二维码在内容较少时无法填满(Margin 已设置为 0)给定大小的图片。因此导致前端在显示二维码时样式异常。 从图片中我们可以看到&#xff0c;相同大小的图片&#xff0c;留白内容是不一样的。其中上半部分…...

Python | Leetcode Python题解之第452题用最少数量的箭引爆气球

题目&#xff1a; 题解&#xff1a; class Solution:def findMinArrowShots(self, points: List[List[int]]) -> int:if not points:return 0points.sort(keylambda balloon: balloon[1])pos points[0][1]ans 1for balloon in points:if balloon[0] > pos:pos balloo…...

代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作删除二叉搜索树中的节点修剪二叉搜索树

代码随想录 | Day26 | 二叉树&#xff1a;二叉搜索树中的插入操作&&删除二叉搜索树中的节点&&修剪二叉搜索树 主要学习内容&#xff1a; 二叉搜索树的插入删除操作 701.二叉搜索树中的插入操作 701. 二叉搜索树中的插入操作 - 力扣&#xff08;LeetCode&…...

使用Apifox创建接口文档,部署第一个简单的基于Vue+Axios的前端项目

前言 在当今软件开发的过程中&#xff0c;接口文档的创建至关重要&#xff0c;它不仅能够帮助开发人员更好地理解系统架构&#xff0c;还能确保前后端开发的有效协同。Apifox作为一款集API文档管理、接口调试、Mock数据模拟为一体的工具&#xff0c;能够大幅度提高开发效率。在…...

TCP的第三次握手没有回复,会出现哪些问题现象

从三次握手的一开始来讲&#xff0c;刚开始客户端和服务器都处于close状态 这里不能是2次握手的原因就在于&#xff0c;服务器端即女孩子&#xff0c;无法确认客户端即男孩子&#xff0c;是否已经收到了&#xff0c;我也愿意建立连接即我也爱你&#xff0c;这一条最终确认的信息…...

【工具】arxiv_latex_cleaner 去除latex注释

https://github.com/google-research/arxiv-latex-cleaner/issues/24 文章目录 1.修改编码2.如何安装2.1.打包2.2.安装 3.测试功能 注意&#xff1a;需要创建python3.9的环境 1.修改编码 官方提供的arxiv_latex_cleaner的编码格式是有问题的&#xff0c;见这里。这个有位朋友说…...

macOS开发环境配置与应用开发

一、macOS开发环境配置 1. 安装Xcode Xcode 是Apple官方开发环境工具&#xff0c;用于macOS、iOS、watchOS和tvOS应用开发。它集成了代码编辑、编译、调试、性能分析、界面设计等功能。 下载与安装&#xff1a; 打开 App Store&#xff0c;搜索“Xcode”。 点击安装&#xff…...

15分钟学 Python :编程工具 Idea 和 vscode 中配置 Python ( 补充 )

编程工具配置 Python 在 IDE 和 VSCode 中 在编程学习的过程中&#xff0c;选择合适的开发工具至关重要。本文将详细介绍在两种流行的IDE&#xff08;IntelliJ IDEA 和 Visual Studio Code&#xff09;中如何配置Python环境&#xff0c;帮助你更高效地进行Python开发。 一、编…...

MyBatis 如何实现延迟加载?深度探讨 MyBatis 的延迟加载:如何优化数据访问效率

在当今的应用程序开发中&#xff0c;尤其是与数据库交互时&#xff0c;性能成为了重中之重。频繁的数据库访问会导致响应时间变慢&#xff0c;甚至影响用户体验。为了优化数据访问&#xff0c;MyBatis 提供了延迟加载&#xff08;Lazy Loading&#xff09;的强大功能。本文将详…...

springboot系列--web相关知识探索三

一、前言 web相关知识探索二中研究了请求是如何映射到具体接口&#xff08;方法&#xff09;中的&#xff0c;本次文章主要研究请求中所带的参数是如何映射到接口参数中的&#xff0c;也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、…...

AI冲击下的编程职业未来:你缺的不是技术,而是跨学科思维!

随着AIGC技术&#xff08;如ChatGPT、MidJourney、Claude等大语言模型&#xff09;的不断进化&#xff0c;AI辅助编程工具迅速普及&#xff0c;程序员的工作方式正在经历前所未有的转型。代码自动补全、智能化代码生成等功能大幅提升了工作效率&#xff0c;但与此同时&#xff…...

是否是 2 的幂次方

给你一个整数 n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果存在一个整数 x 使得 n 2x &#xff0c;则认为 n 是 2 的幂次方。 示例 1&#xff1a; 输入&#xff1a;n 1 输出&#xff1a;tr…...

音视频入门

一个视频&#xff0c;一秒内普遍大于等于25帧。 入门知识&#xff1a; 1.帧&#xff0c;一张画面就是一帧。一个视频就是由许许多多帧组成的。 帧率&#xff0c;单位时间内帧的数量。单位&#xff1a;帧/秒 或 fps。 分类&#xff1a;I帧&#xff0c;P帧&#xff0c;B帧 I…...

C++随心记 续一

C中的模板 在其它语言中如Java或者C#中可能叫做泛型&#xff0c;在C中为模板&#xff0c;泛型的限制通常比模板多。模板可以解决多次的代码重复问题&#xff0c;如以下场景 #include <iostream> #include <string>void print(int value) {std::cout << val…...

消息中间件:RabbitMQ

消息中间件&#xff1a;RabbitMQ 前言安装Window安装Linux安装 管理页面什么是RabbitMQ&#xff1f;入门基本概念简单队列工作队列&#xff08;Work Queues&#xff09;发布/订阅&#xff08;Publish/Subscribe&#xff09;临时队列 路由&#xff08;Routing&#xff09;主题&a…...

sql-labs:42~65

less42&#xff08;单引号闭合、报错回显&#xff09; login_useradmin login_password123 and if(11,sleep(2),1) # # 单引号闭合 ​ login_useradmin login_password123and updatexml(1,concat(0x7e,database(),0x7e),1)# # 报错回显…...

KaTeX.js渲染数学公式

什么是KaTeX.js ? KaTeX 是一个集成速度快且功能丰富的数学公式渲染库&#xff0c;专为 Web 设计。它由 Khan Academy 开发&#xff0c;提供接近印刷品质的数学公式展示&#xff0c;同时保持与浏览器的高效互动性。KaTeX 特点包括快速渲染速度、高质量的输出、独立运行、跨平…...

算法训练营打卡Day19

目录 1.二叉搜索树的最近公共祖先 2.二叉树中的插入操作 3.删除二叉搜索树中的节点 题目1、二叉搜索树的最近公共祖先 力扣题目链接(opens new window) 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有…...

H.264编解码工具 - FFmpeg

一、简介 FFmpeg是一款用于处理多媒体数据的开源软件,可以完成音频、视频和多媒体流的编解码、转码、解码、录制、流媒体播放等功能。它提供了丰富的命令行工具和库函数,适用于各种平台和操作系统。 FFmpeg支持多种常见的音视频格式,包括MP3、WAV、FLAC、MP4、AVI、MKV等。它…...

60 序列到序列学习(seq2seq)_by《李沐:动手学深度学习v2》pytorch版

系列文章目录 文章目录 系列文章目录一、理论知识比喻机器翻译Seq2seq编码器-解码器细节训练衡量生成序列的好坏的BLEU(值越大越好)总结 二、代码编码器解码器损失函数训练预测预测序列的评估小结练习 一、理论知识 比喻 seq2seq就像RNN的转录工作一样&#xff0c;非常形象的比…...

elementPlus的tree组件点击后有白色背景

在使用elementPlus的tree组件时&#xff0c;需要对它进行样式的重写&#xff0c;下面是相关代码 <script setup> import { ref } from vue const data [{label: Level one 1,children: [{label: Level two 1-1,children: [{label: Level three 1-1-1}]}]},{label: Leve…...

【Git】Git在Unity中使用时的问题记录

个人向笔记。 &#xff08;为什么没截图&#xff0c;因为公司电脑没法截图&#xff01;&#xff09; 1 前言 主要记录在使用Git协同开发时的各种问题&#xff0c;方便以后查阅。 2 记录 2.1 合并冲突 git pull下来后直接给合并了&#xff0c;麻了。若不想直接合并应该先把分…...

python学习记录6

&#xff08;1&#xff09;循环嵌套 可以将一个循环语句所属的语句块也可以是一个完整的一个循环语句&#xff0c;一般嵌套不应该超过3层。 嵌套可以是while-while、for-for,也可以是while-for。 基本图形输出&#xff1a;正方形&#xff0c;直角三角形 #输入一个数字n&…...

MongoDB 的基本使用

目录 数据库的创建和删除 创建数据库 查看数据库 删除数据库 集合的创建和删除 显示创建 查看 删除集合 隐式创建 文档的插入和查询 单个文档的插入 insertOne insertMany 查询 嵌入式文档 查询数组 查询数组元素 为数组元素指定多个条件 通过对数组元素使…...

数据揭秘:分类与预测技术在商业洞察中的应用与实践

分类与预测&#xff1a;数据挖掘中的关键任务 在数据挖掘的广阔天地中&#xff0c;分类与预测就像是一对互补的探险家&#xff0c;它们携手深入数据的丛 林&#xff0c;揭示隐藏的宝藏。 一、分类&#xff1a;数据的归类大师 分类是一种将数据点按照特定的属性或特征划分到不…...

学MybatisPlus

1.设置MySql的数据库 spring:datasource:url: jdbc:mysql://127.0.0.1:3306/mp?useUnicodetrue&characterEncodingUTF-8&autoReconnecttrue&serverTimezoneAsia/Shanghaidriver-class-name: com.mysql.cj.jdbc.Driverusername: rootpassword: MySQL123 logging:l…...