• C++图解模板


    c++模板

    1.内容引入

    ​ 不知道大家是否在高中时背过英语范文模板,以下就是博主的回忆:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QNn4QUSM-1662721853348)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909164444041.png)]

    ​ 这篇模板是一些英语比较好的老师写的。

    ​ 每当碰到感谢信时,我都会狂喜,尽管感谢的内容不同,地点不同,我都可以去根据模板,再根据作文分析模板的那些空对应应该填入什么。

    其实呢c++中也用模板,但是这个时候,我们是写模板的人,而编译器变成了那个根据模板照葫芦画瓢的人

    2.模板函数

    c语言写交换函数
    #include
    using namespace std;
    
    void Swapi(int* a, int* b)
    {
    	int tmp = *a;
    	*a = *b;
    	*b = tmp;
    }
    
    void Swapd(double* a, double* b)
    {
    	double tmp = *a;
    	*a = *b;
    	*b = tmp;
    }
    
    //……
    
    int main()
    {
    	int a = 1, b = 2;
    	Swapi(&a, &b);
    	double c = 1.1, d = 2.2;
    	Swapd(&c, &d);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    ​ 要实现不同类型的交换,实参不仅要传地址,而且不同类型的函数的名字要保持不同

    至于为什么会这样,大家可以去看看我的文章。解释了为什么c语言不支持函数重载:

    (196条消息) C++入门语法之函数重载与引用的再度解析_龟龟不断向前的博客-CSDN博客


    C++写交换函数
    #include
    using namespace std;
    
    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;
    }
    
    //……
    
    int main()
    {
    	int a = 1, b = 2;
    	Swap(a, b);
    
    	double c = 1.1, d = 2.2;
    	Swap(c, d);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    ​ C++在语法上增加了引用和函数重载,在一定程度上弥补了c语言的不足,但是上述代码明明逻辑很相似,却还是要我们去实现不同类型的代码,对于我们这种懒人来说,简直就是煎熬,连我马云爸爸也说过懒人创造世界。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GH6dYuwx-1662721853352)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909170100833.png)]

    ​ 但是计算机他是一个任劳任怨的好铁,不来不会感到疲劳,厌倦,是一个头脑优点笨笨的但是计算能力超强的大铁块。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n8sbDawb-1662721853355)(D:\gitee仓库\博客使用的表情包\玩电脑.jpg)]


    模板交换函数的语法及其原理
    语法
    #include
    using namespace std;
    
    template <class T>
    void Swap(T& x, T& y)
    {
    	T tmp = x;
    	x = y;
    	y = tmp;
    }
    
    int main()
    {
    	int a = 1, b = 2;
    	Swap(a, b);
    
    	double c = 1.1, d = 2.2;
    	Swap(c, d);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    ​ 这样写交换函数是不是就轻松多了,但是我们思考以下,上述代码调用的是一个Swap函数还是两个Swap函数呢

    回顾我们说的模板,是我们写的模板,然后编译器照着模板帮我们写出了intdouble类型的交换函数。


    原理

    图解:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5AIhvYQ7-1662721853358)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909171041891.png)]


    我们也可以通过调试上述代码,转到反汇编,看看调用的函数是否真的是不同的函数。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cEYJkyhC-1662721853360)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220909171157206.png)]


    理解显示实例化和隐式实例化

    我们那模板加法函数来理解

    #include
    using namespace std;
    
    T Add(const T& x,const T& y)
    {
    	return x + y;
    }
    
    int main()
    {
    	int a = 1, b = 2;
    	double c = 1.1, d = 2.2;
    
    	cout << Add(a, b) << endl;//编译器要自己推类型的是隐式实例化
    	cout << Add(c, d) << endl;
    
    	//cout << Add(a, c) << endl;//error这样的写法就错了,为难编译器了,编译器也推不出来了
    	cout << Add<int>(a, c) << endl;//不需要编译器去推的是显示实例化
    	cout << Add<double>(b, d) << endl;
    
    	cout << Add(a, (int)c) << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    编译器要自己去推T是什么类型的,就是隐式实例化

    而由我们告诉编译器T是什么类型的,就是显示实例化


    关于编译器也是懒人这件事

    我们来看几道模板函数的代码来看看编译器是如何做事的:

    #include
    using namespace std;
    
    int Add(int left, int right)
    {
    	return left + right;
    }
    
    // 通用加法函数
    template<class T>
    T Add(T left, T right)
    {
    	return left + right;
    }
    
    int main()
    {
    	Add(1, 2);       // 与非模板函数匹配,编译器不需要特化
    	Add<int>(1, 2);  // 调用编译器特化的Add版本
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    ​ 如果调试了上述代码就会发现,编译器第一次调用的是第一个Add函数,第二次由于我们的指定,编译器调用的是模板加法函数。


    #include
    using namespace std;
    
    int Add(int left, int right)
    {
    	return left + right;
    }
    
    template < class T1, class T2>
    T1 Add(const T1 x,const T2 y)
    {
    	return x + y;
    }
    
    int main()
    {
    	Add(1, 2);
    	Add(1, 2.0);//如果不写模板,会进行一个类型转换,再去调用第一个
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3.类模板

    由于c++的顺序表是用vector表示的,下面咱们的类名也用vector表示

    像以前我们实现一个顺序表是这样的。

    typedef int VDateType;
    class vector
    {
    public:
    	//……
    private:
    	VDateType* _a;
    	size_t _size;
    	size_t _capacity;
    };
    
    int main()
    {
    	vector v1;
    	vector v2;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    但是我们无法让v1是int类型的顺序表,v2是double类型的顺序表。


    用模板类来实现

    #include
    #include
    using namespace std;
    
    namespace kcc
    {
    	template<class T>
    	class vector
    	{
    	public:
    		vector()
    			:_a(nullptr)
    			, _size(0)
    			, _capacity(0)
    		{}
    
    		// 拷贝构造和operator= 这里涉及深浅拷贝问题,还挺复杂,后面具体再讲
    
    		~vector()
    		{
    			delete[] _a;
    			_a = nullptr;
    			_size = _capacity = 0;
    		}
    
    		void push_back(const T& x)
    		{
    			if (_size == _capacity)
    			{
    				int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
    				T* tmp = new T[newcapacity];
    				if (_a)
    				{
    					memcpy(tmp, _a, sizeof(T) * _size);
    					delete[] _a;
    				}
    				_a = tmp;
    				_capacity = newcapacity;
    			}
    
    			_a[_size] = x;
    			++_size;
    		}
    
    		// 读+写
    		T& operator[](size_t pos);
    		size_t size();
    	private:
    		T* _a;
    		size_t _size;
    		size_t _capacity;
    	};
    
    	// 模板不支持分离编译,也就是声明在.h ,定义在.cpp,原因后面再讲
    	// 建议就是定义在一个文件 xxx.h  xxx.hpp
    	// 在类外面定义
    	template<class T>
    	T& vector<T>::operator[](size_t pos)
    	{
    		assert(pos < _size);
    
    		return _a[pos];
    	}
    
    	template<class T>
    	size_t vector<T>::size()
    	{
    		return _size;
    	}
    }
    
    int main()
    {
    	kcc::vector<int> v1;		// int
    	v1.push_back(1);
    	v1.push_back(2);
    	v1.push_back(3);
    	v1.push_back(4);
    
    	// v1.operator[](3);
    	//cout << v1[3] << endl;
    	//cout << v1[5] << endl;
    	for (size_t i = 0; i < v1.size(); ++i)
    	{
    		v1[i] *= 2;
    	}
    	cout << endl;
    
    	for (size_t i = 0; i < v1.size(); ++i)
    	{
    		cout << v1[i] << " ";
    	}
    	cout << endl;
    
    
    	kcc::vector<double> v2;   // double
    	v2.push_back(1.1);
    	v2.push_back(2.2);
    	v2.push_back(3.3);
    	v2.push_back(4.4);
    
    	for (size_t i = 0; i < v2.size(); ++i)
    	{
    		cout << v2[i] << " ";
    	}
    	cout << endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    如果内部成员函数在类的外面定义的话,要加上类名::

    当然了,本文章并不是重点介绍顺序表vector的实现,而是让大家看看类模板的效果

    vector会在后续的文章中更新,敬请期待!

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OPoAfNMy-1662721853365)(D:\gitee仓库\博客使用的表情包\给点赞吧.jpg)]
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sRpdbA8n-1662721853367)(D:\gitee仓库\博客使用的表情包\要赞.jpg)]

    感谢观看!

  • 相关阅读:
    探讨Unity新的收费模式:对开发者与游戏行业的影响、负面因素的解析及面对挑战的建议
    C# 调用外部EXE程序,接收exe传递的参数,python打包成exe文件
    Ubuntu镜像下载地址
    ucOS-II在STM32F1移植
    phpcms头像上传漏洞
    工业智能网关BL110详解之1:实现三菱 PLC FX1S 接入Modbus TCP Server云平台
    你好,法语!A2知识点总结(2)
    字节的一个小问题 npm 和 yarn不一样吗?
    技术分享 oracle中fm的作用
    centos pip失效
  • 原文地址:https://blog.csdn.net/m0_64361907/article/details/126788130