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

news2024/10/4 21:52:52

 更多精彩内容.....

🎉❤️播主の主页✨😘

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
    //实际输出7
    return 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> //true

template<class Tx,Ty,Tz> //false
template<typename Tx,Ty> //false

template<class Tx,class Ty=int> //true

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

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

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

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

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

int main(){
    int ia=1,ib=2;
    double da=10.3,db=20.5;
    //true,函数模板自动推算出T=int
    add(ia,ib);
    //true,函数模板自动推算出T=double
    add(da,db);
    //false,函数模板没有理解你的T到底是什么意思
    add(ia,db);
    //true,函数模板自动推算出T=int
    add(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,就会尝试将所有参数类型转化为int
    add<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.coloradmin.cn/o/2188767.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java中的while和do...while循环

while和do...while循环 while循环基本语法执行流程注意事项练习 do...while循环基本语法说明流程图练习 while循环 基本语法 循环变量初始化; while(循环条件){循环体&#xff08;语句&#xff09;;循环变量迭代; }1&#xff09;while循环也有四要素&#xff1a;循环变量初始…

【JNI】普通类型的基本使用

简单使用 在上一期我们介绍了JNI的基本使用&#xff0c;这里简单介绍一下普通类型 HelloJNI.java&#xff1a;这里计算两个整型数的平均值&#xff0c;返回值类型为double public class HelloJNI { static {System.loadLibrary("hello"); }private native String …

electron-builder 首次执行报错问题解决

假日想研究一下 react electron 的使用&#xff0c;结果发现首次打包疯狂报错&#xff0c;研究了一下之后才发现是第一次的话 electron-builder 会从外面下载依赖包到我们系统中&#xff0c;由于某种力量导致压缩包无法下载或者是下载过慢导致失败&#xff0c;要解决其实也简单…

认知战认知作战:2024年9月30日中国股市大涨背景下的认知战分析报告

认知战认知作战&#xff1a;2024年9月30日中国股市大涨背景下的认知战分析报告 关键词&#xff1a;认知战, 中国股市, 信息操纵, 心理战, 技术战, 信息监管, 投资者素养, 国际合作, 法律法规, 协同作战, 谣言澄清, 市场情绪,认知作战,新质生产力,人类命运共同体,认知战,认知域…

《Linux从小白到高手》理论篇:深入理解Linux的计划任务/定时任务

值此国庆佳节&#xff0c;深宅家中&#xff0c;闲来无事&#xff0c;就多写几篇博文。本篇详细深入介绍Linux的计划任务/定时计划。 Linux的计划任务 在很多时候为了自动化管理系统&#xff0c;我们都会用到计划任务&#xff0c;比如关机&#xff0c;重启&#xff0c;备份之类…

二叉树--堆

1.二叉树的顺序结构 普通的二叉树是不适合用数组来存储的&#xff0c;因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结 构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储&#xff0c;需要注意的是这里的堆和操作系统 虚拟进程地址空间中的堆是两…

CSP-J Day 4 模拟赛补题报告

姓名&#xff1a;王胤皓&#xff0c;校区&#xff1a;和谐校区&#xff0c;考试时间&#xff1a; 2024 2024 2024 年 10 10 10 月 4 4 4 日 9 : 00 : 00 9:00:00 9:00:00~ 12 : 30 : 00 12:30:00 12:30:00&#xff0c;学号&#xff1a; S 07738 S07738 S07738 请关注作者的…

Windows应用开发-解析AVI视频文件

本Windows应用解析AVI视频文件&#xff0c;以表格的方式显示AVI文件结构。并可以将结果保存到bmp图片。下面是&#xff0c;使用该应用解析一部AVI电影获得的图片。 应用开发信息 定义一个INFO结构&#xff0c;包含两个字符串对象&#xff0c;一个ULONGLONG变量&#xff0c;和…

奔驰AMG GT50升级原厂阀门运动排气声浪效果

AMG 排气系统 声浪级别可控制的AMG高性能排气系统可带来不同凡响的听觉体验。借助可调式废气风门&#xff0c;按下按钮&#xff0c;即可按需改变车辆的声浪&#xff0c;体验不同音色。静谧深沉或动感澎湃&#xff0c;悦耳声浪&#xff0c;如你所愿。

Python画笔案例-076 绘制纯画笔弹球

1、绘制纯画笔弹球 通过 python 的turtle 库绘制 纯画笔弹球,如下图: 2、实现代码 绘制纯画笔弹球,以下为实现代码: """纯画笔弹球动画.py读者可以在此基础上把它修改成一个拦球游戏。步骤为,建立一个Rect类,即矩形类。然后采用按键检测,当按了键时重画…

​​Python+Matplotlib可视化简单反函数和复合函数

import numpy as np import matplotlib.pyplot as plt# 设置中文字体 plt.rcParams[font.sans-serif] [SimHei] # 用黑体显示中文 plt.rcParams[axes.unicode_minus] False # 正常显示负号# 创建图形和子图 fig, (ax1, ax2) plt.subplots(1, 2, figsize(15, 6))# 反函数示…

Line: 折线图

对北京市、天津市、上海市、重庆市的近10年人口&#xff0c;做出折线图&#xff0c;效果 参考&#xff1a;Line - Basic_line_chart - Document (pyecharts.org) 1、折线图模板 import pyecharts.options as opts from pyecharts.charts import Linex_data ["Mon"…

基于Springboot+Vue的中医院问诊系统的设计与实现 (含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统中…

内存卡数据恢复软件大揭秘,拯救你的重要数据

我们的生活中充斥着各种各样的数据&#xff0c;而内存卡作为一种常见的数据存储设备&#xff0c;承载着我们的照片、视频、文档等重要信息。然而存储在电子设备上就有可能导致数据丢失的情况。今天我们一起来探讨内存卡数据恢复的一些工具吧。 1.福晰内存卡数据恢复 连接直达…

ros2使用roscore报错

如果你在ros2中使用roscore命令&#xff0c;可能会出现报错。 即使你按照下面的命令安装python3-roslaunch&#xff0c;还是会报错。 注意&#xff0c;在ROS2中&#xff0c;已经不需要通过roscore命令启动ROS系统了。 ROS2中&#xff0c;不再需要ROS1中的roscore命令来启动一个…

【Python】Arrow使用指南:轻松管理日期与时间

Arrow 是一个基于 Python 的日期与时间管理库&#xff0c;提供了更人性化和直观的 API 处理时间数据。与 Python 标准库中的 datetime 模块相比&#xff0c;Arrow 极大地简化了时间创建、转换、格式化和操作的步骤。它通过统一的接口封装了常见的时间操作&#xff0c;支持时区转…

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

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

Python从入门到高手4.4节-算法实战之计算次大值

目录 4.4.1 四个随机数中的次大值 4.4.2 计算次大值的算法思路 4.4.3 使用循环计算次大值 4.4.4 祝祖国繁荣昌盛 4.4.1 四个随机数中的次大值 假设有四个整型变量&#xff0c;它们值的大小未知&#xff0c;该怎么计算出四个中的次大值&#xff1f; 次大值即第二大的数。初…

vsomeip用到的socket

概述&#xff1a; ​ vsomeip用到的socket的代码全部都在implementation\endpoints目录下面&#xff0c;主要分布在下面六个endpoint类中&#xff1a; local_client_endpoint_impl // 本地客户端socket&#xff08;UDS Socket或者127.0.0.1的socket&#xff09;local_server…

深入挖掘C++中的特性之一 — 继承

目录 1.继承的概念 2.举个继承的例子 3.继承基类成员访问方式的变化 1.父类成员的访问限定符对在子类中访问父类成员的影响 2.父类成员的访问限定符子类的继承方式对在两个类外访问子类中父类成员的影响 4.继承类模版&#xff08;注意事项&#xff09; 5.父类与子类间的转…