【C++】C++ 核心进阶(一):泛型编程基石 —— 模板初阶与 STL 体系开篇

目录

一,泛型编程

二,函数模板

1,函数模板的格式:

2,模板函数的原理

3,函数模板的实例化

4,模板参数的匹配原则

三,类模板

1,类模板的定义格式:

2,类模板的使用

3,类模版的内存开辟和释放

四,STL

1,了解STL

2,STL的六大组件:

五,补充:C++当中的隐式类型转换

1,自动类型转换

2,强制类型转换(显式转换)

3,运算符重载中的隐式转换

4,用户定义转换运算符


一,泛型编程

之前在C语言当中,进行交换变量值,对于不同类型的变量的交换需求就需要重新创建一个新的函数来接收不同的类型变量,例如:

对于这种函数只有类型不同的情况,可以创建一个模版,传入不同类型的数据就形成不同的函数。这样的编程就可以称为泛型编程。

泛型编程:编写与类型无关的通用代码,模版是泛型编程的基础。

模版分为:1,函数模版 2,类模版

二,函数模板

1,函数模板的格式:

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

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

typename也可以换成class

返回值类型可以是T,也可以是自己设置的类型

注意:参数列表当中的参数类型为T1,T2,T3......

实例展示:内容交换

cpp 复制代码
//模板函数
//只能同种类型直接进行交换
template<typename T>
void Swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}
//支持同种类型进行交换,也支持不同中类型的交换
//两个函数的函数名相同,根据传入的类型进行匹配函数
template<class T1,class T2>
void Swap(T1& a, T2& b)
{
	T1 tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int i = 1, j = 2;
	double m = 3.0, n = 2.3;
    //同类型调用第一个swap()
	swap(i, j);
	swap(m, n);
    //不同类型调用第二个swap()
	Swap(i, m);
	return 0;
}

2,模板函数的原理

函数模板其实相当于一个蓝图,他不是真正的函数,是编译器在编译期间根据具体的调用实例出的具体函数。

3,函数模板的实例化

使用不同类型的参数使用模版09

函数就是函数的实例化,模板参数的实例化分为:隐式实例化和显式实例化

1,隐式实例化:让编译器根据传入的实参来推演参数类型

cpp 复制代码
//用函数模板生成对应的函数--模板实例化
template <typename T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int i = 2, j = 3;
	double m = 2.4, n = 5.5;
	//推导实例化--先强制类型转换
	cout << Add(i, j) << endl;
	cout << Add(m, n) << endl;
	//先强制类型转换
	cout << Add(i, (int)m) << endl;
	cout << Add((double)i, m) << endl;
	return 0;
}

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

补充解释:显示实例化已经确定了函数参数的类型,将不同类型的实参传入函数的参数时,会生成临时对象并传入其中,当函数执行完后,临时对象也会进行销毁,传入的参数生成临时对象的过程,相当于进行隐式类型转化生成一个新的对象。

4,模板参数的匹配原则

1,一个非模板函数和一个模板函数同时存在,且实参的类型和非模板函数的参数匹配时,会直接使用非模板函数。

2,当两者同时存在但是非模板函数的参数类型和实参不匹配时,优先使用模板函数。

函数的返回值类型T2是double

补充:在模板参数推导时,编译器不会进行隐式类型转换;但在模板显式实例化后(类型已确定)就相当于一个普通函数,函数调用时会发生隐式类型转换。

举个例子:

"模板推导是'看菜下饭'(每次都根据实参重新匹配),上一顿吃的 int,不影响这一顿吃 double。只有当你强行指定'今天只吃 int'(显式指定 <int>)时,厨房才会把 double 硬塞进 int 的盘子里。"

三,类模板

1,类模板的定义格式:

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

class 类模板名

{

//类内成员定义

}

2,类模板的使用

复制代码
//类模板
template <typename T>
class Stack
{
public:
	Stack(int n = 4)
		:array(new T[n])
		, size(0)
		, capacity(n)
	{
	}
	~Satck()
	{
		delete[] array;
		array = nullptr;
		size = capacity = 0;
	}
	void Push(const T& x)
	{
		if (size == capacity)
		{
            //C++中不能使用realloc扩展空间
			//手动扩容--直接创建一个新的对象
			T* tmp = new T[capacity * 2];
			memcpy(tmp, array, capacity*sizeof(T));
			delete[] array;//清理原空间
			array = tmp;
			capacity *= 2;
		}
		array[size++] = x;
	}
private:
	T* array;
	size_t size;
	size_t capacity;
};
int main()
{
	//模板类只有显示实例化
	Stack<int> str1;
	str1.Push(1);
	str1.Push(1);
	str1.Push(1);
	Stack<double> str2;
	str2.Push(1.1);
	str2.Push(1.1);
	str2.Push(1.1);
	return 0;
}

灵魂拷问:

1,template定义类中数组的类型,类似于typedef,那为什么不使用typedef呢?

typedef只能定义一种确定的类型,不能进行随意的切换。

2,为什么C++中不能使用realloc?

当原来的空间不够时,realloc会在一片新的地址开辟新空间,然后将旧字节原封不动的拷贝过来,旧内存空间归还给处理器,旧指针会失效,找不到原来的空间。

如果在旧内存当中开辟了资源,找不到原空间就无法调用析构函数进行资源的释放,造成内存泄漏。

就算提前定义一个变量记录旧地址,依旧不能调用析构函数进行释放,这是因为realloc在拷贝字节后,新对象的内存处于"假活"状态。即:由于realloc是一个一个字节的拷贝,新对象内部的指针依旧指向久空间已经被释放的地址,之后访问新对象时,对象的指针就是"悬挂指针",即指向无效内存的指针 。

所以形成了死结,使不使用析构都不行,而且偏偏类会自己调用析构。

3,析构中的delete会不会和Push中的delete造成冲突?

答:不会

具体原因如下:

补充:

注意:在类模板当中进行声明,在类模板外面定义时,需要重新加上template

并且模板不支持声明和定义放在不同的文件当中

3,类模版的内存开辟和释放

按照1->2->3->4的步骤执行

比较两种情况:

四,STL

1,了解STL

1,STL(标准模版库):是C++标准库的重要组成部分 ,不仅是一个可以复用的组件库,还是一个数据结构和算法的软件框架


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

2,STL的六大组件:

五,补充:C++当中的隐式类型转换

隐式类型转换分为两种:1,自动类型转换 2,强制类型转换

1,自动类型转换

特点:

1,发生在兼容类型之间:可以进行数值转化

2,小范围自动转化为大范围类型

char/short ->int ->long - >float ->double

3,安全进行转换,不丢失数据

4,遵循算术提升原则

转化展示:

2,强制类型转换(显式转换)

主动标明要进行成为的类型

1,强制类型转换可以对不兼容的数据进行转换,但是可能会出错

2,可以跳过范围的限制,但是大范围转换为小范围可能会丢失精度

这里的65转化成16进制就是0x41,在内存当中由小到大就是 41 00 00 00,char刚好只取第一个字节,取到41转化为对应的字符就是'A'

ASCII码值为2的字符为控制字符,无法进行打印,所以就不会显示值。

3,运算符重载中的隐式转换

演示:

复制代码
//运算符重载中的隐式转换
class complex
{
private:
	double real, image;
public:
	complex(double i =0 , double j = 0)
		:real(i)
		,image(j)
	{}
	//const函数:不会修改对象中的变量real,image
	complex operator+(const complex& tr)const
	{
		return complex(real + tr.real, image + tr.image);
	}
	void print()const
	{
		cout << real << "+" << image << "i" << endl;
	}
};
int main()
{
	complex r1 = complex(2, 3);
	complex r2 = r1 + 4; //4转换为complex类型:complex(4,0)
	r2.print();// 6+3i
	//相当于下面
	complex r3 = r1 + complex(4, 0);
	r3.print();//6+3i
	return 0;
}

4,用户定义转换运算符

演示:

复制代码
class Time
{
private: 
	int hour, minutes, seconds;
public:
	Time(int a = 0, int b = 0, int c=0)
		:hour(a)
		,minutes(b)
		,seconds(c)
	{
	}
	//Time转换为int - 转换为秒
	//类型转换运算符:不需要返回值
	//因为int即是函数名的一部分也是返回值类型
	operator int()const
	{
		return hour * 3600 + minutes * 60 + seconds;
	}
	//Time转换为double - 转换为小时
	operator double()const
	{
		//60和60.0不同 带小数和不带小数不同,带小数计算后的值是double类型带小数
		//不带小数就是取整数
		return hour + minutes / 60.0 + seconds / 3600.0;
	}
	void Push()const
	{
		cout << hour << minutes << seconds << endl;
	}
};
int main()
{
	Time t1 = Time(3, 100, 300);
	//隐式转换 Time->int
	int r1 = t1;
	cout << r1 << endl;
	//隐式转换 Time->double
	double r2 = t1;
	cout << r2 << endl;
	//显式转换
	int r3 = (int)t1;
	cout << r3 << endl;
	return 0;
}

运行结果:

17100

4.75

17100

相关推荐
Escalating_xu38 分钟前
【Makefile 进阶】从自动发现源文件、模式规则到目录分离与多模块递归构建
linux·开发语言
艾莉丝努力练剑1 小时前
【AI大模型接入SDK】Gemini模型接入知识体系
java·开发语言·网络·人工智能·网络协议·学习·http
传奇开心果编程1 小时前
【Rust入门知识点学与练】第13课:枚举 Enum
开发语言·学习·rust
一木 之林1 小时前
四、STL 容器与数据结构(进阶)(二)
数据结构·c++·哈希算法
zhanghaha13141 小时前
Python进阶教程:28_queue 队列模块 零基础超详细教程
java·开发语言·python
司小豆1 小时前
第七课:DeepSeek Harness 服务与依赖注入
java·服务器·开发语言·github·ai编程
会周易的程序员1 小时前
5Draft使用说明书
服务器·c++·分布式·raft·共识
weixin199701080161 小时前
[特殊字符]《闲鱼 + 淘宝 + 1688 三平台库存同源:二手ERP主数据治理与超卖防御》(附Python源码)
开发语言·python
ltqshs1 小时前
C语言-链表例程
c语言·开发语言·链表