C++之模板和可变模板参数

本文围绕C++模板展开,介绍了定义模板的原因,如简化程序、解决强类型语言冲突等。详细阐述了模板的定义方式,以及函数模板和类模板的类型、实例化、重载等内容。还讲解了C++11新增的可变模板参数,包括模板参数包和函数参数包,以及其在参数个数和类型推导上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、为什么要定义模板

模板的优点:

二、模板的定义

三、模板的类型

3.1、函数模板

3.1.1、实例化:隐式实例化与显示实例化

3.1.2、函数模板、普通函数间的关系

3.1.2.1易错点:

3.1.2.2重载例子:

3.1.2.3优先级与执行顺序:

3.1.3、模板头文件与实现文件

3.1.4、模板的特化:偏特化与全特化

3.1.5、函数模板的参数类型

3.1.6、成员函数模板

3.2、类模板

注意:

模板的嵌套:

模板做参数:

四、可变模板参数

4.1、模板参数包

4.2、函数参数包

4.3、可变模板参数的优势(有两条)


一、为什么要定义模板

现在的 C++ 编译器实现了一项新的特性:模板( Template ),简单地说, 模板 是一种通用的描述机制,也就是说,使用模板允许使用 通用类型 来定义函数或类等,在使用时,通用类型可被具体的类型,如 int double 甚至是用户自定义的类型来代替。模板引入一种全新的编程思维方式,称为 泛型编程 通用编程
泛型编程:不是针对某一种具体的类型进行编程,而是针对一类类型进行编程,将类型抽象成T(类型参数化)
#形象地说,把函数比喻为一个游戏过程,函数的流程就相当于 游戏规则。
#在以往的函数定义中,总是指明参数是 int 型还是 double 型等等,这就像是为张三(好比 int 型)和李四(好比 double 型)比赛制定规则。可如果王五( char* 型)和赵六( bool 型)要比赛,还得提供一套函数的定义,这相当于又制定了一次规则,显然这是很麻烦的。
#模板的的引入解决了这一问题 ,不管是谁和谁比赛,都把他们定义成 A B 比赛,制定好了 A B 比赛的规则(定义了关于 A B 的函数)后,比赛时只要把 A 替换成张三,把 B 替换成李四就可以了, 大大简化了程序代码量 维持了结构的清晰 大大提高了 程序设计 的效率 。该过程称为 类型参数化
# 强类型程序设计中,参与运算的所有对象的类型在编译时即确定下来,并且编译程序将进行严格的类型检查。为了解决 强类型的严格性和灵活性的冲突。有以下3中方式解决:
#带参数宏定义 (原样替换)
#重载函数 (函数名相同,函数参数不同)
#模板 (将数据类型作为参数)
#include <iostream>
using namespace std;

int add(int x, int y)			//定义两个int类型相加的函数
{
	return x + y;
}

double add(double x, double y) //重载两个double类型相加的函数
{
	return x + y;
}

char* add(char* px, char* py)			//重载两个字符数组相加的函数
{
	return strcat(px, py);				//调用库函数strcat
}

int main()
{
	cout << add(1, 2) << endl;			//调用add(const int,const int)

	cout << add(3.0, 4.0) << endl;		//调用add(const double,const double)

	char x[10] = "Hello ";				//创建字符数组,注意要留够大小
	char y[] = "C++";
	cout << add(x, y) << endl;			//调用add(char*,char*)

	return 0;
}

模板的优点:

1、简化程序,少写代码,维持结构的清晰,大大提高程序的效率。
2、解决强类型语言的严格性和灵活性之间的冲突。
2.1、带参数的宏定义(原样替换)
2.2、函数重载(函数名字相同,参数不同)
2.3、模板(将数据类型作为参数)
3、强类型语言程序设计:C/C++/Java等,有严格的类型检查,如int a = 10,在编译时候明确变量的类型,如果有 问题就可以在编译时发现错误,安全,但是不够灵活,C++引进auto其实就是借鉴弱类型语言的特征。
弱类型程序语言设计:js/python等,虽然也有类型,但是在使用的时候直接使用let/var number,不知道变量具体类型,由编译器解释变量类型,属于解释型语言。如果有错,到运行时才发现,虽然灵活,但是不安全。

二、模板的定义

#模板的引入使得函数定义摆脱了类型的束缚,代码更为高效灵活。 C ++中,通过下述形式定义一个模板:

template <class T...>

template<typename T,....>

#早期模板定义使用的是 class ,关键字 typename 是最近才加入到标准中的,相比 class typename 更容易体现 类型 的观点,虽然两个关键字在模板定义时是等价的,但从代码兼容的角度讲,使用 class 较好一些。
#模板有 函数模板 类模板 之分。通过 参数实例化 构造出具体的函数或类,称为 模板函数 模板类
//模板
#include <iostream>
#include <string>					//使用标准类库中提供的string类时必须包含此头文件
using namespace std;

template <typename T>
T add(const T &a, const T &b)
{
	return a + b;
}

int main()
{
	cout << add(10, 20) << endl;	//调用add(const int,const int)
	cout << add(1.0, 2.0) << endl;	//调用add(const double,const double)
	string x("Hello,"), y("world");
	cout << add(x, y) << endl;		//调用add(string,string)

	return 0;
}
#代码中的 add 函数便是一个函数模板, 编译器 根据函数模板的定义,检查传入的参数类型,生成相应的函数,并调用之。 函数模板 的定义形式如下:

  template <模板参数表>

  返回类型 函数名(参数列表)

  {  //函数体  }

#关键字 template 放在模板的定义与声明的最前面,其后是用逗号分隔的 模板参数表 ,用尖括号( <> )括起来。 模板参数表不能为空 ,模板参数有两种类型:
#class typename 修饰的类型参数,代表一种类型
# 非类型参数 表达式 , 必须是 整型类型 ,使用已知类型符,代表一个常量
//带非类型参数
template <typename T, int NUM>
T fun(T a)
{
	return a * NUM;
}

//调用
cout << fun<int, 4>(3) << endl;
#返回类型和函数的参数列表中可以包含类型参数,在函数中可以使用模板参数表中的常量表达式,如:
template <class T1,class T2,int number>

double fun(T1 a,int b,T2 c)

{

  return a * (number + b) * c;

  //函数体,其中number可以作为一个int型常量来使用

}

三、模板的类型

函数模板与类模板。通过参数实例化构造出具体的函数或者类,称为模板函数或者模板类。

3.1、函数模板

template <typename T>//模板参数列表
T add(T x, T y)
{
  cout << "T add(T, T)" << endl;
  return x + y;
}

                        模板参数推导(在实参传递的时候进行推导)
函数模板------------------------------------------------------------------------模板函数
                                            实例化

3.1.1、实例化:隐式实例化与显示实例化

#函数模板实际上不是个完整的函数定义,因为其中的类型参数还不确定,只是定义了某些类型的角色(或变量)在函数中的操作形式,因此,必须将 模板参数实例化 才能使用函数,用模板实例化后的函数也称为模板函数 .
#分为 隐式实例化 显式实例化
显示实例化:显示的将类型参数写出来,以免靠编译器去推导。
隐式实例化:不主动写出参数类型,靠编译器自己推导(类似与auto)
//12-3 函数模版的隐式实例化
#include <iostream>
using namespace std;

template <class T>
T Max(T x, T y);	//函数模版的申明

int main()
{	
	int intX = 1, intY = 2;
	double dblX = 3.9, dblY = 2.9;
	cout << Max(intX, intY) << endl;	//实参为int型,生成int型模板函数,并对第二个参数进行检查
	//或者cout << Max<int>(intX, intY) << endl;
	cout << Max(dblX, dblY) << endl;	//实参为double型,生成double型模板函数,并对第二个参数进行检查
	//或者cout << Max<double>(dblX, dblY) << endl;

	return 0;
}

template <class T>
T Max(T x, T y)		//函数模版的实现
{
	return (x > y ? x : y);
}

3.1.2、函数模板、普通函数间的关系

1、函数模板与普通函数是可以进行重载的
2、普通函数优先于函数模板执行
3、函数模板与函数模板之间也是可以进行重载的

3.1.2.1易错点:
# 函数模板支持重载 ,既可以模板之间重载(同名模板),也可以实现模板和普通函数间的重载,但模板的重载相比普通函数的重载要复杂一点,首先看一个例子:

template <class T1,class T2>

T1 Max(T1 a,T2 b){……}

    与

template <class T3,class T4>

T3 Max(T3 c,T4 d){……}

#看似不同的两个模板,仔细分析后发现,其本质是一样的,如果调用 Max(2,3.5); ,都实例化为 Max(int,double); ,会出现重复定义的错误。

      仅仅依靠返回值不同的模板重载也是不合法的,如:

template <class T1,class T2>

T1 Greater(T1 a,T2 b){……}

     

template <class T3,class T4>

                                        T3* Greater(T3 c,T4 d){……}

3.1.2.2重载例子:
//1函数模板和确定数据类型的函数的重载
#include <iostream.h>

template < class T >
T Max(T x, T y);

int Max(int x, int y)
{
	return x > y ? x : y;
}

int main()
{
	int intX = 1, intY = 2;
	double dblX = 3.0, dblY = 2.9;
	
	cout << Max(intX, intY) << endl;			//调用Max(int,int)

	cout << Max<double>(dblX, dblY) << endl;		//显示实例化为double型,生成double型模板函数
	cout << Max('A', '8') << endl;				//隐式实例化char型,生成char型模板函数
	return 0;
}

template <class T>
T Max(T x, T y)
{
	return x > y ? x : y;
}

//2函数模板和函数模板的重载
#include <iostream.h>

template < class T >
T Max(T x, T y);

template <class T>
T Max(T x, T y, T z)
{
	return x > y ? (x > z ? x : z) : (y > z ? y : z);
}

int main()
{
	int intX = 1, intY = 2, intZ = 3;
	double dblX = 3.0, dblY = 2.9;
	
	cout << Max<int>(intX, intY) << endl;	//调用实例化的Max(int,int)
	cout << Max<int>(intX, intY, intZ) << endl;	//调用实例化的Max(int,int,int)
	cout << Max<double>(dblX, dblY) << endl;	//显示实例化为double型,生成double型模板函数
	cout << Max('A', '8') << endl;			//隐式实例化char型,生成char型模板函数
	return 0;
}

template <class T>
T Max(T x, T y)
{
	return x > y ? x : y;
}

//3 普通函数模板和数组的重载
#include <iostream>
using namespace std;

template <typename T>
T MAX(T a, T b)
{
	return a > b ? a : b;
}

template <typename T>
T MAX(T a[], int n)
{
	T max = a[0];
	for(int i = 1; i < n; i++)
	{
		if(max < a[i])
		{
			max = a[i];
		}
	}

	return max;
}

int main()
{
	cout << MAX(3, 4) << endl;	//或cout << MAX<int>(3, 4) << endl;

	int a[] = {2, 9, 7, 3, 8, 5};
	cout << MAX(a, sizeof(a) / sizeof(a[0])) << endl;//或cout << MAX<int>(a, sizeof(a) / sizeof(a[0])) << endl;
	return 0;
}

3.1.2.3优先级与执行顺序:
int Max(int i1,int i2)
{
	cout<<"Normal Max"<<endl;
	return i1>i2? i1:i2;
}
template<class T> 
T Max(T t1, T t2)
{
	cout<<"Template Max,sizeof(t1):"<<sizeof(t1)<<endl;
	return t1>t2? t1:t2;
}
int main(int argc, char* argv[])
{
	int i1=1,i2=9;
	char c1='a',c2='b';
	int iRet=Max(i1,i2);			//调用普通函数int Max(int i1,int i2)
	char cRet=Max(c1,c2);			//调用模板实例化生成的char Max(char a,char b)
	int iRet2=Max(c1,c2);			//调用模板实例化生成的char Max(char a,char b),最后将返回值隐式转换成int型
	int cRet2=Max<char>(i1,i2);		//调用模板实例化生成的char Max(char a,char b),
	return 0;
}

3.1.3、模板头文件与实现文件

注:模板不能写成头文件与实现文件形式(类似inline函数),或者说不能将声明与实现分开,这样会导致编译报错。分开可以编译,但是在链接的时候是有问题的。

3.1.4、模板的特化:偏特化与全特化

template <> //此处模板的参数只有一个,全部特化出来就是全特化
const char *add(const char *pstr1, const char *pstr2)
{
  size_t len = strlen(pstr1) + strlen(pstr2) + 1;
  char *ptmp = new char(len);
  strcpy(ptmp, pstr1);
  strcat(ptmp, pstr2);
  return ptmp;;
}

具体的模板特化例子:

template <typename T>
T add(T x, T y)
{
  cout << "T add(T, T)" << endl;
  return x + y;
}
template <typename T>
T add(T x, T y, T z)
{
  cout << "T add(T, T, T)" << endl;
  return x + y + z;
}
int add(int x, int y)
{
  cout << "int add(int, int)" << endl;
  return x + y;
}
double add(double x, double y)
{
  cout << "double add(double, double)" << endl;
  return x + y;
}
template <>
const char *add(const char *pstr1, const char *pstr2)
{
  size_t len = strlen(pstr1) + strlen(pstr2) + 1;
  char *ptmp = new char(len);
  strcpy(ptmp, pstr1);
  strcat(ptmp, pstr2);
  return ptmp;;
}
void test()
{
  int ia = 3, ib = 4, ic = 5;
  double da = 1.1, db = 5.5;
  char ca = 'a', cb = 1;
  string s1 = "hello";
  string s2 = "world";
  const char *pstr1 = "hubei";
  const char *pstr2 = ",wuhan";
  cout << "add(ia, ib) = " << add(ia, ib) << endl;//隐式实例化
  cout << "add(da, db) = " << add<double>(da, db) << endl;//显示实例化
  cout << "add(ca, cb) = " << add(ca, cb) << endl;
  cout << "add(s1, s2) = " << add(s1, s2) << endl;
  /* cout << "add(ia, db) = " << add(ia, db) << endl;//函数模板必须进行严格的
推导,如果没有普通函数形式,这就话就error */
  cout << "add(ia, ib, ic) = " << add(ia, ib, ic) << endl;
  cout << "add(pstr1, pstr2) = " << add(pstr1, pstr2) << endl;
}

3.1.5、函数模板的参数类型

1、类型参数,class T 这种就是类型参数
2、非类型参数 常量表达式,整型:bool/char/short/int/long/size_t,注意:float/double这些就不是整型

代码例子:

#include<iostream>
using namespace std;

template <typename T = int, short kMin = 10>
T multiply(T x, T y)
{
	return x * y * kMin;
}
int main()
{
	int ia = 3, ib = 4;
	double da = 3.3, db = 4.4;
	cout << "multiply(ia, ib) = " << multiply(ia, ib) << endl; 
	cout << "multiply(ia, ib) = " << multiply<int, 4>(ia, ib) << endl;
	cout << "multiply(ia, ib) = " << multiply<double, 4>(da, db) << endl;
}

代码运行结果:

3.1.6、成员函数模板

就是类的成员函数也可以设置为模板

class Point
{
public:
  //.............
  //成员函数模板,成员函数模板也是可以设置默认值
  template <typename T = int>
  T func()
 {
    return (T)_dx;
 }
private:
  double _dx;
  double _dy;
};
void test()
{
Point pt(1.1, 2.2);
cout << "pt.func() = " << pt.func<int>() << endl;
cout << "pt.func() = " << pt.func<double>() << endl;
cout << "pt.func() = " << pt.func() << endl;
}

3.2、类模板

使用与函数模板也差不多,只是要注意模板的嵌套(函数模板与类模板都可以嵌套,比如函数参数是模板,类模板里面还有类模板),直接使用例子看类模板。

#include<iostream>
using namespace std;

//类模板
template <typename T, size_t kSize = 10>//类型参数T与非类型参数kSize
class Stack
{
public:
	Stack()
		: _top(-1)
		, _data(new T[kSize]())
	{
	}
	~Stack();
	bool empty() const;
	bool full() const;
	void push(const T &t);
	void pop();
	T top() const;
private:
	int _top;
	T *_data;
};
//类模板在类外面定义成员函数时候需要注意,模板是有类型的,需要使用参数加类型
template <typename T, size_t kSize>
Stack<T, kSize>::~Stack()
{
	if (_data)
	{
		delete[] _data;
		_data = nullptr;
	}
}
template <typename T, size_t kSize>
bool Stack<T, kSize>::empty() const
{
	return -1 == _top;//_top = -1
}
template <typename T, size_t kSize>
bool Stack<T, kSize>::full() const
{
	return _top == kSize - 1;
}
template <typename T, size_t kSize>
void Stack<T, kSize>::push(const T &t)
{
	if (!full())
	{
		_data[++_top] = t;
	}
	else
	{
		cout << "The Stack is full, cannot push any data" << endl;
	}
}
template <typename T, size_t kSize>
void Stack<T, kSize>::pop()
{
	if (!empty())
	{
		--_top;
	}
	else
	{
		cout << "The Stack is empty" << endl;
	}
}
template <typename T, size_t kSize>
T Stack<T, kSize>::top() const
{
	return _data[_top];
}
void test() 
{
	Stack<int, 8> st;
}
void test1()
{
	Stack<string> st;
}
int main()
{
	test();
	test1();

	return 0;
}

注意:

模板的嵌套:

//嵌套模版类的模版类
#include <iostream>
using namespace std;

template<class T>
class Outside			//外部Outside类定义
{
public:
	template <class R>
	class Inside		//嵌套类模板定义
	{
	private:
		R r;
	public:
		Inside(R x)	//模板类的成员函数可以在定义时实现
		{
			r=x;
		}
		//void disp();
		void disp() {cout << "Inside: " << r << endl;}
	};

	Outside(T x) : t(x)			//Outside类的构造函数
	{}
	
	//void disp();
	void disp()
	{
		cout<<"Outside:";
		t.disp();
	}

private:
	Inside<T> t;
};

//template<class T>
//template<class R>
//void Outside<T>::Inside<R>::disp()	//模板类的成员函数也可以在定义外实现
//{			//但必须是在所有类定义的外边,不能放在Outside内Inside外去实现.
//	cout<<"Inside: "<<Outside<T>::Inside<R>::r<<endl;
//}

//template<class T>
//void Outside<T>::disp()
//{
//	cout<<"Outside:";
//	t.disp();
//}

int main()
{
	Outside<int>::Inside<double> obin(3.5);	//声明Inside类对象obin
	obin.disp();

	Outside<int> obout(2);		//创建Outside类对象obout
	obout.disp();

	return 0;
}

代码运行结果:

#在 Outside 类内使用 Inside<T> t; 语句声明了 Inside<T> 类的对象,在 Outside 模板类对象创建时,首先采用 隐式实例化 先生成 Inside<T> 类的定义,而后根据此定义创建对象成员 t

总结:

#模板的套嵌可以理解成在另外一个模板里面定义一个模板。以模板(类,或者函数)作为另一个模板(类,或者函数)的成员,也称 成员模板
# 成员模版不能声明为 virtual

模板做参数:

#模板包含类型参数(如 class Type )和非类型参数(如 int NUM NUM 是常量),实际上, 模板的参数可以是另一个模板 ,也就是说,下述形式是合法的:

template<template <class T1> class T2, class T3,int Num>

#上述简单示例将原来简单的 class T2 Typename T2 扩充为 template <class T1> class T2 ,来看一段示例代码。
//A模板做为B模板的参数

//文件“Stack.h”的内容如下
template <class T,int num>		//类型参数表
class Stack						//Stack类定义
{
	private:
		T sz[num];				//存储空间,用数组表示
	public:
		int ReturnNum();		//判断栈是否为空
};

template<class T1,int num1>		//参数列表不要求字字相同,但形式要相同
int Stack<T1, num1>::ReturnNum()
{
	return num1;				//返回数组大小
}


#include <iostream>
//#include "Stack.h"
using namespace std;

template<template<class Type,int NUM> class TypeClass, class T1, int N>
void disp()				//函数模板,其类型参数表中包含一个类模板
{
	TypeClass<T1,N> ob;		//类模板的隐式实例化,创建对象ob
	cout<<ob.ReturnNum()<<endl;	//调用ob的public成员函数
}

int main()
{
	disp<Stack,int,8>();		//函数模板的隐式实例化,并调用
	system("pause");
	return 0;
}

#上述代码中定义了函数模板disp(),该模板的类型参数表中又包含了一个类模板TypeClass,在函数模板disp内可以对类TypeClass进行实例化处理。

四、可变模板参数

是C++11新增的最强大的特性之一,它对参数进行了高度的泛化,它能表示0到任意个数、任意类型的参数。

4.1、模板参数包

template<typename… Args> class tuple;//tuple是元组的意思,其模板参数就是模板参数包

Args标识符的左侧使用了省略号,在C++11中Args被称为“模板参数包”,表示可以接受任意多个参数作为模板参数,编译器将多个模板参数打包成“单个”的模板参数包.

4.2、函数参数包

template<typename…T> 
void f(T…args);//args就是函数参数包 

args 被称为函数参数包,表示函数可以接受多个任意类型的参数.
在C++11标准中,要求函数参数包必须唯一且是函数的最后一个参数; 模板参数包则没有。
当使用参数包时,省略号位于参数名称的右侧,表示立即展开该参数,这个过程也被称为解包。

4.3、可变模板参数的优势(有两条)

1、参数个数,那么对于模板来说,在模板推导的时候,就已经知道参数的个数了,也就是说在编译的时候就确定 了,这样编译器就存在可能去优化代码

#获取可变模板参数的个数
sizeof...(Args)
sizeof...(args)

2、参数类型,推导的时候也已经确定了,模板函数就可以知道参数类型了。

#include<iostream>
#include<string>
using namespace std;

template <typename... Args>
void print(Args... args)
{
	cout << "sizeof...(Agrs) = " << sizeof...(Args) << endl;
	cout << "sizeof...(agrs) = " << sizeof...(args) << endl;
}
void display()
{
	cout << endl;
}
template <typename T, typename... Args>
void display(T t, Args... args)
{
	cout << t << " ";
	display(args...);//当... 位于args右边的时候叫做解包
}
void test()
{
	string s1 = "hello";
	print();
	print(1, 2.2);
	print('a', true, s1);
	print(1, 2.2, 'b', "hello");
}
void test2()
{
	string s1 = "hello";
	display();
	display(1, 2.2);
	display('a', true, s1);
	display(1, 2.2, 'b', "hello");
}
template <class T>
T sum(T t)
{
	return t;
}
template <typename T, typename... Args>
T sum(T t, Args... args)
{
	return t + sum(args...);
}
void test3()
{
	cout << "sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) = "
		<< sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) << endl;
}

test2运行结果:

test3运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_56054625

你的鼓励是我写下去最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值