【C++初阶】—— 类和对象 (下)

📝个人主页🌹:EterNity_TiMe_
⏩收录专栏⏪:C++ “ 登神长阶 ”
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述

类和对象

  • 1. 运算符重载
    • 运算符重载
    • 赋值运算符重载
    • 前置++和后置++重载
  • 2. 成员函数的补充
  • 3. 初始化列表
    • 初始化列表的概念
    • 初始化列表的特征
    • explicit关键字
  • 4. static成员
  • 5. 友元
  • 6. 类的匿名对象
  • 7. 总结


前言:类的6个默认成员函数,我们了解三个,讲完剩下的成员函数,其实类和对象的大致内容已经结束,最后我们在了解一些C++类和对象的剩下的的细节,我们就正式结束类和对象

如果你还对前面三个默认成员函数不太了解,建议先阅读这篇博客
类的成员函数


1. 运算符重载

运算符重载

在一个自定义变量里,如果我们想实现对它的加减乘除,是无法直接使用的,因此C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数

关键字operator 后面接需要重载的运算符符号

函数原型: 返回值类型 operator操作符(参数列表)

举个例子:

// 重载 ==
bool operator==(const Date& d)
{return _year = d._year;&& _month = d._month;&& _day = d._day;
}

注意:

  • 重载操作符必须有一个自定义类型参数
  • 运算符重载定义在类外时不能访问类中的私有成员,因此重载成成员函数
  • 作为类成员函数重载时,成员函数的第一个参数为隐藏的this

赋值运算符重载

1. 关于赋值运算符重载:

  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回 *this

我们以下例子将使用日期类
例如:

class Date
{
public:Date(){}Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << ' ' << _month << ' ' << _day << endl;}Date& operator=(const Date& d){// 检查是否给直接赋值if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 5, 23);Date d2 = d1;// Date d2;// 实际上operator=的调用// d2.operator=(d1);d1.Print();d2.Print();return 0;
}

2. 赋值运算符只能重载成类的成员函数不能重载成全局函数

// 假设我们在类外面重载成全局函数
// 注意:在类外是没有 this 指针的
Date& operator=(Date& this, const Date& d)
{if (&this != &d){this._year = d._year;this._month = d._month;this._day = d._day;}return this;
}

我们将写好的代码拿去运行一下,我们发现无法编译
在这里插入图片描述

其实,赋值运算符比较特殊如果不显式实现,编译器会生成一个默认的。如果在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了

在这里插入图片描述


3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝

这里我们要格外注意:
系统默认生成一个默认赋值运算符重载它和之前的拷贝构造一样,发生的是浅拷贝,内置类型成员变量可以直接使用,而自定义类型成员变量需要我们自己调用对应类的赋值运算符重载


前置++和后置++重载

关于前置++和后置++:

  • 前置++:返回+1之后的结果
  • 后置++:是先使用后+1,因此需要返回+1之前的旧值

格式:

  • 因为前置++和后置++符号一样,我们为了要想正确完成重载,C++规定,后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
// 前置++
Date& operator++()
{_day += 1;return *this
}
// 后置++
Date& operator++(int)
{Date temp(*this);_day += 1;return temp;
}
// 前置--
Date& operator--()
{_day -= 1;return *this
}
// 后置--
Date& operator--(int)
{Date temp(*this);_day -= 1;return temp;
}

最后补充一点,关于运算符重载,并不是所有的运算符都需要重载,而是要根据自定义的类需要重载哪些运算符!

注意以下运算符不能重载:

  • .*
  • ::
  • sizeof
  • ?:
  • .

讲到这里类和对象的大致内容已经结束,剩下两个成员函数,我们简单了解一下


2. 成员函数的补充

const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改
在这里插入图片描述

例如:

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}void Print() const{cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{// 编译器会优先调用符合的函数,如果没有则会根据权限来调用// 本质是:权限能缩小,但是不能放大// 及非const对象可以调用const成员函数// 非const成员函数内可以调用其它的const成员函数Date d1(2024,5,23);d1.Print();const Date d2(2024,5,23);d2.Print();
}

取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成!

Date* operator&()
{return this ;
}const Date* operator&()const
{return this ;
}

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,我们能够修改别人获取的地址


3. 初始化列表

  • 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值
  • 对象中有了一个初始值,因此构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值

初始化列表的概念

初始化列表: 以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式

Date(int year, int month, int day): _year(year), _month(month){_day = day}// 函数体里面能够放数据
//Date(int year, int month, int day)
//{
//		_year = year;
//		_month = month;
//		_day = day;
//}

初始化列表的特征

使用初始化列表时注意:

  • 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  • 类中包含以下成员,必须放在初始化列表位置进行初始化:
    • 引用成员变量
    • const成员变量
    • 自定义类型成员(且该类没有默认构造函数时)

例如:

class A
{
public:A(int a):_a(a){}
private:int _a;
};class B
{
public:B(int a, int ref):_aobj(a),_ref(ref),_n(10){}
private:A _aobj; // 没有默认构造函数int& _ref; // 引用const int _n; // const
};
int main()
{B bb(1,2);A aa(1);
}

在这里插入图片描述

特征:
1. 尽量使用初始化列表初始化

因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先经过初始化列表初始化

2. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

3. 当用户没有显示传参初始化时,编译器会用用户定义的缺省值

public:B(int a):b(a) // b = 1;{}
private:int b = 1;

explicit关键字

构造函数不仅能构造和初始化对象,对于单个参数或除第一个参数无默认值,其余均有默认值的构造函数,还有隐式类型转换的作用,隐式类型转换是在编程中编译器自动进行的一种类型转换方式

class pxt
{
public:explicit pxt(int a = 0):_a(a){cout << "pxt(int a)" << endl;}~pxt(){cout << "~pxt()" << endl;}
private:int _a;
};
int main()
{pxt a1 = 2024;// 用一个整形变量给自定义类型对象赋值// 编译器会用2024构造一个无名对象,最后用无名对象给a1对象进行赋值// 正常情景是能赋值的,但是explicit修饰构造函数后,会禁止构造函数的隐式转换return 0;
}

关键字explicit修饰构造函数,将会禁止构造函数的隐式转换


4. static成员

static成员的概念

概念:

  • 声明为static的类成员称为类的静态成员,
    static修饰的成员变量,称之为静态成员变量,
    static修饰的成员函数,称之为静态成员函数
  • 静态成员变量一定要在类外进行初始化
class pxt
{
public:void Print(){cout << _a << endl;}
private:// 在类中声明static int _a;//如果是静态成员函数,则没有this指针
};
// 在类外定义
int pxt:: _a = 100;int main()
{pxt A;A.Print();
}

static成员的特征

特性:

  • 静态成员为所有类对象所共享,存放在静态区
  • 静态成员变量必须在类外定义,类中只是声明
  • 类静态成员可用 类名::静态成员 或者 对象.静态成员 来访问
  • 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  • 静态成员也是类的成员,受访问限定符的限制

5. 友元

友元: 提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

友元分为:

  • 友元函数
  • 友元类

友元函数

如果尝试去重载operator<<,我们发现没办法将operator<<重载成成员函数,因为函数的参数位置不一样,cout的输出流对象和隐含的this指针在抢占第一个参数的位置,重载operator>>同理
在这里插入图片描述
d << cout; -> d.operator<<(&d, cout); 不符合常规调用
因为成员函数第一个参数一定是隐藏的this,所以d必须放在<<的左侧

但是问题来了,如果我们写成全局函数,又无法使用私有的成员变量,这时友元的作用就凸显出来了!

友元函数: 可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字

例如:

class Date
{// 不声明友元,将无法调用私有成员friend ostream& operator<<(ostream& _out, const Date& d);
public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}//void operator<< (ostream& _out)//{//	_out << _year << " " << _month << " " << _day;//}
private:int _year; // 年int _month; // 月int _day; // 日
};ostream& operator<< (ostream& _out, const Date& d)
{_out << d._year << " " << d._month << " " << d._day;return _out;
}int main()
{Date d(2024,5,23);cout << d << endl;// d << cout;
}

关于友元函数有以下几点:

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员

友元类的特征:
友元关系是单向的,不具有交换性
友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元
友元关系不能继承,在继承位置再给大家详细介绍

关于友元关系的单向性我举个例子:

class A
{friend class B;
public:// ......
private:int _year;int _month;int _day;
};
class B
{
public:void test(int year, int month, int day){// 直接访问A类私有的成员变量// 但是A 不能访问B 中私有的成员变量_d._year = year;_d._month = month;_d._day = day;
}
private:int good;A _d;
};

B能直接访问A类私有的成员变量,但是A 不能访问B 中私有的成员变量


讲到友元类,我们再来介绍一下一个跟友元类有很大关系的内部类
内部类

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限

注意:内部类就是外部类的友元类,内部类能访问外部类中的所有成员,反之则不能!

class A
{
public:// ......A(int year = 2024, int month = 5, int day = 20): _year(year),_month(month),_day(day){}class B{public:void test(const A& _d){cout << _d._year << " " << _d._month << " " << _d._day << endl;}};
private:int _year;int _month;int _day;
};
int main()
{A::B b;b.test(A());
}

在这里插入图片描述
内部类的特征

特性:

  • 内部类可以定义在外部类的所有成员
  • 内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
  • sizeof(外部类)=外部类,和内部类没有任何关系

6. 类的匿名对象

class pxt
{
public:pxt(int a = 0):_a(a){cout << "pxt(int a)" << endl;}~pxt(){cout << "~pxt()" << endl;}
private:int _a;
};
int main()
{// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数// 匿名对象pxt();// 隐式类型转换pxt a1 = 2024;return 0;
}

在这里插入图片描述

  • 生命周期只有一行,会自动调用析构函数
  • 匿名对象的特点不用取名字

因此当我们只是想使用类中的某一个函数时,我们能创建匿名对象!


7. 总结

类和对象的所有内容已经了解完毕,类和对象在整个C++上都有举足轻重的作用,大家千万不要忽视,而类和对象的重点在四个成员函数上,下节我将学习C++的内存管理

谢谢大家支持本篇到这里就结束了,祝大家天天开心!
在这里插入图片描述


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

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

相关文章

Java中String类常用方法

写笔记记录自己的学习记录以及巩固细节 目录 1.String类的常用方法 1.1 字符串构造 1.2 String对象的比较 1.2.1 比较两个字符串是否相等 1.2.2 比较两个字符串的大小 1.3 字符串查找 1.4 字符串的转化 1.4.1 字符串转整数 1.4.2 字符串转数字 1.4.3 大小写的转换 1…

IT革命浪潮:技术革新如何改变我们的生活与工作

一、技术革新与行业应用 当前的IT行业正处于前所未有的技术革新阶段。其中&#xff0c;量子计算和虚拟现实是两项引人注目的技术。 量子计算&#xff1a;量子计算以其超越传统计算的潜力&#xff0c;正在逐步从理论走向实践。在材料科学、药物研发和气候模型等复杂计算领域&a…

利用kubeadm安装k8s集群 以及跟harbor私有仓库下载镜像

目录 环境准备 master&#xff08;2C/4G&#xff09; 192.168.88.3 docker、kubeadm、kubelet、kubectl、flannel node01&#xff08;2C/2G&#xff09; 192.168.88.4 docker、kubeadm、kubelet、kubectl、flannel node02&#xff08;…

Ansible自动化运维中的file文件模块模块应用详解

作者主页&#xff1a;点击&#xff01; Ansible专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年5月21日15点21分 &#x1f4af;趣站推荐&#x1f4af; 前些天发现了一个巨牛的&#x1f916;人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xf…

向npm发布自己写的vue组件,使用vite创建项目

向npm发布自己写的vue组件&#xff0c;使用vite创建项目 创建项目 pnpm create vite输入项目名称 由于我的组件是基于 ant-design-vue和vue的&#xff0c;需要解析.vue文件&#xff0c;我又安装了下面4个。 然后执行 pnpm i安装依赖 vite.config.ts import { defineC…

linux系统——ps命令的两种参数模式

ps命令后面接参数时&#xff0c;有“—”符号与无此符号&#xff0c;在具体实现功能上有很大区别 能够清晰表达进程之间层级关系

前端菜鸡,对于35+程序员失业这个事有点麻了

“经常看到30岁程序员失业的新闻&#xff0c;说实话&#xff0c;有点麻。目前程序员供求关系并未失衡&#xff0c;哪怕是最基础的前端或者后台、甚至事务型的岗位也是足够的。 事实上&#xff0c;现在一个开出的岗位要找到一位尽职尽责能顺利完成工作的程序员并不是一件那么容…

从零到一:手把手教你将项目部署上线-环境准备

部署步骤 引言1.Java环境配置2.ngnix安装好书推荐 引言 将自己的项目从本地开发环境顺利部署上线&#xff0c;是每个开发者必经的里程碑。今天&#xff0c;我们就从零开始&#xff0c;一步一步教你如何将手中的项目部署到线上&#xff0c;让全世界见证你的创造力。 首先&#x…

必应bing国内广告开户首充和开户费是多少?

微软必应Bing作为国内领先的搜索引擎之一&#xff0c;其广告平台凭借其精准的投放、高效的数据分析和广泛的用户覆盖&#xff0c;已成为众多企业的首选。 根据最新政策&#xff0c;2024年必应Bing国内广告开户预充值金额设定为1万元人民币起。这一调整旨在确保广告主在账户初始…

计算机操作系统总结(1)

1操作系统的概念&#xff08;定义&#xff09;功能和目标 (1)什么是操作系统&#xff1f; &#xff08;2&#xff09;操作系统的功能和目标—作为系统资源的管理者 &#xff08;3&#xff09;操作系统的功能和目标—向上层提供方便易用的服务 &#xff08;4&#xff09;操作系…

vue从入门到精通(四):MVVM模型

一,MVVM MVVM&#xff08;Model–view–viewmodel&#xff09;是一种软件架构模式。MVVM有助于将图形用户界面的开发与业务逻辑或后端逻辑&#xff08;数据模型&#xff09;的开发分离开来。详见MVVM 二,Vue中的MVVM Vue虽然没有完全遵循 MVVM 模型&#xff0c;但是 Vue 的设…

Xcode代码格式化SwiftFormat安装使用,以及不生效问题

官方仓库地址&#xff1a;GitHub - nicklockwood/SwiftFormat: A command-line tool and Xcode Extension for formatting Swift code 安装SwiftFormat 有两种安装方式&#xff0c;两种方式都可以&#xff0c;二选一即可&#xff1a; 第一种使用brew&#xff0c;如果已经使用…

启动docker报错:Failed to listen on Docker Socket for the API.

说明&#xff1a; 1、安装部署docker完成后&#xff0c;启动docker报错&#xff1a;Failed to listen on Docker Socket for the API&#xff0c;如下图所示&#xff1a; 2、将SocketGroupdocker更改成&#xff1a;SocketGrouproot即可 一、解决方法&#xff1a; 1、执行命令…

文件传输服务应用1——java集成smb2/3实现文件共享方案详细教程和windows共享服务使用配置

在实际项目开发过程中&#xff0c;读取网络资源或者局域网内主机的文件是必要的操作和需求。而FTP&#xff08;文件传输协议&#xff09;和SMB&#xff08;服务器消息块&#xff09;是两种最为常见的文件传输协议。它们各自在文件传输领域拥有独特的优势和特点&#xff0c;但同…

MySQL进阶 日志结尾以及8.0新特性

日志结尾 前面我们聊了mysql的undo日志,redo日志,binlog等等,也从一条update语句来分析了一下日志的执行思路以及版本控制是怎么回事,四大特性是怎么实现的等等 今天我们来说说最后一个错误日志 其实用处不大 因为对我们开发人员来说基本上是没有权限来查看错误日志的 一般…

vlan综合实验

一&#xff0c;实验拓扑&#xff1a; 二&#xff0c;实验需求&#xff1a; 1、pc1和pc3所在接口为access;属于vlan 2;P可以骄问pcepces不的一段;其中pc2可以访问pc4/pc5/pc6; 2、pc1/pc3与pc2/pc4/pc5/pc6不在同一网段; 3、所有pc通过DHCP获取Ip地址&#xff0c;且pc1/pc3可以…

BGP选路规则实验

实验拓扑及要求如下 注意&#xff1a; 在完成要求时&#xff0c;默认区域内IGP搭建完成&#xff0c;IBGP和EBGP的对等体关系建立完成 结果演示如下 IBGP内部搭建&#xff1a;使用OSPF IBGP与EBGP对等体建立 要求一&#xff1a;PreVal策略 PV属性默认值为0&#xff0c;规则是…

Vue学习穿梭框Transfer组件

Vue学习Transfer组件 一、前言1、案例一2、案例二 一、前言 在 Vue 3 中使用 el-transfer 组件可以帮助你实现数据的穿梭功能&#xff0c;让用户可以将数据从一个列表转移到另一个列表。下面是一个简单示例&#xff0c;演示如何在 Vue 3 中使用 el-transfer 组件&#xff1a; …

【加密与解密(第四版)】第二十二章笔记

第二十二章 补丁技术 补丁&#xff1a;文件补丁&#xff08;修改文件本身的某个数据&#xff09;和内存补丁&#xff08;对正在运行的程序的数据进行修改&#xff0c;以达到某种效果&#xff09; 22.1 文件补丁 文件补丁直接修改可执行文件或某功能模块的二进制代码。 22.2 内…

小而美:两步完成从源码到应用的极简交付

作者&#xff1a;花三&#xff08;王俊&#xff09; Serverless 应用引擎 SAE 是阿里云推出的一款零代码改造、极简易用、自适应弹性的容器化应用托管平台&#xff0c;面市以来为几万家企业客户提供服务&#xff0c;运行稳定&#xff0c;广受好评。 SAE 的出现解决了众多企业…