当前位置: 首页 > news >正文

【C++模版初阶】告别重复造轮子!让代码“活”起来~

C++模版初阶

每次写相同逻辑、只换类型的代码,真的又累又啰嗦。而 C++ 模板,就是来帮我们偷懒的——一套代码,通吃所有类型,从此告别重复CV。今天就从零基础,轻松搞定模板初阶。ദ്ദി˶ー̀֊ー́ )✧

文章目录

  • C++模版初阶
    • 1. 泛型编程
    • 2. 函数模版
      • 2.1 函数模版概念
      • 2.2 函数模版格式
      • 2.3 函数模版的原理
      • 2.4 函数模版的实例化
        • 2.4.1 隐式实例化
        • 2.4.2 显示实例化
      • 2.5 模版参数的匹配原则
    • 3. 类模版
      • 3.1 类模版的定义格式
      • 3.2 成员函数的定义:放在类里还是类外?
        • 3.2 1 类内定义(简单直接)
        • 3.2.2 类外定义(需要一点小技巧)
      • 3.3 一个大坑:声明和定义不要分开放两个文件
      • 3.4 怎么使用类模板?—— 实例化
    • 结语:

1. 泛型编程

你是否也曾陷入过这样的困境:为了实现一个功能,只是参数类型不同,就要把同样的逻辑反复写好几遍?比如交换两个变量的值,int写一遍,double写一遍,char再写一遍……

voidSwap(int&left,int&right){inttemp=left;left=right;right=temp;}voidSwap(double&left,double&right){doubletemp=left;left=right;right=temp;}voidSwap(char&left,char&right){chartemp=left;left=right;right=temp;}

看着是不是很眼熟?没错,这就是函数重载。虽然它能解决问题,但缺点也显而易见:

  • 代码臃肿:每增加一种类型,就得手动新增一个函数,复用率极低。
  • 维护噩梦:如果交换逻辑需要修改,你得同时改好几个地方,漏一个就是 bug!

那有没有一个“万能模具”,告诉编译器一个模子,让它根据不同的类型自动生成对应的代码呢?
有的兄弟,有的!ƪ(˘⌣˘)ʃ
那就是 C++ 的模板,泛型编程的核心工具。


2. 函数模版

2.1 函数模版概念

函数模板代表了一个函数家族,该函数模板与类型无关,它本身不是一个真正的函数, 而是告诉编译器如何根据你传入的参数类型产生函数的特定类型版本


2.2 函数模版格式

template<typename T1,typename T2,......,typename Tn> 返回值类型 函数名(参数列表){ }

例如:

template<typenameT>voidSwap(T&left,T&right){T temp=left;left=right;right=temp;}

敲黑板:

  • template关键字:表明这是一个模板。
  • <typename T>:T 是一个类型参数,你可以把它想象成一个占位符,将来会被intdouble等真实类型替代。typename也可以用class,效果完全一样。

现在,你只需要维护这一份代码,编译器会在你调用Swap(a, b)时,根据ab的实际类型,自动生成对应版本的Swap函数。


2.3 函数模版的原理

当你写下Swap(x, y)时,编译器会:

  1. 查看xy的类型(比如都是double)。
  2. 将模板中的T全部替换成double
  3. 生成一份处理double类型交换的完整代码。

整个过程不需要你动手,全自动完成。


2.4 函数模版的实例化

模板写好了,怎么用?有两种方式让编译器真正生成代码:隐式实例化和显式实例化

2.4.1 隐式实例化

让编译器自己根据实参的类型去猜T是什么。

template<classT>TAdd(constT&left,constT&right){returnleft+right;}intmain(){inta=3,b=5;doublec=2.5,d=1.2;Add(a,b);// 编译器推导 T 为 int,生成 int 版本Add(c,d);// 编译器推导 T 为 double,生成 double 版本// 下面的调用会出问题:一个 int,一个 double,T 该是什么?// Add(a, c); // 编译错误!类型不明确return0;}

如果参数类型不统一怎么办?你可以手动“协调”一下:

Add(a,(int)c);// 把 double 强转成 intAdd((double)a,c);// 把 int 强转成 double
2.4.2 显示实例化

用尖括号<类型>直接告诉编译器你要用哪种类型,不用它猜。

Add<int>(a,c);// 强制 T 为 int,c 会被隐式转换为 intAdd<double>(a,c);// 强制 T 为 double,a 会被隐式转换为 double

显式实例化尤其适用于模板参数无法从函数参数中推导的场景(比如返回值类型与参数无关):

template<classT>T*CreateArray(intn){returnnewT[n];}intmain(){// 无法从参数推导 T,必须显式指定double*arr=CreateArray<double>(10);return0;}

2.5 模版参数的匹配原则

有时候,你既写了模板,又写了一个同名的普通函数,编译器会怎么选?

敲黑板:

  1. 如果普通函数完全匹配,优先调用普通函数——编译器觉得你手动写的肯定有特殊意图。ᴖᗜᴖ
  2. 如果模板能生成一个更匹配的版本,那就调用模板生成的版本——模板更灵活。
  3. 普通函数支持隐式类型转换,模板函数一般不做转换(因为模板匹配要求严格)。

来看个例子:

// 普通函数intAdd(intx,inty){return(x+y)*10;// 故意乘 10,便于区分}// 函数模板template<classT>TAdd(T x,T y){returnx+y;}intmain(){inta=1,b=2;doublec=1.5,d=2.5;cout<<Add(a,b)<<endl;// 调用普通函数,输出 (1+2)*10 = 30cout<<Add(c,d)<<endl;// 模板生成 double 版本,输出 4.0cout<<Add(a,c)<<endl;// 模板生成更匹配的 double 版本?不,这里会报错或需要显式实例化// 若改为 Add<double>(a, c),则强制使用模板,输出 2.5return0;}

小提示:如果希望总是优先使用模板,可以写成Add<>(a, b),空尖括号表示“就要模板,不要普通函数”。


3. 类模版

3.1 类模版的定义格式

template<class T1,class T2,......,class Tn> class 类模版名 { //类内成员定义 };

例如:

template<typenameT>classStack{public:Stack(size_t capacity=4);voidPush(constT&data);// 其他成员函数...private:T*_array;// 指向动态数组的指针,数组元素类型为 Tsize_t _capacity;size_t _size;};

这里的T还是一个类型占位符。你可以叫它T,也可以叫Type, 只要统一就行。编译器会在你真正使用这个类的时候,把T替换成真实的类型(比如intdouble)。

注意:typenameclass在这里完全等价,用哪个看个人习惯。不过,老派程序员好像更偏爱class(¯▽¯)ゞ


3.2 成员函数的定义:放在类里还是类外?

3.2 1 类内定义(简单直接)

直接把函数体写在类里面,和普通类一模一样:

template<typenameT>classStack{public:voidPush(constT&data){// 直接写扩容和赋值逻辑if(_size==_capacity){// 扩容...}_array[_size++]=data;}// ...};

这种方式最省心,不会有额外的语法烦恼。

3.2.2 类外定义(需要一点小技巧)

有时候你想把声明和定义分开,让类的“接口”看起来更清晰。这时每个成员函数在类外定义时,都必须再次声明模板参数,并且用类名<T>::来限定作用域。

template<typenameT>voidStack<T>::Push(constT&data){if(_size==_capacity){// 扩容:新开一块空间,复制过去,释放旧空间T*tmp=newT[_capacity*2];for(size_t i=0;i<_size;++i)tmp[i]=_array[i];delete[]_array;_array=tmp;_capacity*=2;}_array[_size++]=data;}

看到那个Stack<T>::了吗?因为Stack现在是一个模板名,而不是一个具体的类。只有Stack<int>Stack<double>才是真正的类。所以定义成员函数时必须告诉编译器:这个函数属于Stack<T>这个家族。


3.3 一个大坑:声明和定义不要分开放两个文件

写普通类时,我们习惯把声明放.h,定义放.cpp。但是对模板类,这样做几乎必然导致链接错误

原因很简单:编译器在编译.cpp文件时,如果看不到模板的完整定义(比如Push的实现),就无法为Stack<int>生成真正的代码。等链接时,就会报“找不到Stack<int>::Push的符号”。

解决方案:把类模板的声明和所有成员函数的定义都放在同一个头文件里(通常就叫Stack.h)。


3.4 怎么使用类模板?—— 实例化

类模板和函数模板的一个显著区别是:类模板不支持隐式实例化。你必须显式地告诉编译器,你要用哪种类型。

intmain(){// 创建一个存放 int 的栈Stack<int>st1;st1.Push(10);st1.Push(20);// 创建一个存放 double 的栈Stack<double>st2;st2.Push(1.1);st2.Push(2.2);// 也可以用在堆上Stack<char>*pst=newStack<char>(100);pst->Push('A');// ...deletepst;return0;}

Stack<int>就是类模板的一个实例化结果,它是一个实实在在的类。
Stack<double>是另一个完全不同的类。它们各自拥有独立的代码,互不干扰。


结语:

今天的内容到这里就结束了,希望你能有所收获~

代码无bug,学习不迷路,我们下篇再见!(•̀ᴗ•́)و

http://www.jsqmd.com/news/858720/

相关文章:

  • Fs工作室_免Root虚拟位置保护软件
  • 企业AI算力平台怎么选?AIOS vs 华为昇腾 vs 浪潮AIStation vs 自建K8s横评 - 博客万
  • 【Linux】基本指令1
  • 如何用YOLOv8 AI瞄准技术轻松提升FPS游戏水平:从入门到精通的完整指南
  • Visual Studio彻底清理指南:为什么你需要专业的卸载工具?
  • 论文的重复率是什么?
  • 3步解锁B站缓存视频:m4s-converter让你的离线收藏永不过期
  • 【Java】HashMap底层原理
  • AI写专著必备攻略:借助AI专著写作工具,3天完成20万字专著撰写!
  • 软件工程师在智能体视觉时代的机遇(24)
  • 告别InfluxDB命令行:免费图形化管理工具的终极解决方案
  • Pearcleaner:macOS应用彻底卸载的终极解决方案,3步告别残留文件
  • 如何快速掌握Robomongo:免费MongoDB管理工具的完整指南
  • Vue3转React实战:VuReact 可控混写迁移指南
  • 通过curl命令快速测试Taotoken各模型效果与兼容性
  • 2026论文降AI率必备清单:2026权威工具测评榜与精准避坑指南
  • 腾讯企业邮箱怎么注册申请?留存实用申请联系电话 - 品牌2025
  • DiffSinger歌声合成:3大技术革新与完整部署指南
  • OpenXR-Toolkit技术深度解析:VR渲染优化与API层注入架构剖析
  • rust语言学习笔记Trait(一)Copy、Clone(拷贝)
  • OpCore-Simplify:开源系统硬件适配的自动化配置引擎
  • 如何使用Python和TensorFlow Lite实现高效人脸检测与面部特征分析
  • 通过 curl 命令快速测试 Taotoken 大模型 API 可用性与返回格式
  • 如何突破Switch游戏限制:Ryujinx开源模拟器的5大实战解决方案
  • 安徽GEO优化公司|从技术培训到全案代运营,安徽GEO服务商各司所长 - 行业深度观察C
  • Transparent Background 实战指南:一键式智能背景去除工具深度解析
  • 工业自动化调试实战:OpenModScan解决Modbus协议调试的5大挑战
  • 鲜炖燕窝品牌哪个好:燕口福冻干即食燕窝与传统鲜炖的全面对比 - 新闻快传
  • 如何快速配置Live Server Web Extension:提升开发效率的完整指南
  • 缅甸语语音合成效果断崖式下降?紧急排查ElevenLabs API v2.3.1兼容性漏洞,48小时内必须升级!