【C++】模板 - - - 泛型编程的魔法模具,一键生成各类代码

💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 C++。

💡个 人 主 页:@笑口常开xpr 的 个 人 主 页

📚系 列 专 栏:C++ 炼 魂 场:从 青 铜 到 王 者 的 进 阶 之 路✨代 码 趣 语:模 板 是 "万 能 模 具" - - - 不 管 是 铁(int)、铜 (double)还 是 塑 料(自 定 义 类),只 要 往 模 具 里 一 放,就 能 按 同 样 的 造 型(逻 辑)做 出 对 应 材 质 的 零 件,不 用 每 种 材 质 都 重 新 造 个 新 模 具。

💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。

📦gitee 链 接:gitee

文 章 目 录

  • [一、模 版](#一、模 版)
    • [(1)作 用](#(1)作 用)
    • (2)typename
    • [(3)非 类 型 模 板 参 数](#(3)非 类 型 模 板 参 数)
    • [(4)按 需 实 例 化](#(4)按 需 实 例 化)
    • (5)array
  • [二、类 模 板](#二、类 模 板)
    • [(1)定 义](#(1)定 义)
    • [(2)作 用](#(2)作 用)
    • [(3)基 本 语 法](#(3)基 本 语 法)
  • [三、特 化](#三、特 化)
    • [(1)定 义](#(1)定 义)
    • [(2)类 模 板](#(2)类 模 板)
      • [1、全 特 化](#1、全 特 化)
      • [2、偏 特 化](#2、偏 特 化)
    • [(3)函 数 模 板 特 化](#(3)函 数 模 板 特 化)
  • [四、模 板 的 分 离 编 译](#四、模 板 的 分 离 编 译)
  • [五、优 缺 点](#五、优 缺 点)
    • [(1)优 点](#(1)优 点)
    • [(2)缺 陷](#(2)缺 陷)
  • [六、总 结](#六、总 结)

本 文 围 绕 C++ 模 板 核 心 知 识(类 / 函 数 模 板、特 化、分 离 编 译 等)及 vector、array 容 器 特 性 展 开,结 合 代 码 示 例 拆 解 关 键 概 念,助 力 入 门 者 掌 握 泛 型 编 程 与 容 器 使 用 基 础。


一、模 版

模 版

(1)作 用

  1. 模 版 可 以 控 制 容 器 的 数 据 类 型。
  2. 模 版 可 以 控 制 某 种 设 计 逻 辑,比 如 适 配 器 模 式、数 组 栈、链 表 栈 等 等。
  3. 传 递 类 型,使 用 仿 函 数,让 类 重 载 operator()

(2)typename

  1. 定 义 模 版 时 可 以 使 用 typename 或 者 class,二 者 是 等 价 的。

    template<typename Container>
    template<class Container>

  2. 使 用 类 模 板 使 用 :: 时 要 注 意 前 面 是 类 型 还 是 对 象。如 果 是 类 型,前 面 要 加 typename,如 果 是 对 象,前 面 不 需 要。例 如 将 函 数 改 成 泛 型 时,使 用 迭 代 器 之 前 要 加 typename

javascript 复制代码
#include<vector>
#include<iostream>
using namespace std;

void Print(const vector<int>& v)
{
	vector<int>::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}
//template<typename Container>//class和typename的作用一样
template<class Container>
void Print(const Container& v)
{
	typename Container::const_iterator it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}
int main()
{
	vector<int> vec;
	vec.push_back(1);
	vec.push_back(2);
	vec.push_back(3);
	vec.push_back(4);
	for (auto e : vec)
	{
		cout << e << " ";
	}
	cout << endl;
	Print(vec);
	return 0;
}

Print 函 数 在 没 有 使 用 模 版 之 前,使 用 迭 代 器 时 进 行 了 实 例 化,这 里 是 类 型,使 用 模 版 参 数 后 没 有 实 例 化,无 法 区 分 迭 代 器 前 面 是 类 型 还 是 对 象,使 用 typename 来 区 分 这 里 是 类 型 还 是 对 象。如 果 使 用 auto 则 不 需 要 使 用 typename


(3)非 类 型 模 板 参 数

模 板 参 数 分 为 类 类 型 形 参 与 非 类 型 形 参。

类 型 形 参:出 现 在 模 板 参 数 列 表 中,跟 在 class 或 者typename 之 类 的 参 数 类 型 名 称。

非 类 型 形 参:用 一 个 常 量 作 为 类 (函 数) 模 板 的 一 个 参 数,在 类 ( 函 数 ) 模 板 中 可 将 该 参 数 当 成 常 量 来 使 用。非 类 型 模 板 参 数 必 须 是 常 量 或 者 整 型,主 要 用 于 定 义 数 组。

javascript 复制代码
#include<iostream>
using namespace std;
template<class T,size_t N>
class stack
{
//静态栈
private:
	T _a[N];
	int _top;
};
int main()
{
	stack<int,10> st1;  //10
	stack<int,100> st2;  //100
	return 0;
}

上 面 的 代 码 中 N 是 非 类 型 模 板 参 数,N 是 常 量,不 能 修 改 N,比 define 更 灵 活。作 用 域 是 整 个 类,形 参 的 作 用 域 是 函 数。T 是 类 型 模 板 参 数。

注 意

  1. 浮 点 数、类 对 象 以 及 字 符 串 是 不 允 许 作 为 非 类 型 模 板 参 数 的。
  2. 非 类 型 的 模 板 参 数 必 须 在 编 译 期 就 能 确 认 结 果。

(4)按 需 实 例 化

函 数 调 用 会 进 行 实 例 化,如 果 不 调 用 不 会 实 例 化。有 的 编 译 器 会 出 现,Vs 编 译 器 会 出 现 这 种 情 况。

javascript 复制代码
#include<iostream>
using namespace std;
template<class T,size_t N>
class stack
{
//静态栈
public:
	void Func()
	{
		N = 0;
	}
private:
	T _a[N];
	int _top;
};
int main()
{
	stack<int,10> st1;  //10
	stack<int,100> st2;  //100
	
	//按需实例化
	//st1.Func();
	return 0;
}

不 调 用 Func

代 码 编 译 成 功。

调 用 Func

代 码 编 译 失 败。


(5)array

array

array 属 于 C++ 中 比 较 没 用 的 东 西,用 处 比 较 小,没 有 vector 好 用。也 就 是 C 语 言 中 的 数 组。array 的 优 势 在 于 处 理 数 组 越 界 的 问 题,数 组 越 界 时 使 用 array 会 发 生 断 言 处 理。C 语 言 的 数 组 只 能 检 查 少 部 分 的 越 界 处 理。

javascript 复制代码
#include<iostream>
#include<array>
using namespace std;
int main()
{
	array<int, 10> a;
	for (auto e : a)
	{
		cout << e << " ";
	}
	cout << endl;
	a[10];//越界访问会中断程序
	return 0;
}

二、类 模 板

类 模 板

(1)定 义

类 模 板 允 许 定 义 一 个 带 参 数 的 类,这 个 参 数 可 以 是 任 意 类 型,从 而 让 类 能 适 应 多 种 数 据 类 型,而 不 需 要 重 复 写 多 份 代 码。

(2)作 用

实 现 类 的 泛 型 化,提 高 代 码 复 用 性。

(3)基 本 语 法

javascript 复制代码
#include<iostream>
using namespace std;
template <class T>  // T是类型参数
class ClassName 
{
public:
    T member;
    ClassName(T m) : member(m) 
    {}
    void print() 
    {
        cout << member << endl;
    }
};
int main() 
{
    ClassName<int> obj1(10);      
    ClassName<double> obj2(3.14); 
    obj1.print(); 
    obj2.print(); 
    return 0;
}

使 用 类 模 板 时,必 须 显 式 指 定 类 型 参 数(编 译 器 不 会 像 函 数 模 板 那 样 自 动 推 导 类 模 板 类 型)。

三、特 化

(1)定 义

通 常 情 况 下,使 用 模 板 可 以 实 现 一 些 与 类 型 无 关 的 代 码,但 对 于 一 些 特 殊 类 型 的 可 能 会 得 到 一 些 错 误 的 结 果,需 要 特 殊 处 理。在 原 模 板 类 的 基 础 上,针 对 特 殊 类 型 所 进 行 特 殊 化 的 实 现 方 式。

(2)类 模 板

1、全 特 化

全 特 化 即 是 将 模 板 参 数 列 表 中 所 有 的 参 数 都 确 定 化。(必 须 包 括 原 来 的 类 型)

javascript 复制代码
#include<iostream>
using namespace std;
template <class T>
class Pair 
{
public:
    Pair(T a, T b) 
        : first(a)
        , second(b) 
    {}
    void print() 
    {
        cout << first << " " << second << endl;
    }
private:
    T first, second;
};

//全特化版本
template<>
class Pair<int> 
{
public:
    Pair(int a, int b) 
        : first(a)
        , second(b) 
    {}
    void print() 
    {
        cout << "int special: " << first << ", " << second << endl;
    }
private:
    int first, second;
};
int main()
{
    Pair<double> p1(1.2, 3.4); //调用普通模板
    Pair<int> p2(10, 20);      //调用全特化版本
    p1.print();
    p2.print();
	return 0;
}

2、偏 特 化

任 何 针 对 模 版 参 数 进 一 步 进 行 条 件 限 制 设 计 的 特 化 版 本。类 模 板 特 有,函 数 模 板 不 支 持。比 如 对 于 以 下 模 板 类:

javascript 复制代码
template<class T1, class T2>
class Data
{
public:
	Data() 
	{ 
		cout << "Data<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

两 种 表 现 方 式

  1. 部 分 特 化:将 模 板 参 数 类 表 中 的 一 部 分 参 数 特 化。
javascript 复制代码
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
	Data() 
	{ 
		cout << "Data<T1, int>" << endl;
	}
private:
	T1 _d1;
	int _d2;
};
  1. 参 数 更 进 一 步 的 限 制
    偏 特 化 并 不 仅 仅 是 指 特 化 部 分 参 数,而 是 针 对 模 板 参 数 更 进 一 步 的 条 件 限 制 所 设 计 出 来 的 一 个 特 化 版 本。
javascript 复制代码
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
	Data() 
	{ 
		cout << "Data<T1*, T2*>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
	Data(const T1& d1, const T2& d2)
		: _d1(d1)
		, _d2(d2)
	{
		cout << "Data<T1&, T2&>" << endl;
	}
private:
	const T1& _d1;
	const T2& _d2;
};
int main()
{
	Data<double, int> d1;	   //调用特化的int版本
	Data<int, double> d2;	   //调用基础的模板
	Data<int*, int*> d3;	   //调用特化的指针版本
	Data<int&, int&> d4(1, 2); //调用特化的指针版本
	return 0;
}

(3)函 数 模 板 特 化

步 骤

  1. 必 须 要 先 有 一 个 基 础 的 函 数 模 板
  2. 关 键 字 template 后 面 接 一 对 空 的 尖 括 号 <>
  3. 函 数 名 后 跟 一 对 尖 括 号,尖 括 号 中 指 定 需 要 特 化 的 类 型
  4. 函 数 形 参 表:必 须 要 和 模 板 函 数 的 基 础 参 数 类 型 完 全 相 同,如 果 不 同 编 译 器 可 能 会 报 一 些 奇 怪 的 错 误。
  5. 函 数 模 板 可 以 全 特 化(不 能 偏 特 化)。
  6. 如 果 需 要 针 对 某 类 类 型 做 特 殊 处 理,通 常 用 函 数 重 载 代 替 特 化。
javascript 复制代码
#include<iostream>
using namespace std;
//函数模板 --- 参数匹配
template <class T>
bool Less(T a, T b) 
{
	cout << "Less(T a, T b)" << " ";
	return a < b;
}
//全特化版本(针对int*)
template <>
bool Less<int*>(int* a, int* b) 
{
	cout << "Less<int*>" << " ";
	return *a < *b;
}
int main()
{
	int a = 1;
	int b = 2;
	cout << Less(a, b) << endl;
	cout << Less(&a, &b) << endl;
	return 0;
}

四、模 板 的 分 离 编 译

定 义

一 个 程 序(项 目)由 若 干 个 源 文 件 共 同 实 现,而 每 个 源 文 件 单 独 编 译 生 成 目 标 文 件,最 后 将 所 有 目 标 文 件 链 接 起 来 形 成 单 一 的 可 执 行 文 件 的 过 程 称 为 分 离 编 译 模 式。

stack.cpp

javascript 复制代码
#include"stack.h"
using namespace sta;

template<class T, class Container>
void stack<T,Container>::push(const T& x)
{
	_con.push_back(x);
}

template<class T, class Container>
void stack<T, Container>::pop()
{
	_con.pop_back();
}

void A::func1()
{

}
//void A::func2()

stack.h

javascript 复制代码
#pragma once
#include<vector>
#include<list>
#include<iostream>
using namespace std;
namespace sta
{
	template<class T,class Container = vector<T>>
	class stack
	{
	public:
		void push(const T& x);
		void pop();
		T& top()
		{
			return _con.back();
		}
		size_t size()
		{
			return _con.size();
		}
		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
	class A
	{
	public:
		void func1();
		void func2();
	};
}

test.cpp

javascript 复制代码
#include"stack.h"
using namespace sta;
int main()
{
	stack<int> st;
	//有声明和定义
	st.push(1);
	st.push(2);
	st.push(3);
	A aa;
	aa.func1();//有声明和定义
	aa.func2();//有声明没有定义
	return 0;
}

这 里 是 链 接 错 误,找 不 到 地 址。编 译 阶 段 看 只 有 声 明,声 明 是 一 种 承 诺,所 以 编 译 阶 段 检 查 声 明 函 数 名 参 数 返 回 可 以 对 上。等 着 链 接 的 时 候,使 用 修 饰 后 的 函 数 去 查 找。

3 种 情 况

  1. func1 有 声 明 和 定 义,链 接 成 功。
  2. func2 有 声 明 没 有 定 义,链 接 失 败。
  3. push 有 声 明 和 定 义,链 接 失 败。

方 法 1

可 以 在 所 有 定 义 最 后 面 添 加 下 面 的 代 码 即 可,这 种 方 式 被 称 做 显 示 实 例 化,func2 需 要 添 加 定 义。

复制代码
template
class stack<int>;

方 法 2

将 声 明 和 定 义 放 到 一 个 文 件 "xxx.hpp" 里 面 或 者 "xxx.h" 其 实 也 是 可 以 的。这 里 推 荐 使 用 这 种 方 式。


五、优 缺 点

(1)优 点

  1. 模 板 复 用 了 代 码,节 省 资 源,更 快 的 迭 代 开 发,C++ 的 标 准 模 板 库 (STL) 因 此 而 产 生
  2. 增 强 了 代 码 的 灵 活 性

(2)缺 陷

  1. 模 板 会 导 致 代 码 膨 胀 问 题,也 会 导 致 编 译 时 间 变 长
  2. 出 现 模 板 编 译 错 误 时,错 误 信 息 非 常 凌 乱,不 易 定 位 错 误。


六、总 结

本 文 梳 理 了 C++ 模 板 的 复 用 逻 辑、特 化 规 则 及 分 离 编 译 问 题,也 明 确 了 vector 与 array 的 特 性 差 异;模 板 虽 提 升 代 码 灵 活 性,但 需 注 意 编 译 膨 胀 与 错 误 定 位 问 题,掌 握 这 些 内 容 是 高 效 编 写 C++ 代 码 的 重 要 基 础。

相关推荐
AI视觉网奇3 小时前
pyqt 触摸屏监听
开发语言·python·pyqt
AA陈超3 小时前
虚幻引擎5 GAS开发俯视角RPG游戏 P05-01.创建游戏玩法标签
c++·游戏·ue5·游戏引擎·虚幻
立志成为大牛的小牛3 小时前
数据结构——十四、构造二叉树(王道408)
数据结构·笔记·学习·程序人生·考研
香菜+3 小时前
python脚本加密之pyarmor
开发语言·python
数据知道3 小时前
Go基础:一文掌握Go语言泛型的使用
开发语言·后端·golang·go语言
啃啃大瓜3 小时前
常用库函数
开发语言·python
IT小番茄3 小时前
Kubernetes云平台管理实战:自动加载到负载均衡(七)
算法
笑口常开xpr4 小时前
【C++继承】深入浅出C++继承机制
开发语言·数据结构·c++·算法
代码AC不AC4 小时前
【C++】红黑树实现
c++·红黑树·底层结构