掘根宝典之C++类模板大全

1 类模板语法

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

语法:

template<typename T>
模板类声明


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

示例:

#include<iostream>
using namespace std;
//类模板
template<class NameType, class AgeType>
class Person
{
public:Person(NameType name, AgeType age){this->m_Age = age;this->m_Name = name;}void showPerson(){cout << "name: " << this->m_Name << "age: " << this->m_Age << endl;}NameType m_Name;AgeType m_Age;
};void test01()
{Person<string, int> p1("孙悟空", 999);//先创建了<string,int>版本的类定义,再将括号里的东西传进去p1.showPerson();
}int main()
{test01();return 0;
}

运行结果是

name:孙悟空age:999

我们必须注意的一个点是仅在程序包含模板并不会生成模板类,必须请求实例化。也就是说,必须要将模板的泛型换为具体类型 

我们可能有点懵,来看个例子

#include<iostream>
using namespace std;
//类模板
template<class NameType, class AgeType>
class Person
{
public:Person(NameType name, AgeType age){this->m_Age = age;this->m_Name = name;}void showPerson(){cout << "name: " << this->m_Name << "age: " << this->m_Age << endl;}NameType m_Name;AgeType m_Age;
};int main()
{int a=10;return 0;
}

上面这个例子没有生成类定义,他只是告诉编译器如何生成类定义 ,这一点和函数模板是相同的


2 类模板与函数模板的区别

类模板与函数模板区别主要有两点:

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

示例://类模板与函数模板的区别

先看类模板的

template<class NameType, class AgeType = int> //指定默认参数
class Person
{
public:Person(NameType name, AgeType age){this->m_Age = age;this->m_Name = name;}void showPerson(){cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;}NameType m_Name;AgeType m_Age;
};void test01()
{//Person p("孙悟空", 1000);错误的,类模板无法用自动类型推导Person<string, int>p("孙悟空", 1000);//正确,只能用显式指定类型推导p.showPerson();
}void test02()
{Person<string>p("猪八戒", 999); //类模板在参数列表中有默认参数
}int main()
{test01();system("pause");return 0;
}

再来看函数模板的

#include<iostream>
using namespace std;//其实可以这么写template<class T=int>,但是不建议
template<class T>
void A(T a)
{cout << a << endl;
}
void B()
{A(3.0);//自动类型推断
}
int main()
{B();
}


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

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

1,普通类中的成员函数一开始就可以创建
2,类模板中的成员函数在调用时才创建
(因为我们写下模板类,只是告诉编译器如何去定义一个类,但是又不会创建类对象出来,只是一团虚的东西)

示例:

//类模板中成员函数的创建时机
class Person1
{
public:void showPerson1(){cout << "Person1 show" << endl;}
};class Person2
{
public:void showPerson2(){cout << "Person2 show" << endl;}
};template<class T>
class Myclass
{
public:T obj;//类模板中的成员函数在调用的时候才创建,所以不会报错void func1(){obj.showPerson1();}void func2(){obj.showPerson2();}
};void test01()
{Myclass<Person1>m;m.func1();//因为我们没有创建Person2的版本啊,所以也没有showPerson2这个函数啊//m.func2(); 无法调用
}int main()
{test01();system("pause");return 0;
}


4 类模板对象做函数参数

学习目标:类模板实例化出的对象,向函数传参的方式

一共有三种传入方式:

指定传入的类型:直接显示对象的数据类型
参数模板化:将对象中的参数变为模板进行传递
整个类模板化:将这个对象类型模板化进行传递

示例:

//类模板对象做函数参数
template<class T1,class T2>
class Person
{
public:Person(T1 name,T2 age){this->m_Age = age;this->m_Name = name;}void showPerson(){cout << "name: " << this->m_Name << " age:" << this->m_Age << endl;}T1 m_Name;T2 m_Age;
};//1、指定传入类型
void printPerson1(Person<string, int>&p)
{p.showPerson();
}
void test01()
{Person<string, int>p("孙悟空", 199);printPerson1(p);
}// 2、参数模板化
template<class T1,class T2>
void printPerson2(Person<T1,T2>&p)
{p.showPerson();cout << "T1的类型为:" << typeid(T1).name() << endl;cout << "T2的类型为:" << typeid(T2).name() << endl;
}
void test02()
{Person<string, int>p("猪八戒", 90);printPerson2(p);
}// 3、整个类模板化
template<class T>
void printPerson3(T &p)
{p.showPerson();cout << "T的类型为:" << typeid(T).name() << endl;
}
void test03()
{Person<string, int>p("唐僧", 60);printPerson3(p);
}int main()
{test01();test02();test03();system("pause");return 0;
}


运行结果:

注:使用比较广泛的是指定传入类型的传参方式

5 类模板与继承

当类模板碰到继承时,需要注意以下几点:

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

示例:

//类模板与继承
template<class T>
class Base
{T m;
};//class Son: public Base //错误,必须要知道父类中的T类型,才能继承给子类
class Son :public Base<int>
{};void test01()
{Son s1;
}//如果想灵活指定父类中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 obj;
};void test02()
{Son2<int,char> s2;
}int main()
{test02();system("pause");return 0;
}


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

示例:

//类模板成员类外实现
template<class T1, class T2>
class Person
{
public:Person(T1 name, T2 age);/*{this->m_Name = name;this->m_Age = age;}*/void showPerson();/*{cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;}*/T1 m_Name;T2 m_Age;
};//构造函数的类外实现
template<class T1,class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{this->m_Name = name;this->m_Age = age;
}//成员函数的类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}void test01()
{Person<string, int>p("Tom", 30);p.showPerson();
}int main()
{test01();system("pause");return 0;
}


7 类模板分文件编写

如果工程中需要利用多个类模板,那么将这些类模板都写在同一个文件中将会导致代码可读性变差,所以有必要对类模板进行分文件编写,但是类模板的分文件编写面临着一些问题,以下是类模板分文件编写面临的问题及解决方法。

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

解决:

解决方式1:直接包含.cpp源文件
解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制的
示例1:(未进行分文件编写)

template<class T1, class T2>
class Person
{
public:Person(T1 name, T2 age);/*{this->m_Name = name;this->m_Age = age;}*/void showPerson();/*{cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;}*/T1 m_Name;T2 m_Age;
};//构造函数的类外实现
template<class T1,class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{this->m_Name = name;this->m_Age = age;
}//成员函数的类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}void test01()
{Person<string, int>p("Tom", 30);p.showPerson();
}int main()
{test01();system("pause");return 0;
}


实例2:(进行分文件编写,利用.cpp)

1.创建头文件person.h,写一些声明

#pragma once
#include <iostream>
using namespace std;
#include <string>template<class T1, class T2>
class Person
{
public:Person(T1 name, T2 age);void showPerson();T1 m_Name;T2 m_Age;
};


2.创建person.cpp,写具体实现

#include "person.h"//构造函数的类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{this->m_Name = name;this->m_Age = age;
}//成员函数的类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}


3.main函数编写
错误代码:

#include <iostream>
using namespace std;
#include <string>
#include "person.h"void test01()
{Person<string, int>p("Tom", 30);p.showPerson();
}int main()
{test01();system("pause");return 0;
}


注:因为如果包含person.h文件,那么编译器将会看到person.h中的代码。但是由于类模板中的成员函数一开始是不创建的,导致编译器没有看到person.cpp中的代码,所以执行test01时,无法解析其中的代码。

正确代码:(不常用)

#include <iostream>
using namespace std;
#include <string>
#include "person.cpp"void test01()
{Person<string, int>p("Tom", 30);p.showPerson();
}int main()
{test01();system("pause");return 0;
}


注:就是将person.h文件改成了person.cpp代码。编译器首先看到了person.cpp文件,因为person.cpp文件中有person.h文件,编译器又看到了person.h文件,所以能够解析test01中的代码。但是一般很少直接包含.cpp文件的,所以这个方法不常用。

实例3:(分文件编写,利用.hpp)
将person.h和person.cpp的内容写到一起,并将后缀名改为.hpp,这是类模板分文件编写最常用的方式

1.编写person.hpp文件:

#pragma once
#include <iostream>
using namespace std;
#include <string>template<class T1, class T2>
class Person
{
public:Person(T1 name, T2 age);void showPerson();T1 m_Name;T2 m_Age;
};//构造函数的类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{this->m_Name = name;this->m_Age = age;
}//成员函数的类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}


2.编写main函数

#include <iostream>
using namespace std;
#include <string>
#include "person.hpp"void test01()
{Person<string, int>p("Tom", 30);p.showPerson();
}int main()
{test01();system("pause");return 0;
}


8 类模板与友元

非模板友元

为模板类提供非模板友元函数,像下面这样子做就可以了

#include<iostream>
using namespace std;
void A(int a)//非模板友元函数
{cout << a << endl;
}
template<class T>
class AA
{
private:T a_;
public:AA(T a):a_(a){}friend void A(int a);//友元声明
};
int main()
{A(2);//正常使用//AA a(2);模板类不能自动类型推断AA<int> a(2);//OK
}

上面这个友元函数将成为所以模板实例化的友元 。例如它将是AA<float>和AA<char>等版本的友元函数。

假如我们要为友元函数提供模板类参数,可以像下面这样子做吗?

friend void A(AA&t);

答案是不行的,要提供模板类参数,必须指明具体化,像下面这两种做法都是可以的

template<class T>
class AA
{
friend void A(AA<T>&t);
friend void B(AA<int>&t);
....
}

 

模板类的约束模板友元函数

说白了就是来使类的每一个具体化都获得一个与友元匹配的具体化

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

1.全局函数的类内实现


template<class T1, class T2>
class Person
{//全局函数类内实现friend void printPerson(Person<T1, T2> p){cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;}
public:Person(T1 name, T2 age){this->m_Name = name;this->m_Age = age;}private:T1 m_Name;T2 m_Age;
};void test01()
{Person<string, int>p("Tom", 30);printPerson(p);
}
int main()
{test01();system("pause");return 0;
}


2.全局函数类外实现

//提前让编译器知道Person类的存在
template<class T1, class T2>
class Person;//类外实现
template<class T1, class T2>
void printPerson(Person<T1, T2> p)
{cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}template<class T1, class T2>
class Person
{//全局函数类外实现 //加空模板参数列表//如果全局函数是类外实现,需要让编译器提前知道这个函数的存在friend void printPerson<>(Person<T1, T2> p);
public:Person(T1 name, T2 age){this->m_Name = name;this->m_Age = age;}private:T1 m_Name;T2 m_Age;
};void test01()
{Person<string, int>p("Tom", 30);printPerson(p);
}int main()
{test01();system("pause");return 0;
}


注:需要注意各个函数声明之间的顺序。在Person类模板中有友元的声明friend void printPerson<>(Person<T1, T2> p),因为类模板中友元的类外实现需要让编译器提前知道这个函数,所以需要将printPerson函数写在前面。而printPerson函数中又涉及Person类,所以在printPerson函数前面需要提前声明Person类模板的存在。
总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别。

模板类的非约束模板友元函数

说白了就是每个函数具体化都是每个类具体化的友元。对于非约束友元,友元模板类型参数与模板类类型参数是不同的

template<class T>
class AA
{
.....
template<class A,class B>feiend void A(A&,B&);
.....
}

模板的具体化

隐式实例化

到目前为止,上面所有例子用的都是隐式实例化,即它们声明一个或多个对象,指出所需类型,而编译器使用通用模板提供的处方生成具体的类声明

AA<int> a;

需要注意的是:需要对象之前,不会生成类的隐式实例化。

AA<int>*t;
t=new AA<int>;
//第二句导致编译器生成类定义,并根据该定义创建一个对象

显式实例化

显式实例化声明必须位于模板定义所在的名称空间里。

格式大概是

template class 类模板名<具体类型>;

我们看个例子

#include<iostream>
using namespace std;
void A(int a)
{cout << a << endl;
}
template<class T>
class AA
{
private:T a_;
public:AA(T a):a_(a){}friend void A(int a);
};template class AA<int>;//注意不能放main函数里面int main()
{A(2);//AA a(2);AA<int> a(2);
}

显式具体化

显式具体化是指在类模板外部对类模板的特定类型进行具体化。通过显式具体化,我们可以为模板类给出特定类型的定义,以覆盖默认的通用定义。

以下是一个示例,展示如何显式具体化一个类模板:

// 定义一个类模板
template <typename T>
class MyTemplate {
public:MyTemplate(T value) : m_value(value) {}void print() {std::cout << "Generic template: " << m_value << std::endl;}private:T m_value;
};// 显式具体化模板类的特定类型
template<>
class MyTemplate<int> {
public:MyTemplate(int value) : m_value(value) {}void print() {std::cout << "Specialized template for int: " << m_value << std::endl;}private:int m_value;
};int main() {MyTemplate<double> obj1(3.14);obj1.print();  // 输出:Generic template: 3.14MyTemplate<int> obj2(42);obj2.print();  // 输出:Specialized template for int: 42return 0;
}

在上述示例中,定义了一个模板类 MyTemplate,它可以用于任意类型的参数 T。然后,通过显式具体化,我们为模板类 MyTemplate 的特定类型 int 提供了特殊化的定义,其中包含一个特定的成员函数 print,用于输出特定类型的值。

main 函数中,我们创建了两个 MyTemplate 的对象,一个是 MyTemplate&lt;double> 类型的,另一个是 MyTemplate&lt;int> 类型的。当调用它们的 print 成员函数时,将根据对象类型的特化定义,分别输出不同的结果。

这样,通过显式具体化,我们可以为特定类型提供自定义的定义,以满足特定的需求。

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

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

相关文章

UE4 C++联网RPC教程笔记(一)(第1~4集)

UE4 C联网RPC教程笔记&#xff08;一&#xff09;&#xff08;第1~4集&#xff09; 前言1. 教程介绍与资源2. 自定义 Debug 功能3. Actor 的复制4. 联网状态判断 前言 本系列笔记将会对梁迪老师的《UE4C联网RPC框架开发吃鸡》教程进行个人的知识点梳理与总结&#xff0c;此课程…

ARMv8-AArch64 的异常处理模型详解之异常处理详解(进入异常以及异常路由)

在上篇文章 ARMv8-AArch64 的异常处理模型详解之异常处理概述Handling exceptions中&#xff0c;作者对异常处理整体流程以及相关概念做了梳理。接下来&#xff0c;本文将详细介绍处理器在获取异常、异常处理以及异常返回等过程中都做了哪些工作。 ARMv8-AArch64 的异常处理模型…

sdxl-turbo、playground文生图模型使用案例

1、sdxl-turbo SDXL-Turbo是一种快速生成的文本到图像模型,可以在单个网络评估中从文本提示合成逼真的图像。 参考:https://huggingface.co/stabilityai/sdxl-turbo 对比效果相比PixArt模型差很多,参考https://blog.csdn.net/weixin_42357472/article/details/135520142 …

axios

2022年10月11日&#xff0c;在《AXAJ与JavaScript中的异步编程元素》文章中我总结了对于前端开发者中需要了解和掌握的8种异步编程元素&#xff0c;其中XMLHTTPRequest很关键&#xff0c;它是原生JavaScript提供的用于发送 HTTP 请求的对象&#xff0c;用于在前端与服务器进行数…

「C++ 内存管理篇 0」C和C++对内存的划分

目录 一、C和C对内存的划分 二、小测试 【题目】 【答案】 【解释】 一、C和C对内存的划分 1. 栈区 &#xff1a;存储非静态局部变量/函数参数/返回值等等&#xff0c;栈是向下增长的。 2. 共享区&#xff1a;用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存…

汽车金融市场研究:预计2029年将达到482亿美元

汽车金融公司作为汽车流通产业链的重要一环&#xff0c;认真贯彻落实国家有关政策&#xff0c;采取多种措施助力汽车产业发展&#xff0c;为促进推动汽车消费、助力畅通汽车产业链、支持稳定宏观经济大盘发挥了积极作用。 益于国内疫情得到有效控制&#xff0c;我国经济持续稳定…

UE5 C++ UENUM 和 USTRUCT

一.首先在APawn里声明 UENUM 和 USTRUCT。UENUM 有两种定义方式 一种是使用命名空间&#xff1a; 还有是继承uint8&#xff1a; 通过申明class类 别名来替代 USTRUCT的定义 上面的第二种有类似但仍然有很多的差异&#xff1a; 首先要有GENERATED_USTRUCT_BODY()这个函数 并且…

网络编程_TCP通信综合练习:

1 //client&#xff1a;&#xff1a; public class Client {public static void main(String[] args) throws IOException {//多次发送数据//创建socket对象,填写服务器的ip以及端口Socket snew Socket("127.0.0.1",10000);//获取输出流OutputStream op s.getOutput…

ChatGPT魔法1: 背后的原理

1. AI的三个阶段 1&#xff09; 上世纪50~60年代&#xff0c;计算机刚刚产生 2&#xff09; Machine learning 3&#xff09; Deep learning&#xff0c; 有神经网络&#xff0c; 最有代表性的是ChatGPT, GPT(Generative Pre-Trained Transformer) 2. 深度神经网络 llya Suts…

linux 实用技能

1.查看系统版本 cat /etc/redhat-release cat /etc/redhat-release 2. 查看磁盘实用情况 df du 3.查看内存 top -Hp 2214 4. 网络配置 vi /etc/hostname vi /etc/hosts vi /etc/sysconfig/network-scripts/ifcfgens33 6. sed ‘s/a/b/g’ aaa.txt 替换 7. scp …

Eclipse - Colors and Fonts

Eclipse - Colors and Fonts References 编码最好使用等宽字体&#xff0c;Ubuntu 下自带的 Ubuntu Mono 可以使用。更换字体时看到名字里面带有 Mono 的基本都是等宽字体。 Window -> Preferences -> General -> Appearance -> Colors and Fonts -> C/C ->…

数据分析 — 动画图 pyecharts

目录 一、概念二、安装和导入三、绘图逻辑四、绘图1、柱状图2、折线图3、散点图4、饼图5、南丁格尔图6、Geo() 地理坐标第7、Map() 绘制区域8、词云图9、层叠图10、3D 图11、仪表板 一、概念 Pyecharts 是一个基于 Echarts 的 Python 可视化库&#xff0c;它通过 Python 生成 …

mac东西拷不进硬盘怎么回事 mac东西拷不进硬盘怎么办 mac硬盘读不出来怎么解决 mac拷贝不了东西到u盘

有时候我们在使用mac的过程中&#xff0c;可能会遇到一些问题&#xff0c;比如mac东西拷不进硬盘。这是一种很常见的情况&#xff0c;但是会影响我们的工作和生活。那么&#xff0c;mac东西拷不进硬盘是怎么回事呢&#xff1f;mac东西拷不进硬盘又该怎么办呢&#xff1f;本文将…

ACM/NOI/CSP比赛经验分享

ACM/NOI/CSP比赛经验分享 一、引言 在信息学竞赛的舞台上&#xff0c;ACM/ICPC、NOI和CSP是众多学子梦寐以求的赛事。这些比赛不仅考验了参赛者的算法和数据结构知识&#xff0c;更是对团队协作、时间管理和心理素质的全面挑战。作为一名曾经参与过这些比赛的选手&#xff0c…

最新 Vue3、TypeScript、组合式API、setup语法糖 学习笔记

最新 Vue3、TypeScript、组合式API、setup语法糖 学习笔记 1、创建 Vue3 工程基于 `vue-cli` 创建(基于webpack实现)基于 `vite` 创建(推荐)2、Vue3 项目开发 `vscode` 插件推荐3、`Vue3` 核心语法【optionsAPI】与【CompositionAPI】Options API 的弊端Composition API 的…

洛谷 P8630 [蓝桥杯 2015 国 B] 密文搜索

题目描述 福尔摩斯从 X 星收到一份资料&#xff0c;全部是小写字母组成。 他的助手提供了另一份资料&#xff1a;许多长度为 8 的密码列表。 福尔摩斯发现&#xff0c;这些密码是被打乱后隐藏在先前那份资料中的。 请你编写一个程序&#xff0c;从第一份资料中搜索可能隐藏…

K8S更新部署docker的两种方法举例

前提条件 imagePullPolicy: Always 方法1&#xff1a;删除更新法 test-project为命名空间 --删除所有asp-svc下面的pod,这会导致从新拉取镜像 kubectl delete pods -l appasp-svc -n test-project --删除指定的pod&#xff0c;这会导致从新拉取镜像 kubectl delete pod …

JSON协议详解、语法及应用

文章目录 一、什么是JSON二、JSON协议结构协议结构包括要素JSON语法规则JSON的协议结构示例 三、JSON的特点四、JSON常见应用场景 一、什么是JSON JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;它以易于阅读和编写的文本格式…

MVC 、DDD(domain-driven design,软件主动学习业务)、中台、Java SPI(Service Provider Interface)

文章目录 引言I 单体架构DDD实现版本1.1 核心概念1.2 DDD四层架构规范1.3 案例1.4 请求转发流程II 领域服务调用2.1 菱形对称架构2.2 中台III Java SPI3.1 概念3.2 实现原理3.3 例子:本地SPI找服务see alsojava -cp<

Linux第60步_“buildroot”构建根文件系统第2步_配置“buildroot下的busybox”并测试“buildroot”生成的根文件系统

1、查看“buildroot下的busybox”安装路径 打开终端 输入“ls回车” 输入“cd linux回车/”&#xff0c;切换到到“linux”目录 输入“ls回车”&#xff0c;查看“linux”目录下的文件和文件夹 输入“cd buildroot/回车”&#xff0c;切换到到“buildroot”目录 输入“ls…