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相关面试…

如何在 Vue 组件中正确地使用 data 函数?

在 Vue 组件中正确使用 data 函数有以下几点需要注意: 返回一个对象: data 函数必须返回一个对象,这个对象包含了组件实例需要用到的所有数据属性。export default {data() {return {message: Hello, Vue!,count: 0}} }不要使用箭头函数: data 函数不应该使用箭头函数 () >…

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

《最游记》首款西游降魔修仙网游—还原《西游记》小说经典&#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;在下面两行代码中仅仅是一个参数名字的区别…

发布npm自己的插件包

要发布自己的npm插件包&#xff0c;你需要遵循一系列步骤来确保你的包可以正确地被其他人使用。以下是一个基本的指南&#xff1a; 1. 创建一个npm账户 首先&#xff0c;你需要在npm网站上注册一个账户。这可以通过npm的命令行工具或npm网站完成。 2. 初始化你的项目 在你的…

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;系列的…

力扣2953.统计完全子字符串

力扣2953.统计完全子字符串 先分组循环求出每个组 分别算个数 对于每个组 枚举一遍其中有多少种字母用cnt数组记录每个字母出现次数 check的时候也是暴力枚举cnt中每个字母出现次数 **分组循环&#xff1a;**从i(新一组的起点)开始&#xff0c;当满足条件时&#xff0c;最后退…

CentOS 7基础操作03_Linux命令的分类

1、Linux命令的分类 Linux命令的执行必须依赖于 Shell命令解释器。Shell实际上是在Linux操作系统中运行的一种特殊程序,它位于操作系统内核与用户之间&#xff0c;负责接收用户输入的命令并进行解释&#xff0e;将需要执行的操作传递给系统内核执行&#xff0c;Shell在用户和内…

搜维尔科技: 使用 Xsens 和 HTC Vive进行电影制作案例

搜维尔科技&#xff1a; 使用 Xsens 和 HTC Vive进行电影制作案例 搜维尔科技&#xff1a; 使用 Xsens 和 HTC Vive进行电影制作案例

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

随着本地生活下半场的开启&#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修…

HAL_UART_IRQHandler(UART_HandleTypeDef *huart)分析

//接收没错误&#xff1a;执行UART_Receive_IT(huart) //接收有错误&#xff1a;执行UART_Receive_IT(huart)、HAL_UART_ErrorCallback(huart) void HAL_UART_IRQHandler(UART_HandleTypeDef *huart) {uint32_t isrflags READ_REG(huart->Instance->SR);uint32_t cr1…

java项目部署脚本

一、java项目部署脚本示例 在Java项目中&#xff0c;部署脚本通常依赖于项目的构建工具&#xff08;如Maven或Gradle&#xff09;以及部署环境&#xff08;如Docker、Tomcat、Kubernetes等&#xff09;。以下是一个基于Maven和Shell脚本的Java项目部署示例&#xff0c;假设我们…

8*8LED点阵点亮一个点

#include<reg51.h> typedef unsigned int u16; //对系统默认数据进行重定义 typedef unsigned char u8; //定义74HC595控制管脚 sbit SRCLKP3^6; //移位寄存器时钟输入 sbit RCLKP3^5; //存储寄存器时钟输入 sbit SERP3^4; //串…

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

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