目录

C++模板初阶 

2. 函数模板

2.1 函数模板概念

2.2 函数模板格式

2.3 函数模板的原理

2.4 函数模板的实例化

2.5 模板参数的匹配原则

3. 类模板

3.1 类模板的定义格式

3.2 类模板的实例化

STL简介

1. 什么是STL

2. STL的版本

3. STL的六大组件

4. STL的重要性

5. 如何学习STL

6.STL的缺陷


C++模板初阶 

泛型编程:不再是针对某种类型,能适应广泛的类型

如下的交换函数:

void Swap(int& left, int& right)
{
    int temp = left;
    left = right;
    right = temp;
}
 
void Swap(double& left, double& right)
{
    double temp = left;
    left = right;
    right = temp;
}
 
void Swap(char& left, char& right)
{
    char temp = left;
    left = right;
    right = temp;
}

使用函数重载虽然可以实现,但是有一下几个不好的地方:

1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数

2. 代码的可维护性比较低,一个出错可能所有的重载均出错

那能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

C++引入了模板的概念,就像在模具中浇筑一样,我们只需要浇筑不同的材料液,就能到由对应材料的物体。C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(数据类型),来获得不同材料的铸件(即生成具体类型的代码

 泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

2. 函数模板

2.1 函数模板概念

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

2.2 函数模板格式

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

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

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

int main()
{
    int a = 0, b = 1;
    double c = 1.1, d = 2.2;


    //不是调用的同一个函数
    /*Swap(a, b);
    Swap(c, d);*/
    swap(a, b);   //库里有

    int* p1 = &a, * p2 = &b;
    swap(p1, p2);

    //Date d1(2026,3,11), d1(2026,3,12)    //自定义类型也可以

	return 0;
}

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

可以定义多个模板参数

template <typename T1, typename T2>
T1 Func(const T1& x, const T2& y)
{
	cout << x << " " << y << endl;

	return x;

}

////函数模板实例化生成具体函数
////函数模板根据调用,自己推到模板参数的类型,实例化出对应的函数
int main()
{
    Func(1,2);
    Func(1,2.2);

    return 0;
}

2.3 函数模板的原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模 板就是将本来应该我们做的重复的事情交给了编译器

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供 调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然 后产生一份专门处理double类型的代码,对于字符类型也是如此。

2.4 函数模板的实例化

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

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

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

template<class T>
T Add(const T& left, const T& right)
{
    return left + right;
}

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

int main()
{
    int a1 = 10, a2 = 20;
    double d1 = 10.1, d2 = 20.2;

    //隐式实例化:让编译器根据实参推演模板参数的实际类型
    //实参传递的类型,推演T的类型
    cout << Add(a1, a2) << endl;
    cout << Add(d1, d2) << endl;

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

    //显示实例化,用指定实例化
    cout << Add<int>(a1, d1) << endl;
    cout << Add<double>(a1, d1) << endl;

    //有些函数无法自动推,只能显示实例化
    double* p1 = Alloc<double>(10);




    return 0;
}

2.5 模板参数的匹配原则

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

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

3. 类模板

3.1 类模板的定义格式

template<class T1, class T2, ..., class Tn> 
class 类模板名
{
    // 类内成员定义
};
// 类模板
template <class T>
//typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		/*_array = (T*)malloc(sizeof(T) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}*/

		_array = new T[capacity];

		_capacity = capacity;
		_size = 0;
	}


	Stack(size_t capacity = 3);

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

	void Push(const T& data);

	// 其他方法...

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

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


int main()
{
    //自定义类型没办法自动推导
    Stack<int> S1;    //int
    Stack<double> S2; //double
    Stack<char> S3;  //char
}

类模板声明和定义分离

//声明
// 类模板
template <class T>
//typedef int DataType;
class Stack
{
public:


	Stack(size_t capacity = 3);

	

	void Push(const T& data);

	// 其他方法...

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

private:
	T* _array;
	int _capacity;
	int _size;
};
//定义
template<class T>
Stack<T>::Stack(size_t capacity)
{
	_array = new T[capacity];

	_capacity = capacity;
	_size = 0;
}

template<class T>
void Stack<T>::Push(const T& data)
{
	// CheckCapacity();
	_array[_size] = data;
	_size++;
}

普通类,类名和类型是一样的
类模板,类名和类型不一样
类名:Stack
类型:Stack<T>

3.2 类模板的实例化

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

 // Vector类名,Vector<int>才是类型
 Vector<int> s1;
 Vector<double> s2;

STL简介

1. 什么是STL

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

2. STL的版本

原始版本

Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意 运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使 用。 HP 版本--所有STL实现版本的始祖。

P. J. 版本

由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低, 符号命名比较怪异。

RW版本

由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。

SGI版本

由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好, 可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码, 主要参考的就是这个版本。

3. STL的六大组件

4. STL的重要性

1.笔试
在OJ笔试中,会使用STL会舒服很多,向链表、队列、栈等操作,不需要像之前一样得先写一个链表、队列、栈出来,可以直接使用STL中的模板产生实例。

2.面试

5. 如何学习STL

一位C++的dalao将STL的学习比喻为三个境界或层次:

第一境界:熟悉并正确使用STL

第二境界:了解泛型技术的内涵与STL的学理乃至实作

第三境界:扩充STL

即学习STL的三个境界:能用、明理、能扩展
在这里推荐一本比较好的书籍:侯捷老师的《STL源码剖析》

6.STL的缺陷

1. STL库的更新太慢了。这个得严重吐槽,上一版靠谱是C++98,中间的C++03基本一些修订。C++11出 来已经相隔了13年,STL才进一步更新。

2. STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。 3. STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。

4. STL的使用会有代码膨胀的问题,比如使用vector/vector/vector这样会生成多份代码,当然这是模板语法本身导致的。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐