模板初识与STL简介

引言

模板是泛型编程的基础

在之前我们介绍过函数重载,可以定义许多函数名相同参数列表不同的重载函数,来实现不同类型的相似操作。调用重载函数时会根据传参调用一个合适的重载函数,这方便了调用这类方法。

但是,函数重载还是有一些不足:
重载的函数还是需要自己定义的,代码复用率不高。当需要增加一种类型的实现时,就需要我们再定义一个重载函数;重载函数的可维护性不高,如果出错了可能需要逐个修改每个重载函数。

泛型编程就可以解决这个问题,可以只写一份对所有类型通用的代码,在需要使用的时候由编译器生成相应的代码,是代码复用的一种手段。模板是泛型编程的基础

C++模板分为函数模板与类模板:
在这里插入图片描述

函数模板

函数模板代表了一类作用相同的函数,与类型无关。函数模板在使用时会由编译器自动实例化,生成一个特定类型的函数

定义

使用
template<typename T1, typename T2, ...typename Tn>
返回值 函数名(参数列表) {}
可以定义一个函数模板,其中typename也可以用class代替。

例如一个交换函数模板:


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

然后就可以像使用函数一样使用函数模板:

扫描二维码关注公众号,回复: 15571012 查看本文章
int main()
{
    
    
	int a = 10;
	int b = 20;
	Swap(a, b);
	cout << a << " " << b << endl;
	return 0;
}

在这里插入图片描述
需要注意的是,template声明的typename只在其接下来的函数内有效

实例化

在使用函数模板时,编译器会生成一个指定类型的函数,即函数模板的实例化。

在指定生成函数的类型时,就有两种方式,即隐式与显式:

隐式实例化

隐式实例化即编译器根据调用时的实参自动指定模板参数的类型:
例如这段代码:

template<typename T>
T Add(const T& a, const T& b)
{
    
    
	return a + b;
}
int main()
{
    
    
	int a = 10;
	int b = 20;
	cout << Add(a, b) << endl;
	return 0;
}

在这里插入图片描述
此时变量ab均为int,编译器自然可以通过两个均为int的实参推演出T的类型为int,这并不难理解。
但是当实参的类型不相同时,就无法确定T究竟是两个参数类型中的哪一个了:

	int a = 10;
	double b = 20;
	//cout << Add(a, b) << endl;  //错误代码,调用Add时模板参数不明确

在这里插入图片描述
在模板中,不会擅自进行类型转换。 比如这种情况,在类型T不明确时,并不确定要将int转化为double还是将double转换为int。

解决的方法当然也很简单:

  1. 可以在传参时将实参的类型显式的转换为一致:
template<typename T>
T Add(const T& a, const T& b)
{
    
    
	return a + b;
}
int main()
{
    
    
	int a = 10;
	double b = 20;
	cout << Add(a, (int)b) << endl;
	return 0;
}

在这里插入图片描述
需要注意的是,类型转换会生成一个临时变量,临时变量具有常性。所以如果模板的形参类型不是const修饰的话,就会发生权限放大,这种方法就是不可取的。

  1. 当然也可以显式的指定类型T,这样编译器就会尝试进行隐式类型转换,即显式实例化

显式实例化

显式实例化即在调用时函数名后的<>中显式的指定模板参数的类型: 函数名<模板参数列表>(实参列表);
例如上面的Add模板生成函数的调用:

int main()
{
    
    
	int a = 10;
	double b = 20;
	cout << Add<int>(a, b) << endl; // Add<int>(a, b)
	return 0;
}

在这里插入图片描述
当指定了模板参数的类型后,编译器当然就能在传参时进行隐式类型转换了,转换失败时就会报错。

需要注意的是:

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

例如:

int Add(const int& a, const int& b)
{
    
    
	return a + b;
}

template<typename T1, typename T2>
T1 Add(const T1& a, const T2& b)
{
    
    
	return a + b;
}
int main()
{
    
    
	cout << Add(1, 2) << endl;
	cout << Add(1, 2.0) << endl;
	return 0;
}

在第一次调用Add函数时,实参类型均为int,所以优先调用非模板函数:
在这里插入图片描述
第二次调用Add函数时,第一个参数类型为int,第二个参数为double,模板生成的Add函数能更好的匹配,所以调用模板:
在这里插入图片描述
3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换:

类模板

类似与函数,我们也可以定义一种与类型无关的类,即类模板。在使用时,再由编译器实例化为对于指定模板参数类型的类类型。

定义

使用
template<typename T1, typename T2, ...typename Tn>
class 类模板名 {};
可以定义一个函数模板,其中typename也可以用class代替。

例如我们可以写一个简陋的栈:

template<typename T>
class Stack
{
    
    
public:
	Stack(size_t size = 0, size_t capacity = 0)  //构造函数
		: _size(size)
		, _capacity(capacity)
		, _date(nullptr)
	{
    
    
		_date = new T[_capacity + 1]{
    
     0 };
	}
	void push(T n)    //压栈
	{
    
    
		if (_size == _capacity)
		{
    
    
			if (_capacity == 0)
			{
    
    
				reserve(6);
			}
			else
			{
    
    
				reserve(_capacity * 2);
			}
		}
		_date[_size] = n;
		++_size;
	}
	void reserve(size_t capacity)    
	{
    
    
		if (capacity > _capacity)
		{
    
    
			T* newdate = new T[capacity + 1]{
    
     0 };
			_capacity = capacity;
			for (int i = 0; i < _size; ++i)
			{
    
    
				newdate[i] = _date[i];
			}
			delete[] _date;
			_date = newdate;
		}
	}
	T top()
	{
    
    
		return _date[_size - 1];
	}
	size_t size()   
	{
    
    
		return _size;
	}
	~Stack()   //析构函数
	{
    
    
		delete[] _date;
		_size = 0;
		_capacity = 0;
	}
private:
	size_t _size;
	size_t _capacity;
	T* _date;
};

对于int型数据:

int main()
{
    
    
	Stack<int> nums;
	nums.push(1);
	nums.push(2);
	nums.push(3);
	nums.push(4);
	nums.push(5);
	cout << nums.top() << endl;
	cout << nums.size() << endl;
	return 0;
}

在这里插入图片描述
对于char型数据:

int main()
{
    
    
	Stack<char> str;
	str.push('a');
	str.push('b');
	str.push('c');
	str.push('d');
	str.push('e');
	cout << str.top() << endl;
	cout << str.size() << endl;
	return 0;
}

在这里插入图片描述
不难发现,对于不同的模板参数类型,这个简陋的栈可以实现其效果。

实例化

不同于函数模板,类模板不能通过参数来推断模板参数的类型,所以类模板的实例化必须在调用时的类模板名后的<>中显式指出模板参数。

例如对上面的栈模板的使用:

int main()
{
    
    
	//实例化类模板并实例化类对象
	Stack<char> str;
	Stack<int> nums;
	Stack<double> dnums;

	//对不同类型的栈堆栈
	str.push('a');
	str.push('b');
	nums.push(3);
	nums.push(4);
	dnums.push(20.0);
	dnums.push(30.0);

	//打印不同类型栈的栈顶元素及元素个数
	cout << str.top() << endl;
	cout << str.size() << endl;
	cout << nums.top() << endl;
	cout << nums.size() << endl;
	cout << dnums.top() << endl;
	cout << dnums.size() << endl;
	return 0;
}

在这里插入图片描述

需要注意的是:类模板名字不是真正的类,而实例化的结果才是真正的类

STL简介

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架

STL六大组件

在这里插入图片描述
容器:string、vector、list、deque、map、set等
算法:find、swap、reverse、sort等

(这里只做了解,接下来将会逐渐详细介绍)

总结

到此,关于模板初阶的内容就介绍完了
包括函数模板与类模板

如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

猜你喜欢

转载自blog.csdn.net/weixin_73450183/article/details/131139199