【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)

news2024/11/15 19:54:35

🌟🌟作者主页: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++的后续内容,以及实现对象的相关功能有很大帮助。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

监控系列之-prometheus部署说明

一、Prometheus介绍 Prometheus是一款开源的监控系统&#xff0c;主要用于收集、存储和查询时间序列数据&#xff0c;以便于对系统进行监控和分析Prometheus的架构由四个主要组件组成&#xff1a; 1、Prometheus Server &#xff1a;Prometheus Server是Prometheus的核心组件&a…

带你0到1之QT编程:十二、视图宝典,点通views的任督二脉

此为QT编程的第十二谈&#xff01;关注我&#xff0c;带你快速学习QT编程的学习路线&#xff01; 每一篇的技术点都是很很重要&#xff01;很重要&#xff01;很重要&#xff01;但不冗余&#xff01; 我们通常采取总-分-总和生活化的讲解方式来阐述一个知识点&#xff01; …

text2sql(NL2Sql)综述《The Dawn of Natural Language to SQL: Are We Fully Ready?》

《The Dawn of Natural Language to SQL: Are We Fully Ready?》(github)出自2024年6月的NL2SQL(Natural language to SQL )综述论文。这篇论文尝试回答如下三个问题&#xff1a; 问题1:NL2SQL的现状是什么&#xff1f;(Q1:Where Are we Now?) 论文图1总结了近20年NL2SQL方法…

【移动端】菜单的自动展开与收回

前言 为了满足手机上菜单栏随用户移动&#xff0c;菜单的自动展示与隐藏&#xff0c;特此记录 基本原理 实现逻辑 window.addEventListener(‘scroll’, debouncedScrollHandler) – 监听文档视图滚动事件 document.querySelector(‘.header’) – 选择器匹配元素 创建show和h…

论文速递!Auto-CNN-LSTM!新的锂离子电池(LIB)剩余寿命预测方法

论文标题&#xff1a;A Data-Driven Auto-CNN-LSTM Prediction Model for Lithium-Ion Battery Remaining Useful Life 期刊信息&#xff1a;IEEE TII (中科院1区, JCR Q1, IF11.7) 引用&#xff1a;Ren L, Dong J, Wang X, et al. A data-driven auto-CNN-LSTM prediction m…

JavaScript web API part3

web API DOM 日期对象 > 得到当前系统的时间 new这个操作就是实例化 语法 const date new Date() or const date new Date(2004-11-3 08:00:00) 可以指定时间 > 可应用于通过系统时间和指定时间实现倒计时的操作 //得到当前时间const date new Date()console.lo…

多维时序 | Matlab基于BO-LSSVM贝叶斯优化最小二乘支持向量机数据多变量时间序列预测

多维时序 | Matlab基于BO-LSSVM贝叶斯优化最小二乘支持向量机数据多变量时间序列预测 目录 多维时序 | Matlab基于BO-LSSVM贝叶斯优化最小二乘支持向量机数据多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于BO-LSSVM贝叶斯优化最小二乘支…

Vue介绍、窗体内操作、窗体间操作学习

系列文章目录 第一章 基础知识、数据类型学习 第二章 万年历项目 第三章 代码逻辑训练习题 第四章 方法、数组学习 第五章 图书管理系统项目 第六章 面向对象编程&#xff1a;封装、继承、多态学习 第七章 封装继承多态习题 第八章 常用类、包装类、异常处理机制学习 第九章 集…

树莓派5上手

1 安装系统 Raspberry Pi OS 是基于 Debian 的免费操作系统&#xff0c;针对 Raspberry Pi 硬件进行了优化。Raspberry Pi OS 支持超过 35,000 个 Debian 软件包。树莓派 5 可以安装各种系统&#xff0c;但是如果对于系统没有特殊的要求&#xff0c;还是安装 Raspberry Pi OS …

【MySQL】MySQL索引与事务的透析——(超详解)

前言 &#x1f31f;&#x1f31f;本期讲解关于MySQL索引事务&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;【MySQL】MySQL表的增删改查&#xff08;进阶篇&#xff09;——之查询操作&#xff08;超级详解&#xff09;-CSDN博客 &#x1f308;感…

CSP-CCF★★★201903-2二十四点★★★

目录 一、问题描述 二、解答 方法一&#xff1a;穷举法&#xff08;只列举了一部分&#xff09; 方法二&#xff1a;中缀表达式直接求值&#xff0c;两个栈&#xff0c;一个存放数值&#xff0c;一个存放符号 方法三&#xff1a;将中缀表达式转换为后缀来计算注意&#xff…

台风,也称为热带气旋,是一种在热带海洋上形成的强烈风暴系统。台风的形成需要满足以下几个条件:

台风&#xff0c;也称为热带气旋&#xff0c;是一种在热带海洋上形成的强烈风暴系统。台风的形成需要满足以下几个条件&#xff1a; 1. **温暖的海水**&#xff1a;台风通常在海面温度至少达到26.5C&#xff08;79.7F&#xff09;的海域形成&#xff0c;因为温暖的海水能够提供…

八股(8)——Spring,SpringBoot

八股&#xff08;8&#xff09;——Spring&#xff0c;SpringBoot 基础1.Spring 是什么&#xff1f;特性&#xff1f;有哪些模块&#xff1f;Spring 有哪些特性呢&#xff1f; 2.Spring 有哪些模块呢&#xff1f;3.Spring 有哪些常用注解呢&#xff1f;Web 开发方面有哪些注解呢…

利用模糊综合评价法进行数值评分计算——算法过程

1、‌模糊综合评价法概述 ‌模糊综合评价法是一种基于模糊数学的综合评价方法&#xff0c;它通过模糊数学的隶属度理论将定性评价转化为定量评价&#xff0c;适用于解决复杂、难以量化的问题。该方法具有结果清晰、系统性强的特点&#xff0c;能够处理多种因素制约下的综合评价…

热门数据恢复软件大盘点

现在大家的数据都喜欢存放在一些电子设备里保存吧。这样既方便存放&#xff0c;也方便我们查找。但是这些设备可能因为病毒、误删除等原因造成数据的丢失。这篇文章我将介绍几款类似易我数据恢复软件的数据恢复工具&#xff0c;减少为数据丢失给我们造成损失。 1.FOXIT数据恢复…

vue国际化

前言 现在的大公司都走国际化路线&#xff0c;我们应用程序也不例外。今天就在 Vue3 项目中整一个比较简单的国际化 背景 之前搞国际化的时候&#xff0c;也搜索了很多帖子&#xff0c;但是没有一个可以完整的实现。今天有空搞了一版&#xff0c;大家有什么问题欢迎留言探讨…

Java设计模式—面向对象设计原则(五) ----->迪米特法则(DP) (完整详解,附有代码+案例)

文章目录 3.5 迪米特法则(DP)3.5.1 概述3.5.2 案例 3.5 迪米特法则(DP) 迪米特法则&#xff1a;Demeter Principle&#xff0c;简称DP 3.5.1 概述 只和你的直接朋友交谈&#xff0c;不跟“陌生人”说话&#xff08;Talk only to your immediate friends and not to stranger…

【CSS in Depth 2 精译_031】5.3 Grid 网格布局的两种替代语法

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…

Linux服务器配合Xshell+Tensorboard实现深度学习训练过程可视化

问题背景&#xff1a; 在深度学习领域&#xff0c;监控模型的训练过程是非常重要的。TensorBoard 是 TensorFlow 提供的一个可视化工具&#xff0c;可以帮助我们直观地理解模型的训练和验证过程。我们一般在 Windows 系统只需要在自己的浏览器输入localhost:6006就可以观察训练…

[Linux]:进程间通信(上)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;Linux学习 贝蒂的主页&#xff1a;Betty’s blog 1. 进程间通信介绍 1.1 进程间通信的概念 进程间通信简称IPC&#xff08;In…