C++之——类模板与函数模板用法

模板是一种对类型进行参数化的工具;
通常有两种形式:函数模板和类模板;
函数模板针对仅参数类型不同的函数;
类模板针对仅数据成员和成员函数类型不同的类。
使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个swap函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个swap模板函数,即可以实现int 型,又可以实现double型的交换。模板可以应用于函数和类。下面分别介绍。
注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
一、函数模板通式
1、函数模板的格式:
template <class 形参名,class 形参名,......>
返回类型 函数名(参数列表)
{
函数体
}
其中template和class是关键字class可以用typename 关键代替在这里typename 和class没区别,<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。比如swap的模板函数形式为
template <class T> void swap(T& a, T& b){},当调用这样的模板函数时类型T就会被被调用时的类型所代替,比如swap(a,b)其中a和b是int 型,这时模板函数swap中的形参T就会被int 所代替,模板函数就变为swap(int &a, int &b)。而当swap(c,d)其中c和d是double类型时,模板函数会被替换为swap(double &a, double &b),这样就实现了函数的实现与类型无关的代码。
2、注意:对于函数模板而言不存在 h(int,int) 这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行 h(2,3) 这样的调用,或者int a, b; h(a,b)。
二、类模板通式
1、类模板的格式为:
template<class  形参名,class 形参名,…>   
class 类名
{ ... };
类模板和函数模板都是以template开始后接模板形参列表组成,模板形参不能为空,一但声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。比如
template<class T> class A{public: T a; T b; T hy(T c, T &d);};
在类A中声明了两个类型为T的成员变量a和b,还声明了一个返回类型为T带两个参数类型为T的函数hy。
2、类模板对象的创建:比如一个模板类A,则使用类模板创建对象的方法为A<int> m;在类A后面跟上一个<>尖括号并在里面填上相应的类型,这样的话类A中凡是用到模板形参的地方都会被int 所代替。当类模板有两个模板形参时创建对象的方法为A<int, double> m;类型之间用逗号隔开。
3、对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;用这种方法把模板形参设置为int是错误的(编译错误:error C2079: 'a' uses undefined class 'A<int>'),类模板形参不存在实参推演的问题。也就是说不能把整型值2推演为int 型传递给模板形参。要把类模板形参调置为int 型必须这样指定A<int> m。
4、在类模板外部定义成员函数的方法为:
template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体},
比如有两个模板形参T1,T2的类A中含有一个void h()函数,则定义该函数的语法为:
template<class T1,class T2> void A<T1,T2>::h(){}。
注意:当在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致。
5、再次提醒注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。

用类模板实现对任意类型数据的存取实例:

#include <iostream>
#include <cstdlib>
using namespace std;
struct Student {
	int id;
	float gpa;//平均分
};
template <class T>
class Store {//类模板,实现对任意类型数据进行存取
private:
	T item;//item用于存放任意类型的数据
	bool haveValue;//haveValue标记item是否已被存入内容
public://成员函数用来操作数据成员
	Store();//构造函数
	T &getElem();
	void putElem(const T &x);//存入数据函数
};

template <class T>//以下是类成员函数模板
Store<T>::Store():haveValue(false){}//构造函数用来初始化私有数据成员
template <class T>
T &Store<T>::getElem() {//注意写法
	//如试图提取未初始化的数据,则终止程序
	if (!haveValue) {
		cout << "No item present!" << endl;
		exit(1);//使程序完全退出,返回到操作系统
	}
	return item;//返回item中存放的数据
}
template <class T>
void Store<T>::putElem(const T &x) {
	//将haveValue的值置为TRUE,表示item中已存入数组
	haveValue = true;
	item = x;//将x存入item
}


int main()
{
	Store<int> s1, s2;
	s1.putElem(3);
	s2.putElem(-4);
	cout << s1.getElem() << " " << s2.getElem() << endl;

	Student g = { 1000,23 };
	Store<Student> s3;
	s3.putElem(g);
	cout << "The student id is " << s3.getElem().id << endl;
	Store<double> d;
	cout << "Retrieving object D...";
	cout << d.getElem() << endl;
	//d未初始化。执行函数D.getelem()导致程序终止
    return 0;
}


函数传参:(借鉴原文:https://blog.csdn.net/hudfang/article/details/52934655)

(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。

(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

--------------------------------------------------

如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名;
例1
int a ;
const int &ra=a;
ra=1; //错误
a=1; //正确
例2
string foo( );
void bar(string & s);
那么下面的表达式将是非法的:
bar(foo( ));
bar(“hello world”);
原因在于foo( )和”hello world”串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。
引用型参数应该在能被定义为const的情况下,尽量定义为const 。

返回值为引用类型的函数某些情况下可作为表达式的左值,而非引用的则一般不可以。
函数返回值若为引用类型,当返回的是函数的引用形参时,则是对函数外的变量的引用,函数可以作为表达式的左值(被赋予新值)。

而当函数返回的是非引用类型时,返回的值是函数内隐式生成的临时变量,当函数结束析构时释放,函数作为左值被赋予新值没有意义或产生错误。(例外情况,当返回的是函数中用new等动态内存分配函数建立的指针时,可作为左值。)
注:“引用类型”并非是一种新的类型,也没有这种类型,只是为了口头上的方便说的,是对某某类型变量的引用的个人说法。

猜你喜欢

转载自blog.csdn.net/qq_35294564/article/details/81027037