模板详解 --- 函数模板与类模板

目录

函数模板

函数模板的格式:

函数模板的实例化:

模板参数的匹配原则

类模板

类模板的格式:

类模板的实例化:

非类型模板参数


我们为什么要使用模板?

        C++使用模板的目的是为了解决C语言中无法解决的一个关键问题。假如在C语言中我们在一个源文件里面写了一个栈,现在要求你使用同一个栈的类型去创建2个栈,一个栈中的数据存储int类型,另一个存储double类型。你会发现你没办法实现这个功能。然而在C++中,我们可以通过使用类模板,同时实例化出一个int类型的栈和一个double类型的栈。除此之外,使用模板是符合泛型编程的理念的。

模板是干什么的?

        模板就是如字面意思一样,它只是一个模板,但是当我们使用函数模板/类模板去实例化对象的时候,编译器会根据你在实例化时给的类型去自动生成对应类型的函数/类对象让你去使用。举个例子,我们比如要实现1对整形数据的交换(swap),1对浮点类型的数据的交换和1对字符型数据的交换。此时我们需要写3个不同的swap函数去分别实现这三次调用。这样的代码看起来过于冗余了,并且这样的代码维护性也较差。此时我们就可以实现一个函数模板,当我们要进行整形数据的交换时,编译器会自动根据我们传的类型去实例化出一个整形的swap函数。

//不使用函数模板时,我们需要自己写3个不同的函数
void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
void Swap(double& x, double& y)
{
	double tmp = x;
	x = y;
	y = tmp;
}
void Swap(char& x, char& y)
{
	char tmp = x;
	x = y;
	y = tmp;
}

//使用函数模板
template 
void Swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

函数模板

        函数模板不是一个函数而是一个模板。函数模板与类型无关,也就是说我们在写函数模板时写与类型无关的代码。函数模板在使用时才会被实例化,并且它是根据具体的实参类型去实例化不同的函数模板的。

函数模板的格式:

template

函数的实现

注意:class是用来定义模板参数的关键字,它也可以用typename取代。(但是不能用struct)

template     //template 
void Swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

函数模板的实例化:

        当你用不同类型的参数去调用该函数模板时,称为函数模板的实例化。下图就是3种不同类型的参数去调用同一个函数模板,从而实例化出了3个不同的函数。

模板详解 --- 函数模板与类模板_第1张图片

模板参数实例化还可以分为:隐式实例化显示实例化

隐式实例化:就是编译器根据你所给的实参去推演模板参数的实际类型。

显式实例化:在函数名的后面<>中去指定模板参数的类型。

template 
void Swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 1;
	int b = 2;
	double c = 1.1;
	double d = 1.2;
	//隐式实例化   
	Swap(a, b);
	Swap(c, d);
                
	//显式实例化
	Swap(a, b);
	Swap(c, d);

	return 0;
}

 注意:

        对于隐式实例化,只允许传同类型的变量 (当只有一个模板T时)。对于上面的例子,我们如果写 Swap(a, c); 的话,在编译期间,编译器通过a去推到T为int,通过c去推到T为double,这会导致编译器无法确定T的类型,因此会报错。 解决方法:可以写Swap(a, (int)c); 我们将c强转为int类型,这样就没问题了。或者我们去使用显式实例化,Swap(a, c); 这样编译器也知道我们想要T为int类型。 

模板参数的匹配原则

一个非模板函数可以和一个同名的函数模板同时存在那么什么情况下会调用非模板函数?什么情况下会调用函数模板?

调用非模板函数:

当你调用该函数的类型与非模板函数的类型相同时,编译器会优先调用非模板函数。

调用函数模板:

如果没有你调用该函数类型的非模板函数时,编译器就会去调用函数模板从而实例化出你指定的类型的函数。如果非模板函数的类型是你想调用的类型,但是你又不想调用非模板函数,你可以使用<>来显式实例化函数模板,此时编译器就会调用函数模板而不是非模板函数了。

void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
	cout << "非模板函数" << endl;
}

template 
void Swap(T& x, T& y)
{
	T tmp = x;
	x = y;
	y = tmp;
	cout << "函数模板" << endl;
}

int main()
{
	int a = 1;
	int b = 2;

	Swap(a, b);		//调用的是非模板函数
	Swap(a, b);//调用的是模板函数
	return 0;
}

类模板

         类模板的的使用方法和函数模板大相径庭。它同样不是一个类,而是一个模板,只有当使用该类去进行实例化对象的时候,才会生成对应类型的类。 

类模板的格式:

template     //模板参数
class className    //类名
{
    //类中的成员变量和成员函数
}

//具体例子
template     //template 
class Stack
{
public:
	Stack() {};
private:
	T*	   _a;
	size_t _capacity;
	size_t _size;
};

类模板的实例化:

        类模板实例化需要在类模板的名字后面加上<>,<>中指定具体的类型

int main()
{
    //类名中的T替换成具体的类型
	Stack st1;
	Stack st2;

	return 0;
}

注意:

1. 类模板名不是真正的类,实例化的结果才是真正的类。

2. 当类模板中的函数在类的外面进行定义时,需要在每个函数的上面一行都加上参数列表,如:template 。在作用域解析符前面需要指定类的类名。(具体示例如下)

template 
void Stack::push_back(const T& x)
{}

小技巧:你就把类名当做 vector使用就可以了。也就是实例化的对象的类型就是vector...

非类型模板参数

模板参数是什么?

        模板参数就是template ,<>中的T1, T2就是模板参数。

模板参数的分类有哪些?

        模板参数可以分为:类型形参 和 非类型形参

类型形参:在参数列表中,跟在class或typename后面的参数类型的名称。(上面的T1,T2就是)

非类型形参:用一个常量去作为类/函数模板的一个参数。        (下面的例子中的N就是)

//非类型模板参数
template     //这里的T就是类型形参,N是非类型形参且N是个常量
class Array                  //C++11中Array就使用了非类型形参
{
public:
	Array() {}

private:
	T _a[N];	//静态数组
};

int main()
{
    Array a;    //创建了一个大小为10的整形数组
    Array b; //创建了一个大小为15的浮点型数组    

    return 0;
}

 注意:

1. 自定义类型、浮点型、string等类型是无法作为非类型的模板参数的

(通常情况下非类型模板参数使用的都是 字符型 或 整形)

2. 非类型的模板参数必须在编译期间就确定

你可能感兴趣的:(C++)