C++模板初阶学习

目录

  • 1.函数模板
    • 1.1函数模板概念
    • 1.2函数模板格式
    • 1.3函数模板的原理
    • 1.4函数模板实例化
      • 隐式实例化
      • 显示实例化
    • 1.5模板参数适配原则
  • 2.类模板
    • 2.1类模板的定义格式
    • 2.2类模板实例化
  • 总结

1.函数模板

如何实现一个通用的交换函数呢?

#include
using namespace std;
void Swap(int& p1, int& p2)
{
	int tmp = p1;
	p1 = p2;
	p2 = tmp;
}
void Swap(double& p1, double& p2)
{
	double tmp = p1;
	p1 = p2;
	p2 = tmp;
}
void Swap(char& p1, char& p2)
{
	char tmp = p1;
	p1 = p2;
	p2 = tmp;
}
int main()
{
	int a = 10, b = 20;
	cout << "a=" << a << " " << "b=" << b << endl;
	Swap(a, b);
	cout << "a=" << a << " " << "b=" << b << endl;
	double c = 10.1, d = 20.2;
	cout << "c=" << c << " " << "d=" << d << endl;
	Swap(c, d);
	cout << "c=" << c << " " << "d=" << d << endl;
	char x = 'a', y = 'b';
	cout << "x=" << x << " " << "y=" << y << endl;
	Swap(x, y);
	cout << "x=" << x << " " << "y=" << y << endl;
	return 0;
}

代码运行的结果为:
C++模板初阶学习_第1张图片
使用函数重载虽然可以实现,但是有一下几个不好的地方:

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错

我们可以使用以下代码实现:

template<class T>
void Swap(T& p1, T& p2)
{
	T tmp = p1;
	p1 = p2;
	p2 = tmp;
}
int main()
{
	int a = 10, b = 20;
	cout << "a=" << a << " " << "b=" << b << endl;
	Swap(a, b);
	cout << "a=" << a << " " << "b=" << b << endl;
	double c = 10.1, d = 20.2;
	cout << "c=" << c << " " << "d=" << d << endl;
	Swap(c, d);
	cout << "c=" << c << " " << "d=" << d << endl;
	char x = 'a', y = 'b';
	cout << "x=" << x << " " << "y=" << y << endl;
	Swap(x, y);
	cout << "x=" << x << " " << "y=" << y << endl;
	return 0;
}

代码运行的结果为:
C++模板初阶学习_第2张图片
上面的代码也可以实现不同类型的交换,其中涉及了模板的知识点,接下来我们一起学习一下。

1.1函数模板概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

1.2函数模板格式

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
template<typename T>//模板参数--类型
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}

其中,typename是用来定义模板的关键字,也可以使用class,不能使用struct代替class

1.3函数模板的原理

在上面的代码中,我们使用函数模板调用不同类型的数据进行交换,调用的是同一个函数吗?
汇编代码:

	int a = 10, b = 20;
001227FF  mov         dword ptr [a],0Ah  
00122806  mov         dword ptr [b],14h  
	//cout << "a=" << a << " " << "b=" << b << endl;
	Swap(a, b);
0012280D  lea         eax,[b]  
00122810  push        eax  
00122811  lea         ecx,[a]  
00122814  push        ecx  
00122815  call        std::operator<<<std::char_traits<char> > (0121460h)  
0012281A  add         esp,8 
	double c = 10.1, d = 20.2;
0012281D  movsd       xmm0,mmword ptr [__real@4024333333333333 (0129B48h)]  
00122825  movsd       mmword ptr [c],xmm0  
0012282A  movsd       xmm0,mmword ptr [__real@4034333333333333 (0129B58h)]  
00122832  movsd       mmword ptr [d],xmm0  
	//cout << "c=" << c << " " << "d=" << d << endl;
	Swap(c, d);
00122837  lea         eax,[d]  
0012283A  push        eax  
0012283B  lea         ecx,[c]  
0012283E  push        ecx  
0012283F  call        Swap<char> (012145Bh)  
00122844  add         esp,8 

在编译器编译阶段,编译器根据传入的参数,推导出对应的模板参数类型,从而实例化出具体的函数!
C++模板初阶学习_第3张图片
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器

1.4函数模板实例化

实现Add()函数不同类型的数据相加
eg1:

template <class T1,class T2>
T1 Add(T1& x1, T2& x2)
{
	return x1 + x2;
}
int main()
{
	int a = 10;
	double d = 20.1;
	cout << Add(a, d) << endl;
	return 0;
}

代码运行的结果为:
C++模板初阶学习_第4张图片
eg2:

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a = 10;
	double d = 20.1;
	cout << Add(a, d) << endl;
	return 0;
}

代码编译运行的结果:
C++模板初阶学习_第5张图片

当函数模板只有一个参数时,而传递不同类型的实参,编译器因调用不明确而无法推导实例化出具体函数而报错,这就涉及函数模板实例化问题。

函数模板实例化概念:

不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化

隐式实例化

隐式实例化:编译器根据实参推演模板参数的实际类型。

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a = 10;
	double d = 20.1;
	cout << Add(a, (int)d) << endl;//强制类型转换
	return 0;
}

通过强制类型转换,是传递的数据为同一类型,编译器便可以实例化出具体的函数,需要注意的是强制类型转换的临时变量具有常性,函数参数需要加const修饰,以上便是隐式实例化的过程。

显示实例化

显示实例化:在函数名后的<>中指定模板参数的实际类型
eg1:

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
void Test()
{
	int a = 10;
	double d = 20.1;
	cout << Add<int>(a, d) << endl;
}

代码编译运行的结果为:
C++模板初阶学习_第6张图片

如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译将会报错。

eg2:

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

int main()
{
	int* p = Alloc(10);
	return 0;
}

C++模板初阶学习_第7张图片
以上的代码就不能通过隐式实例化调用模板函数,需要通过函数实例化才能调用。
正确的调用方式:

T* Alloc(int n)
{
	return new T[n];
}

int main()
{
	int* p = Alloc<int>(10);
	return 0;
}

1.5模板参数适配原则

1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
	return left + right;
}
void Test()
{
	Add(1, 2); // 与非模板函数匹配,编译器不需要特化
	Add<int>(1, 2); // 调用编译器特化的Add版本
}

2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板

// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
	return left + right;
}
void Test()
{
	Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
	Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}

3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

// 专门处理int的加法函数
int Add(int left, int right)
{
	return left + right;
}
void Test()
{		
	Add(1, 2.0); // 调用普通函数,编译器会自动转换类型
}
//同类型加法函数
template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
void Test()
{		
	Add(1, 2.0); 
	//调用模板函数,编译器不会自动强转而报错,需要自己手动强转或显示实例化
}

2.类模板

2.1类模板的定义格式

eg1:

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

实现不同类型的栈:

typedef int DataType;
class StackInt
{
public:
	StackInt(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	// 其他方法...

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

private:
	DataType* _array;
	int _capacity;
	int _size;
};

typedef double DataType;
class StackDouble
{
public:
	StackDouble(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	// 其他方法...

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

private:
	DataType* _array;
	int _capacity;
	int _size;
};

int main()
{
	StackInt s1; // int
	StackDouble s2; // double

	return 0;
}

平时我们实现要使用栈存储不同类型的数据的时候,需要不同的栈的才能实现,需要对已有其他类型的栈进行相应的修改,即类名以及函数名都要修改;当我们需要添加不同功能函数时,不同的栈都要实现一遍,我们可以使用类模板解决这个问题!

eg2:

template<class T>
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (T*)malloc(sizeof(T) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = capacity;
		_size = 0;
	}

	void Push(T data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}

	// 其他方法...

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

private:
	T* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack<int> s1; // int
	Stack<double> s2; // double
	return 0;
}

使用类模板实现不同类型的栈,需要显示实例化。

eg3:

template<class T>
class Stack
{
public:
//声明
	Stack(size_t capacity = 3);

	void Push(T data);

	// 其他方法...

	~Stack();

private:
	T* _array;
	int _capacity;
	int _size;
};
//定义
template<class T>
Stack<T>::Stack(size_t capacity)
{
	_array = (T*)malloc(sizeof(T) * capacity);
	if (NULL == _array)
	{
		perror("malloc申请空间失败!!!");
		return;
	}

	_capacity = capacity;
	_size = 0;
}
template<class T>
void Stack<T>::Push(T data)
{
	// CheckCapacity();
	_array[_size] = data;
	_size++;
}
template<class T>
Stack<T>::~Stack()
{
	if (_array)
	{
		free(_array);
		_array = NULL;
		_capacity = 0;
		_size = 0;
	}
}
int main()
{
	Stack<int> s1; // int
	Stack<double> s2; // double
	return 0;
}

使用类模板,要实现类成员函数声明和定义分离:①要指定函数的类模板域,如上代码需要在函数名前面加上Stack::②类模板的作用域在最近的{}域里面,在类外面实现成员函数,需要结合类模板一起实现。

2.2类模板实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

//类模板与普通类的区别
//普通类:类名和类型一样,如用普通类实现栈,类名和类型都为Stack
//类模板:类名和类型不同,如用类模板实现栈,类名为Stack,类型为Stack
//类模板实例化
Stack<int> s1; // int
Stack<double> s2; // double

总结

本章我们一起初步学习了C++模板的相关知识,希望对大家了解C++模板有些许帮助,感谢大家阅读,如有不对欢迎纠正!

你可能感兴趣的:(C++初阶学习,c++,学习,开发语言)