c++的函数模板,类模板

现在的c++编译器实现了c++新增的一项特性——函数模板。函数模板是通用的函数描述,也就是说,它们使用泛型可用具体的类型(如int或double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型(而不是具体类型)的方式编写程序,因此有时也被称为通用编程。由于类型是用参数表示的,因此模板特性有时也被称为参数化类型。

目录

1、函数模板

1.1模板概念

1.2函数模板

1.3使用函数模板

1.4具体化,实例化

1.5普通函数和函数模板的区别

1.6函数版本调用规则

1.7模板的局限

2、类模板

2.1类模板

2.2函数模板和类模板区别

2.3模板类的具体化

2.4将模板用作参数

2.5模板类和友元

1、函数模板

1.1模板概念

模板就是建立通用的模具,从而达到大大提高复用性的功能

例如生活中的模板,很多时候都需要用到一寸照片,但一寸照片模板就有很多,背景有多种颜色,头像也各自不同,但大体相同;学习上,很多人都需要用到ppt来做演讲或作业,就有着许许多多的ppt模板。

模板的特点:

1、模板不可以直接使用,只是一个框架

2、模板的通用并不是万能的

1.2函数模板

c++的泛型编程思想,主要利用的技术就是模板,c++提供了两种模板机制:函数模板和类模板,下面先介绍函数模板的相关知识:

函数模板允许以任意类型的方式来定义函数,其作用就是建立一个通用函数,其函数返回值类型和形参可以不具体制定,用一个虚拟的类型来代表。

函数模板的格式:

template<typename T>

函数声明或定义

名词解释:

template:声明创建模板

typename:表明其后面的符号是一种数据类型,可以用class代替

在标c++98添加关键字typename之前,c++使用关键字class来创建模板

T:通用的数据类型,名称可以替换,通常为大写字母

注意:如果需要多个将同一种算法用于不同类型的函数,请使用模板。如果不考虑向后兼容的问题,并愿意键入较长的单词,则声明类型参数是,应使用关键字typename而不使用class。

1.3使用函数模板

使用函数模板有两种方式:自动类型推导、显示指定类型

#include<iostream>
using namespace std;

//交换整型函数
void swapInt(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}

//交换浮点型函数
void swapDouble(double& a, double& b) {
	double temp = a;
	a = b;
	b = temp;
}

//利用模板提供通用的交换函数
template<typename T>
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

template<typename T>
void func()
{
    cout << "func()" << endl;
}

int main() 
{
	int a = 10;
	int b = 20;

    char c = 'c';
	
	//swapInt(a, b);

	//利用模板实现交换
	//1、自动类型推导
	mySwap(a, b);
    //mySwap(a,c);    //错误。推导不出一致的T类型

	//2、显示指定类型
	mySwap<int>(a, b);

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

    //func()    //错误,模板不能独立使用,必须确定出T的类型
    func<int>();    //利用显示指定类型的方式,给T一个类型,才可以使用该模板。    

	return 0;
}

注意:

1、使用自动类型推导时,必须推导出一致的数据类型T,才可以使用

2、模板必须要确定出T的数据类型,才可以使用

(使用模板是必须确定出通用数据类型T,并且能够推导出一致的类型)

1.4具体化,实例化

为进一步了解模板,必须理解术语实例化和具体化。在代码中包含函数模板本身并不生成函数定义,它只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例。函数调用mySwap(a,b)导致编译器生成一个实例,这种实例化方式被称为隐式实例化。现在c++还允许显示实例化。这意味这可以直接命令编译器创建特定的实例,如myswap<int>(),其语法是,声明所需的种类——用<>符号指示类型,并在声明前加上关键字template:

template void myswap<int>(int,int);

实现了这种特性的编译器看到上述声明后,将使用myswap()模板生成一个使用int类型的实例。也就是说,该声明的意思是“使用myswap()模板生成int类型的函数定义。”

与显式实例化不同的是,显式具体化使用下面两个等价的声明之一:

template <> void myswap<int>(int&,int&);

template <> void myswap(int&,int&);

区别在于,这些声明的意思式“不要使用myswap()模板来生成函数定义,而应使用专门为int类型显示地定义地函数定义”。这些原型必须有自己的函数定义。显式具体化声明在关键字template后包含<>,而显式实例化没有。

注意:试图在同一个文件中使用同一种类型的显式实例化和显式具体化将出错

隐式实例化,显式实例化和显式具体化统称为具体化。它们的相同之处在于,它们表示的都是使用具体类型的函数定义,而不是通用描述。

引入显式实例化后,必须使用给新的语法——在声明中使用前缀template和template<>,以区分显式实例化和显式实例化。

//隐式实例化

template<typename T>

void swap(T &,T &);

//显式具体化

template <> void swap<int>(int&,int&);

//显式实例化

template void swap<int>(int&,int&);

第三代具体化(ISO/ANSI C++版本)

试验了其他具体化放啊后,c++98标准使用了下面的方法。

  1. 对于给定的函数名,可以有非模板函数,模板函数和显式具体化模板函数以及它们的重载版本。
  2. 显式具体化的原型和定义应以template<>打头,并通过名称来指出类型。
  3. 具体化优于常规模板,而非模板函数优先于具体化和常规模板。

如果有多个原型,则编译器在选择原型时,非模板版本优先于显式具体化和模板版本,而显式具体化优先于模板生成的版本。

1.5普通函数和函数模板的区别

1、普通函数调用时可以发生自动类型转换(隐式类型转换)

2、函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换

3、如果利用显式指定类型的方式,可以发生隐式类型转换

#include<iostream>
using namespace std;
//普通函数
int myAdd01(int a, int b)
{
	return a + b;
}

//函数模板
template<class T>
T myAdd02(T a, T b)  
{
	return a + b;
}


int main() 
{

	int a = 10;
	int b = 20;
	char c = 'c';
	
	cout << myAdd01(a, c) << endl; //正确,将char类型的'c'隐式转换为int类型  'c' 对应 ASCII码 99

    //使用函数模板时,如果用自动类型推导,不会发生自动类型转换,即隐式类型转换
	//myAdd02(a, c); // 报错,使用自动类型推导时,不会发生隐式类型转换

	myAdd02<int>(a, c); //正确,如果用显示指定类型,可以发生隐式类型转换
	return 0;
}

建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T

1.6函数版本调用规则

对于函数重载、函数模板和函数模板重载,C++需要(且有)一个定义良好的策略,来决定为函数调用使用哪一个函数定义,尤其是有多个参数时。这个过程称为重载解析 (overloading resolution)。详细解释这个策略将需要很大的篇幅,因此我们先大致了解一下这个过程是如何进行的。

  1. 第1步:创建候选函数列表。其中包含与被调用函数的名称相同的函数和模板函数
  2. 第2步:使用候选函数列表创建可行函数列表。这些都是参数数目正确的函数,为此有一个隐式转换序列,其中包括实参类型与相应的形参类型完全匹配的情况。
  3. 第3步:确定是否有最佳的可行函数。如果有,则使用它,否则该函数调用出错

我们需要了解的是普通函数与函数模板的调用规则:

  1.  如果函数模板和普通函数都可以实现,优先调用普通函数
  2.  可以通过空模板参数列表来强制调用函数模板
  3.  函数模板也可以发生重载
  4.  如果函数模板可以产生更好的匹配,优先调用函数模板
#include<iostream>
using namespace std;

void myPrint(int a, int b)
{
	cout << "调用的普通函数" << endl;
}

template<typename T>
void myPrint(T a, T b) 
{ 
	cout << "调用的模板" << endl;
}

template<typename T>
void myPrint(T a, T b, T c) 
{ 
	cout << "调用重载的模板" << endl; 
}

int main() 
{
	//1、如果函数模板和普通函数都可以实现,优先调用普通函数
	// 注意 如果告诉编译器  普通函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不到
	int a = 10;
	int b = 20;
	myPrint(a, b); //调用普通函数

	//2、可以通过空模板参数列表来强制调用函数模板
	myPrint<>(a, b); //调用函数模板

	//3、函数模板也可以发生重载
	int c = 30;
	myPrint(a, b, c); //调用重载的函数模板

	//4、 如果函数模板可以产生更好的匹配,优先调用函数模板
	char c1 = 'a';
	char c2 = 'b';
	myPrint(c1, c2); //调用函数模板
	return 0;
}

1.7模板的局限

假设有如下模板函数:
template <class T>

void f(T a,T b){...}
通常,代码假定可执行哪些操作。例如,下面的代码假定定义了赋值,但如果T为数组,这种假设将不成立:
a=b;
同样,下面的语句假设定义了<,但如果T为结构,该假设便不成立:

if(a>b)
另外,为数组名定义了运算符>,但由于数组名为地址,因此它比较的是数组的地址,而这可能不是您希望的。下面的语句假定为类型T定义了乘法运算符,但如果T为数组、指针或结构,这种假设便不成立:
T c=a*b;

总之,编写的模板函数很可能无法处理某些类型。另一方面,有时候通用化是有意义的,但 C++语法不允许这样做。例如,将两个包含位置坐标的结构相加是有意义的,虽然没有为结构定义运算符+。一种解决方案是,C++允许您重载运算符+,以便能够将其用于特定的结构或类。这样使用运算符+的模板便可处理重载了运算符+的结构。另一种解决方案是,为特定类型提供具体化的模板定义。

2、类模板

继承(公有、私有或保护-和包含并不总是能够满足重用代码的需要。C++的类模板为生成通用的类声明提供了一种更好的方法(C++最初不支持模板,但模板被引入后就一直在演化,因此有的编译器可能不支持这里介绍的所有特性)。

2.1类模板

类模板作用:建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。

类模板的格式:

template<typename T>

class 类名

名词解释:

template:声明创建模板

typename:表明其后面的符号是一种数据类型,可以用class代替

T:通用的数据类型,名称可以替换,通常为大写字母

2.2函数模板和类模板区别

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数

#include <iostream>
#include <string>
using namespace std;
//类模板
template<class NameType, class AgeType = int> 
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

int main() 
{
    //1、类模板没有自动类型推导的使用方式
	// Person p("孙悟空", 1000); // 错误 类模板使用时候,不可以用自动类型推导
	Person <string ,int>p("孙悟空", 1000); //必须使用显示指定类型的方式,使用类模板
	p.showPerson();

    //2、类模板在模板参数列表中可以有默认参数
	Person <string> p("猪八戒", 999); //类模板中的模板参数列表 可以指定默认参数
	p.showPerson();

	return 0;
}

2.3模板类的具体化

类模板与函数模板很相似,因为可以有隐式实例化、显式实例化和显式具体化,它们统称为具体化(specialization)。模板以泛型的方式描述类,而具体化是使用具体的类型生成类声明。

1.隐式实例化

它们声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义。编译器在需要对象之前,不会生成类的隐式实例化。

2.显式实例化

当使用关键字 template 并指出所需类型来声明类时,编译器将生成类声明的显式实例化 (explicitinstantiation)。声明必须位于模板定义所在的名称空间中。

3.显式具体化

显式具体化(cxplicit specialization)是特定类型(用于换模板中的泛型)的定义。有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同。在这种情况下,可以创建显式具体化。

当具体化模板和通用模板都与实例化请求匹配时,编译器将使用具体化版本。

具体化类模板定义的格式:

template <> class Classname<specialized-type-name> { ... }

2.4将模板用作参数

类模板实例化出的对象,向函数传参的方式:

  1.  指定传入的类型   --- 直接显示对象的数据类型
  2. 参数模板化           --- 将对象中的参数变为模板进行传递
  3. 整个类模板化       --- 将这个对象类型 模板化进行传递
#include <iostream>
#include <string>
using namespace std;

//类模板
template<class NameType, class AgeType = int> 
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

//1、指定传入的类型
void printPerson1(Person<string, int> &p) 
{
	p.showPerson();
}

//2、参数模板化
template <class T1, class T2>
void printPerson2(Person<T1, T2>&p)
{
	p.showPerson();
	cout << "T1的类型为: " << typeid(T1).name() << endl;
	cout << "T2的类型为: " << typeid(T2).name() << endl;
}

//3、整个类模板化
template<class T>
void printPerson3(T & p)
{
	cout << "T的类型为: " << typeid(T).name() << endl;
	p.showPerson();

}

int main() 
{

	Person <string, int >p("孙悟空", 100);
	printPerson1(p);

	Person <string, int >p("猪八戒", 90);
	printPerson2(p);

	Person <string, int >p("唐僧", 30);
	printPerson3(p);

	return 0;
}

2.5模板类和友元

全局函数类内实现 - 直接在类内声明友元即可

全局函数类外实现 - 需要提前让编译器知道全局函数的存在

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

//2、全局函数配合友元  类外实现 - 先做函数模板声明,下方在做函数模板定义,在做友元
template<class T1, class T2> class Person;

//如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到
//template<class T1, class T2> void printPerson2(Person<T1, T2> & p); 

template<class T1, class T2>
void printPerson2(Person<T1, T2> & p)
{
	cout << "类外实现 ---- 姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
}

template<class T1, class T2>
class Person
{
	//1、全局函数配合友元   类内实现
	friend void printPerson(Person<T1, T2> & p)
	{
		cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
	}


	//全局函数配合友元  类外实现
	friend void printPerson2<>(Person<T1, T2> & p);

public:

	Person(T1 name, T2 age)
	{
		this->m_Name = name;
		this->m_Age = age;
	}


private:
	T1 m_Name;
	T2 m_Age;

};

int main() 
{
    //1、全局函数在类内实现
	Person <string, int >p("Tom", 20);
	printPerson(p);

    //2、全局函数在类外实现
	Person <string, int >p("Jerry", 30);
	printPerson2(p);

	return 0;
}

模板类声明也可以有友元。模板的友元分3类:

  1. 非模板友元
  2. 约束(bound)模板友元,即友元的类型取决于类被实例化时的类型
  3. 非约束(unbound)模板友元,即友元的所有具体化都是类的每一个具体化的友元
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值