C++模版-函数模版,类模版基础

函数模板

定义

cpp 复制代码
template <typename T>
T sub(T a,T b)
{
    return a-b;
}

实例化

cpp 复制代码
int main() {
	int x=sub<int> (1,2);
	double y=sub<double> (2.1,1.2);
	
	// std::string s=sub<std::string>("212","2131"); // 编译错误,std::string不支持'-'操作。
	return 0;
} 

编译阶段,在对模板实例化前,编译器会判断函数体对于该类型是否可以实例化,若不行则编译错误。

模板参数推断

在推断模板类型时,不能进行自动类型转换,要求类型精确匹配。

cpp 复制代码
template <typename T>
T sub(T a,T b)
{
    return a-b;
}
sub(3,4.0); // 不被允许

其中T的推导规则,请见:

函数模板重载

函数模板重载:函数名称相同,参数数量或参数类型不同。

函数和函数模板可以同时存在,当函数模板和函数都合适时,优先调用函数。可使用<>指定调用函数模板。

cpp 复制代码
template<typename T>
T sub(T a,T b)
{
	std::cout<<"call sub(T,T)\n";
	return a-b;
}

template<typename T>
T sub(T a)
{
	std::cout<<"call sub(T)\n";
	return a--;
}

int sub(int a,int b)
{
	std::cout<<"call sub(int,int)\n";
	return a-b;
}

int main() {
	sub(1,2);
	sub<>(1,2);
	sub(1);
	
	return 0;
} 
/*
输出:
call sub(int,int)
call sub(T,T)
call sub(T)	
*/

特化

特化模板可用于针对特别的模板参数,即某种特别类型做单独的特殊处理。

特化的模板版本相当于泛化模板的一个子集,特化的模板通过泛化的模板生成。故,先有泛化模板,才有特化模板。

全特化

全特化等价于实例化一个函数模板,并不等价于函数重载,即全特化的函数模板和相同参数的函数可以同时存在。

cpp 复制代码
// 泛化版本
template<typename T>
T sub(T a,T b)
{
	std::cout<<"call sub(T,T)\n";
	return a-b;
}

// 针对std::string的全特化版本
template<>
std::string sub<std::string>(std::string a,std::string b)
{
	std::cout<<"call sub<std::string>(T,T)\n";
	int x=a.size()-b.size();
	return std::to_string(x);
}

// 函数
std::string sub(std::string a,std::string b)
{
	std::cout<<"call sub(std::string,std::string)\n";
	int x=a.size()-b.size();
	return std::to_string(x);
}

当自动推导调用时,如sub("123","345");,其调用优先级为:普通函数>特化版本>泛化版本。

偏特化

并不存在函数模板的偏特化,都通过函数模版重载实现。

类模板

定义

cpp 复制代码
template <typename T>
class A
{
public:
	A(T t):_t(t){}
	void show();
private:
	T _t;
};

template<typename T>
void A<T>::show(){ std::cout<<_t<<std::endl;}

类模板特化

特化版本的类模板可认为是和泛化模板完全不同的类,在其中可以重新定义不同的成员或函数。

全特化
  • 全特化类模板

全特化的类模板可以当作一个明确的普通类使用。

cpp 复制代码
template <typename T,typename C>
class TC {};

// 全特化版本
template<>
class TC<int,int>
{
	void show();
};

// 不需要
// template<>
void TC<int,int>::show()
{
	std::cout<<"TC<int,int>.show\n";
}
  • 全特化普通成员函数,全特化静态变量

若是全特化了普通成员函数,或者全特化了静态变量,则无法使用该全特化的特定类型再进行类模板的全特化。因为全特化普通成员函数或者静态变量,本质上会实例化对应版本的类,若是再对该版本类进行全特化会造成重复实例化。

cpp 复制代码
template <typename T,typename C>
class TC 
{
	void show()
	{
		std::cout<<"TC<T,C>::show\n";
	}
	
	static std::string str;
};

// 全特化普通成员函数
template<>
void TC<double,int>::show()
{
	std::cout<<"TC<double,int>::show\n";
}

// 全特化静态变量
template<>
std::string TC<short,char>::str="dadads";
偏特化

偏特化主要在于两方面:

  1. 模板参数数量上
cpp 复制代码
template <typename T,typename C>
class TC {};

// 对部分模板参数进行特化
template <typename C>
class TC<double,C>
{};
  1. 模板参数权限范围上
cpp 复制代码
template <typename T,typename C>
class TC {};

// 缩小权限范围
template <typename T,typename C>
class TC<const T,C>{};

template <typename T,typename C>
class TC<T*,C>{};

成员函数模板

类模板还是普通类,都可以有各自独立的成员函数模板。

类模板中的普通成员函数或是成员函数模板,只有其在源代码中被调用了,才会出现在实例化的类模板中。

编译器暂不支持虚函数成员模板,因为虚函数成员模板会导致该类的虚函数表大小无法确定。

cpp 复制代码
class TC {
// 普通类中的成员函数模板
template <typename T>
void func(T t) {}
};

// 类模板中的成员函数模板
template <typename T>
class AC {
template <typename C>
void func();
};

// 类外定义类模板的成员函数模板
template<typename T> // 先写类模板的参数,排在上面
template<typename C> // 再写构造函数模板自己的模板参数
void AC<T>::func() {}

拷贝构造函数模板,赋值运算符重载函数模板

其本质上可认为是该类的拷贝构造函数,赋值运算符重载函数的一份重载,即拷贝构造函数模板永远不可能成为拷贝构造函数。

cpp 复制代码
template <typename T>
class AC {
public:
	AC<T>(){}
	
	AC<T>(const AC<T>& _ac)
	{
		std::cout<<"call 拷贝构造\n";
	}
	
	AC<T>& operator=(const AC<T>& _ac)
	{
		std::cout<<"call 赋值运算符重载\n";
		return *this;
	}
	
	template <typename C>
	AC(const AC<C>& _ac)
	{
		std::cout<<"call 拷贝构造模板\n";
	}
	
	template <typename C>
	AC<T>& operator=(const AC<C>& _ac)
	{
		std::cout<<"call 赋值运算符重载模板\n";
		return *this;
	}
};

int main() {
	AC<int> ac{};
    
	AC<int> ac1{ac}; // 调用拷贝构造
	AC<int> ac2{AC<double>{}}; // 调用拷贝构造模版
	ac1=AC<int>{}; // 调用赋值运算符重载
	ac1=AC<char>{}; // 调用赋值运算符重载模版
	
	return 0;
}

分离编译导致的模版实例化问题

CPP采用分离编译,即每个.cpp文件单独编译,在最后链接为一个可执行文件。

若模版的声明和定义在分别在两个文件中,如sub.hsub.cpp:

cpp 复制代码
// sub.h
template <class T>
T sub(T a,T b);

// sub.cpp
#include "sub.h"
template <class T>
T sub(T a,T b)
{
    return a-b;
}

main.cpp通过引用头文件调用函数模版:

cpp 复制代码
// main.cpp
#include "sub.h"

int main()
{
    sub<int>(1,2);
    return 0;
}

sub.cppmain.cpp分离编译,sub.cpp中并没有sub<T>的调用,也就不会进行实例化,也就没有生成sub<int>函数;链接时main.cpp无法找到对应的函数实例,则会出现链接错误。

故:对于模版,声明和定义请写在同一文件中!

相关推荐
SugarFreeOixi2 小时前
Matlab多个图窗重叠问题解决,平铺函数TileFigs
开发语言·matlab
We་ct2 小时前
LeetCode 79. 单词搜索:DFS回溯解法详解
前端·算法·leetcode·typescript·深度优先·个人开发·回溯
二十雨辰2 小时前
[Java]RuoYi框架原理分析
java
东离与糖宝2 小时前
Java 玩转 AI 智能体性能优化:OpenClaw 高并发调用与 Token 成本控制实战
java·人工智能
眼眸流转2 小时前
LeetCode热题100(四)
算法·leetcode·职场和发展
y = xⁿ2 小时前
【从零开始学习Redis|第七篇】Redis 进阶原理篇:消息队列、分布式锁、缓存击穿与事务实现
java·redis·学习·缓存
ryan007liu2 小时前
shell 批量执行locust 脚本压测
linux·服务器·压力测试
相信神话20212 小时前
第零章:新手的第一课:正确认知游戏开发
大数据·数据库·算法·2d游戏编程·godot4·2d游戏开发
汀沿河2 小时前
2 模型预训练、微调、强化学习的格式
人工智能·算法·机器学习