泛型编程--模板【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等)结合使用,以实现更复杂的功能。


感谢大家!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/438670.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

国外电商系统开发-运维系统批量添加服务器

您可以把您准备的txt文件&#xff0c;安装要求的格式&#xff0c;复制粘贴到里面就可以了。注意格式&#xff01; 如果是“#” 开头的&#xff0c;则表示注释&#xff01;

网盘能否作为FTP替代产品?企业该如何进行FTP国产化替代?

近年来&#xff0c;信创的概念引入和高效实践落地让更多的行业企业自发性地进行国产化替代&#xff0c;目前信创国产化替代还多发生在操作系统和应用层面&#xff0c;软件工具等目前还在下一阶段规划&#xff0c;但很多企业未雨绸缪&#xff0c;已经在做调研和尝试。 FTP作为世…

一些 Go Web 开发笔记

原文&#xff1a;Julia Evans - 2024.09.27 在过去的几周里&#xff0c;我花了很多时间在用 Go 开发一个网站&#xff0c;虽然不知道它最终会不会发布&#xff0c;但在这个过程中我学到了一些东西&#xff0c;想记录下来。以下是我的一些收获&#xff1a; Go 1.22 现在有了更…

github项目——gpt-pilot自动创建应用

今天扯一扯在github上看到的一个项目gpt-pilot&#xff0c;声称“首个AI程序员”。本来打算玩一下&#xff0c;结果需要配置大语言模型的API&#xff0c;并且只支持OpenAI和claude&#xff08;Qwen呢&#xff09;。有没有玩过的老哥说一下好不好用&#xff01;&#xff01;(对了…

【重学 MySQL】五十四、整型数据类型

【重学 MySQL】五十四、整型数据类型 整型类型TINYINTSMALLINTMEDIUMINTINT&#xff08;或INTEGER&#xff09;BIGINT 可选属性UNSIGNEDZEROFILL显示宽度&#xff08;M&#xff09;AUTO_INCREMENT注意事项 适合场景TINYINTSMALLINTMEDIUMINTINT&#xff08;或INTEGER&#xff0…

Django 后端数据传给前端

Step 1 创建一个数据库 Step 2 在Django中点击数据库连接 Step 3 连接成功 Step 4 settings中找DATABASES Step 5 将数据库挂上面 将数据库引擎和数据库名改成自己的 Step 6 在_init_.py中加上数据库的支持语句 import pymysql pymysql.install_as_MySQLdb() Step7 简单创建两…

ElementUI el-tree 树组件 增加辅助线

需求 项目需求给elementUI的el-tree添加辅助线&#xff0c;并且不能使用其他插件&#xff0c;没办法只能该样式了。 效果 代码 html <template><div><el-scrollbar class"long-content"><el-tree node-key"id":data"deptTre…

项目:微服务即时通讯系统客户端(基于C++QT)]四,中间界面搭建和逻辑准备

四&#xff0c;中间界面搭建 前言:当项目越来越复杂的时候&#xff0c;或许画草图是非常好的选择 一&#xff0c;初始化中间窗口initMidWindow void mainWidget::initMidWindow() {//使用网格布局进行管理QGridLayout* layout new QGridLayout();//距离上方 20px 的距离&…

高效录制,尽在掌握:四大录屏软件对比分析!

屏幕录制是一种重要的信息传递方式。今天&#xff0c;我们就来一起探索几款市场上备受好评的录屏工具——福昕录屏大师、转转大师录屏、爱拍录屏以及OCAM录屏&#xff0c;看看它们各自都有哪些独特之处。 福昕录屏工具 直达链接&#xff1a;www.foxitsoftware.cn/REC/ 作为一…

pyqt打包成exe相关流程

1、首先是安装pyinstaller, 在cmd中输入以下安装命令&#xff1a; pip3 install pyinstaller -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/ 2、安装完毕之后&#xff0c;下一步就是找到你要打包的工程&#xff0c;打包的logo放置如下位置&#xff1a; 3、将log…

[C语言]--编译和链接

文章目录 目录 文章目录 前言 一、环境介绍 二、翻译环境 1.预处理&#xff08;预编译&#xff09; 2.编译 3.汇编 4.链接 三、运行环境 前言 对编译和链接 进行简单的介绍 一、环境介绍 在ANSIC的任何⼀种实现中&#xff0c;存在两个不同的环境。 翻译环境&#xff0c;在这…

flutter_鸿蒙next(win)环境搭建

第一步 拉取鸿蒙版本flutterSDK仓库 仓库地址&#xff1a;OpenHarmony-SIG/flutter_flutter 第二步 找到拉取的仓库中的README.md 并根据说明配置环境 第三步 配置好环境变量之后 用管理员开启cmd 输入&#xff1a;flutter dcotor 并查看此时flutter所支持的系统 包括&…

《深度学习》OpenCV 图像拼接 原理、参数解析、案例实现

目录 一、图像拼接 1、直接看案例 图1与图2展示&#xff1a; 合并完结果&#xff1a; 2、什么是图像拼接 3、图像拼接步骤 1&#xff09;加载图像 2&#xff09;特征点检测与描述 3&#xff09;特征点匹配 4&#xff09;图像配准 5&#xff09;图像变换和拼接 6&am…

【若依】postman调试出现认证失败,无法访问系统资源

如果前后端都已经连接通了&#xff0c;但是调试出现错误代码&#xff0c;可能是因为没有授权的问题&#xff0c;需要获得授权。 授权内容在cookie中 把cookie中的token内容粘贴到postman里面 这个时候再在postman里测试接口&#xff0c;发现可以拿到数据了

pytorch之梯度累加

1.什么是梯度&#xff1f; 梯度可以理解为一个多变量函数的变化率&#xff0c;它告诉我们在某一点上&#xff0c;函数的输出如何随输入的变化而变化。更直观地说&#xff0c;梯度指示了最优化方向。 在机器学习中的作用&#xff1a;在训练模型时&#xff0c;我们的目标是最小…

TransFormer 视频笔记

TransFormer BasicsAttention单头注意力 single head attentionQ&#xff1a; query 查寻矩阵 128*12288K key matrix 128*12288SoftMax 归一 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/19e3cf1ea28442eca60d5fc1303921f4.png)Value matrix 12288*12288 MLP Bas…

【Linux】进程地址空间、环境变量:从理论到实践(三)

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 &#x1f680; 前言一&#xff1a;&#x1f525; 环境变量 &#x1f95d; 基本概念&#x1f95d; 常见环境变量&#x1f95d; 查看环境变量方法 二&#xff1a;&#x1f525; 测试 &…

前端算法合集-1(含面试题)

(这是我面试一家中厂公司的二面算法题) 数组去重并按出现次数排序 题目描述: 给定一个包含重复元素的数组&#xff0c;请你编写一个函数对数组进行去重&#xff0c;并按元素出现的次数从高到低排序。如果次数相同&#xff0c;则按元素值从小到大排序。 let arr [2, 11,10, 1…

GPTQ vs AWQ vs GGUF(GGML) 速览和 GGUF 文件命名规范

简单介绍一下四者的区别。 参考链接&#xff1a;GPTQ - 2210.17323 | AWQ - 2306.00978 | GGML | GGUF - docs | What is GGUF and GGML? 文章目录 GPTQ vs AWQ vs GGUF&#xff08;GGML&#xff09; 速览GGUF 文件命名GGUF 文件结构文件名解析答案 附录GGUF 文件命名GGUF 文件…

15分钟学 Python 第35天 :Python 爬虫入门(一)

Day 35 : Python 爬虫简介 1.1 什么是爬虫&#xff1f; 网页爬虫&#xff08;Web Crawler&#xff09;是自动访问互联网并提取所需信息的程序。爬虫的主要功能是模拟用户通过浏览器访问网页的操作&#xff0c;从而实现对网页内容的批量访问与信息提取。它们广泛应用于数据收集…