【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
🌟🌟作者主页:ephemerals__
🌟🌟所属专栏:C++
目录
前言
一、取地址运算符重载
1. const修饰成员函数
2. 取地址运算符重载
二、深究构造函数
三、类型转换
四、static修饰成员
1. static修饰成员变量
2. static修饰成员函数
3. 小练习
五、友元
六、内部类
七、匿名对象
总结
前言
之前我们学习了类中的一些默认成员函数:构造函数、析构函数、拷贝构造函数、赋值重载。今天,我们接着学习剩下的取地址运算符重载以及其他关于类和对象的知识。
一、取地址运算符重载
取地址运算符重载分为两种:普通对象的取地址重载和const对象取地址重载。为了说明这两种取地址重载的区别,我们首先引入一个概念:const修饰成员函数。
1. const修饰成员函数
在c++中,成员函数可以被const修饰,修饰时要将const写在成员函数参数列表的后面。例如:
void fun() const
{}
const修饰成员函数的本质是修饰this指针指向的内容,它的作用是防止该函数内部对成员变量的值进行修改。
对于一个普通成员函数,const对象是无法调用的,因为const对象的成员变量不允许被修改;而当成员函数被const修饰时,就确保了函数内部不会修改成员变量的值,const对象就可以调用该函数。
2. 取地址运算符重载
普通对象的取地址重载用于返回普通对象的地址;而const对象的取地址重载用于返回const对象的地址。两种重载函数的区别是:前者没有被const修饰,后者被const修饰。这就使得两个函数构成了重载,便于不同的对象调用。我们简单实现一下这两个函数:
class MyClass
{
public://构造函数MyClass(int a = 10, int b = 20){_a = a;_b = b;}//取地址重载MyClass* operator&(){return this;}const MyClass* operator&() const//为保证类型匹配,返回值也要用const修饰{return this;}
private:int _a;int _b;
};
一般情况下,编译器自动生成的取地址重载函数我们就可以直接使用,不需要显示实现了。当我们不希望使用者能够获取到对象的地址时,可以显示实现取地址重载,并将空指针或者野指针作为返回值。
二、深究构造函数
之前我们已经学习了构造函数的特点、使用规则等知识,不过构造函数的知识还不止这些,接下来我们对之前构造函数的内容进行一些补充。
之前我们在实现构造函数时,都是在函数体内部对成员变量赋初值,实际上,对成员变量进行初始化的方式还有一种:初始化列表。它位于构造函数的参数列表之后,函数体大括号之前。它的使用方式是以冒号开始,将需要被初始化的成员以逗号分隔,成员之后写一个放在括号当中的值或者表达式,用于初始化成员。例如:
//构造函数
MyClass(int a = 10, int b = 20):_a(a)//将a的值给成员变量_a, _b(b)//将b的值给成员变量_b
{//...
}
需要注意的是:
1. 每一个成员变量在初始化列表中只能出现一次。
2. 初始化列表初始化的顺序与成员在类中的声明顺序一致,而与列表中的成员顺序无关。
3. 一下三种变量必须在初始化列表中进行初始化,否则会编译报错:引用类型的成员变量、const成员变量、不存在默认构造的类类型成员变量。
这里的 “ 必须在初始化列表中进行初始化 ” 并不是指我们一定要将该变量显示写在初始化列表中,我们也可以使用如下方式:
class MyClass
{
public://构造函数MyClass(int a = 10, int b = 20):_a(a), _b(b){//...}
private:int _a;int _b;const int _c = 1;
};
可以看到,对于const成员“_c”,我们并没有显示在初始化列表中对其进行初始化,而是在其声明时为其赋了一个缺省值(初值)。这是c++11规定的语法,该初值是给初始化列表的,当初始化列表当中没有显示对一个成员进行初始化时,如果声明时有缺省值,则会用这个值进行初始化(本质也是通过初始化列表初始化,只不过并没有显示写出),所以程序并不会发生报错。当然,对于普通成员,我们也可以在声明时赋缺省值,但是相比显示写在初始化列表当中,会有一些效率的损耗。
注:对类类型的成员变量通过初始化列表进行初始化时,本质也是在调用它的构造函数。
如果我们既没有显示地在初始化列表对成员进行初始化,也没有在声明时赋缺省值,那么对于内置类型的成员,当对象被创建时编译器一般不会对其初始化;对于自定义类型的成员,对象被创建时就会调用它的默认构造函数,如果没有默认构造函数,就会发生报错。
接下来,我们总结一下成员变量通过初始化列表进行初始化的逻辑:
三、类型转换
首先来看一段代码:
class MyClass
{
public://构造函数MyClass(int a = 10):_a(a){}void Print() const {cout << _a << endl;}
private:int _a;
};int main()
{MyClass m = 1;m.Print();return 0;
}
上述程序中,我们创建了一个MyClass类对象m,并且将其初始化为1。我们都知道,它的本质是在调用构造函数,不过它的运行过程并不是这么简单。在 MyClass m = 1 语句中,等号右边的 “ 1 ”是整形,而“ m ”是MyClass类型,这个过程中就需要发生类型转换。程序首先会调用构造函数,将“ 1 ”构造为MyClass类型的一个临时对象,然后将该临时对象拷贝构造给m。对于这种调用构造函数+调用拷贝构造的情况,编译器会将其优化为直接调用构造函数,所以我们无法感受到类型转换的过程,但它的确是存在的。当我们在构造函数之前加上关键字“ explicit ”之后,就无法调用该构造函数进行隐式类型转换。当然,如果有合适的构造函数,类与类之间也可以发生类型转换。
对于有多个参数的情况,也可以进行类型转换:
class MyClass
{
public://构造函数MyClass(int a = 10, int c = 20):_a(a),_c(c){}void Print() const {cout << _a << endl;cout << _c << endl;}
private:int _a;char _c;
};int main()
{MyClass m = { 1,'w' };//大括号赋值的写法C++11以后才支持m.Print();return 0;
}
四、static修饰成员
在C++当中,static可以修饰成员变量和成员函数,它们在面向对象编程中有着很重要的作用。
1. static修饰成员变量
用static修饰的成员变量叫做静态成员变量。静态成员变量必须要在类外进行初始化,而不是类中(因为在类中给的初值是给初始化列表的,而静态成员变量不走初始化列表)。例如:
class MyClass
{
public://...
private:static int _m;//类里面声明
};int MyClass::_m = 0;//类外进行初始化
注意:静态成员变量存储在静态区中,不属于任何一个对象,而是被所有对象所共享的,使用对象或者类的作用域限定符就可以访问到静态成员变量。当然,既然是成员变量,也会受到 public / private / protected 的限制。接下来,我们尝试通过不同方式访问静态成员变量:
#include <iostream>
using namespace std;class MyClass
{
public://...static int _m;
};int MyClass::_m = 5;int main()
{cout << MyClass::_m << endl;//使用作用域限定符访问MyClass a;cout << a._m << endl;//使用对象访问return 0;
}
运行结果:
由于 _m 是公有成员,所以我们直接访问到了该变量。当静态成员变量是私有成员时,该如何访问呢?这就需要静态成员函数了。
2. static修饰成员函数
用static修饰的成员函数称之为静态成员函数,静态成员函数与普通成员函数的显著区别是:它不存在this指针。
由于静态成员函数不存在this指针,所以它也就无法访问到普通成员变量,只能访问静态成员变量。当然,如果一个成员函数是非静态的,它也可以访问静态成员变量。接下来我们用静态成员函数来访问私有的静态成员变量:
#include <iostream>
using namespace std;class MyClass
{
public:static int func(){return _m;}
private:static int _m;
};int MyClass::_m = 5;int main()
{cout << MyClass::func() << endl;//通过作用域限定符调用函数MyClass a;cout << a.func() << endl;//通过对象调用函数return 0;
}
运行结果:
3. 小练习
接下来我们运用static修饰成员的知识,尝试做一个小练习:计算1+2+3+...+n的值,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
思路分析:要计算1+2+3+...+n的值,习惯的思路是循环累加或者使用等差数列求和公式,但是由于禁止使用乘除法和循环语句,这两种方法就行不通了。那么递归能否解决呢?由于递归一定要有限制条件,而if...else语句也被禁用了,所以递归也是不行的。那该怎么办呢?我们都知道,静态成员变量为所有对象所公有,那么我们就可以定义一个静态成员变量x,每当一个对象被创建出时,x的值就+1。这样,我们创建出n个对象,并将每一次得到的x值累加到另一个变量中,就可以算出最终结果了。
话不多说,我们尝试实现:
class MyClass
{
public://在构造函数中操作静态变量,使得对象被创建时就会累加MyClass(){x++;sum += x;}//获取sum的值static int GetNum(){return sum;}
private:static int x;static int sum;
};//初始化
int MyClass::x = 0;
int MyClass::sum = 0;//求和函数
int Sum(int n)
{//用new关键字创建n个对象MyClass* arr = new MyClass[n];//返回累加后的值return MyClass::GetNum();
}
我们测试一下程序的正确性:
int main()
{int n = 0;cout << "请输入n值";cin >> n;cout << "结果为:" << Sum(n) << endl;return 0;
}
运行结果:
五、友元
当类中的成员被设置为私有,外部无法访问到时,友元就可以突破这种封装,使得外部可以访问这些私有成员。友元可以分为友元函数和友元类,我们需要使用友元时,在函数或类的声明之前加上关键字 friend ,并将其放在另一个类(宿主类)当中。此时该函数或类就成为了宿主类的友元。
友元函数和友元类的特点如下:
1. 友元函数只是一种声明,并不是说一个函数成为了一个类的友元,他就是该类的成员函数了。
2. 友元函数可以在类的任意地方声明,并不受public等限定符的限制。
3. 一个函数可以称为多个类的友元。
4. 友元类中的成员函数都可以访问宿主类的成员,不受限定符限制。
5. 友元类的关系是单向且不可传递的,比如:A是B的友元,但不意味着B是A的友元;A是B的友元,B是C的友元,并不意味着A是C的友元。
接下来我们尝试使用一下友元:
#include <iostream>
using namespace std;class A
{
public:friend void fun1();//函数fun1称为类A的友元friend class B;//类B成为类A的友元
private:int _a = 1;
protected:int _b = 2;
};void fun1()
{A a;cout << a._a << endl;cout << a._b << endl;
}class B
{
public:void fun2(){A a;cout << a._a << endl;cout << a._b << endl;}
};int main()
{fun1();cout << endl;B b;b.fun2();return 0;
}
运行结果:
不难看出,将函数或类声明为友元后,就可以访问宿主类的私有或保护成员了。友元有时虽然提供了便利,但是它明显是破坏了类的封装性,不符合“高类聚,低耦合”的设计原则,所以实际开发中不宜多用。
六、内部类
如果一个类A定义在另一个类B当中,那么类A就成为了类B的内部类。内部类与全局定义的类相比,它受到外部类的类域和访问限定符限制,并且默认是外部类的友元类。这里要注意:内部类是一个类定义在令一个类当中,而不是将对象作为一个类的成员,不要将两者混淆。
内部类的本质也是一种封装的体现,当我们需要让一个类B仅供类A使用,那么就可以考虑让B成为A的内部类。
接下来我们定义一个内部类:
#include <iostream>
using namespace std;class A
{
public:class B//此时B是A的内部类{public:void Print(const A& a){cout << a._m << endl;cout << a._n << endl;}};
private:int _m = 3;int _n = 5;
};int main()
{A a;A::B b;//受到类域限制,声明时要限定类域b.Print(a);return 0;
}
运行结果:
七、匿名对象
顾名思义,匿名对象就是没有实际名字的对象,它的定义方法是:
MyClass(10);//构造函数传参
MyClass();//不传参
注意:匿名对象的生命周期只有当前一行,当程序运行到下一行时,该对象就被销毁。我们来验证一下:
#include <iostream>
using namespace std;class MyClass
{
public:MyClass(int a = 1):_a(a){cout << "调用构造函数" << endl;}~MyClass(){cout << "调用析构函数" << endl;}
private:int _a;
};int main()
{MyClass();cout << "hehe" << endl;return 0;
}
运行结果:
可以看到,程序在打印hehe之前,就已经调用了析构函数,意味着匿名对象已经被销毁。
当我们需要创建一个临时对象,并且只使用一次时,就可以考虑创建匿名对象。相比普通对象,它能够很大限度地简化代码。
总结
今天我们学习了类和对象相关的新概念和知识,例如:取地址重载、static修饰成员、友元、内部类等,它们对于我们深入学习并理解c++的后续内容,以及实现对象的相关功能有很大帮助。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤
相关文章:

【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
🌟🌟作者主页:ephemerals__ 🌟🌟所属专栏:C 目录 前言 一、取地址运算符重载 1. const修饰成员函数 2. 取地址运算符重载 二、深究构造函数 三、类型转换 四、static修饰成员 1. static修饰成员变…...

Apache POI 学习
Apache POI 学习 1. 引言2. 环境搭建MavenGradle 3. 基础概念4. 基本操作4.1 创建 Excel 文件4.2 读取 Excel 文件 5. 进阶操作5.1 设置单元格样式5.2 数据验证5.3 图表创建5.4 合并单元格5.5 居中对齐5.6 设置边框和字体颜色 6. 性能优化7. 总结 1. 引言 Apache POI 是一个用…...

福建科立讯通信 指挥调度管理平台 SQL注入漏洞
北峰通信-福建科立讯通信 指挥调度管理平台 SQL注入漏洞 厂商域名和信息收集 域名: 工具sqlmap python sqlmap.py -u "http://ip:端口/api/client/down_file.php?uuid1" --batch 数据包 GET /api/client/down_file.php?uuid1%27%20AND%20(SELECT%20…...

4.qml单例模式
这里写目录标题 js文件单例模式qml文件单例模式 js文件单例模式 直接添加一个js文件到qml中 修改内容 TestA.qml import QtQuick 2.0 import QtQuick.Controls 2.12 import "./MyWork.js" as MWItem {Row{TextField {onEditingFinished: {MW.setA(text)}}Button…...

CACTI 0.8.7 迁移并升级到 1.2.7记录
升级前后环境 升级前: CactiEZ 中文版 V10 升级后: Ubuntu 2204 Cacti 1.2.7 升级原因:风险漏洞太多,升不尽,补不完. 升级流程 Created with Raphal 2.3.0 开始 DST:安装Ububtu/Mariadb/apache/php SRC:备份 DB/RRA 数据导入 结束 Cacti 依赖包 注意:UBUNTU下有些包,它非另外…...

OrionX vGPU 研发测试场景下最佳实践之Jupyter模式
在上周的文章中,我们讲述了OrionX vGPU研发测试场景下最佳实践之SSH模式,今天,让我们走进 Jupyter模式下的最佳实践。 • Jupyter模式:Jupyter是最近几年算法人员使用比较多的一种工具,很多企业已经将其改造集成开发工…...

国风编曲:了解国风 民族调式 五声音阶 作/编曲思路 变化音 六声、七声调式
中国风 以流行为基础加入中国特色乐器、调式、和声融为一体的风格 如:青花瓷、菊花台、绝代风华、江南等等等等 省流:中国风=流行民族乐 两者结合,民族元素越多越中国风 流行民族/摇滚民族/电子民族 注意:中国风≠…...

HTTP 响应状态码详解
HTTP状态码详解:HTTP状态码,是用以表示WEB服务器 HTTP响应状态的3位数字代码 小技巧: CtrlF 快速查找 Http状态码状态码含义100客户端应当继续发送请求。这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当…...

在服务器上开Juypter Lab教程(远程访问)
在服务器上开Juypter Lab教程(远程访问) 文章目录 在服务器上开Juypter Lab教程(远程访问)一、安装anaconda1、安装anaconda2、提权限3、运行4、同意协议5、安装6、是否要自动初始化 conda7、结束8、检查 二、Anaconda安装Pytorch…...

【硬件模块】SHT20温湿度传感器
SHT20是一个用IIC通信的温湿度传感器。我们知道这个就可以了。 它支持的电压范围是2.1~3.6V,推荐是3V,所以如果我们的MCU是5V的,那么就得转个电压才能用了。 IIC常见的速率有100k,400k,而SHT20是支持400k的(…...

Redhat 8,9系(复刻系列) 一键部署Oracle23ai rpm
Oracle23ai前言 Oracle Database 23ai Free 让您可以充分体验 Oracle Database 的能力,世界各地的企业都依赖它来处理关键任务工作负载。 Oracle Database Free 的资源限制为 2 个 CPU(前台进程)、2 GB 的 RAM 和 12 GB 的磁盘用户数据。该软件包不仅易于使用,还可轻松下载…...

SIPp uac.xml 之我见
https://sipp.sourceforge.net/doc/uac.xml.html 这个 uac.xml 有没有问题呢? 有! 问题之一是: <recv response"200" rtd"true" rrs"true"> 要加 rrs, 仔细看注释就能看到 问题之二是࿱…...

引领智能家居新风尚,WTN6040F门铃解决方案——让家的呼唤更动听
在追求高效与便捷的智能家居时代,每一个细节都承载着我们对美好生活的向往。WTN6040F,作为一款专为现代家庭设计的低成本、高性能门铃解决方案,正以其独特的魅力,悄然改变着我们的居家生活体验。 芯片功能特点: 1.2.4…...

Android 蓝牙服务启动
蓝牙是Android设备中非常常见的一个feature,设备厂家可以用BT来做RC、连接音箱、设备本身做Sink等常见功能。如果一些设备不需要BT功能,Android也可以通过配置来disable此模块,方便厂家为自己的设备做客制化。APP操作设备的蓝牙功能ÿ…...

【安全系列--处理挖矿】
现象:我们云上waf提示有台服务器存在挖矿行为 解决思路: 1、查看服务器的进程情况 top发现服务的CPU使用率非常高 2、使用性能分析工具perf查看占用的cpu进程 sudo apt install linux-tools-common发现一些kernel进程存在异常 3、使用find查一下这…...

SpringBoot集成Thymeleaf模板引擎,为什么使用(详细介绍)
学习本技术第一件事:你为什么要使用,解决什么问题的? 1.为什么使用(使用背景)? 首先应用场景是单体项目,如果是前后端分离就不用关注这个了,因为单体项目你前后端都是写在一个项目…...

Docker突然宣布:涨价80%
从11月15日起,Docker的付费订阅中Pro和Team的价格都将大幅上调:Pro从原来的5美元每月激增到9美元每月,直接涨了80%;而Team也从之前的9美元每月来到15美元每月,涨价66.7%。只有Business保持此前的24美元每月不变。 同时…...

工厂方法模式和抽象工厂模式
工厂方法模式 一个工厂只能创建一种产品 工厂方法模式的结构 工厂方法模式包含以下4个角色 Product(抽象产品) ConcreteProduct(具体产品) Factory(抽象工厂) ConcreteFactory(具体工厂…...

【星海出品】go语言环境兼install
官网 https://golang.google.cn/dl/ go的安装包下载地址 https://go.dev/dl/ set GO111MODULEon //是否以Go modules的模式运行项目 auto,on,off set GOARCHamd64 //目标可执行程序操作系统构架 包括 386,amd64,arm set GOBIN //项目的第三方可执行文件目…...

Spring 源码解读:自定义实现BeanPostProcessor的扩展点
引言 在Spring的生命周期管理中,BeanPostProcessor是一个非常重要的扩展点。它允许开发者在Bean初始化的前后插入自定义的逻辑,从而实现更灵活的Bean管理。BeanPostProcessor是Spring框架中用于对Bean实例进行修改的机制之一。通过实现该接口࿰…...

Spring Boot-分布式系统问题
Spring Boot 在分布式系统中的常见问题及解决方案 随着互联网的发展,系统规模和复杂度越来越大,分布式系统成为应对高并发、大数据量场景的重要架构选择。Spring Boot 作为一种轻量级的开发框架,广泛应用于构建微服务和分布式系统中。然而&a…...

面试题总结(三) -- 内存管理篇
面试题总结(三) – 内存管理篇 文章目录 面试题总结(三) -- 内存管理篇<1> C 中堆内存和栈内存的区别是什么?<2> 如何在 C 中手动管理内存(new/delete 操作符)?<3> C 中内存泄漏的原因和避免方法<4> 谈谈…...

Qt 定时器-定时备份
定时备份 在Qt 中,可以使用QTimer类来实现定时备份功能。以下是一个示例代码,每隔一段时间自动执行备份操作: #include <QTimer>QTimer timer; int backupInterval 24 * 60 * 60 * 1000;//备份间隔为24小时connect(&timer, &…...

天融信把桌面explorer.exe删了,导致开机之后无windows桌面,只能看到鼠标解决方法
win10开机进入桌面,发现桌面无了,但是可以ctrlaltdelete调出任务管理器 用管理员权限打开cmd,输入: sfc /scanfilec:\windowslexplorer.exe 在运行C:\windows\Explorer.exe;可以进入桌面,但是隔离几秒钟…...

视频分割操作教程
1、打开剪映 2、点击开始创作上面的“”,选择视频,点击添加按钮,导入一个视频素材到剪映 3、滑动视频,让视频竖线到合适位置 4、点击视频,出现白色边框 5、点击工具栏“分割”,然后点击需要删除的视频部分 …...

唯品会大数据面试题及参考答案(3万字长文)
synchronized 和 volatile 的区别 synchronized是 Java 中的关键字,用于实现同步机制,确保在同一时刻只有一个线程可以访问被它修饰的代码块或方法。volatile也是 Java 中的关键字,主要用于保证变量的可见性。 功能方面: synchronized可以保证原子性、可见性和有序性。它通…...

使用容器技术快速入门MinIO
使用容器技术快速入门MinIO 使用容器技术(docker或者podman)快速部署一个单节点单磁盘 MinIO 服务器,用于对MinIO对象存储及其兼容 S3 的 API 层进行早期的开发和评估。 1. 准备工作 机器已经安装了 Podman 或者 Docker 。 对用于持久卷的…...

ros2教程(一):使用python和C++发布摄像头原始图像和压缩图像
1. 使用python发布图像 在ROS 2中,可以通过使用rclpy库来发布压缩图像和原始图像。发布原始图像可以使用sensor_msgs.msg.Image消息类型,压缩图像则使用sensor_msgs.msg.CompressedImage消息类型。 #!/usr/bin/env python3# function: usbcam publish r…...

【自动化测试】UI自动化的分类、如何选择合适的自动化测试工具以及其中appium的设计理念、引擎和引擎如何工作
引言 UI自动化测试主要针对软件的用户界面进行测试,以确保用户界面元素的交互和功能符合预期 文章目录 引言一、UI自动化的分类1.1 基于代码的自动化测试1.2 基于录制/回放的自动化测试1.3 基于框架的自动化测试1.4 按测试对象分类1.5 按测试层次分类1.6 按测试执行…...

深入理解Python中的“_,”:一个实用的语法特性
在Python编程中,你可能经常会看到一个特殊的标识符“_”。这个符号在Python中有多种用途,其具体含义依上下文而定。本文将探讨其中一种常见用法——作为一个临时性的占位符——并解释它在实际编程中的实用性和应用场景。 1. “_”作为占位符 在Python中…...