C++入门全集(4):类与对象【下】

news2024/12/21 14:08:56

一、再谈构造函数

1.1 构造函数体内赋值

我们知道,在创建对象时,编译器会自动调用构造函数给对象中的各个成员变量一个合适的初始值

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;
	int _month;
	int _day;
};

虽然调用构造函数后,各个成员变量已经有了一个初始值,但是对于这种在函数体内赋值的语句不能称为对成员变量的初始化,只能将其称为赋初值。

因为初始化只能有一次,而构造函数体内可以写多个赋值的语句,例如:

1.2 初始化列表

像这样,被const修饰的成员变量,必须在声明时就进行初始化

虽然C++11允许我们给成员变量一个缺省值,但是C++11以前是怎么做的呢?

这里引入一个新的概念:初始化列表

初始化列表定义在构造函数的函数名和函数体之间,以一个冒号开始,接着是一个用逗号分隔的数据成员列表,每个成员变量后面跟着一个放在括号中的初始值或表达式,例如:

Date(int year, int month, int day)
	: _year(year)
	, _month(month)
	, _day(day)
{}

像这样,才能称之为真正的初始化

但是,如果一个成员变量既给了缺省值,又在初始化列表中显式定义了,那它最后的值如何呢?

通过监视窗口我们可以观察到:

如果一个成员变量既有缺省值又在初始化列表中定义了,那么就按照初始化列表中的值进行初始化

如果一个成员变量有缺省值,但是没在初始化列表中定义,那么就用它的缺省值初始化

如果一个成员变量既没有缺省值又没在初始化列表中定义,那么就给一个随机值

初始化列表有以下几个特点:

(1)每个成员变量在初始化列表中只能出现一次,因为只能初始化一次

(2)类中的以下几个成员变量,必须放在初始化列表中进行初始化

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

原因如下: 

对于引用类型的成员变量,我们必须在声明变量时就给出它的引用对象

对于const成员变量前面已经提到过了,也是必须在声明时就初始化

对于没有默认构造函数的自定义类型成员变量,我们在初始化时需要传参

(3)尽量多使用初始化列表去初始化,因为不管你是否使用,对于自定义类型成员变量都一定会先使用初始化列表去初始化

例如:

可以看到,Date类里有一个Time类的成员函数,虽然我们没有在初始化列表中初始化它,它也会使用初始化列表去调用自己的默认构造函数

如果该自定义类型成员变量的构造函数不是无参的或者全缺省的,我们就需要手动将该变量添加至初始化列表中并给出参数。

总之,我们平时写构造函数的时候尽量用初始化列表来初始化成员变量即可

(4)成员变量在类中的声明顺序就是它在初始化列表中的初始化顺序,与其在初始化列表中的先后顺序无关

例如:

class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};

int main() {
	A aa(1);
	aa.Print();
}

最后打印的结果是什么呢?

可能很多人认为是 1 1,实际上是:

因为_a2比_a1先声明,所以在初始化列表中也是_a2先初始化

最后,拷贝构造函数因为也是构造函数,所以它也有初始化列表 

1.3 explicit关键字

我们知道,内置类型变量在发生类型转换的时候会生成一个临时的常性变量,例如:

int a = 1;
double b = a;

但是,类型转换不止能发生在内置类型中,内置类型也可以转换成自定义类型,这里就和构造函数扯上关系了。

一个类的构造函数,不仅起到初始化成员变量的作用,对于单个参数或第一个参数无缺省值的半缺省构造函数来说,它还具有类型转换的作用

或许没看懂,那就举一个例子

像这样,对象aa1在创建时正常调用构造函数,aa2又是怎么回事?为什么一个自定义类型能被内置类型初始化?

前面说到,内置类型在发生类型转换的时候会生成一个临时的常性变量,这里也是一样

首先编译器使用1作为参数调用构造函数,创建一个临时变量,再用这个临时变量调用拷贝构造函数对aa2赋值

所以aa1只调用了一次构造函数,而aa2这一行代码调用了一次构造函数和一次拷贝构造函数

这种方式既影响代码可读性,又增加了消耗,有什么办法可以禁止构造函数类型转换呢?

这里引入explicit关键字,在构造函数的前面加上它,即可禁止类型转换了

这是单参数的构造函数,对于第一个参数无缺省值的半缺省构造函数也是同理

只要是只传递一个参数的构造函数,用这种方式都会发生类型转换

另外,对于需要传递多个参数的构造函数,在C++11后也开始支持类型转换了,例如:

如果不想类型转换,用explicit修饰构造函数即可


二、static成员

2.1 概念

static修饰的类成员称为类的静态成员,static修饰的成员变量称为静态成员变量,static修饰的成员函数称为静态成员函数

有一道面试题:实现一个类,计算程序中创建过多少个类对象

可以看到,使用静态成员变量和静态成员函数可以很轻松的解决这个问题

2.2 特性

根据上面的图,我们可以得出以下几点 

(1)静态成员不属于某个对象,而是属于所有对象、属于整个类,存放在静态区

所以我们上面可以直接使用类名和作用域限定符来访问静态成员函数GetCount,当然也可以创建一个对象来访问静态成员函数,但是有点多此一举了

2)静态成员变量必须在类外进行定义和初始化,不需要添加static,在类中声明时才需要加

(3)静态成员函数没有隐式的this指针形参,所以不能访问任何非静态成员

(4)静态成员也是类的成员,受public、protected和private访问限定符的限制

像上面,因为_count是私有的,我们只能用函数来获取它,如果它是公有的,我们也可以直接访问


三、友元

当我们在类外定义了一个函数,想要访问类中的私有成员变量怎么办呢?这里就涉及到友元

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

友元又分为:友元函数友元类

3.1 友元函数

我们知道,cout和流插入运算符可以实现将内置类型打印的效果,那么假设我想将流插入运算符重载,让自定义类型也可以使用呢?

我们试试在类中实现流插入运算符的重载函数

可以看到,实现的重载函数没有起效

这是因为,成员函数的第一个变量是this指针,在重载函数中对应第一个变量的是第一个操作数

所以如果像这样就可以正常运行了

但是这样也太怪了吧,和平时用cout一点也不一样,有没有别的办法呢?

我们只好在类外去实现流插入运算符的重载函数了,但是类外的函数又没办法访问类内的私有成员

像这种必须定义在类外,但是又需要访问类内的私有成员的函数,就需要友元来解决了

友元函数可以直接访问类内的私有成员,需要在类的内部进行声明,声明时需要加friend关键字

需要说明以下几点:

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

3.2 友元类

和友元函数类似,我们也可以在类中声明一个友元类

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

需要注意以下几点:

  • 友元关系是单向的,不具有交换性

比如上面,Date类是Time类的友元,所以可以直接在Date类中访问Time类的私有成员变量;

但是不代表Time类是Date类的友元,不能在Time类中访问Date类的私有成员变量

  • 友元关系不能传递

例如A是B的友元,B是C的友元,不代表A就是C的友元了

  • 友元关系不能继承

这里在讲到继承后再给大家详细介绍


四、内部类

4.1 概念

如果一个类定义在另一个类的内部,这个定义在内部的类就称为内部类。

内部类是一个独立的类,它不属于外部类,我们更不能通过外部类的对象去访问内部类的成员

外部类对内部类没有任何优越的访问权限。

内部类与外部类的关联在于:

(1)内部类受外部类的类域限制

例如我们想创建一个内部类类型的变量,需要用作用域限定符

(2)内部类天生就是外部类的友元,但是外部类不是内部类的友元

4.2 特性

(1)内部类定义在外部类的public、protected和private中都是可以的

(2)内部类可以直接访问外部类中的静态成员,而不需要借助外部类的对象或类名

(3)外部类的大小不包括内部类

例如

可以看到,外部类A的大小并没有包括内部类B,所以可以知道内部类的空间也是独立的


五、匿名对象

有时候我们可能只需要调用一次某个类的成员函数,为此如果特意去创建一个对象的话就太麻烦了

这里就可以用到匿名对象。我们平时创建一个对象可能是这样的:

如果要创建一个匿名对象的话,是这样的:

顾名思义,匿名对象在创建的时候是不用取名字的。

匿名对象的特点在于,它的生命周期只在这一行,一旦程序走到了下一行,就会自动调用析构函数销毁。

假设此时我们要调用一次A类中的Print函数,就可以用匿名对象去调用,而不用特意创建一个对象

对于各种一次性的对象创建,我们都可以使用匿名对象。

完.

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

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

相关文章

leetcode 长度最小的子数组

在本题中&#xff0c;我们可以知道&#xff0c;是要求数组中组成和为target的最小子数组的长度。所以&#xff0c;我们肯定可以想到用两层for循环进行遍历&#xff0c;然后枚举所有的结果进行挑选&#xff0c;但这样时间复杂度过高。 我们可以采用滑动窗口&#xff0c;其实就是…

网络加速CDN详细介绍

1、为什么要有网络加速 互联网从逻辑上看是一张大网&#xff0c;但实际上是由许多小网络组成的&#xff0c;这其中就有小网络“互连互通”的问题&#xff0c;典型的就是各个电信运营商的网络&#xff0c;比如国内的电信、联通、移动三大家。 这些小网络内部的沟通很顺畅&#…

c++之通讯录管理系统

1&#xff0c;系统需求 通讯录是一个记录亲人&#xff0c;好友信息的工具 系统中需要实现的功能如下&#xff1a; 1&#xff0c;添加联系人&#xff1a;向通讯录中添加新人&#xff0c;信息包括&#xff08;姓名&#xff0c;性别&#xff0c;年龄&#xff0c;联系电话&#…

计算机网络-第2章 物理层

本章内容&#xff1a;物理层和数据通信的概念、传输媒体特点&#xff08;不属于物理层&#xff09;、信道复用、数字传输系统、宽带接入 2.1-2.2 物理层和数据通信的概念 物理层解决的问题&#xff1a;如何在传输媒体上传输数据比特流&#xff0c;屏蔽掉传输媒体和通信手段的差…

Java:三种代理模式示例

什么是代理模式&#xff1f; 代理&#xff08;Proxy&#xff09;是一种设计模式&#xff0c;为其他对象提供一种代理以控制对这个对象的访问。 代理模式的组成 抽象角色&#xff1a;通过接口或抽象类声明真实角色实现的业务方法。代理角色&#xff1a;实现抽象角色&#xff…

kibana自动补全功能失效的几个原因

文章目录 不能自动补全index&#xff1f;不能自动补全field&#xff1f; 不能自动补全index&#xff1f; 当用户在 kibana 的 Dev Tools 页面手写查询时&#xff0c;理应可以自动补全 index 的名称&#xff0c;如下图&#xff1a; 如果不能自动补全&#xff0c;则点击 Settin…

【网站项目】295演唱会购票系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

在线绘图利器:支持在线使用的电脑画图软件推荐!

计算机绘图软件是现代设计师和创作者必不可少的工具之一。伴随着技术的不断发展&#xff0c;越来越多的在线计算机绘图软件应运而生&#xff0c;为用户提供了更加便捷、高效的创作方法。对初学者而言&#xff0c;选择一款易于使用、功能强大的计算机绘图软件至关重要。本文将介…

Tomcat服务部署优化

目录 一.Tomcat的基本内容 1.概念 2.构成 &#xff08;1&#xff09;web容器 &#xff08;2&#xff09;servlet容器&#xff08;catalina&#xff09; &#xff08;3&#xff09;JSP容器 3.Tomcat顶层架构 &#xff08;1&#xff09;Tomcat中最顶层的容器是Server&…

DCFL: for Oriented Tiny Object Detection

文章目录 AbstractIntroductionContributionRelated Work定向目标检测微小目标检测多尺度学习标签分配上下文信息特征增强MethodOverview动态先验Coarse Prior MatchingFiner Dynamic Posterior MatchingAblation StudyAnalysis不平衡问题的调解可视化速度Conclusionhh 源代码 …

麒麟KYLINSOS服务器操作系统SP3安装

原文链接&#xff1a;安装麒麟服务器操作系统V10 SP3 在当今的IT环境中&#xff0c;内网仓库的部署对于确保网络安全、加快本地访问速度以及保持软件包的一致性至关重要。特别是对于企业和组织而言&#xff0c;内网仓库可以极大地提升工作效率和系统稳定性。今天&#xff0c;我…

ArmSoM Rockchip系列产品 通用教程 之 CAN 使用

CAN 使用 1. CAN 简介 CAN (controller Area Network)&#xff1a;控制器局域网络总线&#xff0c;是一种有效支持分布式控制或实时控制的串行通信网络。 目前世界上绝大多数汽车制造厂商都采用CAN总线来实现汽车内部控制系统之间的数据通信。 RK3568/RK3588的CAN驱动文件&a…

Effective objective-c-- 内存管理

Effective objective-c-- 内存管理 前言理解引用计数引用计数工作原理属性存取方法中的内存管理自动释放池保留环要点 以ARC简化引用计数使用ARC时必须遵循的方法和命名规则变量的内存管理语义ARC如何清理实例变量覆写内存管理方法要点 在dealloc方法中只释放引用并解除监听要点…

品牌与时间函数:在时间的长河中铸造品牌

品牌推广是一个与时间紧密相连的复杂过程。时间不仅是品牌推广的见证者&#xff0c;更是其推动者和塑造者。迅腾文化深刻理解品牌推广与时间之间的微妙关系&#xff0c;提出的“显”的原则&#xff0c;旨在通过巧妙的策略&#xff0c;使品牌在时间的流转中逐渐显现出其特别的魅…

Less is More: Generating Grounded Navigation Instructions from Landmarks

摘要 我们研究根据室内路线捕获的 360 图像自动生成导航指令。现有的生成器视觉基础较差&#xff0c;导致它们依赖语言先验并对物体产生幻觉。我们的 MARKY-MT5 系统通过关注视觉地标来解决这个问题&#xff1b;它包括第一级地标检测器和第二级生成器——多模式、多语言、多任…

基于springboot实现计算机类考研交流平台系统项目【项目源码+论文说明】

基于springboot实现计算机类考研交流平台系统演示 摘要 高校的大学生考研是继高校的高等教育更上一层的表现形式&#xff0c;教育的发展是我们社会的根本&#xff0c;那么信息技术的发展又是改变我们生活的重要因素&#xff0c;生活当中各种各样的场景都存在着信息技术的发展。…

U盘遇阻?解决“位置不可用”的困扰

U盘遇阻&#xff1a;当“位置不可用”成为难题 在数字化时代&#xff0c;U盘已成为我们存储和传输数据的重要工具。然而&#xff0c;当U盘突然提示“位置不可用”时&#xff0c;这无疑是一个令人头疼的问题。这不仅意味着我们无法访问存储在U盘中的文件&#xff0c;而且可能还…

PlantUML - 时序图

时序图主要内容 下面是一个简单的时序图&#xff0c;我们可以很容易并且美观的表达我们的交互流程&#xff0c;只需要在箭头的两边指定一个名字&#xff0c;加上描述即可&#xff1a; startuml bkloanapply -> bkloanapprove : request bkloanapprove --> bkloanapply :…

信息系统安全与对抗-作业2

目录 1、使用自己姓名拼音创建一个账户&#xff0c; 并使用命令和图形化查看 2、使用自己拼音打头字母创建一个隐藏账户 &#xff0c;并使用命令和图形化查看 3、使用命令启动 telnet 服务 4、使用命令打开防火墙 23 端口 5、熟悉LINUX系统&#xff0c;使用命令行创建用户…

排序(1)——直接插入排序+冒泡排序

目录 1 排序的概念及其应用 1.1 排序的概念 1.2 排序的应用 1.3 常见的排序算法 2 直接插入排序 2.1 基本思想 2.2 基本思路 2.3 代码实现 2.4 时间复杂度 3 冒泡排序&#xff08;回顾&#xff09; 3.1 思路分析 3.2 时间复杂度 4 比较 1 排序的概念及其应用 1.…