C++ 初始模板

模板

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;
}

如上述所示,我们在实现函数的时候,有很多函数会像上述一样,实现过程差不多,但是由些许不同,那么我们都把这些实现出来会显得很呆,C++祖师爷也想到了,所以设计了模板。

函数模板

 像上述的三个函数,算法相同,只是 参数的类型不同,那么我们就可以定义 函数模板,这样不管是什么类型,都可以使用这个函数模板,来实现相同的 算法。

语法:

template<typename T1, typename T2,......,typename Tn>

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

 上述的 typename 是用来定义模板参数关键字,也可以使用 class (注意: 不能使用 struct 来代替 class)

其实就是在模板出来的早期用的就是 clsss ,但是后面觉得不好区分就重写定义了 typename 这个关键字。

像上述交换变量的 函数我们就不用写 三个了,写一个就行了:

template<typename T>
void Swap(T& left, T& right)
{
	T temp = left;
	left = right;
	right = temp;
}

我们不管使用内置类型还是自定义类型都可以实现上述的 算法:

 而且,模板函数并不是调用的 这个模板,而是 编译器帮我们实现了 这些重载函数,然后去调用这些重载函数:

 我们发现上述 两个 Swap 函数的地址不同。

 编译器会根据我们 传入的参数来推演,推演出参数的类型:

 编译器推导出之后,就会自己实现这个模板当前对应类型的 实例化函数。

 这里其实就是 模板的 实例化,和对象的实例化有点类似。

 而且,这里推导的类型,可以是任意类型,不管是内置类型 还是 自定义类型都可以推导出来,也就是说不管是什么类型都可以使用 模板推导出来。

像上述这样使用 模板来实现的 编程,也被称为是 泛型编程,因为这里的 函数并不是针对某一种 类型,而是针对 很多的类型。

 模板的  " <> "  当中其实和 函数的参数列表差不多,我们可以指定多个参数类型,同时 模板当中的参数也可以是 函数的返回值类型:

 对于 template 函数模板的作用范围就是这个函数。

 模板函数的实例化

 模板函数的实例化分为两种:

隐式实例化,这是编译器自己去推导出来的实例化函数,但是有的时候,对应的模板参数并不能实现 我们想要的参数类型,如下例子:

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
	cout << Add(a1, a2) << endl;
	cout << Add(d1, d2) << endl;

    cout << Add(a1 , d1 ) << endl; // 不能编译通过

	return 0;
}

我们在模板当中只定义了 一个 T 的模板参数,但是我们传入的参数确是 int 和 double 类型的,这样做编译器就很为难,他不知道应该 使用哪一个 类型作为推导出来的类型,报错:
 

 当我们遇到这种问题的时候,有两种解决方式,第一种就是进行强制类型转换,像上述例子就是把其中一个参数 强转成 另一个参数的类型:

	cout << Add(a1, (int)d1) << endl;
	cout << Add((double)a1, d1) << endl;

  而且向上述 使用 强制转换的方式 实现传参的时候要注意,上述是 传入的是引用:因为强制转换会产生临时变量,像上述的强制转换的 变量传参,不是直接传这个 d1 或者是 a1 这个变量引用给函数形参,而是把强制类型转换所产生的临时变量 赋值传给 函数形参,而 临时变量具有常性,所以,如果我们在函数形参位置不加 const 修饰的话,就会发生权限的放大 ,就会报错:

第二种,就是我们模板实例化的 第二种形式:显式实例化。

	cout << Add<int>(a1, d1) << endl;
	cout << Add<double>(a1, d1) << endl;

这个时候就不需要 编译器自己去进行参数类型的推演了,向上述代码,第一种就直接实例化为了 int ,第二种就是直接实例化为了 double。

显式实例化的 使用的场景最多是如下的 这种形式:

 有些函数不能自动推导 参数类型,只能使用显示实例化。

template<typename T>
T* Carray(int n)
{
	return new T[n];
}

int main()
{
	Carray(10);

	return 0;
}

 编译器进行推导的时候,必须通过参数进行推导,返回值,或者是函数其中的 算法来推导等等的这些方式都是不行的。

所以这里就要使用 显示实例化:

	Carray<double>(10);

通过显示实例化,在指定 模板当中的参数的类型,这样就可以实现上述函数的调用。

类模板

 C++当中想到,在类当中也有类似的,和上述函数算法相同,但是参数类型不同的问题,所以在C++ 当中 有类模板来解决上述问题。

语法:

template<class T1, class T2, ..., class Tn>
class 类模板名
{
    // 类内成员定义
};

我们在C当中实现 栈的时候,是把存储的数据类型 用 typedef 来重新命名,这样做的好处是以后我们想要栈存储其他类型的 数据的时候,只需要修改 typedef 这一行代码就行了,但是这样做只能修改一个 栈的实现。当我现在想要同时实现多个 存储不同数据类型的栈的时候,typedef 就不能实现了。

像上述功能,在C++当中使用 类模板就可以很方便的实现:

template<class T>
class Stack
{
public:
	Stack(size_t a = 3)
	{
		cout << "Stack(size_t a = 1)" << endl;

		_array = new T[capacity];
		if (_array == NULL)
		{
			perror("malloc fail");
			return;
		}

		_capacity = a;
		_size = 0;

	}

	~Stack()
	{
		cout << "~Stack()" << endl;

		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}

protected:
	T* _array;
	int         _size;
	int         _capacity;
};

那么对于上述实现的类模板,我们就可以这样来使用 这些类了:

int main()
{
	Stack<int> s1;
	Stack<char> s1;
	Stack<double> s1;

	return 0;
}

因为我们上述类的构造函数,没有使用 T 这个模板参数,所以,构造函数不一定都要 使用 类模板当中 参数,其实构造函数就不一定需要 使用 推演,像上述的初始化方式函数很香的。

 那么类模板当中也可以传入多个参数,如果我们有需求的话。

 对于 template 类模板的作用范围就是这个类。

 如果类模板当中的成员函数在类外面 定义的话,和普通类的 类外定义有些许不一样。

  •  首先,因为 对于 template 类模板的作用范围就是这个类  那么当我们类外定义的时候,编译器就不认识函数当中的 T 这个模板参数了。所以我们在类外进行 定义成员函数的时候,需要给这个函数重新声明这个 T 模板参数。
  • 其次,对于普通类,类名就是类型,我们在调用普通类的时候,都是直接使用 类名来作为这个 对象的 类的类型的;但是 模板类的类名不是类型,类模板的类型是 类名<模板类型> 这样的形式。所以,我们在 用 " :: " 来指明 这个函数是哪一个 域当中函数的时候,需要使用 类名<模板类型>  这样的方式来表明这个函数的域。

 那么根据上述两点,我们在类模板外声明 其中的成员函数的时候,应该这样来实现:

template <class T>
Stack<T>::Stack()
{
	cout << "Stack(size_t a = 1)" << endl;

	_array = new T[capacity];
	if (_array == NULL)
	{
		perror("malloc fail");
		return;
	}

	_capacity = a;
	_size = 0;
}

猜你喜欢

转载自blog.csdn.net/chihiro1122/article/details/130712640