
💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 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)作 用
- 模 版 可 以 控 制 容 器 的 数 据 类 型。
- 模 版 可 以 控 制 某 种 设 计 逻 辑,比 如 适 配 器 模 式、数 组 栈、链 表 栈 等 等。
- 传 递 类 型,使 用 仿 函 数,让 类 重 载
operator()
。
(2)typename
-
定 义 模 版 时 可 以 使 用
typename
或 者class
,二 者 是 等 价 的。template<typename Container>
template<class Container> -
使 用 类 模 板 使 用
::
时 要 注 意 前 面 是 类 型 还 是 对 象。如 果 是 类 型,前 面 要 加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 是 类 型 模 板 参 数。
注 意
- 浮 点 数、类 对 象 以 及 字 符 串 是 不 允 许 作 为 非 类 型 模 板 参 数 的。
- 非 类 型 的 模 板 参 数 必 须 在 编 译 期 就 能 确 认 结 果。
(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 属 于 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;
};
两 种 表 现 方 式:
- 部 分 特 化:将 模 板 参 数 类 表 中 的 一 部 分 参 数 特 化。
javascript
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:
Data()
{
cout << "Data<T1, int>" << endl;
}
private:
T1 _d1;
int _d2;
};
- 参 数 更 进 一 步 的 限 制
偏 特 化 并 不 仅 仅 是 指 特 化 部 分 参 数,而 是 针 对 模 板 参 数 更 进 一 步 的 条 件 限 制 所 设 计 出 来 的 一 个 特 化 版 本。
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)函 数 模 板 特 化
步 骤
- 必 须 要 先 有 一 个 基 础 的 函 数 模 板
- 关 键 字 template 后 面 接 一 对 空 的 尖 括 号 <>
- 函 数 名 后 跟 一 对 尖 括 号,尖 括 号 中 指 定 需 要 特 化 的 类 型
- 函 数 形 参 表:必 须 要 和 模 板 函 数 的 基 础 参 数 类 型 完 全 相 同,如 果 不 同 编 译 器 可 能 会 报 一 些 奇 怪 的 错 误。
- 函 数 模 板 可 以 全 特 化(不 能 偏 特 化)。
- 如 果 需 要 针 对 某 类 类 型 做 特 殊 处 理,通 常 用 函 数 重 载 代 替 特 化。
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 种 情 况
- func1 有 声 明 和 定 义,链 接 成 功。
- func2 有 声 明 没 有 定 义,链 接 失 败。
- push 有 声 明 和 定 义,链 接 失 败。
方 法 1
可 以 在 所 有 定 义 最 后 面 添 加 下 面 的 代 码 即 可,这 种 方 式 被 称 做 显 示 实 例 化,func2 需 要 添 加 定 义。
template
class stack<int>;
方 法 2
将 声 明 和 定 义 放 到 一 个 文 件 "xxx.hpp" 里 面 或 者 "xxx.h" 其 实 也 是 可 以 的。这 里 推 荐 使 用 这 种 方 式。
五、优 缺 点
(1)优 点
- 模 板 复 用 了 代 码,节 省 资 源,更 快 的 迭 代 开 发,C++ 的 标 准 模 板 库 (STL) 因 此 而 产 生
- 增 强 了 代 码 的 灵 活 性
(2)缺 陷
- 模 板 会 导 致 代 码 膨 胀 问 题,也 会 导 致 编 译 时 间 变 长
- 出 现 模 板 编 译 错 误 时,错 误 信 息 非 常 凌 乱,不 易 定 位 错 误。

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