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;		//100
    std::cout << obj(1, 2, 3) << std::endl; //106
    std::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) {}

    // 类型转换运算符,允许隐式转换为double
    operator double() const {
        return value;
    }

    // 显式类型转换运算符
    explicit operator int() const {
        return static_cast<int>(value);
    }
    
    //使用类型转换运算符在条件表达式中隐式的转换为bool
    operator bool() const {
        return value > 1;
    }
};

int main() {
    Percentage p(35.7);

    // 隐式转换为double
    double d = p;

    // 显式转换为int
    int 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.虚函数

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Fast-Retry:一个支持百万级多任务异步重试框架【送源码】

前言 假设你的系统里有100万个用户&#xff0c;然后你要轮询重试的获取每个用户的身份信息, 如果你还在使用SpringRetry和GuavaRetry 之类的这种单任务的同步重试框架&#xff0c;那你可能到猴年马月也处理不完&#xff0c;即使加再多的机器和线程也是杯水车薪&#xff0c;而F…

6.4 cf E(题目难理解)

Problem - E - Codeforces 翻译&#xff1a; 小车在0点&#xff0c;时间为0时开始移动&#xff0c;从0&#xff0c;a1,a2......ak有k1个标志点&#xff0c;对应的时间为0&#xff0c;b1,b2...bk 在任意两个标志间&#xff0c;小车以匀速行驶&#xff0c;所以 vai1​−ai​​…

SpringBoot 统一返回格式

目录 一、为什么要统一返回&#xff1f; 二、全局异常处理代码 三、统一返回对象代码 四、使用方法 五、结果展示 一、为什么要统一返回&#xff1f; 在Spring Boot应用中&#xff0c;为了保持API接口的响应格式统一&#xff0c;通常会采用全局异常处理和自定义返回对象的方…

Sd-CDA (自退化对比域适应框架):解决工业故障诊断中数据不平衡问题

现代工业故障诊断任务常常面临分布差异和双不平衡的双重挑战。现有的域适应方法很少关注普遍存在的双不平衡问题&#xff0c;导致域适应性能差或甚至产生负面迁移。在这项工作中&#xff0c;提出了一种自降级对比域适应&#xff08;SdCDA&#xff09;诊断框架&#xff0c;用于处…

如何实现单例模式及不同实现方法分析-设计模式

这是 一道面试常考题&#xff1a;&#xff08;经常会在面试中让手写一下&#xff09; 什么是单例模式 【问什么是单例模式时&#xff0c;不要答非所问&#xff0c;给出单例模式有两种类型之类的回答&#xff0c;要围绕单例模式的定义去展开。】 单例模式是指在内存中只会创建…

Nginx location 与 Rewrite

Nginx正则表达式 location 通过前缀或正则匹配用户的URL访问路径做页面跳转、访问控制和代理转发 location 大致可以分为三类&#xff1a; 精准匹配&#xff1a;location / {...} 一般匹配&#xff1a;location / {...} 正则匹配&#xff1a;location ~ / {...} location…

外汇天眼:Bitpanda 扩大与德意志银行的合作

金融科技独角兽Bitpanda正在扩大与德意志银行的合作&#xff0c;为德国用户提供实时支付解决方案&#xff0c;以处理进出交易。 这种基于API的账户解决方案将使Bitpanda能够访问德国的IBAN账户&#xff0c;优化和增强用户体验&#xff0c;同时确保信任、速度和效率。 这只是Bi…

通过仪器分类方式修订看监测仪器发展新趋势

随着科技的进步和监测需求的不断升级&#xff0c;监测仪器的分类方式亟需与时俱进。本文旨在探讨《混凝土坝监测仪器系列型谱》中对现有仪器分类方式的修订&#xff0c;以及监测仪器发展的新趋势相关内容。 一、仪器分类方式的修订 传统的仪器分类方式往往基于功能、原理或应用…

太极图形课——渲染——光线追踪实战第一部分呢

根据概念部分我们逐步通过太极实现光线追踪 总共可以分为5步 第一步&#xff1a;如何发射出一道光&#xff1f; 首先明确何为一道光&#xff0c;光从我们眼睛&#xff08;摄像机&#xff09;射出&#xff0c;那么在三维虚拟世界里&#xff0c;我们可以认为这道光就是一条射线…

【微信小程序】事件绑定和事件对象

文章目录 1.什么是事件绑定2.button组件3.事件绑定4.input组件 1.什么是事件绑定 小程序中绑定事件与在网页开发中绑定事件几乎一致&#xff0c;只不过在小程序不能通过on的方式绑定事件&#xff0c;也没有click等事件&#xff0c;小程序中 绑定事件使用bind方法&#xff0c;c…

6个音效、配乐素材网站,免费可商用

视频剪辑必备的6个音效、配乐素材网站&#xff0c;免费下载&#xff0c;剪辑师们赶紧收藏&#xff01; 1、菜鸟图库 音效素材下载_mp3音效大全 - 菜鸟图库 菜鸟图库音效素材免费下载。站内不仅有大量音频素材&#xff0c;还有很多设计、办公、图片、视频等素材。音频素材全部都…

2024年端午节放假通知

致尊敬的客户以及全体同仁&#xff1a; 2024年端午节将至&#xff0c;根据国务院办公厅通知精神&#xff0c;结合公司的实际情况&#xff0c;现将放假事宜通知如下&#xff1a; 2024年6月8日&#xff08;星期六&#xff09;至6月10日&#xff08;星期一&#xff09;&#xff…

Linux文本处理三剑客之awk命令

官方文档&#xff1a;https://www.gnu.org/software/gawk/manual/gawk.html 什么是awk&#xff1f; Awk是一种文本处理工具&#xff0c;它的名字是由其三位创始人&#xff08;Aho、Weinberger和Kernighan&#xff09;的姓氏首字母组成的。Awk的设计初衷是用于处理结构化文本数…

Spring boot实现基于注解的aop面向切面编程

Spring boot实现基于注解的aop面向切面编程 背景 从最开始使用Spring&#xff0c;AOP和IOC的理念就深入我心。正好&#xff0c;我需要写一个基于注解的AOP&#xff0c;被这个注解修饰的参数和属性&#xff0c;就会被拿到参数并校验参数。 一&#xff0c;引入依赖 当前sprin…

OBD诊断协议

上周领导需要做个OBD相关的功能&#xff0c;我对OBD没有啥概念&#xff0c;于是周末就了解下这到底是个啥东西。了解过后发现很简单&#xff0c;其实就是个UDS协议的简化版&#xff0c;OBD是英文On-Board Diagnostics的缩写&#xff0c;中文翻译为“车载自动诊断系统”&#xf…

Redisson 分布式锁 - RLock、RReadWriteLock、RSemaphore、RCountDownLatch(配置、使用、原理)

目录 前言 Redisson 分布式锁 环境配置 1&#xff09;版本说明 2&#xff09;依赖如下 3&#xff09;配置文件如下 4&#xff09;项目配置 RLock 1&#xff09;使用方式 2&#xff09;加锁解释 3&#xff09;加锁时手动设置时间 4&#xff09;加锁时&#xff0c;到…

JVM运行数据区-Java堆

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

王学岗鸿蒙开发(北向)——————(一)鸿蒙开发环境的搭建与ArkTs介绍

1&#xff0c;鸿蒙系统开始研发的时间是在2012年。 2&#xff0c;目前鸿蒙有两个开发:HarmonyOS和OpenHarmony,前者内聚AOSP(Android的东西)&#xff0c;前者是双框架结构&#xff0c;后者不是双框架结构&#xff0c;没有内置安卓。 3&#xff0c;Harmony地址 4&#xff0c;我们…

训练Pytorch深度学习模型出现StopIteration

训练一个深度学习检测模型&#xff0c;突然出现&#xff1a; 是因为next(batch_iterator)&#xff0c;可能迭代器读出来的数据为空。 # load train data# 原先代码images, targets next(batch_iterator)# 更改为&#xff1a;try:images, targets next(batch_iterator)except…

对接钉钉登陆步骤

背景 之前事情较少的时候&#xff0c;帮公司写过一个系统&#xff0c; 这个系统的话主管有要求要对接钉钉登陆。 话不多说我们直接开干。流程 先进入开发者平台点击开发者后台 没有组织的 我们先在手机上先创建一个组织 创建完成后&#xff0c;就可以看到这个组织了 创建…