C++:模板——详解函数模板与类模板

1. 模板的概念

C++的模板(Templates)是泛型编程的基础,它允许编写与类型无关的代码,从而提高代码的复用性和灵活性。通过模板,你可以编写一种通用的函数或类,而不需要为每种特定的数据类型单独定义多个函数或类。这种机制主要分为函数模板类模板

模板就是建立通用的模具,大大提高复用性

2. 函数模板

函数模板可以用于编写可以接受不同类型参数的函数。你定义一次模板,编译器会根据你调用时的参数类型自动生成相应的函数版本。其基本语法如下:

template <typename T>
T add(T a, T b) {return a + b;
}

解释

  • template – 声明创建模板
  • typename – 表明其后面的符号是一种数据类型,可以用class代替
  • T – 通用的数据类型,名称可以替换,通常为大写字母

这里的template <typename T>声明了一个模板,T是模板参数,表示一种泛型类型。函数add会根据实际调用时的参数类型推导出T,比如当你传递两个整数时,Tint类型;当你传递两个浮点数时,Tfloat类型:

int x = add(5, 3);       // T是int
double y = add(5.2, 3.8); // T是double

2.1 两种方式使用函数模板

  1. 自动类型推导myswap(a,b);
  2. 显示指定类型myswap<int>(a,b);

注意事项

  • 自动类型推导,必须推导出一致的数据类型T,才可以使用
  • 模板必须要确定T的数据类型,才可以使用

2.2 普通函数与函数模板区别

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转换

2.3 普通函数与函数模板的调用规则

  • 如果函数模板和普通函数都可以实现(同名且参数个数相同),优先调用普通函数

  • 可以通过空模板参数列表来强制调用函数模板

    void myPrint(int a,int b){cout<<"调用普通函数"<<endl;
    }template<typename T>
    void myPrint(T a,T b){cout<<"调用模板函数"<<endl;
    }int main(){myPrint(a,b);  //优先调用普通函数myPrint<>(a,b);  //通过空模板参数列表强制调用函数模板
    }
    
  • 函数模板也可以发生重载

  • 如果函数模板可以产生更好的匹配,优先调用函数模板

2.4 模板的局限性

模板并不是万能的,有些特定数据类型,需要用具体化方式做特殊实现

3. 类模板

3.1 基本概念

类模板与函数模板类似,但用于类的定义。使用类模板,可以创建一个通用的类,允许在实例化时为类指定不同的数据类型。其基本语法如下:

template <typename T>
class Box {
private:T value;
public:Box(T v) : value(v) {}T getValue() { return value; }
};

在上面的示例中,Box是一个类模板,T是一个泛型类型。你可以在创建Box对象时为T指定不同的类型:

Box<int> intBox(123);      // T是int
Box<double> doubleBox(45.6); // T是double

3.2 类模板与函数模板区别

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数
template<class NameType,class AgeType = int> //模板参数列表有默认参数
class Person {
public:Person(NameType name, AgeType age) {this->name = name;this->age = age;}void showPerson() {cout << "姓名:" << name << ",年龄:" << age << endl;}NameType name;AgeType age;
};
int main()
{//Person p("孙悟空",1000);  //错误,无法用自动类型推导//Person<string, int> p("孙悟空",1000);  //正确,只能用显示指定类型Person<string> p("猪八戒",999);  //由于模板参数列表有默认参数,所以只需要指出string即可,函数模板不能这么用p.showPerson();return 0;
}

3.3 类模板中成员函数创建时机

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建

3.4 类模板对象做函数参数

template<class T1,class T2>
class Person{
public:T1 name;T2 age;Person(T1 name,T2 age){this->name = name;this->age = age;}void showPerson(){cout << "name:"<<this->name<<"  age:"<<this->age<<endl;}
};

类模板实例化出的对象,向函数传参的方式

  1. 指定传入的类型 – 直接显示对象的数据类型(使用广泛

    void printPerson1(Person<string,int>&p){  //指定传入的类型p.showPerson();
    }
    int main()
    {Person<string,int> p("孙悟空",18);printPerson1(p);
    }
    
  2. 参数模板化 - 将对象中的参数变为模板进行传递

    template<class T1,class T2>
    void printPerson2(Person<T1,T2>&p){  //参数模板化p.showPerson();cout << "T1 的类型为:" << typeid(T1).name() << endl;  //得到T1的类型
    }
    int main()
    {Person<string,int> p("白骨精",18);printPerson2(p);
    }
    
  3. 整个类模板化 - 将这个对象类型模板化进行传递

    template<class T>
    void printPerson3( T &p ){    //整个类模板化p.showPerson();
    }
    int main()
    {Person<string,int> p("牛魔王",18);printPerson3(p);
    }
    

3.5 类模板与继承

注意

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型

    template<class T>
    class Base {T m;
    };
    //class Son : public Base { };//报错:必须要知道父类中的T类型才能继承给子类
    class Son : public Base<int> {};
    int main() {Son s1;
    }
    
  • 如果不指定,编译器无法给子类分配内存

  • 如果想灵活指定出父类中T的类型,子类也需变为类模板

    template<class T>
    class Base {T m;
    };template<class T1,class T2>
    class Son2 : public Base<T1> {T2 obj;
    };int main()
    {Son2<int,char> S2;
    }
    

3.6 类模板成员函数类外实现

template<class T1,class T2>
class Person {
public:Person(T1 name, T2 age);void showPerson();T1 name;T2 age;
};template<class T1,class T2>
Person<T1,T2>::Person(T1 name, T2 age)   //注意Person后必须写一个模板参数列表
{this->name = name;this->age = age;
}template<class T1, class T2>
void Person<T1, T2>::showPerson()  //   //注意Person后必须写一个模板参数列表
{cout << "name:" << this->name << " age:" << this->age << endl;
}int main() {Person<string,int> p("沙和尚",18);p.showPerson();
}

3.7 类模板分文件编写

类模板成员函数创建时机是在调用阶段,导致分文件编写时链接不到

解决方法:

  • 方法一:直接包含.cpp源文件
  • 方法二:将函数声明和实现写到同一个文件中,并更改后缀名为.hpphpp是约定的名称,并不是强制

3.8 类模板与友元

全局函数类内实现:直接在类内声明友元即可

全局函数类外实现:需要提前让编译器知道全局函数的存在

4. class和typename

在C++模板的声明中,classtypename都是用于定义类型参数的关键字,它们的功能几乎完全相同。也就是说,在模板的上下文中,使用classtypename来表示模板参数时,它们是可以互换的。

template <class T>
T add(T a, T b) {return a + b;
}template <typename T>
T multiply(T a, T b) {return a * b;
}

上面两个模板函数,一个使用class声明类型参数,另一个使用typename,但它们的效果完全相同。T在这两种情况下都表示一个类型参数,编译器会根据你传递的参数类型来推导T的具体类型。

区别

尽管classtypename在大多数情况下是可以互换的,但它们之间有一些细微的区别和约定:

  • 历史原因:在最早的C++标准中,class是最早引入模板时用来定义类型参数的关键字。后来,C++标准委员会为了更加语义化和明确,才引入了typename。因此,class作为模板类型参数的关键字更多是历史遗留,而typename则从语义上更符合“表示类型”的含义。

  • 在嵌套依赖类型中,必须使用typename:在某些复杂的模板表达式中,特别是涉及到嵌套类型时,typename是必须的。例如,处理模板参数中包含的嵌套类型时,你必须用typename来明确告知编译器该嵌套依赖的标识符是一个类型,而不是一个变量或其他符号。

    例如

    template <typename T>
    void func() {typename T::value_type val;  // 必须用typename,表明T::value_type是一个类型
    }
    

    在这里,typename是必需的,因为T::value_type可能是一个类型名称。编译器需要通过typename来确认这个符号确实是一个类型,而不是某个成员变量。

总结

虽然classtypename可以在模板参数中互换,但很多C++开发者倾向于使用typename,因为它更能直观地表达模板参数是一个“类型”的含义。class这个关键字容易让人联想到类,而不是类型(尽管类也是一种类型)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/53928.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

写论文还在卡壳?教你用ChatGPT轻松搞定过渡段落!

AIPaperGPT&#xff0c;论文写作神器~ https://www.aipapergpt.com/ 在写论文的路上&#xff0c;最让人头疼的除了查重率飙升&#xff0c;估计就是文献综述了吧&#xff01; 想想看&#xff0c;文献一篇接着一篇&#xff0c;脑子都快炸了&#xff0c;还得想办法把它们连接得…

【测试开岗面试】知识点总结

1.知识点总结 Q:请你分别介绍一下单元测试、集成测试、系统测试、验收测试、回归测试 单元测试 (Unit Testing) 单元测试是对软件中最小可测试单元&#xff08;通常是函数或方法&#xff09;进行验证的过程。它的目的是确保每个单元在设计时的功能能够正常运行。单元测试通常由…

在C++中,如何避免出现Bug?

C中的主要问题之一是存在大量行为未定义或对程序员来说意外的构造。我们在使用静态分析器检查各种项目时经常会遇到这些问题。但正如我们所知&#xff0c;最佳做法是在编译阶段尽早检测错误。让我们来看看现代C中的一些技术&#xff0c;这些技术不仅帮助编写简单明了的代码&…

实际测试工作中成功应用风险评估和应对措施的案例

以下是一些在实际测试工作中成功应用风险评估和应对措施的案例&#xff1a; 案例一&#xff1a;金融软件项目 项目背景&#xff1a; 某金融机构开发一款新的网上银行系统&#xff0c;涉及大量的资金交易和用户敏感信息。项目时间紧、任务重&#xff0c;且对安全性和稳定性要求极…

vue2实践:el-table实现由用户自己添加删除行数的动态表格

需求 项目中需要提供一个动态表单&#xff0c;如图&#xff1a; 当我点击添加时&#xff0c;便添加一行&#xff1b;点击右边的删除时&#xff0c;便删除这一行。 至少要有一行数据&#xff0c;但是没有上限。 思路 这种每一行的数据固定&#xff0c;但是不定行数的&#x…

2024.9.14(RC和RS)

一、replicationcontroller &#xff08;RC&#xff09; 1、更改镜像站 [rootk8s-master ~]# vim /etc/docker/daemon.json {"registry-mirrors": ["https://do.nark.eu.org","https://dc.j8.work","https://docker.m.daocloud.io",&…

【Kubernetes】linux centos安装部署Kubernetes集群

【Kubernetes】centos安装Kubernetes集群 1、环境准备 系统centos7 配置yum源参考文章 Centos系统换yum源 yum -y update 步骤1-3是所有主机都要配置&#xff0c;主机名和hosts配置完后可以使用工具命令同步 1.1 主机 一主二从 主机名ipk8smaster192.168.59.148k8snode11…

git 更新LingDongGui问题解决

今天重新更新灵动gui的代码&#xff0c;以便使用最新的arm-2d&#xff0c;本来以为是比较简单的一件事情&#xff08;因为以前已经更新过一次&#xff09;&#xff0c;却搞了大半天&#xff0c;折腾不易啊&#xff0c;简单记录下来&#xff0c;有同样遇到问题的同学参考&#x…

Maven私服Nexus安装及使用

前言 周末在家闲着无聊&#xff0c;不知道做点啥&#xff0c;就想着自己搭建一个Maven私服来玩玩。刚好使用自己之前在电脑上搭建的虚拟机服务器来操作体验了一把。搭建好私服后&#xff0c;以后自己写的一些小模块啊&#xff0c;工具包啥的就可以发布到自己的私服上了&#xf…

时序预测 | Matlab实现PSO-CNN粒子群优化卷积神经网络时间序列预测

时序预测 | Matlab实现PSO-CNN粒子群优化卷积神经网络时间序列预测 目录 时序预测 | Matlab实现PSO-CNN粒子群优化卷积神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现PSO-CNN粒子群优化卷积神经网络时间序列预测&#xff08;完整源码和数…

yml在线格式转换工具(properties)

网站地址&#xff1a; 在线yaml转properties-在线properties转yaml-ToYaml.com yml&#xff0c;即yaml文本格式文件的后缀名&#xff0c;yaml可以用来替代properties&#xff0c;配置文件短的情况下可读性更好一些。 但是Spring Boot项目配置项多&#xff0c;yml文件看起来不…

一步一步将PlantUML类图导出为自定义格式的XMI文件

一步一步将PlantUML类图导出为自定义格式的XMI文件 说明&#xff1a; 首次发表日期&#xff1a;2024-09-08PlantUML官网&#xff1a; https://plantuml.com/zh/PlantUML命令行文档&#xff1a; https://plantuml.com/zh/command-line#6a26f548831e6a8cPlantUML XMI文档: http…

LabVIEW编程语言出于什么原因开发的?

LabVIEW最初由美国国家仪器公司&#xff08;NI&#xff09;于1986年开发&#xff0c;目的是为工程师和科学家提供一种图形化编程环境&#xff0c;简化数据采集、仪器控制、自动化测试和测量系统开发等工作。开发LabVIEW的主要原因包括以下几点&#xff1a; 简化复杂系统开发&am…

Redis面对数据量庞大处理方法

当Redis面对数据量庞大时&#xff0c;其应对策略需要从多个维度出发&#xff0c;包括数据分片、内存优化、持久化策略、使用集群、硬件升级、数据淘汰策略、合理设计数据结构以及监控系统性能等。以下是对这些策略的详细阐述&#xff0c;以期提供不少于2000字的深入解答。 一、…

别用 npm config set registry 设置淘宝镜像了!!!

常规写法 npm config set registry https://registry.npmmirror.com我相信大部分人都会用这个命令来切换淘宝镜像。我之前也是&#xff0c;我有一个问题那就是我每当想切换镜像的时候都会搜一下淘宝npm镜像。因为我大部分时候都会忘记这个命令是什么样子的。 大宝贝 nrm 自动…

哈工大“计算机设计与实践”(cpu)处理器实验设计报告

哈工大“计算机设计与实践”&#xff08;cpu&#xff09;处理器实验设计报告 【哈工大“计算机设计与实践”&#xff08;cpu&#xff09;处理器实验设计报告】 在计算机科学领域&#xff0c;CPU&#xff08;中央处理器&#xff09;是计算机系统的核心部件&#xff0c;负责执行指…

91、K8s之ingress上集

一、Ingress service模式&#xff1a; loadbalance NodePort&#xff1a;每个节点都会有一个指定的端口 30000-32767 内网 clusterip&#xff1a;默认模式&#xff0c;只能pod内部访问 externalName&#xff1a;需要dns提供域名 1.1、对外提供服务的ingress service&…

SQL Server小技巧之遍历日期

使用背景 一般项目中会遇到&#xff0c;求每日的日报这种&#xff0c;以及计算2个日期内的工作日&#xff0c;或者休息日可能会用到&#xff0c;计算休息日可以用额外的一个字段用来标记当前日期是否是休息日 遍历方式一 DECLARE StartDate DATE 2023-01-01, EndDate DATE …

jmeter之TPS计算公式

需求&#xff1a; 如何确定环境当中的TPS指标 PV:&#xff08;Page View&#xff09;即页面访问量&#xff0c;每打开一次页面PV计数1&#xff0c;刷新页面也是。PV只统计页面访问次 数。 UV(Unique Visitor),唯一访问用户数&#xff0c;用来衡量真实访问网站的用户数量。 一般…

中国电子学会202406青少年软件编程(Python)等级考试试卷(三级)真题与解析

202406Python 三级真题 一、选择题 第 1 题 现有一组初始记录无序的数据“5,8,6,3,9,2”,使用冒泡排序算法,按从小到大的顺序排列,第一轮排序的结果为? A:5,6,3,8,9,2 B:5,6,3,8,2,9 C:5,6,8,3,2,9 D:5,8,3,6,9,2 第 2 题 列表l=[9,…