【C++】类与对象(下)

news2024/10/7 10:15:27

系列文章

 若想了解什么是类、封装的意义可以移步 【C++】类与对象(引入)

 若对六大成员函数或const成员函数有疑问的这篇文章可能可以帮到你 【C++】类与对象(上)


目录

系列文章

前言

1.初始化列表

1.1概念

1.2特性 

1.2.1必须使用初始化列表的情况

1.2.2初始化的顺序

2.explicit关键字

3.Static成员

3.1静态成员变量

3.2静态成员函数

3.3功能实现

4.友元

4.1友元函数

4.2友元类

5.内部类

6.匿名对象

6.1使用

6.2证明生命周期

7.拷贝对象时编译器的优化

总结


前言

🧋这一期博客算是给整个类与对象的系列做个收尾,补充类的一些其他功能,还有对类与对象更多的细节理解。


1.初始化列表

🧋虽然说我们已经学会了使用构造函数对函数进行初始化,但下面这个情况是否出乎你的意料呢?const 的成员变量,因其特性一开始我们便需要对其初始化但想在函数之中修改却又无法修改,这是为什么呢?

        

 🧋有同学说,在定义变量的时候使用缺省不就好了吗?这个方式确实能够解决问题,但缺省的这个功能是 C++11 才出现的,在那之前难道就无法解决这个问题吗?究其本质,在构造函数体内的这种操作已经算是赋值了真正初始化变量的地方并不在这,而是我们接下来要讲的初始化列表。

1.1概念

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

🧋位置位于构造函数名与函数内容之间

class A
{
public:
	A()
		:_a(5)    //初始化列表初始化_a和_b
		,_b(3)
	{}
private:
	int _a;
	const int _b;
};

int main()
{
	A a;
	return 0;
}

1.2特性 

🧋每个成员变量只能在初始化列表中出现一次。就像外部定义变量那样,同名的变量也不可以定义两次!

🧋正如上文所说,构造函数的函数体的本质只是给变量赋值,而初始化的这个步骤则交给了初始化列表,因此:

🧋不管是否显式写在初始化列表里,都会在初始化列表里初始化 。

1.2.1必须使用初始化列表的情况

🧋以下三种情况必须使用初始化列表进行初始化:

  • 引用成员变量
  • const成员变量
  • 没有默认构造函数的自定义类型成员 

🧋前面两种成员是因为其本身特性而导致不得不在初始化的时候就得赋值,但第三种不太一样,需要单独拎出来讲一讲。

🧋没有默认的构造函数即指类中至少有一个非全缺省的构造函数,这才满足类中没有默认构造函数的情况。我们知道对于自定义类型,编译器会自动调用目标类的默认构造函数,因此当前情况便会出现没有函数能够调用的情况

🧋使用初始化列表为该类的构造函数传入一个初始值,去调用该类的构造函数,便可完成该成员的初始化。 

class B
{
public:
	B(int b)
	{
		_b = b;
	}
private:
	int _b;
};

class A
{
public:
	A()
		:_a(5)    //初始化列表初始化_a
		,_bb(8)
	{}
private:
	int _a;
	B _bb;
};

int main()
{
	A a;
	return 0;
}

1.2.2初始化的顺序

🧋先看以下代码:

class A
{
public:
	A()
		:a1(2)   
		,a2(a1)
	{}
	void print()
	{
		printf("%d %d", a1, a2);
	}
private:
	int a2;
	int a1;
};

int main()
{
	A a;
	a.print();
	return 0;
}

🧋我们发现最后输出的结果竟然一个是 另一个是随机值。这是因为变量初始化的顺序是根据声明的顺序进行的。由于 a2 先声明,所以先初始化 a2 ,但由于此时 a1 还未被初始化,因此 a2 就被初始化成了随机值。

为了避免编译错误,尽量使用初始化列表初始化

2.explicit关键字

🧋我们都知道定义对象时还有这样的一种写法,并知道他是通过调用构造函数来进行初始化的。但真的是这样吗?其实在这个过程之中会产生一个隐式类型转换,用 来构建一个 的临时变量,之后用这个变量对 进行拷贝构造。之后编译器对这个过程进行了优化,才变成只出现调用一次构造函数。

A a = 1;

🧋如何证明这其中生成了临时变量呢?看下图,为什么使用普通的引用就会报错,而使用常引用没有出现错误了呢?

 

 🧋总所周知,临时变量具有常性,若用一个常变量给一个普通变量赋值,则会出现权限放大的情况,这时只要定义一个常变量便不会出现权限放大的情况。由此便可以证明我们上面所说的隐式类型转换是存在的

🧋若我们不想要让这种类型转换发生,就需要引入这个关键字 explict 

🧋在构造函数前增加这个关键字,上面的代码便无法通过编译。其针对的不仅仅是单参数的构造函数。

 

🧋而多参数的构造函数需要这样使用才能够初始化(需在C++11的环境下) 

class A
{
public:
	A(int a,int b)
		:_a(a)
		,_b(b)
	{}
private:
	int _a;
	int _b;
};

int main()
{
	A a = { 1,1 };
	return 0;
}

🧋且这也同样能用 explicit 禁止类型转换。

 

3.Static成员

3.1静态成员变量

🧋用 static 修饰的类成员称为类的静态成员,用 static 修饰的成员变量,被称为静态成员变量

🧋而静态成员变量不属于某个对象,而是属于整个类。因此静态成员变量一定要在类外进行初始化

🧋根据如下代码就完成了对静态成员变量的定义:

class A
{
public:
	A()
	{}
private:
	static int _a;       //声明静态成员变量
};

int A::_a = 5;           //初始化静态成员变量

int main()
{
	A a;
	return 0;
}

3.2静态成员函数

🧋静态成员函数的本质还是静态成员,同样,他也是所有类中共享的,一般用来访问静态成员变量

class A
{
public:
	A()
	{}
	static int get_a()   //静态成员函数
	{
		return _a;       //返回静态成员变量
	}
private:
	static int _a;       //声明静态成员变量
};

int A::_a = 5;           //初始化静态成员变量

int main()
{
	A a;
	cout << a.get_a(); 
	return 0;
}

3.3功能实现

🧋认识了静态成员后,我们就可以根据他们的这个性质实现一个类的小功能。我们能够实现统计当前类的实例化个数

class A
{
public:
	A()
	{
		_cnt++;             //实例化则增加1计数
	}
	A(const A& a)
	{
		_cnt++;             //实例化则增加1计数
	}
	~A()
	{
		_cnt--;             //析构则减少1计数
	}
	static int get_memnum()  //使用静态成员函数获取静态成员变量
	{
		return _cnt;
	}
private:
	static int _cnt;       //声明静态成员变量
};

int A::_cnt = 0;           //初始化静态成员变量为0

int main()
{
	A a;
	cout << a.get_memnum(); 
	return 0;
}


4.友元

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

其中分作友元函数友元类

4.1友元函数

🧋当我们对<<运算符进行重载时,会发现若在类中定义,则第一个操作数便固定为this指针,便与我们平时使用流提取的写法相反。

class A
{
public:
	A(int a,int b)
		:_a(a)
		,_b(b)
	{}

	ostream& operator<<(ostream& _cout)
	{
		_cout << _a << _b << endl;
		return _cout;
	}
private:
	int _a;
	int _b;
};

int main()
{
	A a = { 1,3 };
	a << cout;
	return 0;
}

 🧋因此,这个重载的函数便不能写在类中,但写在类外又访问不到类中的私有成员变量。因此便可以将这个函数定义成友元。就像是一种信任关系,对这个函数去掉访问限定符的限制。这样一来,外部的函数便可以访问到内部的的保护成员和私有成员

🧋使用时需要在类的内部声明,声明时需要加 friend 关键字。

class A
{
public:
	A(int a,int b)
		:_a(a)
		,_b(b)
	{}

	friend ostream& operator<<(ostream& _cout, const A& a);
private:
	int _a;
	int _b;
};


ostream& operator<<(ostream& _cout, const A& a)
{
	_cout << a._a << a._b << endl;
	return _cout;
}

int main()
{
	A a = { 1,3 };
	cout << a;
	return 0;
}

 [注意] 

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

4.2友元类

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

友元关系是单向的,不具有交换性。如下面的代码,在B中声明A为B的友元,在A中能够便能够访问B的私有成员,但B却无法访问A中的私有成员
友元关系不能传递,如果C是B的友元, B是A的友元,但C不是A的友元

友元关系也不能继承

class B
{
public:
	friend class A;    //声明A为B的友元
	B(int b)
		:_b(b)
	{}
private:
	int _b;
};

class A
{
public:
	A(int a)
		:_a(a)
		,b(2)
	{}
	void print()
	{
		cout << b._b << endl;  //在A中就能够访问b中的私有成员
	}
private:
	int _a;
	B b;
};

int main()
{
	A a = 1;
	a.print();
	return 0;
}


5.内部类

🧋如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,不能用外部类的对象去访问内部类的成员。即内部类是外部类的友元类内部类可以访问外部类的所有成员,但外部类却无法访问内部类的私有成员

 [注意] 

内部类外部类类域的限制

内部类可以定义在外部类的任意位置

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

class A
{
public:
	A(int a)
		:_a(a)
	{}
	class B                  //B天生是A的友元
	{
	public:
		B(int b)
			:_b(b)
		{}
		void print(const A& a)
		{
			cout << a._a << " " << _b << endl;  //b能够访问到a中的私有成员变量
		}
	private:
		int _b;
	};
private:
	int _a;
};

int main()
{
	A a = 1;
	A::B b = 5;
	b.print(a); 
	return 0;
}


6.匿名对象

6.1使用

当我们在某个时刻想要使用一个类,且仅使用一次以后便不再使用的情况下,可以试试匿名对象。

直接在类名后加上括号便可以使用,其生命周期只在定义的这一行内,这行结束便会自动销毁。

class A
{
public:
	A(int a)
		:_a(a)
	{}
	void print()
	{
		cout << _a << endl;   //输出成员变量的值
	}
private:
	int _a;
};

int main()
{
	A(3).print();    //使用匿名对象
	return 0;
}

6.2证明生命周期

 当我们调试到匿名对象的下一行语句时,该类的构造函数已经被调用了,即此时上一行的匿名对象已经被销毁了

7.拷贝对象时编译器的优化

🧋新的编译器在传参和传返回值时,会做一些优化,减少对对象的拷贝。当一行中满足以下条件便会进行优化。

构造 + 拷贝构造 -> 优化为直接构造

拷贝构造 + 拷贝构造 -> 1个拷贝构造

构造 + 构造 -> 1个直接构造

🧋由于传引用传参本质就是传一个别名,因此无优化

🧋因为是否优化的判断区间为当前一行,因此正确使用匿名对象有利于编译器的优化

🧋对象返回的总结

接收返回值对象,尽量以拷贝构造的方式接收不要赋值接收

函数中返回对象时,尽量返回匿名对象 

🧋函数传参的总结

尽量使用 const& 传参 。


总结

今天,我们在原来类的基础上补充了一些细节,讲述了初始化列表explicit 关键字,静态变量友元匿名对象,以及编译器在拷贝对象时的优化。每个知识点的内容都比较零散,因此要在自己理解后再用代码进行实现,才能真正的掌握。掌握编译器的规律来优化代码的效率,才能对语言的理解更近一步。

🧋好了,今天类与对象下半部分讲解到这里就结束了,如果这篇文章对你有用的话还请留下你的三连加关注。

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

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

相关文章

29考虑特性分布的储能电站接入的电网多时间尺度源储荷协调调度策略MATLAB程序-日前日内实时+需求响应+协调调度

资源地址&#xff1a; 29考虑特性分布的储能电站接入的电网多时间尺度源储荷协调调度策略MATLAB程序-日前日内实时需求响应协调调度-Matlab文档类资源-CSDN文库 参考文献&#xff1a; 考虑特性分布的储能电站接入的电网多时间尺度源储荷协调调度策略——金力 主要内容&…

Python入门教程+项目实战-9.3节: 字符串的操作方法

目录 9.3.1 字符串常用操作方法 9.3.2 获取字符串长度 9.3.3 字符串的大小写操作 9.3.4 删除字符串中的空白字符 9.3.5 字符串的子串查找 9.3.6 字符串的子串统计 9.3.7 字符串的子串替换 9.3.8 字符串的拆分函数 9.3.9 字符串的前缀与后缀9.3.10 知识要点 9.3.11 系…

一文教你认识分布式微服务开源框架

说到办公效率提质增效的问题&#xff0c;就需要了解分布式微服务开源框架了。因为这是助力企业做好数据管理、实现信息互通的得力助手和工具。在数字化发展进程越来越快的今天&#xff0c;实现办公自动化发展是不少用户的心愿和发展方向&#xff0c;采用分布式微服务开源框架可…

APK打包流程

&#xff08;图是从别地儿抠过来的&#xff0c;所以不清晰 。&#xff09; AAPT: 首先&#xff0c;安卓APP的资源这一块&#xff0c;会通过aapt进行一个编译&#xff0c;这个工具编译的时候&#xff0c;会首先会把我们的这个xml文件生成这一个R.java&#xff0c;然后再就是像m…

google账号注册流程升级了!2023年谷歌gmail邮箱帐号注册申请教程(完整版)

google账号注册升级了&#xff01; 2023年4月份google账号注册流程升级了&#xff0c;升级之前的版本是完成验证手机号码后才填写用户资料&#xff0c;升级之后的版本是需要先填写用户资料才能注册谷歌gmail邮箱帐号&#xff1b; 2023年谷歌gmail邮箱帐号注册申请教程 1、打开…

ChatGPT扩展系列之解决ChatGPT 被大面积封号的终极方案

ChatGPT扩展系列之解决ChatGPT 被大面积封号的终极方案 本节介绍了一个解决ChatGPT在中国大陆无法使用和担心被封号的问题的方法。近期有很多亚洲用户被封号,原因是有人滥用API接口或者批量注册账号,不符合官方规定。对于这个问题,提出了一个解决方法,可以在中国大陆无需翻…

【NLP】pyltp工具介绍、安装和使用代码+示例

【NLP】pyltp工具介绍、安装和使用pyltp 文章目录【NLP】pyltp工具介绍、安装和使用pyltp1. 介绍2. 使用2.1 分句2.2 分词2.3 词性标注2.4 命名实体识别2.5 依存句法分析2.6 词义角色标注2.7 完整示例3. 参考1. 介绍 什么是pyltp pyltp 是LTP的 Python 封装&#xff0c;提供了…

uniapp - 全平台兼容的 “多图上传“ 功能,搭配 uview 组件库中的 upload 上传组件(附带详细的示例源码及注释,可直接复制使用或简单修改)

效果图 使用 uniapp 开发,多平台全端兼容的多图上传功能,支持限制个数及移除等。 组件库使用的是 uview 框架,上传组件基于 Upload组件,功能完美无bug。 准备阶段 Upload组件支持手动上传与

CMMI 3.0究竟有哪些变化?

新鲜出炉&#xff0c;CMMI 3.0究竟有哪些变化&#xff1f; 2023年4月6日&#xff0c;ISACA&#xff08;国际信息系统审计协会&#xff09;下属的CMMI研究院发布了CMMI 3.0版本。有哪些具体的变化呢&#xff1f;本文做了系统梳理&#xff0c;分为更名、新增、删除、实践域内的变…

代码随想录-67-450. 删除二叉搜索树中的节点

目录前言题目1.二叉搜索树特性递归找到要删除的节点2. 本题思路分析&#xff1a;3. 算法实现4. 算法坑点前言 我在刷卡哥的“代码随想录”&#xff0c;自己的总结笔记均会放在“算法刷题-代码随想录”该专栏下。 代码随想录此题链接 题目 1.二叉搜索树特性递归找到要删除的节…

【Python】Python中使用Matplotlib绘制折线图、散点图、饼形图、柱形图和箱线图

【Python】使用Matplotlib绘制折线图、散点图、饼形图、柱形图和箱线图 python数据可视化课程&#xff0c;实验二 Matplotlib 中文API&#xff1a;API 概览 | Matplotlib 一、实验任务的数据背景 提供的源数据&#xff08;数据文件employee.csv&#xff09;共拥有4个特征&…

Vue3自定义指令之前端水印功能实现

一、前置知识 — Vue 中的自定义指令 先来说说 vue2和vue3中自定义全局指令的区别 相同点&#xff1a;指令的应用场景&#xff0c;原理是一致的&#xff1b; 不同点&#xff1a;生命周期钩子函数名&#xff0c;指令定义的格式不一样。 vue2中自定义全局指令&#xff1a; 定义…

实时翻译器-实时自动翻译器

自动翻译器——让语言不再是障碍。 在当今全球化的背景下&#xff0c;语言已不再是跨文化交流的障碍。而自动翻译技术作为突破语言壁垒的有效手段&#xff0c;越来越受到关注和需求。我们的自动翻译器就是一个高效、准确的翻译工具&#xff0c;它能够根据用户输入的内容自动识…

mysql知识点看这一篇就够了!

存储引擎 InnoDB InnoDB 是 MySQL 默认的事务型存储引擎&#xff0c;只要在需要它不支持的特性时&#xff0c;才考虑使用其他存储引擎。 InnoDB 采用 MVCC 来支持高并发&#xff0c;并且实现了四个标准隔离级别(未提交读、提交读、可重复读、可串行化)。其默认级别时可重复读…

Springboot项目怎么设计业务操作日志功能?

目录 前言 需求描述与分析 系统日志 操作日志 设计思路 Spring AOP Filter和HandlerInterceptor 过滤器 拦截器 SpringAOP、过滤器、拦截器对比 实现方案 环境配置 依赖配置 表结构设计 代码实现 测试 调试方法 验证结果 总结 前言 很久以前都想写这篇文章…

[ 应急响应基础篇 ] Windows系统隐藏账户详解(Windows留后门账号)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

Spring Web MVC DispatcherServlet详解—官方原版

一、概述 Spring Web MVC是基于Servlet API构建的原始Web框架&#xff0c;从一开始就包含在Spring框架中。正式名称“SpringWebMVC”来自其源模块&#xff08;Spring-webmvc&#xff09;的名称&#xff0c;但它更常见的名称是“SpringMVC”。 与Spring Web MVC并行&#xff0c…

【AIGC】GitHub Copilot 免费注册及在 PyCharm 中的安装使用

欢迎关注【youcans的 AIGC 学习笔记】原创作品 《GitHub Copilot 免费注册及在 VS Code 中的安装使用》 《GitHub Copilot 免费注册及在 PyCharm 中的安装使用》 GitHub Copilot 免费注册及在 PyCharm 中的安装使用1. GitHub Copilot 功能介绍2. 用户注册与申请2.1 个人订阅 Gi…

经典算法50例-无敌五十剑-算法五十重天

这里写目录标题1.汉诺塔2.费式数列3.巴斯卡三角形4.三色棋5.老鼠走迷官&#xff08;一&#xff09;6.老鼠走迷官&#xff08;二&#xff09;7.骑士走棋盘8.八皇后9.八枚银币10.生命游戏11.字串核对12.双色、三色河内塔13.背包问题14.蒙地卡罗法求 PI15.Eratosthenes筛选求质数1…

LeetCode——遍历序列构造二叉树

105从前序与中序遍历序列构造二叉树 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,2…