C++ 模板保姆级详解——template<class T>(什么是模板?模板分哪几类

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

⭐总结:
        所以,总结上面的这么一个技术,C++的祖师爷呢就想到了**【模版】**这个东西,告诉编译器一个模子,然后其余的工作交给它来完成,根据不同的需求生成不同的代码

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

💦C++模板的分类

1️⃣: 函数模板(function tempalte):使用泛型参数的函数(function with generic parameters)

2️⃣:类模板(class template):使用泛型参数的类(class with generic parameters)

三、函数模板

知晓了模版的基本概念后,首先我们要来看的就是**【函数模版】**

💦函数模板概念

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本
通过函数模板,可以编写一种通用的函数定义,使其能够适用于多种数据类型,从而提高代码的复用性和灵活性。

💦函数模板格式

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表)
{
    //……
}
注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)

因此,交换函数就可以这样套用模板:

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

int main()
{
	
	int x = 2, y = 3;
	cout << x << " " << y << endl << endl;
	Swap(x, y);
	cout << x << " " << y << endl << endl;
	cout << "-------------------------------" << endl;

	char a = 'a', b = 'b';
	cout << a << " " << b << endl << endl;
	Swap(a, b);
	cout << a << " " << b << endl << endl;
	cout << "-------------------------------" << endl;


	double c = 2.1, d = 3.1;
	cout << c << " " << d << endl << endl;
	Swap(c, d);
	cout << c << " " << d << endl << endl;
	cout << "-------------------------------" << endl;
	return 0;
}

我们通过这个函数模版,分别传入不同数据类型的参数,通过结果的观察可以发现这个函数模版可以根据不同的类型去做一个自动推导,继而去起到一个交换的功能。

💦函数模板的原理

💬 那我现在想问一个问题,请问它们调用的真的是同一个函数吗?
        当然不是,这里我们三次Swap不是调用同一个函数,其实Swap的时候根据不同的类型通过模板定制出专属你的类型的函数,然后再调用,这里可以通过反汇编观察到:

可以发现,在进行汇编代码查看的时候,被调用的函数模版生成了两个不同的函数,它们有着不同的函数地址,因此可以回答上一小节所提出的问题了,两次所调用的函数是不一样的,是根据函数模版所生成的

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

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

  • 补充:

其实库里面有一个swap函数,因此我们也不需要自己写模板了:

直接套用swap即可:

int main()
{
	
	int x = 2, y = 3;
	cout << x << " " << y << endl << endl;
	swap(x, y);
	cout << x << " " << y << endl << endl;
	cout << "-------------------------------" << endl;

	char a = 'a', b = 'b';
	cout << a << " " << b << endl << endl;
	swap(a, b);
	cout << a << " " << b << endl << endl;
	cout << "-------------------------------" << endl;


	double c = 2.1, d = 3.1;
	cout << c << " " << d << endl << endl;
	swap(c, d);
	cout << c << " " << d << endl << endl;
	cout << "-------------------------------" << endl;
	return 0;
}

💦函数模板的实例化

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

🍎隐式实例化

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

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
	Add(a1, a2); //编译器推出T是int
	Add(d1, d2); //编译器推出T是double
    return 0;
}

**⚠ 注意:**但是我调用的时候如若这样就会出错:

int main()
{
    int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
	Add(a1, d1); //err 编译器推不出来
	/*
该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型
通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有
一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅
    */
}

**此时我们发现在模板函数里面,**形参不匹配,就会出现报错

编译器无法确定这里的T到底是int还是double。此时有两种处理方式:

法一:用户自己来强制转化

int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
    Add(a1, (int)d1); //强制类型转换。或者Add((double)a1, d1);
}

法二:使用显式实例化
接下来进行讲解。

🍉显式实例化

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

继刚才的例子,法二:使用显式实例化**

template<class T>
T Add(const T& left, const T& right)
{
	return left + right;
}
int main()
{
	int a1 = 10, a2 = 20;
	double d1 = 10.0, d2 = 20.0;
    //显示实例化
	Add<int>(a1, d1); //double隐式类型转换成int 
	Add<double>(a1, d2); 
    return 0;
}

💦模板支持多个模板参数

template<class K, class V> //两个模板参数
void Func(const K& key, const V& value)
{
	cout << key << ":" << value << endl;
}
int main()
{
	Func(1, 1); //K和V均int
	Func(1, 1.1);//K是int,V是double
	Func<int, char>(1, 'A'); //多个模板参数也可指定显示实例化不同类型
}

💦模板参数的匹配原则

  • 原则1: 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
//专门处理int的加法函数
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函数?
}

首先,这俩Add可以同时存在,关键是我调用Add时调的是模板函数Add,还是专门的Add?

通过反汇编得知,调用的是专属Add函数。得出结论:编译器在调用时,有现成的就调用现成的,没有就套用模板。当然,我们也有办法强制让编译器走模板函数,如下:

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函数
}

四、类模板

💦类模板的概念

类模板是对成员数据类型不同的类的抽象,它说明了类的定义规则,一个类模板可以生成多种具体的类。与函数模板的定义形式类似类模板也是使用template关键字和尖括号“<>”中的模板形参进行说明,类的定义形式与普通类相同。

💦类模板格式

  • 首先来看到的就是其定义格式,函数模版加在函数上,那对于类模版的话就是加在类上
template<class T1, class T2, ..., class Tn>
class 类模板名
{
    // 类内成员定义
};

我们以下面这个Stack类为例来进行讲解

  • 如果你学习了模版的相关知识后,一定会觉得这个类的限制性太大了只能初始化一个具有整型数据的栈,如果此时我想要放一些浮点型的数据进来的话也做不到,代码如下:
// C++ 正常情况下,如果需要不同类型的类(int char double 等)
typedef int DataType;
class Stack
{
public:
    // 构造函数
    Stack(int capacity = 3)   //初始化列表
        :_array(new DataType[capacity])   // 开辟一个DateType的动态数组,并进行初始化
        , _capacity(capacity)
        ,_size(0)
    {}
    void Push(DataType data)
    {
        // CheckCapacity();
        _array[_size] = data;
        _size++;
    }
    // 其他方法...
    ~Stack()
    {
        delete[]_array;
        _array = nullptr;
        _size = _capacity = 0;
    }
private:
    DataType* _array;
    int _capacity;
    int _size;
};
int main()
{
    Stack s1;
    return 0;
}

💬 如果没有模版技术的话你会如何去解决这个问题呢?很简单那就是定义多个类
这是我们同学最擅长的事,CV一下两个栈就有了,StackInt存放整型数据,StackDouble存放浮点型数据

class StackInt
class StackDouble

但是本文我们重点要讲解的就是【模版技术】,技术界有一句话说得好 “不要重复造轮子”

  • 下面就是使用模版去定义的一个类,简称【模板类】,不限制死数据类型,将所有的DataType都改为【T】,代码如下:
template<class T1>
class Stack
{
public:
	// 构造函数
	Stack(int capacity = 4)
		:_a(new T1[capacity])
		,_capacity(capacity)
		,_size(0)
	{}

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

ataType都改为【T】,代码如下:**

template<class T1>
class Stack
{
public:
	// 构造函数
	Stack(int capacity = 4)
		:_a(new T1[capacity])
		,_capacity(capacity)
		,_size(0)
	{}

[外链图片转存中…(img-HmVtxOAn-1715734571807)]
[外链图片转存中…(img-ZMFL1kgn-1715734571807)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一个用C++模板将无向图的邻接矩阵转换为对应邻接表的代码示例。代码如下: ```cpp template<typename T> class Graph { private: T** matrix; int n; public: Graph(T** matrix, int n) { this->matrix = matrix; this->n = n; } std::unordered_map<int, std::vector<int>> toAdjacencyList() { std::unordered_map<int, std::vector<int>> adjacencyList; for (int i = 0; i < n; i++) { std::vector<int> neighbors; for (int j = 0; j < n; j++) { if (matrix[i][j] != 0) { neighbors.push_back(j); } } adjacencyList[i] = neighbors; } return adjacencyList; } }; ``` 这个类模板接受一个邻接矩阵和一个整数n作为构造函数的参数。其中,邻接矩阵是类型为T的指针的指针(即T**),表示了无向图的结构;整数n表示了无向图的顶点数。 该类模板的成员函数toAdjacencyList()将邻接矩阵转换为对应的邻接表。它返回一个无序映射(即std::unordered_map),其中键为顶点的编号,值为与该顶点相邻的所有顶点的编号组成的向量(即std::vector<int>)。 您可以像这样使用该类模板: ```cpp int main() { int matrix[3][3] = { {0, 1, 1}, {1, 0, 1}, {1, 1, 0} }; Graph<int> graph((int**)matrix, 3); auto adjacencyList = graph.toAdjacencyList(); for (auto entry : adjacencyList) { std::cout << entry.first << ": "; for (auto neighbor : entry.second) { std::cout << neighbor << " "; } std::cout << std::endl; } return 0; } ``` 这个例子中,我们创建了一个3个顶点的无向图的邻接矩阵,并将其转换为邻接表。然后,我们遍历邻接表,输出每个顶点的相邻顶点。输出如下: ``` 0: 1 2 1: 0 2 2: 0 1 ``` 希望这个例子对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值