【C++笔记】模板初阶

【C++笔记】模板初阶

前言:

        C++模板是C++中实现泛型编程的核心工具,允许程序员编写与类型无关的代码,从而提高代码的复用性和灵活性。模板在编译时进行实例化,根据实际使用的类型生成具体的代码,因此不会带来运行时开销。

        

一、模板基础

        

1.1 为什么需要模板?

        

在编写函数或类时,如果希望它们能处理多种数据类型(如intdoublestring),传统方法是使用函数重载,但这样会产生大量重复代码或失去类型信息。

模板允许将类型作为参数,编译器根据调用时传入的具体类型生成对应的代码。

        

场景:需要编写一个求两个数最大值的函数,支持 intdouble 和 string(按字典序)。

        

①传统方法:函数重载

#include <iostream> #include <string> using namespace std; // 为 int 重载 int max(int a, int b) { cout << "int version\n"; return a > b ? a : b; } // 为 double 重载 double max(double a, double b) { cout << "double version\n"; return a > b ? a : b; } // 为 string 重载 string max(const string& a, const string& b) { scout << "string version\n"; return a > b ? a : b; } int main() { cout << max(3, 5) << "\n"; // int version cout << max(3.14, 2.7) << "\n"; // double version cout << max("hello", "world") << "\n"; // string version }

        

缺点:

        

①代码重复:每个类型都要写一遍几乎相同的函数体。

        

②扩展性差:若要支持新类型(如 char、float),必须添加新重载。

        

③维护困难:修改算法(如改为 a < b 返回较小值)需要改动所有重载函数。

        

②模板方法:函数模板

#include <iostream> #include <string> using namespace std; template <typename T> T max(T a, T b) { cout << "template version for " << typeid(T).name() << "\n"; return a > b ? a : b; // 要求 T 支持 operator> } int main() { cout << max(3, 5) << "\n"; // T = int cout << max(3.14, 2.7) << "\n"; // T = double cout << max("hello", "world") << "\n"; // T = string // 甚至支持新类型,只要它实现了 operator> cout << max('a', 'b') << "\n"; // T = char }

        

优点

        

①一份代码适用于所有类型,只要该类型支持 operator>。

        

②无需为每种类型单独编写,维护成本低。

        

③编译器根据实际调用生成对应版本的函数,效率与手写重载相同。


        

1.2 泛型编程思想        

        

模板是C++支持泛型编程的基础,其核心思想是“参数化类型”——将类型也视为一种参数,让算法和数据结构独立于具体类,从而编写与类型无关的通用代码,使之能够进行代码复用。

        

1.3 模板的分类

        

一般而言,模板分为两类:函数模板和类模板。

温馨提示:一个模板只能服务于 一个函数 或则 一个类 

        

二、函数模板

        

2.1 函数模板的概念

        

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

        

        

2.2 函数模板的定义

        

函数模板定义以template关键字开头,后跟模板参数列表,然后是函数声明。

        

模板函数的语法:

        

①关键字template <class 模板名称>   或   template<typename 模板名称>

        

例如: template<class T> 或   template<typename T>      

        

备注: 这里的模板名称为:T (可自定义为任意名称)

        

温馨提示:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替 class)            

        

        

        

②通过函数模板定义函数


        

template<class T>

返回值类型函数名(参数列表)

{


      //函数实现

}

        

例如1: 函数模板定义函数

        

template<class T>

void Swap( T& a, T& b)

{        

        //函数实现 

}       


        

例如2:这里的返回类型也可以是模板参数

        

template<class T>

T max(T a, T b) 



{        

        //函数实现 

}   

            

        

③通过模板定义多个模板,实现接受不同类型的参数    

        

例如:template<class T1, class T2>

        

温馨提示:

        

A.当只有一个模板参数时,一般须传入相同类型的实参。

        

B.当有两个模板参数时,可以传不同类型的实参

        

代码示例1:通过函数模板编写一个交换两个数的函数。

#include<iostream> using namespace std; template<class T> void Swap( T& a, T& b) { T temp = a; a = b; b = temp; } int main() { int m = 10, n = 20; double a = 1.2, b = 2.5; Swap(m, n); Swap(a, b); return 0; }

        

代码示例2:通过函数模板编写一个求两个数最大值的函数。

#include<iostream> using namespace std; template <typename T> T max(T a, T b) { return a > b ? a : b; } int main() { int i = max(3, 5); // T 被推导为 int double d = max(3.14, 2.7); // T 被推导为 double return 0; } 

        

代码示例3:通过模板定义多个模板,以实现接受不同类型的参数 

#include<iostream> using namespace std; template<class T1, class T2> void func2(const T1& x, const T2& y) { cout << x << " " << y << endl; } int main() { int a=10; double b = 3.14; func(a,b); //T1被推导为int类型,T2被推导为double类型 return 0; }

        

2.3 函数模板的原理

        

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具,所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

        

如图所示:swap函数通过编译器进行推演不同的类型

        

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应 类型的函数以供调用。

        

例如:当用double类型使用函数模板时,编译器通过对实参类型的推演, 将T确定为double类型,然后产生一份专门处理double类型的代码,对于char类型也是如此。

        

2.4 函数模板的实例化

        

不同类型的参数使用函数模板时,称为函数模板的实例化,模板参数实例化分为:隐式实例化 和 显式实例化。

        

1. 隐式实例化:让编译器根据实参推演模板参数的实际类型

        

代码示例:通过函数模板实现两数相加。

#include<iostream> using namespace std; template<class T> T Add(const T& left, const T& right) {    return left + right; } int main() {    int a1 = 10, a2 = 20;    double d1 = 10.0, d2 = 20.0;    Add(a1, a2);    Add(d1, d2);    return 0; }

        

温馨提示:如果只有一个模板参数时,传入两个不同类型,该语句不能通过编译

#include<iostream> using namespace std; template<class T> T Add(const T& left, const T& right) {    return left + right; } int main() {    int a1 = 10;    double d1 = 3.14;    Add(a1, d1); //编译报错    return 0; } 

        

报错原因:

        

因为在编译期间,当编译器看到该实例化时,需要推演其实参类型:

        

通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T 确定为 int 或者 double 类型而报错。


        

注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅。  

        

解决办法一:用户自己来强制转化

#include<iostream> using namespace std; template<class T> T Add(const T& left, const T& right) {    return left + right; } int main() {    int a1 = 10;    double d1 = 3.14;    Add(a1,(int)d1); //编译报错    return 0; } 

        

解决办法二:显式实例化

#include<iostream> using namespace std; template<class T> T Add(const T& left, const T& right) {    return left + right; } int main() {    int a1 = 10;    double d1 = 3.14;    Add<int>(a1,d1); //编译报错    return 0; } 

        

详情解释:显式指定模板参数

        

例如: Add<int>(a1, d1)  或   Add<double>(a1, d1)

        

这意味着你在调用时明确告诉编译器:“模板参数 T 就是 int(或 double),不要再推导了”,此时编译器会跳过类型推导过程,直接使用你指定的类型来实例化函数模板。

                

        

编译器会检查这个调用是否合法:

        

①对于 Add<int>(a1, d1),生成的函数签名是 int Add(const int&, const int&)。

        

②实参 a1 是 int,可以直接绑定到 const int&。

        

③实参 d1 是 double,但 double 可以隐式转换为 int,因此编译器会生成一个临时 int 对象(值为 d1 截断后的整数)传递给函数。

    这种隐式转换是允许的,因此调用成功。

        

同理,Add<double>(a1, d1) 会将 a1 从 int 隐式转换为 double,也可以正常调用。

        

        

2. 显式实例化:在函数名后的<>中指定模板参数的实际类型         

        

代码示例:显示实例化Add函数的模板参数

 #include<iostream> using namespace std; template<class T> T Add(const T& left, const T& right) {    return left + right; } int main() {    int a = 10;    double b = 20.0;        // 显式实例化    Add<int>(a, b);    return 0; }

        

2.5 模板参数的匹配原则

        

匹配原则一:

一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。

        

代码示例:验证匹配原则一

 // 专门处理int的加法函数 int Add(int left, int right) { return left + right; } // 通用加法函数 template<class T> T Add(T left, T right) { return left + right; } void Test() { Add(1, 2);       // 与非模板函数匹配,编译器不需要特化 Add<int>(1, 2);  // 调用编译器特化的Add版本 }

        

匹配原则二:

①对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而 不会从该模板产生出一个实例。

        

②如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。

        

代码示例:验证匹配原则二

 // 专门处理int的加法函数 int Add(int left, int right) { return left + right; } // 通用加法函数 template<class T1, class T2> T1 Add(T1 left, T2 right) { return left + right; } void Test() { Add(1, 2);     // 与非函数模板类型完全匹配,不需要函数模板实例化 Add(1, 2.0);   // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数 }

        

三、 类模板

        

3.1 类模板的概念

        

类模板不是具体的类,而是一个“类的蓝图”。它使用模板参数(通常是类型参数)来描述类中成员的类型,这些成员包括数据成员、成员函数的参数和返回值等。

        

3.2 类模板的定义

        

类模板的定义必须以 template 关键字开头,后面紧跟模板参数列表(用尖括号 <> 包围)。这是类模板语法的核心标志,告诉编译器接下来的类是一个模板,可以参数化类型或值。

        

基本语法定义如下所示:        

        

template<class T1, class T2, ..., class Tn>    或   template<typenameT1, typename T2, ..., typename Tn>

        

class 类模板名

{

      // 类内成员定义

};



温馨提示:        


        

①template <typename T> 和 template <class T>   中的 T 是一个占位符类型名,在使用时会被实际类型替换。

        

②类定义内可以使用 T 声明成员变量、成员函数参数和返回类型。

        

③成员函数如果在类外定义,必须再次声明模板参数,并使用完整的类名 <T> 限定。

        

代码示例:演示语法

template <typename T> // T 是类型参数,也可用 class 代替 typename class Stack { public: void push(const T& x); T pop(); privte: T data[100]; int top; };

      

代码示例: 成员函数的外部定义

        

成员函数如果在类外定义,必须再次声明模板参数,并使用完整的类名 <T> 限定:

template <typename T> // T 是类型参数,也可用 class 代替 typename class Stack { public: void push(const T& x); T pop(); privte: T data[100]; int top; }; template <typename T> void Stack<T>::push(const T& x) { data[++top] = x; } template <typename T> T Stack<T>::pop() { return data[top--]; }

        

3.2 类模板的实例化

        

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

        

简单来说:对于模板类而言,类名不能代表类型,类名需要绑定模板参数才能成为类型。

        

代码示例:演示stack类代码实例化

template<class T> class Stack { public: Stack(int n = 4) : _array(new T[n]) , _capacity(n) ,_size(0) {} //类内声明 类外定义的写法 void Push(const T& x); ~Stack() { delete[] _array; } private: T* _array; size_t _capacity; size_t _size; }; template<class T> void Stack<T>::Push(const T& x) { if (_size == _capacity) { //扩容处理: 开新空间 -> 拷贝内容 -> 释放旧空间 -> 指向新空间 ->更新容量 int newcapacity = _capacity * 2; //开新空间 T* tmp = new T[newcapacity]; //拷贝内容 memcpy(tmp, _array, _capacity); //释放旧空间 delete[] _array; //指向新空间 _array = tmp; //更新容量 _capacity *= 2; } _array[_size++] = x; } int main() { //类模板都需要显示实例化,不能做到隐式实例化 Stack<int> st1; //整形 int类 st1.Push(1); st1.Push(2); st1.Push(3); Stack<double> st2; //浮点数 double类 st2.Push(1.1); st2.Push(1.2); st2.Push(1.3); Stack<double>* pst = new Stack<double>; return 0; }

        

既然看到这里了,不妨关注+点赞+收藏,感谢大家,若有问题请指正。

Read more

苹果最贵手机要来了!折叠屏iPhone将于9月亮相;部分高校严禁校内使用OpenClaw;黄仁勋预言:传统软件和APP或将消失 | 极客头条

苹果最贵手机要来了!折叠屏iPhone将于9月亮相;部分高校严禁校内使用OpenClaw;黄仁勋预言:传统软件和APP或将消失 | 极客头条

「极客头条」—— 技术人员的新闻圈! ZEEKLOG 的读者朋友们好,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧。(投稿或寻求报道:[email protected]) 整理 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) 一分钟速览新闻点! * 多所高校要求警惕 OpenClaw 安全风险,部分严禁校内使用 * 荣耀 CEO 李健:荣耀机器人全栈自研,将聚焦消费市场 * 马化腾凌晨 2 点发声:还有一批龙虾系产品陆续赶来 * 前快手语言大模型中心负责人张富峥,已加入智源人工智能研究院,负责 LLM 方向 * 最新全球 AI 应用百强榜发布,豆包/DeepSeek/千问上榜 * 苹果折叠 iPhone 将于九月亮相,融合 iPhone 与 iPad 体验

By Ne0inhk
不止“996”!曝硅谷AI创业圈「极限工作制」:每天16小时、凌晨3点下班、周末也在写代码

不止“996”!曝硅谷AI创业圈「极限工作制」:每天16小时、凌晨3点下班、周末也在写代码

编译 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) “如果你周日去旧金山的咖啡馆,会发现几乎每个人都在工作。” 这是 AI 创业公司 Mythril 联合创始人 Sanju Lokuhitige 最近最直观的感受。去年 11 月,他特地搬到旧金山,只为了更接近 AI 创业浪潮的中心。但很快,他也被卷入了这股浪潮带来的另一面——一种越来越极端的工作文化。 Lokuhitige 坦言,他现在几乎每天工作 12 小时,每周 7 天。除了每周少数几场刻意安排的社交活动(主要是为了和创业者们建立联系),其余时间几乎都在写代码、做产品。 “有时候我整整一天都在编程,”他说,“我基本没有什么工作与生活的平衡。”而这样的生活,在如今的 AI 创业圈里并不算罕见。 旧金山 AI 创业圈的真实日常 一位在旧金山一家 AI

By Ne0inhk
黄仁勋公开发文:传统软件开发模式终结,参与AI不必非得拥有计算机博士学位

黄仁勋公开发文:传统软件开发模式终结,参与AI不必非得拥有计算机博士学位

AI 究竟是什么?在 NVIDIA CEO 黄仁勋看来,它早已不只是聊天机器人或某个大模型,而是一种正在迅速成形的“新型基础设施”。 近日,黄仁勋在英伟达官网发布了一篇长文,提出一个颇具形象的比喻——AI 就像一块“五层蛋糕”。从最底层的能源,到芯片、基础设施、模型,再到最上层的应用,人工智能正在形成一整套完整的产业技术栈,并像电力和互联网一样,逐渐成为现代社会的底层能力。 这也是黄仁勋自 2016 年以来公开发表的第七篇长文。在这篇文章中,他从计算机发展史与第一性原理出发,试图解释 AI 技术栈为何会演化成如今的形态,以及为什么全球正在掀起一场规模空前的 AI 基础设施建设。 在他看来,过去几十年的软件大多是预先编写好的程序:人类设计好算法,计算机按指令执行,数据被结构化存储在数据库中,通过精确查询调用。而 AI 的出现打破了这一模式——计算机开始能够理解图像、文本和声音,并根据上下文实时生成答案、推理结果甚至新的内容。 正因为智能不再是预先写好的代码,而是实时生成的能力,支撑它运行的整个计算体系也必须被重新设计。

By Ne0inhk
猛裁1.6万人后,网站再崩6小时、一周4次重大事故!官方“紧急复盘”:跟裁员无关,也不是AI写代码的锅

猛裁1.6万人后,网站再崩6小时、一周4次重大事故!官方“紧急复盘”:跟裁员无关,也不是AI写代码的锅

整理 | 郑丽媛 出品 | ZEEKLOG(ID:ZEEKLOGnews) 过去几年里,科技公司几乎都在同一件事上加速:让 AI 参与写代码。 从自动补全、自动生成函数,到直接修改系统配置,生成式 AI 已经逐渐走进真实生产环境。但最近发生在亚马逊的一连串事故,却给整个行业泼了一盆冷水——当 AI 开始真正参与生产环境开发时,事情可能远比想象复杂。 最近,多家媒体披露,本周二亚马逊内部紧急召开了一场工程“深度复盘(deep dive)”会议,专门讨论最近频繁出现的系统故障——其中,一个被反复提及的关键词是:AI 辅助代码。 一周 4 次严重事故,亚马逊内部紧急复盘 事情的起点,是最近一段时间亚马逊系统稳定性明显下降。 负责亚马逊网站技术架构的高级副总裁 Dave Treadwell 在一封内部邮件中坦言:“各位,正如大家可能已经知道的,最近网站及相关基础设施的可用性确实不太理想。” 为此,公司决定把原本每周例行举行的技术会议

By Ne0inhk