C++提高编程---模板---类模板

目录

一、类模板

1.模板

2.类模板的作用

3.语法

4.声明

二、类模板和函数模板的区别

三、类模板中成员函数的创建时机

四、类模板对象做函数参数

五、类模板与继承

六、类模板成员函数类外实现

七、类模板分文件编写

八、类模板与友元

九、类模板案例


一、类模板

1.模板

模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。

模板是一种对类型进行参数化的工具;

通常有两种形式:函数模板和类模板;

函数模板针对仅参数类型不同的函数;

类模板针对仅数据成员和成员函数类型不同的类。

使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个swap函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个swap模板函数,即可以实现int 型,又可以实现double型的交换。模板可以应用于函数和类。

注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。

2.类模板的作用

建立一个通用类,类中的成员  数据类型可以不具体制定,用一个虚拟的类型来代表

3.语法

template <typename  T>

4.声明

template  -- 声明创建模板

typename  --  表明其后的符号是一种数据类型,可以用clss代替

T      --  通用的数据类型,名称可以替换,通常为大写字母

示例

#include<iostream>using namespace std;// 类模板template<class name_type,class age_type>class person{public:person(name_type name,age_type age){this->name=name;this->age=age;}void show(){cout<<"姓名:"<<this->name<<"\t年龄:"<<this->age<<endl;}name_type name;age_type age;};void test01(){// 类型参数化person<string,int> p1("Ton",89);p1.show();}int main(){test01();return 0;}

运行结果:

总结:类模板和函教模板语法相似。在声明模板template后面加类,此类称为类模板

二、类模板和函数模板的区别

区别:

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数

示例:

#include<iostream>using namespace std;// 类模板template<class name_type,class age_type  = int> // 可以指定某一个通用类型具体的类型class person{public:person(name_type name,age_type age){this->name=name;this->age=age;}void show(){cout<<"姓名:"<<this->name<<"\t年龄:"<<this->age<<endl;}name_type name;age_type age;};void test01(){// 自动类型推导不行// person p1("TON",23);// 只能用指定类型person<string,int> p1("Ton",89);p1.show();}void test02(){// 类模板在模板参数列表中可以有默认值person<string> p2("Jack",90);p2.show();}int main(){test01();test02();return 0;}

运行结果:

三、类模板中成员函数的创建时机

类模板中成员函数和普通类中成员函数创建时机的区别:

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

示例:

#include<iostream>using namespace std;class person1{public:void showperson1(){cout<<"person1的调用"<<endl;}};class person2{public:void showperson2(){cout<<"person2的调用"<<endl;}};template<class T>class my_class{public:T obj;// 类模板中的成员函数//   在运行前都不会创建这两个成员函数void func1(){obj.showperson1();}void func2(){obj.showperson2();}};void test01(){my_class<person1> m;m.func1();//m.func2();  运行出错,说明函数调用才会去创建成员函数my_class<person2> m1;m1.func2();}int main(){test01();return 0;}

运行结果:

四、类模板对象做函数参数

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

三种传入方式:

  1. 指定传入的类型 --- 直接显示对象的数据类型(**常用)**
  2. 参数模板化         --- 将对象中的参数变为模板进行传递
  3. 整个类模板化     --- 将这个对象类型   模板化进行传递

示例:

#include<iostream>#include<typeinfo>using namespace std;/*三种传入方式:1. 指定传入的类型 --- 直接显示对象的数据类型2. 参数模板化         --- 将对象中的参数变为模板进行传递3. 整个类模板化     --- 将这个对象类型   模板化进行传递*/template<class T1,class T2>class person{public:person(T1 name, T2 age){this->name = name;this->age = age;}void show(){cout<<"姓名:"<<this->name<<"\t年龄:"<<this->age<<endl;}T1 name;T2 age;};// 1.指定传入类型void print1(person<string ,int> &p){p.show();}void test01(){// 1. 指定传入的类型 --- 直接显示对象的数据类型person<string ,int> p ("TOM",99);print1(p);}// 2.参数模板化template<class T1,class T2>void print2( person<T1,T2> &p1){p1.show();cout<<"看编译器推断的模板是什么类型"<<endl;cout<<"T1的类型为:"<<typeid(T1).name()<<endl;cout<<"T2的类型为:"<<typeid(T2).name()<<endl;}void test02(){// 2. 参数模板化         --- 将对象中的参数变为模板进行传递person <string ,int> p1 ("JACK",78);print2(p1);}// 3.将整个类模板化template<class T>void print3(T &p2){p2.show();cout<<"T的数据类型"<<endl;cout<<"T的类型为:"<<typeid(T).name()<<endl;}void test03(){person<string,int> p2("LILY",45);print3(p2);}int main(){test01();test02();test03();return 0;}

运行结果:

总结:

  • 通过类模板创建的对象。可以有三种方式向函数中进行传参
  • 使用比较广泛是第一种:指定传入的类型

五、类模板与继承

注意:

  1. 当子类继承父类是一个类模板时,子类在声明的时候,要指定出父类T的类型
  2. 如果不能确定,编译器无法给予子类分配内存
  3. 如果想灵活指定父类中T的类型,子类也需要变为类模板

示例1:

#include<iostream>#include<typeinfo>using namespace std;// 类模板与继承template<class T>class Base{T m;};// class Son:public Base  // 必须要知道父类中T的类型,才能继承给子类class Son:public Base<int>{};int main(){Son s1;return 0;}

生成成功:

示例2:

#include<iostream>#include<typeinfo>using namespace std;template<class T>class Base{T m;};// 如果想要灵活指定父类中T的类型,子类需要变类模板template<class T1,class T2>class Son2:public Base<T2>{public:Son2(){cout<<"T1的类型为:"<<typeid(T1).name()<<endl;cout<<"T2的类型为:"<<typeid(T2).name()<<endl;}T1 ojb;};void test02(){Son2<int ,char>s2;// char 传给父类,int 传给子类}int main(){test02();return 0;}

运行结果:

六、类模板成员函数类外实现

示例:

#include<iostream>using namespace std;// 类模板成员函数类外实现template<class T1,class T2>class person{public:person(T1 name,T2 age);/*{this->name=name;this->age=age;}*/void show();/*{cout<<"姓名:"<<this->name<<"\t年龄:"<<this->age<<endl;}*/T1 name;T2 age;};// 构造函数的类外实现template<class T1,class T2>person<T1,T2>::person(T1 name,T2 age){this->name=name;this->age=age;}// 成员函数的类外实现template<class T1,class T2>void person<T1,T2>::show(){cout<<"姓名:"<<this->name<<"\t年龄:"<<this->age<<endl;}void test01(){person<string,int>p("TOM",28);p.show();}int main(){test01();return 0;}

运行结果:

七、类模板分文件编写

问题:

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

解决

  1. 直接包含 .cpp 源文件
  2. 将声明和实现写到同一个文件中,并更改后缀名为 .hpp , hpp 是约定的名称,并不是强制

示例:

person.hpp文件

#include<iostream>using namespace std;// 类模板成员函数类外实现template<class T1, class T2>class person{public:person(T1 name, T2 age);T1 name;T2 age;};// 构造函数的类外实现template<class T1, class T2>person<T1, T2>::person(T1 name, T2 age){this->name = name;this->age = age;}// 成员函数的类外实现template<class T1, class T2>void person<T1, T2>::show(){cout << "姓名:" << this->name << "\t年龄:" << this->age << endl;}

.cpp文件

#include<iostream>#include"person.hpp"using namespace std;void test01(){person<string, int>p("TOM", 28);p.show();}int main(){test01();return 0;}

八、类模板与友元

类模板配合友元函数的类内实现和类外实现

  1. 全局函数类内实现 -- 直接在类内声明友元即可
  2. 全局函数类外实现 -- 需要提前让编译器知道全局函数的存在

示例:

#include<iostream>using namespace std;template<class T1,class T2>class person;// 类外实现template<class T1,class T2>void print2(person<T1,T2> p){cout<<"类外实现---姓名:"<<p.name<<"\t年龄:"<<p.age<<endl;}// 类模板与友元template<class T1,class T2>class person{// 通过全局函数打印输出// 全局函数类内实现friend void print1(person<T1,T2> p){cout<<"姓名:"<<p.name<<"\t年龄:"<<p.age<<endl;}// 全局函数类外实现// 需要加空模板的参数列表// 如果全局函数是类外实现,需要让编译器提前知道这个函数的存在friend void print2<>(person<T1,T2> p);public:person(T1 name,T2 age){this->name=name;this->age=age;}private:T1 name;T2 age;};void test01(){person<string,int>p("TOM",28);print1(p);print2(p);}int main(){test01();return 0;}

运行结果:

九、类模板案例

目的:

  1. 可以对内置数据类型以及自定义数据类型的数据进行存储
  2. 将数组中的数据存储到堆区
  3. 构造函数中可以传入数组的容量
  4. 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  5. 提供尾插法和尾删除法对数组中的数据进行增加和删除
  6. 可以通过下标的方式访问数组中的元素
  7. 可以获取数组中当前元素的个数和数组的容量

my_array.hpp

#pragma once#include<iostream>using namespace std;// 自己的通用的数组类template<class T>class my_array{public:// 有参构造, 传入容量my_array(int copacity){cout << "my_array的有参构造" << endl;this->m_Capacity = copacity;this->m_Size = 0;this->p_Address = new T[this->m_Capacity];}//拷贝构造my_array(const my_array& arr){this->m_Capacity = arr.m_Capacity;this->m_Size = arr.m_Size;// 浅拷贝 this->p_Address = arr. p_Address;// 深拷贝this->p_Address = new T[arr.m_Capacity];//  将arr中的数据拷贝进来for (int i = 0; i < this->m_Size; i++){this->p_Address[i] = arr.p_Address[i];}}// operator = 防止浅拷贝的问题my_array& operator=(const my_array& arr){// 先判断原来堆区是否有数据,先释放堆区数据if (this->p_Address != NULL){delete[]this->p_Address;this->p_Address = NULL;this->m_Capacity = 0;this->m_Size = 0;}this->m_Capacity = arr.m_Capacity;this->m_Size = arr.m_Size;this->p_Address = new T[arr.m_Capacity];for (int i = 0; i < this->m_Size; i++){this->p_Address[i] = arr.p_Address[i];}return *this;}// 尾插法void Push_Back(const T& Val ){// 判断容量是否最大了if (this->m_Capacity == this->m_Size){return;}this->p_Address[this->m_Size] = Val;  // 在数组的末尾插入数据this->m_Size++;  // 更新数组的大小}// 尾删法void Pop_Back(){// 让用户访问不到最后一个元素,就是删除if (this->m_Size == 0){return;}this->m_Size--;}// 可以通过下标的方式访问数组T& operator[](int index){return this->p_Address[index];}// 返回数组的容量int get_Caoacity(){return this->m_Capacity;}// 返回数组大小int get_Size(){return this->m_Size;}// 析构函数~my_array(){if (this->p_Address != NULL){delete[] this->p_Address;this->p_Address = NULL;}}private:T* p_Address;   // 指针指向堆区开辟的真实数据int m_Capacity;   // 数组容量int m_Size;  // 数组大小};

模板array.cpp

#include"my_array.hpp"#include<iostream>using namespace std;void print_arr(my_array<int>& arr){for (int i = 0; i < arr.get_Size(); i++){cout << arr[i] << endl;}}void test01(){my_array<int> arr1(5);for (int i = 0; i < 5; i++){// 利用尾插法向数组中插入数据arr1.Push_Back(i);}cout << "arr1的打印输出" << endl;print_arr(arr1);cout << "arr1的容量为:" <<arr1.get_Caoacity()<< endl;cout << "arr1的大小为:" << arr1.get_Size() << endl;cout << "arr2的打印输出" << endl;my_array<int> arr2(arr1);print_arr(arr2);// 尾删arr2.Pop_Back();cout << "arr2的容量为:" << arr2.get_Caoacity() << endl;cout << "arr2的大小为:" << arr2.get_Size() << endl;}// 测试自定义的数据类型class person{public:person() {};person(string name,int age){this->name = name;this->age = age;}string name;int age;};void print_person_arr(my_array<person>& arr){for (int i = 0; i < arr.get_Size(); i++){cout << "姓名:" << arr[i].name << "\t年龄:" << arr[i].age << endl;}}void test02(){my_array<person> arr(10);person p1("TON", 78);person p2("JEEYU", 678);person p3("hello", 567);person p4("bgood", 567);person p5("daj", 78);// 将数据插入到数组中arr.Push_Back(p1);arr.Push_Back(p2);arr.Push_Back(p3);arr.Push_Back(p4);arr.Push_Back(p5);// 打印数组print_person_arr(arr);// 输出容量cout << "arr的容量:" << arr.get_Caoacity() << endl;// 输出大小cout << "arr的大小:" << arr.get_Size() << endl;}int main(){test01();test02();return 0;}

运行结果:

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

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

相关文章

第14章_集合与数据结构拓展练习(前序、中序、后序遍历,线性结构,单向链表构建,单向链表及其反转,字符串压缩)

文章目录 第14章_集合与数据结构拓展练习选择填空题1、前序、中序、后序遍历2、线性结构3、其它 编程题4、单向链表构建5、单向链表及其反转6、字符串压缩 第14章_集合与数据结构拓展练习 选择填空题 1、前序、中序、后序遍历 分析&#xff1a; 完全二叉树&#xff1a; 叶结点…

ElasticSearch的常用增删改查DSL和代码

es增删改查常用语法 我们日常开发中&#xff0c;操作数据库写sql倒是不可能忘记&#xff0c;但是操作es的dsl语句有时候很容易忘记&#xff0c;特地记录一下方便查找。 DSL语句 1、创建索引 -- 创建索引 PUT /my_index {"mappings": {"properties": {&…

Python实现Lasso回归模型

• Tibshirani(1996)提出了Lasso(The Least Absolute Shrinkage and Selectionator operator)算法。 • 通过构造一个一阶惩罚函数获得一个精炼的模型&#xff1b;通过最终确定一些指标&#xff08;变量&#xff09;的系数为零&#xff08;岭回归估计系数等于0的机会微乎其微&a…

【HarmonyOS】体验鸿蒙电商平台的未来之旅!

从今天开始&#xff0c;博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”&#xff0c;对于刚接触这项技术的小伙伴在学习鸿蒙开发之前&#xff0c;有必要先了解一下鸿蒙&#xff0c;从你的角度来讲&#xff0c;你认为什么是鸿蒙呢&#xff1f;它出现的意义又是…

【堂堂狼人杀(定制开发)】

堂堂狼人杀游戏♨️风险规避与优势。?零投资也可参与规避五大风险 政策风险(点对点场外交易)账户风险(无资金池&#xff09; 推广风险(免费注册&#xff0c;注册送大礼包&#xff09; 网络风险(大平台&#xff0c;全网场外交易) 人脉风险(预约角色&#xff0c;强制出售) …

力扣hot100 相交链表 超全注释 满级表达

Problem: 160. 相交链表 文章目录 思路复杂度&#x1f496; Ac Code 思路 &#x1f468;‍&#x1f3eb; 参考题解 &#x1f469;‍&#x1f3eb; 参考图解 复杂度 时间复杂度: O ( n m ) O(nm) O(nm) 空间复杂度: 添加空间复杂度, 示例&#xff1a; O ( 1 ) O(1) O(…

python 正则表达式学习(1)

正则表达式是一个特殊的字符序列&#xff0c;它能帮助你方便的检查一个字符串是否与某种模式匹配。 1. 特殊符号 1.1 符号含义 模式描述^匹配字符串的开头$匹配字符串的末尾.匹配任意字符&#xff0c;除了换行符&#xff0c;当re.DOTALL标记被指定时&#xff0c;则可以匹配包…

飞书+ChatGPT+cpolar搭建企业智能AI助手并实现无公网ip远程访问

文章目录 推荐 前言环境列表1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂…

用pandas实现用前一行的excel的值填充后一行

今天接到一份数据需要分析&#xff0c;数据在一个excel文件里&#xff0c;内容大概形式如下&#xff1a; 后面空的格子里的值就是默认是前面的非空的值&#xff0c;由于数据分析的需要需要对重复的数据进行去重&#xff0c;去重就需要把控的cell的值补上&#xff0c;然后根据几…

数字IC后端设计实现 | PR工具中到底应该如何控制density和congestion?(ICC2Innovus)

吾爱IC社区星友提问&#xff1a;请教星主和各位大佬&#xff0c;对于一个模块如果不加干预工具会让inst挤成一团&#xff0c;后面eco修时序就没有空间了。如果全都加instPadding会导致面积不够overlap&#xff0c;大家一般怎么处理这种问题&#xff1f; 在数字IC后端设计实现中…

Python内置的20个高阶函数的功能和示例详解

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com Python是一门功能丰富的编程语言&#xff0c;提供了许多内置函数来处理各种数据操作。其中&#xff0c;高阶函数是一类特殊的函数&#xff0c;它们接受其他函数作为参数&#xff0c;或者返回函数作为结果。高阶函…

如何禁用WordPress站点的管理员电子邮件验证或修改检查频率?

今天boke112百科登录某个WordPress站点时&#xff0c;又出现“管理员邮件确认”的提示&#xff0c;要求确认此站点的管理员电子邮箱地址是否仍然正确。具体如下图所示&#xff1a; 如果点击“稍后提醒我”&#xff0c;那么管理员邮件验证页面就会在3天后重新显示。 说实话&…

Unity - 简单音频

“Test_04” AudioTest public class AudioTest : MonoBehaviour {// 声明音频// AudioClippublic AudioClip music;public AudioClip se;// 声明播放器组件private AudioSource player;void Start(){// 获取播放器组件player GetComponent<AudioSource>();// 赋值…

搭建开源数据库中间件MyCat2-配置mysql数据库双主双从

mycat2官网&#xff1a;MyCat2 前言&#xff1a;mycat2下载地址无法访问&#xff0c;不知道是不是被DNS污染了&#xff0c;还是需要搭梯子访问&#xff0c;所以我只能找到1.21的版本进行安装。搭建mycat2的前提是搭建数据库主从复制。 架构&#xff1a;双主双从 配置&#xf…

Verilog基础:强度建模与net型信号的多驱动问题(三)

相关阅读 Verilog基础https://blog.csdn.net/weixin_45791458/category_12263729.html?spm1001.2014.3001.5482 四、一般情况下的net型信号的线与组合&#xff08;线网多驱动&#xff09; 在Verilog基础&#xff1a;强度建模与net型信号的多驱动问题&#xff08;二&#xff0…

成功解决java.nio.charset.MalformedInputException: Input length = 1

项目启动时报错如下 Connected to the target VM, address: 127.0.0.1:5309, transport: socket 18:01:22.607 [main] ERROR o.s.b.SpringApplication - [reportFailure,843] - Application run failed org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedIn…

开年大瓜!Mobileye股价暴跌

编者按&#xff1a;作为汽车智能化的第一波受益者&#xff0c;Mobileye的市场红利已经接近尾声。尤其是汽车芯片市场的白热化竞争&#xff0c;新一轮格局重构正在开启。 汽车智能化的供给关系&#xff0c;正在发生微妙变化。 本周&#xff0c;作为全球主要的辅助驾驶芯片及感知…

Joern环境的安装(Windows版)

Joern环境的安装(Windows版) 网上很少有关于Windows下安装Joern的教程&#xff0c;而我最初使用也是装在Ubuntu虚拟机中&#xff0c;这样使用很占内存&#xff0c;影响体验感。在Windows下使用源码安装Joern也是非常简单的过程&#xff1a; 提前需要的本地环境&#xff1a; …

【大数据】流处理基础概念(一):Dataflow 编程基础、并行流处理

流处理基础概念&#xff08;一&#xff09;&#xff1a;Dataflow 编程基础、并行流处理 1.Dataflow 编程基础1.1 Dataflow 图1.2 数据并行和任务并行1.3 数据交换策略 2.并行流处理2.1 延迟与吞吐2.1.1 延迟2.1.2 吞吐2.1.3 延迟与吞吐 2.2 数据流上的操作2.2.1 数据接入和数据…

GPS位置虚拟软件 AnyGo mac激活版

AnyGo for Mac是一款一键将iPhone的GPS位置更改为任何位置的强大软件&#xff01;使用AnyGo在其iOS或Android设备上改变其GPS位置&#xff0c;并在任何想要的地方显示自己的位置。这对那些需要测试应用程序、游戏或其他依赖于地理位置信息的应用程序的开发人员来说非常有用&…