C++中操作重载与类型转换

文章目录

  • 基本概念
    • 调用
    • 选择作为成员还是非成员
  • 输入和输出运算符
  • 算术和关系运算符
    • 相等和不等运算符
    • 赋值运算符
    • 下标运算符
    • 递增和递减运算符
    • 成员访问运算符
    • 函数调用运算符
      • lambda是函数对象
      • 标准库定义的函数对象
      • 可调用对象与function
  • 重载、类型转换与运算符
    • 类型转换运算符
    • 避免有二义性的类型转换

基本概念

重载的运算符是具有特殊名字的函数:它们的名字由关键字operator和其后要定义的运算符号共同组成。其参数数量和该运算符的运算数量一样多。除了重载的函数调用运算符operator()之外,其他重载运算符不能含有默认参数。

如果一个运算符函数是成员函数,则它的第一个运算对象绑定到this指针。对于一个运算符函数来说,它或者是类的成员,或者至少含有一个类类型的参数:

int operator+(int, int); //err

我们可以重载大多数已有的运算符,而无权发明新的运算符号。重载后运算符和内置运算符的优先级和结合律保持一致。
在这里插入图片描述
一般不建议重载逗号、取地址、逻辑与和逻辑或运算符。

if (a && b) { ... } 

如果 a 为 false,则 b 根本不会计算。
但一旦重载了 operator&&, operator||,这个短路规则 完全丢失,编译器会强制先算两个操作数,再传给重载函数。

int x = (f1(), f2()); // 保证先执行 f1,再执行 f2  

如果重载了 operator,,它就变成了你自定义的逻辑,而失去了“顺序保证”这个语言特性。

取地址则是对于类类型已经有了内置的含义。

调用

//直接调用
data1 + data2;//间接调用
operator+(data1, data2); //普通运算符
data1.operator+(data2); //成员运算符

选择作为成员还是非成员

  • 赋值(=),下表([]),调用(()),成员访问符(->)等必须是成员;
  • 复合运算符一般是成员;
  • 改变对象状态的运算符或者给定类型密切相关的运算符,如递增、递减和解引用运算符,通常是成员;
  • 具有对称性的运算符,例如算数、相等性、关系和位运算符等,通常是非成员;
  • 输入输出运算符必须是非成员(如果是成员的话,不符合第一个操作对象是输入输出)。

输入和输出运算符

//输出
ostream &operator<<(ostream &os, const Sales_data &item);
Sales_data data;
cout << data;//输入
istream &operator>>(istream &is, Sales_data &item)
{double price;is >> item.bookNo >> item.units_sold >> price;if (is)item.revenue = item.units_sold * price;elseitem = Sales_data();return is;
}
cin >> data;

输入运算符通常会在还会进行错误处理。

算术和关系运算符

如果定义了算术运算符,一般也会定义一个对应的复合运算符:

Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs)
{Sales_data sum = lhs;sum += rhs;return sum;
}

相等和不等运算符

相等运算符和不等运算符中的一个应该把工作委托给另外一个:

bool operator==(const Sales_data &lhs, const Sales_data &rhs)
{return lhs.isbn() == rhs.isbn() && lhs.units_sold == rhs.units_sold &&lhs.revenue == rhs.revenue;
}bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{return !(lhs == rhs);
}

赋值运算符

拷贝赋值和移动赋值可以把类的一个对象赋值给该类的另外一个对象。此外,类还可以定义其他赋值运算符以使得别的类型作为右侧运算对象:

//标准库vector类还定义了第三种赋值运算符,该运算符接受花括号元素列表作为参数
vector<string> v;
v = {"a", "an", "the"};

下标运算符

如果一个类包含下标运算符,则通常会定义两个版本:一个返回普通的引用,另一个则是类的常量成员返回常量引用:

class StrVec
{
public:std::string& operator[](std::size_t n){return element[n];}const std::string& operator[](std::size_t n) const{	return element[n];}
private:std::string *element;
}

递增和递减运算符

定义递增和递减运算符的类应该同时定义前置和后置版本。这些运算符通常被定义为成员。

后置版本接受一个额外的int形参,该形参一般不被使用:

class StrBlobPtr
{
public://前置StrBlobPtr& operator++();StrBlobPtr& operator--();//后置StrBlobPtr operator++(int);StrBlobPtr operator--(int);
}//显示调用
StrBlobPtr p(a1);
p.operator++(0); //后置
p.operator++(); //前置

成员访问运算符

解引用运算符首先检查curr是否在合理范围内,如果是,则返回curr所指对象的一个引用;箭头运算符调用解引用并返回其结果地址:

class StrBlobPtr
{std::string& operator*() const{auto p = check(curr, "dereference past end");return (*p)[curr];}std::string* operator->() const{return & this->operator*();}
}

值得注意的是,这两个运算符都定义成了const成员,这是因为获取一个元素并不会改变该对象的状态。

StrBlob a1 = {"a", "an", "the"};
StrBlobPtr p(a1);
*p = "okay"; //给a1的首元素赋值
cout << p->size() << endl; //打印4,okay的大小
cout << (*p).size() << endl; //等价

和大多数其他运算符一样,我们能令operator*完成任何指定的操作。箭头运算符则不是这样,它只能拥有成员访问这个最基本的含义。形如point->mem的表达式来说,point必须是指向对象的指针或者是一个重载了operator->的类的对象:

(*point).mem; //point为指针
(point.operator->())->mem; //point为重载了operator->的对象

函数调用运算符

如果类重载了函数调用运算符,则我们可以像使用函数一样使用该类的对象。一个类可以定义多个不同版本的调用运算符,相互之间在参数数量或类型上有所区别。

如果定义了函数调用运算符,则该类的对象称作函数对象。

class PrintString
{
public:PrintString(ostream &o = cout, char c =' '): os(o), sep(c) {}void operator()(const string &s) const {os << s << sep};
private:ostream &os;char sep;
}

函数对象常常作为泛型算法的实参:

for_each(vs.begin(), vs.end(), PrintString(cerr, '\n'));

vs中的对象打印到cerr中,并换行分割。

lambda是函数对象

当我们编写一个lambda后,编译器会将该表达式翻译成一个未命名类的未命名对象。该类中含有一个重载函数调用运算符:

stable_sort(words.begin(), words.end(), [](const string &a, const &b){return a.size() < b.size();});//该lambda等价于该类的一个未命名对象
class ShorterString
{
public://lambda默认值捕获,因此生成的唯一成员函数是const//如果是引用捕获,则不是constbool operator()(const string &a, const &b) const{return a.size() < b.size();}
};//等价lambda表达式写法
stable_sort(words.begin(), words.end(), ShorterString());
  • 值捕获:产生的类必须为每个值捕获的变量建立对应的数据成员,同时构建构造函数:
auto wc = find_if(words.begin(), words.end(), [sz](const string &a){ return a.size >= sz;});class SizeComp
{
public:SizeComp(size_t n) : sz(n) {}bool operator()(const string &s) const{return s.size() >= sz;}
private:size_t sz;
};auto wc = find_if(words.begin(), words.end(), SizeComp(sz));
  • 引用捕获:不用生成成员变量和构造函数。

标准库定义的函数对象

标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类,每个类分别定义了一个执行命名操作的调用运算符。
在这里插入图片描述

sort(vec.begin(), vec.end(), greater<string>());

可调用对象与function

C++中有几种可调用的对象:函数、函数指针、lambda表示式、bind创建的对象以及重载了函数调用运算符的类。两种不同类型的可调用对象可以同享一种调用形式,例如:int(int, int)
在这里插入图片描述

可以通过function表示不同类型的可调用对象:

int add(int i, int j) {return i +j;}
auto mod = [](int i ,int j) {return i % j;};
struct divide
{int operater()(int i, int j) {return i / j;}
};map<string, function<int(int, int)>> binops = 
{{"+", add},{"-", std::minus<int>()},{"/", divide()},{"*", [](int i, int j){return i * j;}},{"%", mod}
};

值得注意的是,同名可调用对象有重载时,不能存入function类型的对象中。可通过函数指针或者lambda表示式消除二义性。

重载、类型转换与运算符

转换构造函数和类型转换运算符共同定义了类类型转换。

类型转换运算符

类型转换运算符时类的一种特殊成员函数,它负责将一个类类型的值转换成其它类型:operator type() const;,满足下列条件

  • 一个类型转换函数必须是类的成员函数;
  • 不能声明返回类型;
  • 形参列表必须为空;
  • 函数通常是const的。
class SmallInt
{
public:SmallInt(int i = 0) : val(i) {}//虽然没有指定返回类型,但实际会返回type类型operator int() const {return val;}
private:int val;
}

其中构造函数能够将算术类型转化为SmallInt,而类型转换运算符能够将SmallInt类型转化为算术类型:

SmallInt si;
si = 4; //int->SmallInt
si + 3; //SmallInt->int

有时隐式的类型转换看上去会觉得困扰,例如,当istream含有bool的类型转换时,下面的代码可以正常编译:

int i = 42;
cin << i; //cin -> bool -> int

为了防止这样的情况,可以定义显示的类型转换运算符:

class SmallInt
{
public:explicit SmallInt(int i = 0) : val(i) {}explicit operator int() const {return val;}
};SmallInt si = 3;
si +3; //err
static_cast<int>(si) + 3; //ok

值得注意的是,如果表达式被用作条件,则编译器会将显示的类型转换自动应用于它,即显示的类型转换将被隐式的执行:

  • if、while、do的条件语句部分;
  • for 语句头的条件表达式;
  • 逻辑非、或、与的运算对象;
  • 条件运算符的条件表达式。

大部分类都定义了向bool转化的显示类型转换运算符,例如IO类型:

while (std::cin >> value)

避免有二义性的类型转换

如果定义了一组类型转换,它们的转换源或者转换目标可以通过其他类型转换联系在一起,则会产生二义性问题:

  • 最好只创建一个算术类型的转换
//最好只创建一个算术类型的转换
struct A
{A(int = 0);A(double);operator int() const;operator double() const; 
};void f2(long double);
A a;
f2(a); //err,二义性,不知道用哪一个进行类型转换long lg;
A a2(lg); //err,不知道用哪一个构造函数
  • 最好不要在两个类之间构建相同的类型转换:
struct B;
struct A
{A() = default;A(const B&);
};struct B
{operator A() const;
};A f(const A&);
B b;
A a = f(b); //err//可以显示指定
A a1 = f(b.operator A());
A a2 = f(A(b));

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

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

相关文章

Java学习之——“IO流“的进阶流之转换流的学习

在博主的上一篇博文中&#xff0c;详细的介绍了“IO”流中最基本的一些知识&#xff0c;包括基本的常见的字节流和字符流&#xff0c;以及对应的缓冲流&#xff0c;对于“IO”流基础知识相对薄弱的同学可以先去看博主的上一篇博文Java学习之——万字详解“IO流”中基本的字节流…

PMP考试结构、学习框架与基本术语

一、PMP考试整体结构 考试基本信息 考试形式&#xff1a;纸笔考试&#xff08;中国大陆地区&#xff09;考试时长&#xff1a;230分钟&#xff08;约4小时&#xff09;题目数量&#xff1a;180道题 170道单选题&#xff08;四选一&#xff09;10道多选题包含5道非计分的试验题…

浅谈前端框架

在 Web 开发的演进过程中&#xff0c;前端框架扮演着越来越重要的角色。从早期的 jQuery 到如今的 React、Vue、Svelte 等&#xff0c;前端开发模式发生了翻天覆地的变化。本文将从前端框架的定义、核心特性、分类以及主流框架的差异等方面&#xff0c;带你深入理解前端框架。 …

10.3 马尔可夫矩阵、人口和经济

本节内容是关于正矩阵&#xff08;postive matrices&#xff09;&#xff1a; 每个元素 aij>0a_{ij}>0aij​>0&#xff0c;它核心的结论是&#xff1a;最大的特征值为正实数&#xff0c;其对应的特征向量也是如此。 在经济学、生态学、人口动力系统和随机游走过程中都…

python学习进阶之面向对象(二)

文章目录 1.面向对象编程介绍 2.面向对象基本语法 3.面向对象的三大特征 4.面向对象其他语法 1.面向对象编程介绍 1.1 基本概念 概念:面向对象编程(Object-Oriented Programming, OOP)是一种流行的编程范式,它以"对象"为核心组织代码和数据 在面向对象的世界里: …

VS+QT的编程开发工作:关于QT VS tools的使用 qt的官方帮助

加粗样式 最近的工作用到VS2022QT5.9.9/QT5.12.9&#xff0c;在查找相关资料的时候&#xff0c;发现Qt 官方的资料还是很不错的&#xff0c;特记录下来&#xff0c;要记得抽时间学习下。 Add Qt versions https://doc.qt.io/qtvstools/qtvstools-how-to-add-qt-versions.html B…

【系统分析师】第21章-论文:系统分析师论文写作要点(核心总结)

更多内容请见: 备考系统分析师-专栏介绍和目录 文章目录 一、写作注意事项:构建论文的合规性与专业性 1.1 加强学习 1.2 平时积累 1.3 提高打字速度 1.4 以不变应万变 二、试题解答方法:结构化应对策略 2.1 试题类型分析 2.2 三段式答题框架 2.3 时间分配 三、论文写作方法:…

tailwindcss 究竟比 unocss 快多少?

tailwindcss 究竟比 unocss 快多少&#xff1f; 前言 大家好&#xff0c;我是去年一篇测评 《unocss 究竟比 tailwindcss 快多少&#xff1f;》 的作者 icebreaker。 一晃到了 2025 年&#xff0c;tailwindcss4 也正式发布了&#xff0c;现在最新版本是 4.1.13。 新版本不仅…

算法练习——55.跳跃游戏

1.题目描述给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。示例 1&#xff…

Django 项目6:表单与认证系统

目录 1、form 表单 2、session 保存状态 3、Admin 后台 4、Auth 系统 1、form 表单 &#xff08;1&#xff09;创建 form.py 文件&#xff0c;并完善 from django import forms# 定义一个表单类 class Register(forms.Form):user forms.CharField(max_length30, label用…

tvm/triton/tensorrt比较

1.tvm的主线感觉更新太慢&#xff0c;文档太落后&#xff0c;在自动驾驶领域不支持Blackwell平台&#xff0c;跨平台其实吹牛的更多。我觉得自动驾驶用不起来。2.性能最快的还是tensorrt/tensorrt_llm这条路&#xff0c;纯cuda路线面临大量cuda算子开发&#xff0c;比如vllm ll…

Transform 和BERT、GPT 模型

目录 Transform的由来 Seq2seq 模型 Transform 的内部结构 语言模型 BERT 介绍 BERT 模型的组成 分词器 位置编码 Softmax 残差结构 BERT 模型总结 Transform的由来 传统的语⾔模型&#xff0c;⽐如RNN&#xff08;循环神经⽹络&#xff09;&#xff0c;就像⼀个“短…

2025高教社国赛数学建模A题参考论文35页(含代码和模型)

2025国赛数学建模竞赛A题完整参考论文 目录 摘要 1 问题背景与重述 2 问题分析 2.1 问题一分析 2.2 问题二分析 2.3 问题三分析 2.4 问题四分析 2.5 问题五分析 3 符号说明 4 模型假设 5 模型建立与求解 5.1 问题一 烟幕有效遮蔽时长…

【Linux】常用命令汇总

【Linux】常用命令【一】tar命令【1】可用参数【2】常用案例&#xff08;1&#xff09;创建归档&#xff08;打包&#xff09;&#xff08;2&#xff09;查看归档内容&#xff1a;​​&#xff08;3&#xff09;解包归档 (提取)&#xff1a;​​【二】日志查看命令【1】基础命令…

软考系统架构设计师之软件系统建模

一、软件系统建模 系统建模流程包括如下&#xff1a; 二、人机交互设计 黄金三法则&#xff1a; 1、置于用户控制之下以不强迫用户进入不必要的或不希望的动作的方式来定义交互方式 提供灵活的交互 允许用户交互可以被中断和撤销 当技能级别增加时可以使交互流水化并允许定制交…

Linux系统学习之注意事项及命令基本格式

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01;你要是翻过历史文章的话&#xff0c;肯定特别疑惑&#xff1a;这神经病搞得这个号到底是啥定位&#xff0c;究竟是分享啥类型的&#xff0c;咋乱七八糟的啥都有。真是个杂货铺啥都有&#xff0c;咋又开始分享Linux系统了…

0基础Java学习过程记录——枚举、注解

一、枚举1.基本介绍&#xff08;1&#xff09;枚举对应英文 enumeration&#xff0c;简写为 enum&#xff08;2&#xff09;枚举是一组常量的集合&#xff08;3&#xff09;可以理解为&#xff1a;枚举属于一种特殊的类&#xff0c;里面只包含一组有限的特定的对象2.实现方式&a…

高效计算的源泉:深入浅出冯诺依曼模型与操作系统的管理艺术 —— 构建稳定、高效的应用基石 【底层逻辑/性能优化】

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨个人…

性能测试-jmeter9-逻辑控制器、定时器压力并发

课程&#xff1a;B站大学 记录软件测试-性能测试学习历程、掌握前端性能测试、后端性能测试、服务端性能测试的你才是一个专业的软件测试工程师 性能测试-jmeter逻辑控制器、定时器妙用IF控制器**IF 控制器的作用**循环控制器循环控制器的作用ForEach控制器ForEach 控制器的作用…

T:线段树入门(无区间更新)

线段树.线段树介绍.线段树框架.理解线段树.图式整个过程.线段树代码逐层解析.代码汇总.leetcode练习.线段树介绍 线段树(SegmentTree)\;\;\;\;\;\;\;\;线段树(SegmentTree)线段树(SegmentTree) is 用于高效处理区间查询和单点修改的数据结构&#xff0c;和树状数组很像&#xf…