C++之模板初阶

前言

模板就是实现一个通用的函数,那么如何来实现呢?

cpp 复制代码
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、代码的可维护性比较低,一个出错可能所有的重载均出错

也就是祖师爷为了不让我们写那么多函数重载,写完之后,一个出错,可能剩下的都会出错

从而发明了"模板"!

模板共分为两个,一个是函数模板,一个是类模板

函数模板

函数模板的概念

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

函数模板就是个万能厨师------你说做什么菜(传什么类型),他就现场拿出对应的菜谱(生成具体函数)给你做。

函数模板格式

cpp 复制代码
template<typename T1,typename T2,......,typename Tn>
返回类型 函数名(参数类型)
//根据上面的交换去写
template<typename T>
void Swap(T& x,T& y)
{
    int temp=x;
    x=y;
    y=temp;
}

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

函数模板的原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。

所以其实模板就是将本来应该我们做的重复的事情,交给了编译器

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

注意:在写了函数的时候,不会调用模板,只会调用函数,相反,没有写函数,写了函数模板则会进入到模板里面

cpp 复制代码
template<typename T1, typename T2>
T1 Add(const T1& x, const T2& y)
{
	return x + y;
}
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int i = 1, j = 2;
	double m = 1.1, n = 2.2;
	Add(i, j);

	return 0;
} 

比如,我们的函数是int,我们传参的也是int类型,就会进入到函数里面,如果是其余类型,就进不去函数,只会进去模板里面
那么在调用函数模板的时候,实际上会有两个过程

1、模板推演,推演T的具体类型是什么

2、推演出T的具体类型后实例化生成具体的函数

总结:模板就是给编译器去使用的,编译器其实只是充当了写函数的工具

函数模板的实例化

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

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

cpp 复制代码
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);
    Add(d1, d2);
// 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
    Add(a1, d2);
    return 0;
}

语句是编译不过的,因为在编译期间,当编译器看到该实例化时候,是用a1去推T是int,而用d2去推是double,但是模板参数只有一个T,编译器是不能明确该T是int还是double,T是不明确的,所以编译器会报错

解决的方式有两种:

1、调用者自己强制转换

cpp 复制代码
//实参去推演形参的类型
Add(a1, (int)d2);
Add((double)a1,d2);

2、使用显示实例化

cpp 复制代码
//实参不需要去推演形参的类型,显式实例化指定T的类型
Add<int>(a1, d2);
Add<double>(a1,d2);

模板参数的匹配原则

一个非模板函数可以和一个同名的函数模板同时存在,此时如果调用地方参数与非模板函数完全匹配,则会调用非模板函数

cpp 复制代码
template<typename T1, typename T2>
T1 Add(const T1& x, const T2& y)
{
	return x + y;
}
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int i = 1, j = 2;
	double m = 1.1, n = 2.2;
	Add(i, j);

	return 0;
} 

Add(1,2)参数是int类型,而我们有现成的int参数的Add函数,所以有现成的就用现成的,编译器也会偷懒

那么如果我们想调用的时候调用模板,我们就需要显示实例化

cpp 复制代码
Add<int>(1,2);

这样编译器就会强制去调用模板了

一个非模板函数可以和一个同名函数模板同时存在,此时如果调用的地方参数与非模板函数完全不匹配,则会优先使用模板实例化函数

cpp 复制代码
template<typename T1, typename T2>
T1 Add(const T1& x, const T2& y)
{
	return x + y;
}
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	Add(1.1, 2.1);

	return 0;
} 

模板匹配总结:

有现成完全匹配的,那就直接调用,没有现成调用的,实例化模板生成,如果有需要转换类型才能匹配的函数(也就是不完全匹配),那么它会优先选择去实例化模板生成。

优先级:

完全匹配>模板>转换类型匹配

类模板

类模板的定义格式

cpp 复制代码
template<class T1, class T2, ..., class Tn>
class 类模板名
{
	//类内成员定义
};

看看使用类模板的使用场景:

cpp 复制代码
typedef int STDateType;
class Stack
{
private:
    STDateType* _a;
    int _top;
    int _capacity;
};
int main()
{
    Stack st1;
    Stack st2;
    return 0;
}

我们定义的是数据结构栈,我们创建了两个栈,但是现在st1和st2的存储数据类型是int,要是想转换成数据类型

cpp 复制代码
typedef double STDateType;

如果st1是int,st2是double呢?

cpp 复制代码
Stack st1;//int
Stack st2;//double

此时还需要写多一个类,名字还得不一样

cpp 复制代码
typedef int STDateType1;
typedef double STDateType2;
class IntStack
{
private:
    STDateType1* _a;
    int _top;
    int _capacity;
};
class DoubleStack
{
private:
    STDateType2* _a;
    int _top;
    int _capacity;
};

这样子太麻烦了,但是我们可以用类模板去解决

cpp 复制代码
//类模板
template<class T>
class Stack
{
private:
    T* _a;
    int _top;
    int _capaticy;
};
int main()
{
    //类模板的使用都是显式实例化
    Stack<double> st1;
    Stack<int> st2;
    return 0;
}

注意:stack不是具体得类,是编译器根据被实例化得类型生成具体类得模具

类模板得实例化

cpp 复制代码
//类模板
template<class T>
class Stack
{
public:
    Stack(int capacity = 4)
        :_a(new T(capacity))
         ,_top(0)
         ,_capacity(capacity)
        {}
    ~Stack()
    {
        delete[] _a;
        _a = nullptr;
        _top = _capacity = 0;
    }
    void Push(const T& x)
    {
        //...
    }
private:
    T* _a;
    int _top;
    int _capaticy;
};
int main()
{
    //类模板的使用都是显式实例化
    Stack<double> st1;
    Stack<int> st2;
    return 0;
}

注意:类模板得使用都是显示实例化

假设我们想在类里面声明和类外面定义成员函数呢?

cpp 复制代码
//类模板
template<class T>
class Stack
{
public:
    Stack(int capacity = 4)
        :_a(new T(capacity))
         ,_top(0)
         ,_capacity(capacity)
        {}
    ~Stack()
    {
        delete[] _a;
        _a = nullptr;
        _top = _capacity = 0;
    }
    //假设我们想类里面声明和定义分离呢?
    void Push(const T& x);
private:
    T* _a;
    int _top;
    int _capaticy;
};
//在类外面定义
template<class T>
void Stack<T>::Push(const T& x);
{
    //...
}
int main()
{
    //类模板的使用都是显式实例化
    Stack<TreeNode*> st1;
    Stack<int> st2;
    return 0;
}
cpp 复制代码
//在类外面定义
template<class T>
void Stack<T>::Push(const T& x);
{
    //...
}

在类外面定义我们必须要加模板的关键字,以及需要在实现的函数前面表明域Stack<T>。普通类,类名就是类型,对于类模板,类名不是类型,类型是Stack<T>,需要写指定

注意:

模板不支持把声明写到.h,定义写到.cpp,这种声明和定义分开实现的方式,会出现链接错误

相关推荐
liulilittle2 小时前
CLANG 交叉编译
linux·服务器·开发语言·前端·c++
wen__xvn2 小时前
C++ 中 std::set 的用法
java·c++·c#
梵尔纳多4 小时前
OpenGL 坐标映射
c++·图形渲染
大头流矢5 小时前
C++的类与对象·三部曲:初阶
开发语言·c++
AAA.建材批发刘哥5 小时前
03--C++ 类和对象中篇
linux·c语言·开发语言·c++·经验分享
峥无6 小时前
《二叉搜索树:动态数据管理的利器,平衡树的基石》
开发语言·c++·二叉搜索树
CoderCodingNo6 小时前
【GESP】C++五级真题(数论, 贪心思想考点) luogu-B4070 [GESP202412 五级] 奇妙数字
开发语言·c++·算法
AAA.建材批发刘哥7 小时前
04--C++ 类和对象下篇
linux·c++·经验分享·青少年编程
stolentime7 小时前
洛谷P4417 [COCI 2006/2007 #2] STOL 题解
c++·coci
CoderCodingNo7 小时前
【GESP】C++五级真题(数论考点) luogu-P11961 [GESP202503 五级] 原根判断
开发语言·c++