【C++】类和对象—(下) 收官之战

【C++】类和对象—(下) 收官之战

前言:上一篇文章我们向大家介绍了类和对象的核心六个成员函数中的4个,其余两个以及初始化列表,static成员,内部类,匿名对象等会在本篇文章介绍!

在这里插入图片描述
✨ 坚持用清晰易懂的图解+代码语言, 让每个知识点都简单直观!
🚀 个人主页MSTcheng · ZEEKLOG
🌱 代码仓库MSTcheng · Gitee
📌 专栏系列 :📖 《C语言》🧩 《数据结构》💡 《C++由浅入深》💬 座右铭 :“路虽远行则将至,事虽难做则必成!”

文章目录

一,运算符重载

1.1什么是运算符重载?

类比我们之前学过的函数重载,函数重载的特点就是函数名相同参数不同。那么运算符重载也一样,运算符重载就是将普通的运算符重载成为一个函数,在使用的时候就去调用对应的函数即可。

1.2 为什么要创造运算符重载?

使问题简单化:首先无论是C还是C++的内置类型都支持运算符操作,但是自定义类型是不支持的。比如日期类,日期加日期,日期减日期,这是没办法使用普通的运算符去实现的。因此C++支持这种重载能够让复杂的自定义类型也能够使用简单的运算符符号,本质就是让复杂的问题简单化。扩展语言的表达能力:通过重载<<流提取>>流插入
可以输入输出自定义类型的内容,以及重载下标运算符[]可以让类模拟数组的行为等等。

简单的了解了运算符重载后我们就来介绍一下赋值运算符重载。

二,赋值运算符重载

2.1赋值运算符重载的构成

1.运算符重载的构成为:operator+运算符(参数),所以赋值运算符重载就是
operator =() 其余的运算符在重载的时候也类似。下面以日期类为例:
#include<iostream>usingnamespace std;classDate{public:Date(int year =1,int month =1,int day =1){ cout <<"Date(int year = 1, int month = 1, int day = 1)"<< endl; _year = year; _month = month; _day = day;}//为了避免我们在重载==等号 这个运算符会误将==写成=赋值 所以我们加上const修饰 d就不能改变了那么写成赋值就会报错 Date&operator=(const Date& d){// 要检查⾃⼰给⾃⼰赋值的情况if(this!=&d){ _year = d._year; _month = d._month; _day = d._day;}// d1 = d2表达式的返回对象应该为d1,也就是 * thisreturn*this;}private:int _year;int _month;int _day;};intmain(){ Date d1(2025,4,26);//注意这是拷贝构造 拷贝构造是用一个已经存在的对象去初始化另一个对象 Date d2=d1; Date d3(2025,5,26); Date d4(2025,6,26);//这里是赋值重载 是两个已经存在的对象之间的赋值!!! d1 = d3;//连续赋值 d1 = d3 = d4;return0;}

代码分析:

在这里插入图片描述
观察上面的代码我们可以知道赋值运算符的特点:有返回值:作为六个默认成员函数之一,它相较于其他无返回的成员函数不同它有返回值,且返回什么根据自己的需求定义。比如比较大小的运算符重载返回的就是布尔值。
2.在没有显示写此函数时,编译器也会自动生成。生成的函数跟拷贝构造函数类似会对对象进行值的拷贝(浅拷贝),而对于自定义类型则会调用它的赋值重载函数。

注意:.*::sizeof?:. 注意以上5个运算符不能重载。

2.1 >>流插入<<流提取重载


首先来看库里面的的流插入和流提取,它们也是重载得到的,那在重载自定义类的的流插入和流提取就只要注意this指针抢占问题,下面来看代码:
classA{public://定义在类里面 ostream&operator<<(ostream& out,const Date& d){ out << d._year <<"年"<< d._month <<"月"<< d._day <<"日"<< endl;return out;}}//定义在类外面 全局函数 ostream&operator<<(ostream& out,const Date& d){ out << d._year <<"年"<< d._month <<"月"<< d._day <<"日"<< endl;return out;}
在这里插入图片描述
重载<<和>>时,需要重载为全局函数。 因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调⽤时就变成了对象<<cout,不符合使⽤习惯和可读性。 重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。

3.1const成员函数

const成员函数就是被const修饰的成员,const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为 const Date* const this
在这里插入图片描述

4.1取地址运算符重载

这两个成员函数用的很少一般编译器自动生成的函数就够用了,当我们不想让别人取到对象的地址时才使用:
classDate{public: Date*operator&(){returnthis;//return nullptr;}const Date*operator&()const{returnthis;//return nullptr;}private:int _year ;int _month ;int _day ;}

三,初始化列表

3.1 再探构造函数

在上一篇文章中我们给大家介绍了构造函数,我们知道构造函数就是执行初始化功能的函数,每一个对象在创建好时都会调用对应的构造去初始化对象内部的成员变量,但当时我们并不知道对象里的成员变量到底是怎么初始化的?成员变量实际上是通过初始化列表来初始化的,下面我们就来认识一下初始化列表。

3.2初始化列表

构成:在构造函数中,初始化列表的使⽤方式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。
classDate{public:Date(int year =1,int month =1,int day =1)//初始化列表:_year(year),_month(month),_day(day){}private:int _year;int _month;int _day;};
以上面的日期类举例,以前我们是在函数内部完成初始化,现在我们将这些成员变量拿出来外面初始化,其中还有很多细节我们画图来解释:
在这里插入图片描述

到这里可能有人要问:使用初始化列表跟以前在函数内部的初始化好像没什么区别,那初始化列表存在的意义是什么呢?

首先我们如果写初始化列表其实编译器也会去走初始化列表将成员变量初始化成0或随机值,只不过我们后面在函数体内部又手动的初始化了所以我们感受不到编译器使用初始化列表。
另外如果在成员函数中加入自定义类型成员变量const修饰的变量引用成员变量还放在函数体内初始化吗?答案是否定的,这几类成员函数必须要使用初始化列表初始化!
classDate{public:Date(int& x,int year =1,int month =1,int day =1):_year(year),_month(month),_day(day),_t(12),_ref(x),_n(1){//括号内部不能初始化 _t _ret _n 必须放到初始化列表}private:int _year=2025;int _month;int _day; Time _t=1;// 没有默认构造int& _ref;// 引⽤constint _n;// const};
在这里插入图片描述
到这里可能也还有人会问:初始化列表这么好用那以后是不是全都用初始化列表就行了呢?答案是否定的,来看看特殊情况:
typedefint STDataType;classStack{public:Stack(int n =4):_a((STDataType*)malloc(sizeof(STDataType)* n)),_top(0),_capacity(n){//想栈这样又资源的类,判断空间是否开辟成功使用初始化列表就不行 就要在函数体内部完成!if(nullptr== _a){perror("malloc申请空间失败");return;}}private: STDataType* _a; size_t _capacity; size_t _top;};
下面再给一张图让大家理清初始化列表的逻辑:
在这里插入图片描述

四,类型转换和explicit关键字

在之前C语言阶段,我们所接触的类型转换就是内置类型之间的转换比如:浮点数转整型,整型和指针之间,以及指针和指针之间的转换。但是C++就支持内置类型与类类型的转换,以及类类型与类类型之间的转换:
explicit:构造函数使用explicit关键字就不支持隐式类型转换了。
classA{public:// 构造函数在使用explicit就不再支持隐式类型转换// explicit A(int a1)A(int a1):_a1(a1){}//explicit A(int a1, int a2)A(int a1,int a2):_a1(a1),_a2(a2){}voidPrint(){ cout << _a1 <<" "<< _a2 << endl;}intGet()const{return _a1 + _a2;}private:int _a1 =1;int _a2 =2;};classB{public:B(const A& a):_b(a.Get()){}private:int _b =0;};intmain(){// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3// 编译器遇到连续构造+拷⻉构造->优化为直接构造 A aa1 =1; aa1.Print();const A& aa2 =1;//多参数的类型转换需要使用花括号{} A aa3 ={2,2};//类与类之间的类型转换 aa3隐式类型转换为b对象 B b = aa3;const B& rb = aa3;return0;}

五,static成员

⽤static修饰的成员变量,称之为静态成员变量。
用static修饰的成员函数,称为静态成员函数。
要注意:静态成员变量⼀定要在类外进⾏初始化。
classA{public://静态成员函数被static修饰 是没有this指针的staticintGet_a(){//_b=2; //静态成员函数没有this指针 所以不能访问非静态成员_b 但是可以访问静态成员_areturn _a;}private:// 类里面声明staticint _a;int _b;}//类外面初始化 类里面给缺省值是不显式在初始化列表初始化而给的缺省值,而静态成员不属于对象因此不能在类内初始化。int A::_a=0;intmain(){return0;}
静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
静态成员的访问方式:可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。

六,友元

友元在之前我们就提到过,一个函数想要在类外面访问就要在这个类内部声明成该类的友元,在函数声明或者类声明的前⾯加friend,并且把友元声明放到⼀个类的⾥⾯。友元有两种一种是友元函数一种是友元类。

先来看友元函数:

classA{public://定义在类里面friend ostream&operator<<(ostream& out,const Date& d);}//定义在类外面 全局函数 ostream&operator<<(ostream& out,const Date& d){ out << d._year <<"年"<< d._month <<"月"<< d._day <<"日"<< endl;return out;}
在类里面声明了友元就可以在类外面访问该类的私有成员,它可以在类的任意位置声明,也仅仅是一种声明,不是函数!一个函数可以同时是多个类的友元,可以在多个类里面声明友元。

再来看看友元类:

classA{public:friendclassBprivate:int _a;}classB{public:voidfunc(){ cout<<_a<<endl;}private:int _b;}
如果一个类想访问另一个类的私有成员,那么就声明为该类的友元。
注意友元是单向的,A是B的友元A可以访问B的私有但是B不能访问A的私有;A是B的友元,B是C的友元但是A不是C的友元。

七,内部类

内部类,顾名思义就是一个类定义在另一个类的内部。

要点:

1、内部类和外部类实际上是两个独立的类它们是完全平行的不存在包含关系。
2、内部类是外部类的友元类,因此内部类可以访问内部类的私有成员,但外部类不行!
3、内部类本质上是一种封装,如果两个类关系紧密且一个类的实现主要用来给另外一个类使用那么就定义成内部类。
classA{public:friendclassBprivate:int _a;}classB{public:voidfunc(){ cout<<_a<<endl;}private:int _b;}

八,匿名对象

1、用类型(实参) 定义出来的对象叫做匿名对象,相比之前我们定义的 类型 对象名(实参) 定义出来的叫有名对象。
2、匿名对象生命周期只在当前一行,一般临时定义⼀个对象当前用一下即可,就可以定义匿名对象。
classA{public:A(int a =0){}~A(){}private:int _a;};intmain(){ A aa1;//定义匿名对象不能这样定义,因为编译器无法识别下面的调用是⼀个函数声明,还是对象定义//A aa1();// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数A();A(1); A aa2(2);return0;}
以上就是本篇文章的所有内容了,感谢各位大佬观看,制作不易还望各位大佬点赞支持一下!有什么问题可以加我私信交流!

Read more

【优选算法 | 位运算】位运算基础:深入理解二进制操作

【优选算法 | 位运算】位运算基础:深入理解二进制操作

算法相关知识点可以通过点击以下链接进行学习一起加油!双指针滑动窗口二分查找前缀和 在本篇文章中,我们将全面解析位运算的基本原理与实际应用。位运算通过直接操作数字的二进制表示,能够在许多计算中提供极大的效率提升。无论是用于加速数学运算、优化算法,还是解决特定的技术问题,位运算都扮演着至关重要的角色。 🌈个人主页:是店小二呀 🌈C/C++专栏:C语言\ C++ 🌈初/高阶数据结构专栏: 初阶数据结构\ 高阶数据结构 🌈Linux专栏: Linux 🌈算法专栏:算法 🌈Mysql专栏:Mysql 🌈你可知:无人扶我青云志 我自踏雪至山巅 文章目录 * 一、常见位运算总结 * 二、入门题目 * 面试题 01.01. 判定字符是否唯一 * 268.丢失的数字 * 371.两整数之和 * 137.只出现一次的数字 II * 面试题 17.19. 消失的两个数字

By Ne0inhk
斐波那契数列模型:在动态规划的丝绸之路上追寻斐波那契的足迹(上)

斐波那契数列模型:在动态规划的丝绸之路上追寻斐波那契的足迹(上)

文章目录 * 引言 * 递归与动态规划的对比 * 递归解法的初探 * 动态规划的优雅与高效 * 自顶向下的记忆化搜索 * 自底向上的迭代法 * 性能分析与比较 * 小结 引言 斐波那契数列,这一数列如同一条无形的丝线,穿越千年时光,悄然延续其魅力。其定义简单而优美: F(0)=0,F(1)=1 F(n)=F(n−1)+F(n−2), n>1 这看似简单的递归公式,却蕴含着深刻的数学结构,成为计算机科学中的经典问题之一。斐波那契数列不仅仅出现在数学课本上,它在自然界、计算机算法、金融模型等领域中无处不在。对于程序员而言,斐波那契数列不仅是一个练习递归的好题目,更是一个优化算法的标杆。 在这篇文章中,我们将通过动态规划的技术来探讨如何高效地求解斐波那契数列,从而避免传统递归方法中低效的冗余计算。我们将以 C 语言为例,展示动态规划方法如何一步步揭开这一问题的面纱。 递归与动态规划的对比

By Ne0inhk
2026 前端 / 后端 / 算法岗 AI 技能清单,直接对标大厂

2026 前端 / 后端 / 算法岗 AI 技能清单,直接对标大厂

2026 大厂前端岗 AI 技能清单 核心基础技能 * 大模型前端适配能力:掌握大模型上下文管理,实现对话历史的高效存储与加载,适配流式输出的前端渲染逻辑。 * AI 组件开发:熟练开发基于大模型的智能组件,如代码补全、智能问答、内容生成类组件,支持参数化配置与多模型切换。 * 向量数据库集成:掌握 Pinecone、Weaviate 等向量数据库的前端调用方法,实现语义搜索、相似内容推荐等功能。 进阶实践技能 * 大模型微调适配:理解大模型微调原理,能够基于前端业务场景,将微调后的模型部署至前端环境,实现模型轻量化调用。 * 多模态交互开发:支持文本、图像、音频等多模态输入的前端处理,对接多模态大模型 API 实现智能交互。 * AI 性能优化:实现大模型请求的批量处理、缓存复用与增量更新,降低前端请求延迟与资源消耗。 实战代码示例 以下为基于 OpenAI API 实现的流式对话前端组件,使用 React 18 开发:

By Ne0inhk
《算法闯关指南:优选算法--前缀和》--29.和为k的子数组,30.和可被k整除的子数组

《算法闯关指南:优选算法--前缀和》--29.和为k的子数组,30.和可被k整除的子数组

🔥草莓熊Lotso:个人主页 ❄️个人专栏: 《C++知识分享》《Linux 入门到实践:零基础也能懂》 ✨生活是默默的坚持,毅力是永久的享受! 🎬 博主简介: 文章目录 * 前言: * 29. 和为k的子数组 * 解法(前缀和+哈希表): * 算法思路: * C++算法代码: * 算法总结&&笔记展示: * 30. 和可被k整除的子数组 * 解法(前缀和+哈希表): * 前置知识补充: * 算法思路: * C++算法代码: * 算法总结&&笔记展示: * 结尾: 前言: 聚焦算法题实战,系统讲解三大核心板块:优选算法:剖析动态规划、二分法等高效策略,学会寻找“最优解”。 递归与回溯:掌握问题分解与状态回退,攻克组合、

By Ne0inhk