【C++】泛型编程 ⑩ ( 类模板的运算符重载 - 函数实现 写在类外部的同一个 cpp 代码中 | 类模板 的 外部友元函数二次编译问题 )

文章目录

  • 一、类模板 - 函数声明与函数实现分离
    • 1、类模板 外部 实现 构造函数
    • 2、类模板 外部 实现 普通函数
    • 3、类模板 外部 实现 友元函数
        • ( 1 ) 错误示例及分析 - 类模板 的 外部友元函数 二次编译 问题
        • ( 2 ) 正确写法
  • 二、代码示例 - 函数声明与函数实现分离
    • 1、代码示例
    • 2、执行结果


将 类模板 函数声明 与 函数实现 分开进行编码 , 有 三种 方式 :

  • 类模板 的 函数声明 与 函数实现 都写在同一个类中 , 也就是没有分开进行编码 ;
  • 类模板 的 函数实现 在 类外部进行 , 函数声明 和 实现 写在相同的 .cpp 源码文件中 ;
  • 类模板 的 函数实现 在 类外部进行 , 函数声明 和 实现 写在不同的 .h 和 .cpp 源码文件中 ;

上一篇博客 【C++】泛型编程 ⑨ ( 类模板的运算符重载 - 函数声明 和 函数实现 写在同一个类中 | 类模板 的 外部友元函数问题 ) 实现了第一种情况 , 类模板 的 函数声明 与 函数实现 都写在同一个类中 , 也就是没有分开进行编码 ;

本篇博客 , 开始分析 第二种情况 , 类模板 的 函数实现 在 类外部进行 , 写在相同的 .h 和 .cpp 源码文件中 ;





一、类模板 - 函数声明与函数实现分离




1、类模板 外部 实现 构造函数


原来的构造函数是 :

template <typename T>
class Student
{
public:Student(T x, T y){this->a = x;this->b = y;}
}

如果将 构造函数 实现 , 写在类外部的 .cpp 源码中 ,

  • 首先 , 需要 声明 模板类型 , template <typename T> ;
  • 然后 , 通过 域操作符 访问 构造函数 , 并实现该函数 , 使用域操作符 时 , 前面的类 需要指定 具体的泛型类型 , 这里使用 声明的 T 模板类型 作为 具体的 泛型类型 ;
template <typename T>
Student<T>::Student(T x, T y)
{this->a = x;this->b = y;
}

在 类模板 内部 , 只需要声明该 构造函数 :

template <typename T>
class Student
{
public:Student(T x, T y);
}

2、类模板 外部 实现 普通函数


将 类内部的 普通函数 实现 加法运算符重载 的函数 , 提取到 类模板 外部进行定义 ;

该函数的 返回值 和 参数 都涉及到 类模板 类型 ;

template <typename T>
class Student
{
public:// 重载 + 运算符Student operator+(Student& s){Student student(this->a + s.a, this->b + s.b);return student;}
}

在类外部 实现 该 加号运算符重载 需要注意以下几点 :

  • 首先 , 需要 声明 模板类型 , template <typename T> ;
  • 然后 , 通过 域操作符 访问 构造函数 , Student<T>:: 后面跟上要访问的成员 ;
  • 最后 , 返回值和参数类型 , 如果是 类模板类型 Student , 需要在后面使用尖括号 指明具体的类型 , 这里具体的类型就是泛型 T ;

函数内部 Student 类型 , 可以加 <T> 也可不加 <T> , 不加 <T> 也可以使用 , 加了也不会报错 ;

// 重载 + 运算符
// 使用  Student<T>:: 域操作符访问函数
template <typename T>
Student<T> Student<T>::operator+(Student<T>& s)
{// 函数内部的类的 <T> 模板类型 , 可加 <T> 可不加 <T> // 不加 <T> 也可以使用 , 加了也不会报错Student student(this->a + s.a, this->b + s.b);return student;
}

类模板内部 , 需要声明该 重载函数 ;

template <typename T>
class Student
{
public:// 重载 + 运算符Student operator+(Student& s);
}

3、类模板 外部 实现 友元函数


友元函数 不是 类中的函数 , 是 类外部的函数 , 友元函数 中又用到了 泛型 T , 说明这是一个 模板函数 ;

友元函数 是 全局函数 , 不属于 类模板 , 不要使用 域操作符 访问友元函数 ;

友元函数 中的 泛型类型 , 要当做 函数模板 对待 ;

模板函数就涉及到 二次编译 问题 , 下面先分析一下 模板函数 二次编译 导致的 类模板的友元函数 问题 ;

友元函数 不要乱用 , 只有在 重载 左移 右移 操作符时 , 才使用 友元函数 ;



( 1 ) 错误示例及分析 - 类模板 的 外部友元函数 二次编译 问题

在 类模板 内部声明 友元函数 ,

template <typename T>
class Student
{// 左移运算符重载friend ostream& operator<<(ostream& out, Student& s);
}

在 类外部 实现 友元函数 ,

// Student 类的友元函数
// 左移运算符重载 函数
template <typename T>
ostream& operator<<(ostream& out, Student<T>& s)
{out << "a:" << s.a << " b: " << s.b << endl;return out;
}

运行时会报如下错误 :

已启动生成…
1>------ 已启动生成: 项目: HelloWorld, 配置: Debug Win32 ------
1>Test.obj : error LNK2019: 无法解析的外部符号 "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl operator<<(class std::basic_ostream<char,struct std::char_traits<char> > &,class Student<int> &)" (??6@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@std@@AAV01@AAV?$Student@H@@@Z),函数 _main 中引用了该符号
1>D:\002_Project\006_Visual_Studio\HelloWorld\HelloWorld\Debug\HelloWorld.exe : fatal error LNK1120: 1 个无法解析的外部命令
1>已完成生成项目“HelloWorld.vcxproj”的操作 - 失败。
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0==========

造成上述错误的原因 就是 函数模板 的实现机制 中的 二次编译 有关 ,

  • 第一次编译 函数模板 时 , 只进行 简单的 语法分析 , 词法分析 , 生成一个函数头 ;
  • 第二次编译 函数模板 时 , 又生成一个 函数头 ;

这两次编译生成的 函数头 不一致 , 导致 无法找到 相应的 函数实现 ;


( 2 ) 正确写法

友元函数 不要乱用 , 只有在 重载 左移 右移 操作符时 , 才使用 友元函数 ;


这是 函数模板 二次编译 问题 ,

一般情况下 , 函数模板 只有在 调用时 , 才需要将 泛型类型 指明 , 在 函数名称后面 , 使用 <> 注明泛型类型 ,

但是在 类模板 声明 友元函数 时 , 就需要指定 泛型类型 ;

这样才能将 类模板中的 泛型 T , 与 友元函数在 外部实现时 声明的 template <typename T> 关联起来 ;


在 类模板 内部声明 友元函数时 , 在函数名 operator<< 后面 加上 <T> ;

template <typename T>
class Student
{// 左移运算符重载friend ostream& operator<< <T> (ostream& out, Student& s);
}

在 类外部 实现 友元函数 保持不变 ;

// Student 类的友元函数
// 左移运算符重载 函数
template <typename T>
ostream& operator<<(ostream& out, Student<T>& s)
{out << "a:" << s.a << " b: " << s.b << endl;return out;
}




二、代码示例 - 函数声明与函数实现分离




1、代码示例


#include "iostream"
using namespace std; template <typename T>
class Student
{// 左移运算符重载friend ostream& operator<< <T> (ostream& out, Student& s);public:// 构造函数Student(T x, T y);// 重载 + 运算符Student operator+(Student& s);public:T a, b;
};// 类模板构造函数
// 使用  Student<T>:: 域操作符访问函数
template <typename T>
Student<T>::Student(T x, T y)
{this->a = x;this->b = y;
}// 重载 + 运算符
// 使用  Student<T>:: 域操作符访问函数
template <typename T>
Student<T> Student<T>::operator+(Student<T>& s)
{// 函数内部的类的 <T> 模板类型 , 可加 Student<T> 可不加 Student// 不加 <T> 也可以使用 , 加了也不会报错Student student(this->a + s.a, this->b + s.b);return student;
}// Student 类的友元函数
// 左移运算符重载 函数
template <typename T>
ostream& operator<<(ostream& out, Student<T>& s)
{out << "a:" << s.a << " b: " << s.b << endl;return out;
}int main() {// 模板类不能直接定义变量// 需要将 模板类 具体化之后才能定义变量Student<int> s(666, 888);cout << s << endl;Student<int> s2(222, 111);cout << s2 << endl;// 验证 加法运算符 + 重载Student<int> s3 = s + s2;// 验证 左移运算符 << 重载cout << s3 << endl;// 控制台暂停 , 按任意键继续向后执行system("pause");return 0;
}

2、执行结果


执行结果 :

a:666 b: 888a:222 b: 111a:888 b: 999Press any key to continue . . .

在这里插入图片描述

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

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

相关文章

go同步锁 sync mutex

goroutine http://127.0.0.1:3999/concurrency/11 go tour 到此 就结束了. 继续 学习 可以 从 以下网站 文档 https://golang.org/doc/ https://golang.org/doc/code https://golang.org/doc/codewalk/functions/ 博客 https://go.dev/blog/ wiki 服务器教程 服务器…

level=warning msg=“failed to retrieve runc version: signal: segmentation fault“

安装docker启动后&#xff0c;发现里面没有runc版本信息 目前看是少了runc组件 那我们安装runc https://github.com/opencontainers/runc/releases/download/v1.1.10/runc.amd64 [rootlocalhost ~]# mv runc.amd64 /usr/bin/runc mv&#xff1a;是否覆盖"/usr/bin/runc&q…

Git 分支管理

目录 列出分支 删除分支 分支合并 合并冲突 几乎每一种版本控制系统都以某种形式支持分支&#xff0c;一个分支代表一条独立的开发线。 使用分支意味着你可以从开发主线上分离开来&#xff0c;然后在不影响主线的同时继续工作。 Git 分支实际上是指向更改快照的指针。 有…

CAXA同一个窗口打开文件

重新关联&#xff0c;工具&#xff0c;文件关联工具

文档 + 模型

文档 模型 0: 基于MATLAB的soc锂电池系统设计 1: 电力系统继电保护仿真设计-毕业论文 2: 继电保护仿真-三段式电流保护的方案设计及分析-相间短路 3: 直流电机双闭环控制系统 转速电流双闭环调速 4: matlab电力系统继电保护仿真 三段电流保护仿真-双侧电源系统 5: OFDM-MIMO课…

python实现FINS协议的TCP服务端(篇二)

python实现FINS协议的TCP服务端是一件稍微麻烦点的事情。它不像modbusTCP那样&#xff0c;可以使用现成的pymodbus模块去实现。但是&#xff0c;我们可以根据协议帧进行组包&#xff0c;自己去实现帧的格式&#xff0c;而这一切可以基于socket模块。本文为第二篇。

修复dinput8.dll丢失的简单方法,解决dinput8.dll丢失

在使用电脑时&#xff0c;电脑可能会出现一些特殊的情况&#xff0c;比如电脑中出现关于dinput8.dll丢失会找不到的情况&#xff0c;出现这样的情况可能会不知道该怎么办&#xff0c;但是出现这样的情况其实并不是一件很难解决的事情&#xff0c;修复dinput8.dll丢失方法也是比…

Delphi 取消与设置CDS本地排序

取消与设置CDS本地排序 取消CDS本地排序. cds.IndexDefs.Update; if cds.IndexName<> then begin if cds.IndexDefs.IndexOf(index1)>0 then cds.DeleteIndex(index1); cds.IndexDefs.Clear; cds.IndexName:; end; 设置CDS本地排序 c…

Robust Optimization, imperfect CSI, CSIT and CSIR

文章目录 写在前面CSI, CSIT and CSIR 写在前面 CSIT或者CSIR可不可以用来帮助实现隐蔽通信 人工噪声让窃听者估计出错误的信道。 CSI, CSIT and CSIR MIMO Minimum Total MSE Transceiver Design With Imperfect CSI at Both Ends 2009 TSP 多输入多输出 (MIMO) 系统已成为…

MySQL InnoDB 引擎底层解析(二)

6.2.InnoDB 的表空间 表空间是一个抽象的概念&#xff0c;对于系统表空间来说&#xff0c;对应着文件系统中一个或多个实际文件&#xff1b;对于每个独立表空间来说&#xff0c;对应着文件系统中一个名为表名.ibd 的实际文件。大家可以把表空间想象成被切分为许许多多个页的池…

关于Unity Time.deltaTime的理解和使用

Unity中的Time.deltaTime是一个表示上一帧到当前帧所用时间的浮点数。 它可以让Unity应用程序能够以平滑的方式在不同的帧率下运行。 要深刻理解Time.deltaTime&#xff0c;首先得了解Unity引擎得工作原理。 Unity引擎以每秒帧数&#xff08;FPS&#xff09;的形式运行。 比…

基于tcp协议及数据库sqlite3的云词典项目

这个小项目是一个网络编程学习过程中一个阶段性检测项目&#xff0c;过程中可以有效检测我们对于服务器客户端搭建的能力&#xff0c;以及一些bug查找能力。项目的一个简单讲解我发在了b站上&#xff0c;没啥心得&#xff0c;多练就好。 https://t.bilibili.com/86524470252640…

Shell判断:模式匹配:case(二)

简单的JumpServer 1、需求&#xff1a;工作中&#xff0c;我们需要管理N多个服务器。那么访问服务器就是一件繁琐的事情。通过shell编程&#xff0c;编写跳板程序。当我们需要访问服务器时&#xff0c;看一眼服务器列表名&#xff0c;按一下数字&#xff0c;就登录成功了。 2、…

JAVA毕业设计111—基于Java+Springboot+Vue的养老院管理系统(源码+数据库+12000字论文)

基于JavaSpringbootVue的养老院管理系统(源码数据库12000字论文)111 一、系统介绍 本系统前后端分离&#xff0c;本系统分为销售、人事、服务、餐饮、财务、超级管理员六种角色 系统主要功能如下&#xff1a; 首页统计&#xff1a;包括今日新增咨询、今日新增预定、今日新增…

AI资讯--Meta AI工具“指哪打哪“;OpenAI CEO事件梳理;

看点 Meta展示全新AI图像编辑工具&#xff1a;文本指令“指哪打哪”&#xff0c;主体背景都能换 &#x1f3a8;OpenAI CEO被董事会罢免36小时事件梳理 &#x1f552;OpenAI开掉了最能搞钱的创始人&#xff0c;GPT在他手里可能失控&#xff1f; &#x1f4b8;微软和兴盛资本向O…

树与二叉树堆:堆

堆的概念&#xff1a; 一般是把数组的数据在逻辑结构上看成一颗完全二叉树&#xff0c;如下图所示。 注意&#xff1a;别将C语言中的堆和数据结构的堆混为一谈&#xff0c;本文所讲的数据结构的堆是一种完全二叉树&#xff0c;而C语言中的堆其实是一种内存区域的划分 堆的分类…

Java语言基础第五天

精华笔记&#xff1a; 循环结构&#xff1a; for结构&#xff1a;应用率最高&#xff0c;与次数相关的循环 三种结构如何选择&#xff1a; 先看循环是否与次数相关&#xff1a; 若相关-----------------------------直接上for 若无关&#xff0c;再看要素1与要素3的代码是否相…

linux shell操作 - 04 进程间通信

文章目录 Signal 信号信号定义信号的生命周期信号分类linux进程通信案例 Signal 信号 信号定义 Linux信号是进程间通信的一种方式&#xff0c;通过向目标进程发送一个特定的信号&#xff0c;让其执行相应的处理操作&#xff1b; 向目标进程发送信号时&#xff0c;内核会将信号…

【发明专利】天洑软件再度收获六项国家发明专利授权

近日&#xff0c;南京天洑软件有限公司再度收获行业内六项国家发明专利授权&#xff0c;专利名称为&#xff1a;一种发电机绕组温度预警方法及装置&#xff08;专利号&#xff1a;ZL 2022 1 1525605.3&#xff09;&#xff0c;一种CSTR系统的控制方法及装置&#xff08;专利号&…

《C++PrimePlus》第8章 函数探幽

8.1 内联函数 使用内联函数 #include <iostream> using namespace std;inline double square(double x) { return x * x; }int main(){double a;a square(5.0);cout << "a " << a << endl;return 0; } 8.2 引用变量 将引用用作函数参数&…