C++基础与深度解析 | 类进阶 | 运算符重载 | 类的继承 | 虚函数

文章目录

  • 一、运算符重载
  • 二、类的继承
    • 1.类的继承
    • 2.虚函数

一、运算符重载

  在C++中,operator关键字用于重载运算符,使得类的实例可以使用内置的操作符(如+-*/等)进行操作。

运算符重载的特性

  • 重载不能发明新的运算符,不能改变运算的优先级与结合性,通常不改变运算含义

    重载运算符不能改变其原有的优先级和结合性。重载的运算符应保持原有运算符的基本含义。

  • 函数参数个数与运算操作数个数相同,至少一个为类类型

  • operator() 外其它运算符不能有缺省参数

  • 可以选择实现为成员函数与非成员函数

    运算符可以作为类的成员函数实现,也可以作为非成员函数(友元函数)实现。如果运算符重载为成员函数,*this通常表示第一个操作数,即当前对象。

  • 对于比较运算符==和三元比较运算符<=>(C++20),它们通常不使用*this作为第一个操作数,因为它们用于比较两个对象。

重载运算符分类

详细内容可参考:https://en.cppreference.com/w/cpp/language/operators

  根据重载特性,可以将运算符进一步分为

  • 可重载且必须实现为成员函数的运算符( =,[],(),-> 与转型运算符)

  • 可重载且可以实现为非成员函数的运算符

  • 可重载但不建议重载的运算符( &&, ||, 逗号运算符)

    C++17 中规定了相应的求值顺序但没有方式实现短路逻辑

  • 不可重载的运算符(如 ? :运算符)

对称运算符通常定义为非成员函数

  在C++中,对称运算符(如+-*/等)通常定义为非成员函数(友元函数)以支持首个操作数的类型转换。

示例:对称运算符作为非成员函数

class Rational {
public:int numerator;  // 分子int denominator;  // 分母// 构造函数Rational(int n = 0, int d = 1) : numerator(n), denominator(d) {// 简化分数}// 重载 + 运算符作为友元函数friend Rational operator+(const Rational& lhs, const Rational& rhs);
};// 实现 + 运算符
Rational operator+(const Rational& lhs, const Rational& rhs) {return Rational(lhs.numerator * rhs.denominator + rhs.numerator * lhs.denominator,lhs.denominator * rhs.denominator);
}

Rational类表示有理数,我们重载了+运算符来实现两个有理数的加法。由于+是一个对称运算符,我们将其作为非成员函数实现,并通过友元函数声明允许它访问Rational类的私有成员。

移位运算符一定要定义为非成员函数

  在C++中,移位运算符(<<>>)一定要定义为非成员函数,因为移位运算符是对称的,且它们通常需要与输入输出流一起使用。对于输入输出流(如std::ostreamstd::istream),移位运算符被重载以允许将数据流式传输到输出或从输入。

示例:重载移位运算符以支持自定义类的输入输出

#include <iostream>class MyClass {
private:int val;
public:friend std::ostream& operator<<(std::ostream& os, const MyClass& obj) {// 实现将对象obj输出到流os的逻辑os << obj.val; return os;}friend std::istream& operator>>(std::istream& is, MyClass& obj) {// 实现从流is读取数据到对象obj的逻辑is >> obj.val;return is;}
};int main() {MyClass obj;std::cout << obj << std::endl; // 使用重载的<<运算符输出对象std::cin >> obj;               // 使用重载的>>运算符输入对象return 0;
}

赋值运算符也可以接收一般参数

  在C++中,赋值运算符(=)通常用于将一个值或一个对象的状态赋给另一个对象。赋值运算符可以重载为类的成员函数,也可以重载为非成员函数(例如,全局函数或友元函数)。当重载赋值运算符时,它通常接收一个参数,这个参数的类型是该类的类型或者是一个可接受的赋值的类型(合理即可)。

operator [] 通常返回引用

  在C++中,operator[]用于提供对容器或类似数组结构中元素的访问。通常,operator[]重载返回一个引用,这样做有几个原因:

  1. 可变访问:返回引用允许用户修改通过operator[]访问的元素的值。
  2. 效率:返回引用而不是值的拷贝可以提高性能,因为不需要复制元素。
  3. 一致性:对于像数组这样的数据结构,用户期望能够通过下标访问和修改元素,返回引用提供了这种直观的操作方式。
  4. 链式调用:返回引用允许使用链式语法,例如,可以连续为数组的多个元素赋值。

示例:重载operator[]以返回引用

#include <iostream>class MyArray {
private:int* data;size_t size;public:MyArray(size_t sz) : size(sz) {data = new int[sz];}// 析构函数,释放内存~MyArray() {delete[] data;}// operator[] 重载,返回对数据的引用int& operator[](size_t index) {return data[index];}// 常量版本的 operator[],返回一个 const 引用int& operator[](size_t index) const {return data[index];}
};int main() {MyArray arr(10);arr[0] = 10;  // 使用重载的 operator[] 修改元素int value = arr[0];  // 使用重载的 operator[] 访问元素std::cout << value << std::endl;return 0;
}

自增、自减运算符的前缀、后缀重载方法

  在C++中,自增(++)和自减(--)运算符可以作为成员函数重载,以适应自定义类型的操作。自增和自减运算符有两种形式:前缀(++it)和后缀(it++)。

示例:自增运算符重载

class Counter {
private:int value;public:// 构造函数Counter(int val) : value(val) {}// 前缀自增运算符重载Counter& operator++() {++value;  // 增加值return *this;  // 返回当前对象的引用}// 后缀自增运算符重载Counter operator++(int) {Counter temp = *this;  // 保存当前状态的副本++value;  // 增加值return temp;  // 返回保存状态的副本}
};

示例:自减运算符

// 自减运算符的重载方式与自增类似,只是将增加操作替换为减少操作。
class Counter {
private:int value;public:// 构造函数Counter(int val) : value(val) {}// 前缀自减运算符重载Counter& operator--() {--value;  // 减少值return *this;  // 返回当前对象的引用}// 后缀自减运算符重载Counter operator--(int) {Counter temp = *this;  // 保存当前状态的副本--value;  // 减少值return temp;  // 返回保存状态的副本}
};

后缀自增自减涉及到拷贝,因此,如果能使用前缀自增自减就不要尝试使用后缀自增自减。

解引用运算符(*)和成员访问运算符(->)重载:

  在C++中,解引用运算符(*)和成员访问运算符(->)通常与指针一起使用,以访问所指向对象的成员。对于自定义类型,可以重载operator*operator->来模拟指针的行为,这在实现智能指针或其他类似指针的类时非常有用。

注意:

  • “ .” 运算符不能重载
  • “→” 会递归调用“→”操作

示例:

#include <iostream>class MySmartPointer {
private:int* ptr;public:MySmartPointer(int* p) : ptr(p) {}// 解引用运算符重载int& operator*() {return *ptr; // 返回对指向对象的引用}int& operator*() const {return *ptr; // 返回对指向对象的引用}// 成员访问运算符重载MySmartPointer* operator->() {return this; // 返回指向对象的指针}int val = 5;
};int main()
{//解引用运算符重载的使用int x = 100;MySmartPointer msp(&x);std::cout << *msp << std::endl;  //100*msp = 101;std::cout << *msp << std::endl;  //101//成员访问运算符重载的使用std::cout << msp->val << std::endl; //5
}

函数调用运算符重载

  使用函数调用运算符构造可调用对象,其参数个数不确定。

示例:

#include <iostream>class FunCall {
private:int val;public:FunCall(int p) : val(p) {}int operator() (){return val;}int operator() (int x, int y, int z){return val + x + y + z;}bool operator() (int input){return val < input;}
};int main()
{FunCall obj(100);std::cout << obj() << std::endl;		//100std::cout << obj(1, 2, 3) << std::endl; //106std::cout << obj(101) << std::endl; 	//1
}

类型转化运算符重载

  在C++中,类型转换运算符允许类的实例通过调用特定的成员函数来进行类型转换。这是通过重载operator type()来实现的,其中type是目标转换的类型。

类型转换运算符的一些关键点

  1. 函数声明:类型转换运算符被声明为operator type(),其中type是内置类型或枚举类型。

  2. 隐式转换:类型转换运算符允许隐式类型转换,这意味着编译器可以在需要时自动调用这个运算符。

  3. explicit关键字:使用explicit关键字可以避免隐式类型转换,使得类型转换必须显式进行。

    class MyClass {
    public:explicit operator bool() const {return some_condition;}
    };
    
  4. 单参数构造函数:单参数构造函数也可以隐式地引入类型转换。如果一个类有一个接受单个参数的构造函数,编译器可以自动使用这个构造函数来进行类型转换。

  5. 避免歧义和意外行为:类型转换运算符应该小心设计,以避免引入歧义或意料之外的行为。特别是,应该避免创建可以隐式转换为不相关类型或具有多重含义的类型的转换运算符。

  6. explicit bool的特殊性:用于条件表达式时会进行隐式类型转换

示例:

#include <iostream>class Percentage {
private:double value;
public:Percentage(double val) : value(val) {}// 类型转换运算符,允许隐式转换为doubleoperator double() const {return value;}// 显式类型转换运算符explicit operator int() const {return static_cast<int>(value);}//使用类型转换运算符在条件表达式中隐式的转换为booloperator bool() const {return value > 1;}
};int main() {Percentage p(35.7);// 隐式转换为doubledouble d = p;// 显式转换为intint i = static_cast<int>(p);// 使用类型转换运算符在条件表达式中if (p) {// 条件为真,因为转换为bool的结果是true}return 0;
}

C++ 20 中对 == 与 <=> 的重载

  • ==运算符重载:

      当你为类重载了operator==来比较两个对象是否相等时,编译器可以自动为你生成operator!=,表示不等。

    class MyClass {int value;
    public://==支持交换律bool operator==(const MyClass& other) const {return value == other.value;}
    };
    // 对于MyClass,编译器将自动提供operator!=。
    
  • <==>运算符重载

      <=>运算符允许你定义对象之间的全面比较逻辑,包括小于、等于、大于。

    #include <iostream>class MyClass {int value;
    public:auto operator<=>(const MyClass& other) const {if (value < other.value) return std::strong_ordering::less;if (value > other.value) return std::strong_ordering::greater;return std::strong_ordering::equal;}
    };
    
    • 隐式交换操作数<=>运算符可以交换操作数的位置,这意味着你可以定义一个只比较当前对象与另一个对象的版本,编译器将自动处理另一个顺序。
    • 返回类型<=>运算符的返回类型是std::strong_orderingstd::weak_orderingstd::partial_ordering,这些类型都定义在<compare>头文件中。
      • std::strong_ordering:表示可以确定对象之间的顺序关系,即要么<要么>
      • std::weak_ordering:表示可以确定对象之间的顺序关系,但存在等价关系。
      • std::partial_ordering:表示只能确定部分顺序关系,可能存在无法比较的情况。

二、类的继承

1.类的继承

  在C++中,类的继承是一种多态性机制,它允许一个类(派生类或子类)继承另一个类(基类或父类)的属性和行为。继承关系是一种“是一个”(is-a)的关系。

image-20240604222739958

以下是类继承的一些关键概念和要点:

基本语法

class Base {// 基类成员
};class Derived : public Base {// 派生类成员
};

注意:继承部分不是类的声明

继承方式

  • public继承:最常用的继承方式,基类的公有(public)和保护(protected)成员在派生类中保持原有的访问级别。通常采用public继承
  • protected继承:基类的公有和保护成员在派生类中变为派生类的保护成员。
  • private继承:基类的公有和保护成员在派生类中变为派生类的私有成员。

使用基类的指针或引用可以指向派生类对象

  使用基类指针或引用指向派生类对象的过程称为向上转型(upcasting),这是安全的并且不需要显式的类型转换。

Derived d;
Base* basePtr = &d;
Base& baseRef = d;

静态类型与动态类型

  • 静态类型:在编译时确定的类型,用于类型检查和作用域规则。
  • 动态类型:在运行时确定的类型,与对象的实际类型相关联。可以使用dynamic_cast进行向下转型(downcasting),但需要基类有虚函数。

2.虚函数

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

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

相关文章

通信协议:常见的芯片间通信协议

相关阅读 通信协议https://blog.csdn.net/weixin_45791458/category_12452508.html?spm1001.2014.3001.5482 本文将简单介绍一些常见的芯片间通信协议&#xff0c;但不会涉及到协议的具体细节。首先说明&#xff0c;芯片间通信方式根据通信时钟的区别可以分为&#xff1a;异步…

上位机图像处理和嵌入式模块部署(f407 mcu中的网络开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 和大家想的不太一样&#xff0c;只要mcu当中带有了mac ip&#xff0c;那么就意味着mcu本身支持了网络开发。但是如果需要mcu支持完整的tcp/ip&…

Java面试题:ArrayList底层实现原理、HashMap的实现原理、HashMap的jdk1.7和jdk1.8有什么区别

文章目录 一、List相关面试题1.1 ArrayList源码分析&#xff08;底层实现&#xff09;1.2 ArrayList底层的实现原理是什么1.3 ArrayList listnew ArrayList(10)中的list扩容几次1.4 如何实现数组和List之间的转换1.5 ArrayList 和 LinkedList 的区别是什么 二、HashMap相关面试…

《最游记》游戏全套源码(源码+引擎+文档+客户端+服务端+工具)

《最游记》首款西游降魔修仙网游—还原《西游记》小说经典&#xff0c;华丽场景&#xff0c;玄幻体验&#xff1b;七十二变&#xff0c;机甲配备&#xff1b;仙兵神器&#xff0c;灵兽助阵&#xff1b;降妖除魔&#xff0c;最游天下&#xff01;源码基于 vs 2005&#xff0c;可…

英伟达GPU架构加速狂飙

NVIDIA首席执行官黄仁勋在台湾大学体育馆发表主题演讲&#xff0c;展示了新一代Rubin架构&#xff0c;这是NVIDIA加速推出新架构的最新成果。 在讨论NVIDIA下一代架构时&#xff0c;黄仁勋提到了Blackwell Ultra GPU&#xff0c;并表示它可能会继续升级。然后他透露&#xff0c…

Qt 【Object::connect: No such slot 。。。】解决方法

发生如下所示问题&#xff0c;有三种原因造成&#xff1a; 1.下图中的Q_OBJECT被注释掉或者漏了&#xff08;该问题不常见&#xff09; 2.下图中声明slots漏了&#xff08;新手较常见&#xff09; 3.发生下面两行中的错误&#xff0c;在下面两行代码中仅仅是一个参数名字的区别…

web刷题记录(2)

[鹤城杯 2021]EasyP 就是php的代码审计 从中可以看出来&#xff0c;就是对四个if语句的绕过&#xff0c;然后过滤了一些语句 代码分析&#xff1a; 通过include utils.php;导入了一个叫做"utils.php"的文件&#xff0c;这意味着在该文件中可能定义了一些与本代码相…

模型训练篇 | yolov10来了!手把手教你如何用yolov10训练自己的数据集(含网络结构 + 模型训练 + 模型推理等)

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。YOLOv9还没捂热乎&#xff0c;YOLOv10就推出来了&#xff0c;太卷了&#xff0c;太快了&#xff0c;坐等YOLOv9000&#xff01;自今年2月YOLOv9发布之后&#xff0c; YOLO&#xff08;You Only Look Once&#xff09;系列的…

全域外卖平台是什么?有哪些系统可以推荐?

随着本地生活下半场的开启&#xff0c;以团购外卖为代表的一系列相关业务也迈入了全域时代。在此背景下&#xff0c;全域外卖赛道正式兴起&#xff0c;全域外卖平台也随之备受瞩目。 作为创业的一大新风口&#xff0c;全域外卖囊括了公域和私域内所有的外卖业务&#xff0c;主…

基础篇03——SQL约束

概述 约束示例 完成以下案例&#xff1a; create table user (id int primary key auto_increment comment 主键,name varchar(10) not null unique comment 姓名,age tinyint unsigned check ( age > 0 and age < 120 ) comment 年龄,status char(1) default 1 commen…

嵌入式C语言--Pragma Section与Map文件

嵌入式C语言–Pragma Section与Map文件 嵌入式C语言--Pragma Section与Map文件 嵌入式C语言--Pragma Section与Map文件一. Pragma修饰符二. Map文件1&#xff09;什么是map文件2&#xff09;map文件的构成3&#xff09;常用的段映射地址4&#xff09;map文件生成 三. Section修…

SpaceX: 太空火箭自主精准着陆

本文是根据Lars Blackmore在16年的一篇公开论文翻译而来&#xff0c;虽然有些早而且是科普文章&#xff0c;但是可以初见一些SpaceX火箭着陆的细节&#xff0c;后面我会对spaceX landing control 技术主管MIT博士期间研究火箭控制算法的论文进行讲解&#xff0c;敬请期待。 Lar…

OpenEuler华为欧拉系统安装—从零开始,小白也能学会

介绍 openEuler&#xff08;欧拉&#xff09;是一款开源操作系统。 当前openEuler内核源于Linux&#xff0c;支持鲲鹏及其它多种处理器&#xff0c; 能够充分释放计算芯片的潜能&#xff0c;是由全球开源贡献者构建的高效、 稳定、安全的开源操作系统&#xff0c;适用于数据库…

JVM运行数据区-Java堆

Java堆 堆区&#xff08;Heap区&#xff09;是JVM运行时数据区占用内存最大的一块区域&#xff0c;每一个JVM进程只存在一个堆区&#xff0c;它在JVM启动时被创建&#xff0c;JVM规范中规定堆区可以是物理上不连续的内存&#xff0c;但必须是逻辑上连续的内存。 1、堆区是线程…

「网络编程」基于 UDP 协议实现回显服务器

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;计网 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 实现回显服务器 &#x1f349;socket api&#x1f349;回显服务器&#x1f34c;实现&#x1f95d;服务器&#x1f95d;客户端 &#x1f3…

纯血鸿蒙实战开发—如何添加顶部tab页面

1.Tabs组件 Tabs组件的页面组成包含两个部分&#xff0c;分别是TabContent和TabBar。TabContent是内容页&#xff0c;TabBar是导航页签栏. 根据不同的导航类型&#xff0c;布局会有区别&#xff0c;可以分为底部导航、顶部导航、侧边导航&#xff0c;其导航栏分别位于底部、顶…

react基础学习 JSX

JSX的测试网站 Babel Babel 可以测试代码的效果 JSX实现map列表 注意 key不一样&#xff08;使用遍历的时候&#xff09; 简单条件渲染 复杂条件渲染 绑定事件 function App() {const colorse (e)>{console.log("测试点击",e);}const colorse1 (name)>{…

地理信息科学中的大数据挑战

在信息化爆炸的时代&#xff0c;地理信息科学&#xff08;GIScience&#xff09;正经历着前所未有的变革&#xff0c;其中&#xff0c;地理空间大数据的涌现为科学研究与应用带来了前所未有的机遇与挑战。作为地理信息与遥感领域的探索者&#xff0c;本文旨在深入剖析地理空间大…

揭秘HubSpot集客营销:如何吸引并转化全球潜在客户

随着全球数字化浪潮的推进&#xff0c;企业出海已经成为许多公司扩大市场、增加品牌曝光度的重要战略。HubSpot集客营销作为一种以客户为中心、数据驱动的营销策略&#xff0c;为企业在海外市场的成功提供了强有力的支持。作为HubSpot亚太地区的合作伙伴&#xff0c;NetFarmer将…

godot的安装和使用 1

今天是第一节&#xff0c;因此呢先做godot的安装&#xff0c;其实很简单 godot官网&#xff1a;https://godotengine.org/ 进入官网&#xff0c; 安装好之后呢&#xff0c;会有两个文件 打开第一个就是可视化界面的&#xff0c;进入后是这个样子 说明安装成功了