浅谈 C++ 模板语法
yangfengzhao · · 科技·工程
0. 日志
[2025/8/4] 第一版正式开始编辑!\
[2025/8/13] 第二版,补充模板进阶技巧与附录,突破
1. 模板基础
知周所众,模板(Template) 是用来简化各种函数重载的利器。当需要编写功能相同但类型不同的代码时,模板能避免重复牛马劳动。
1.1 函数模板
一般模板的语法是这样的:
template<typename 模板类型名,typename 模板类型名,...>
<返回类型> <函数名>(<参数列表>)
{
<具体实现>
}
当然,上述代码中的 typename 也可以替换成 class。不过建议使用 typename 来向后兼容 C++ 版本。注意一点:一个模板定义头只能用于一个函数 / 类。
那么上面的 max 函数就可以编写成:
template<typename Type>
Type max(Type a,Type b){return a>b?a:b}
不需要为不同类型写多个相似函数:
template<typename T>
T UserDefineMax(T a,T b)
{
return a>b?a:b;
}
cout<<UserDefineMax(3,5); //T=int
cout<<UserDefineMax(3.14,2.0);//T=double
1.2 类模板
考虑到有些程序需要设计一些不同类型但是实现基本一样的类,于是就有了“模板类”。
template<typename 模板类型名,typename 模板类型名,...>
class <类名>
{
<类的成员>
};
注意,同函数模板一样,每一个模板定义头都只能对应一个函数 / 类。\ 创建通用容器 / 工具类:
template<typename T>
class UserDefineVector
{
private:
T*Data;
int Size;
public:
UserDefineVector()
{
Data=nullptr;
Size=0;
}
void PushBack(T value){/*...*/}
};
UserDefineVector<int>IntVec;
UserDefineVector<string>StrVec;
2.模板进阶
自见,已经不想打字了(下面自己看实例)。
2.1 非类型参数
模板不仅能参数化类型,还能参数化值:
template<typename T,int Size>
class FixedArray
{
private:
T Arr[Size];
public:
T&operator[](int index)
{
return Arr[index];
}
};
FixedArray<double,100>SensorData;
2.2 默认模板参数
像函数默认参数一样方便:
template<typename T=int>//默认int类型
class DataLogger
{
public:
void Log(T data){/*...*/}
};
DataLogger<>DefaultLogger;//自动用int
DataLogger<string>TextLogger;
2.3 模板特化
对特定类型定制实现:
//通用模板
template<typename T>
struct IsPointer{static const bool value=false;};
//针对指针类型的特化
template<typename T>
struct IsPointer<T*>{static const bool value=true;};
cout<<IsPointer<int>::value; //0
cout<<IsPointer<int*>::value; //1
2.4 可变参数模板(C++11)
在 C++11 有了参数安全的变长参数列表函数,此时就可以用像传统的 printf 函数一样,拥有可变参数模板。\
这个是变长参数函数的模板:
template<typename <类型替代名>>
<返回类型><函数名>(<第一对参数><参数包>...);
template<typename <类型替代名>>
<返回类型><函数名>(<一对参数>);
举个栗子:编写一个可以容纳很多参数的 max 函数。\
首先编写
template<typename Tp>
Tp max(Tp n){return n};
template<typename Tp,typename ...Targs>
Tp max(Tp n,Targs... Other){return n>max(Other...)?n:max(Other...)}
我们只看第二个模板函数:\
(1)模板和函数原型中使用了 lst... 是表示整个参数包。
3. 附录
3.1 typename 的双重身份
template<typename T>
void UserDefineFunction()
{
typename T::Iterator it;//必须加typename!
}
T::Iterator可能是类型或静态变量;- 加
typename明确告诉编译器这是类型而不是静态成员。3.2 SFINAE 技巧
"替换失败并非错误"原则:
template<typename T> auto UserDefinePrint(const T&val)->decltype(cout<<val,void()) { cout<<val;//支持operator <<(...)的类型 } void UserDefinePrint(...){cerr<<"[Unprintable]";}//保底方案 UserDefinePrint(42); //调用第一个 UserDefinePrint(vector<>());//调用第二个3.3 编译期计算
用模板在编译时完成计算:
template<intN> structFactorial { static const intvalue=N*Factorial<N-1>::value; };
template<> structFactorial<0> { static const intvalue=1; }; int x=Factorial<5>::value;//120,这个值在编译时就被展开后计算
### 3.4 概念约束(C++20)
给模板参数加条件:
```cpp line-numbers
template<typenameT>conceptAddable=requires(Ta,Tb)
{
{a+b}->convertible_to<T>;//要求支持operator +(...)运算
};
template<AddableT>//约束模板参数
TSum(Ta,Tb){return a+b;}
Sum(3,5);//合法
Sum("a","b");//编译错误:不满足Addable
4. 结语(广告)
模板是 C++ 泛型编程的基石(像废话),从 STL 容器到智能指针都依赖它。掌握模板,你就能造出更强大的轮子(牛马)!\
广告位:浅谈C++类语法\
非常感谢各位大佬看到这里。鄙人不才,才疏学浅。如有错误请私信指出。非常感谢您为我(们)的提出宝贵建议。也希望您能将这篇文章推荐给其他 OIer 阅读并轰炸我的私信。(复制粘贴)