C++类和对象3:关于类内部的更多细节

news2024/11/16 1:25:31

目录

初始化列表:

explicit关键字

​编辑

 static成员

友元

内部类

 匿名对象

 拷贝对象时的一些编译器优化


 

我们已经接触过了构造函数,其功能可以很方便的帮助我们为变量赋值,但是在这里并不是初始化,因为一个构造函数可以为几个变量进行多次赋值,这与初始化的概念相悖。

简而言之,我们前文所提到的初始化方法是比较……冒牌的,如下:

class Date
{
public:
 
	Date(int year=2022, int month=12, int day=29)
	{
		_year = year ;
		_month= month ;
		_day = day   ;
	}
 
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day  << endl;
	}
 
private:
	int _year;
	int _month;
	int _day;
};

 所以,真正的通过构造函数去初始化的流程是使用初始化列表。

初始化列表:

 初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式,如下记录两种写法,两种写法的效果相同,按着喜好写就好

class Date
{
public:
    //写法一
	Date()
        :_year(2022)
        ,_month(1)
        ,_day(2)
	{}

    //写法二
    Date()
        :_year(2022),_month(1),_day(2)
	{}

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

每个成员变量在初始化列表中只能出现一次

初始化列表也可以跟函数体内部的表达式混用,比如栈的初始化

混着来也是有必要的,初始化列表没法干所有的事情。

 这玩意看着也不厉害啊?到底有啥特攻的呢?

类中包含以下成员,必须放在初始化列表位置进行初始化:

1.const成员变量

2.没有默认构造函数的自定义类型

3.引用成员变量


第一个使用场景:const成员变量

  • 由于const只有一次初始化的机会,所以用构造函数的函数体内部去初始化一个const类型的成员变量是不被允许的。因为此时已经走过了定义初始化阶段,而定义初始化阶段就是初始化列表。

  • 而对于内置类型,假如我们不给列表里的值进行初始化编译器会给一个随机值,而在之前我们应对这种情况的解决办法就是缺省值,联系现在的知识其实这个缺省值给予的阶段就是初始化列表的时候,因为不论如何所有的成员变量都会走初始化列表

 第二个使用场景:引用成员变量

class B
{
public :

	B(int use,int a)
		:_i(0)
		,_use(use)
	{
	}

private:
	const int _i;
	int& _use;
	A _A;

};

 第三个使用场景:没有默认构造函数的自定义类型

其实记述到这里,你或许会有一点疑惑,为什么一定要初始化列表才能初始化这三兄弟?主要还是取决于它们的特殊性,例如引用,它在被创建时必须初始化,const对象也是同理。

那么为什么没有默认构造函数的自定义类型也要走初始化列表呢?

我们先回顾以下前文的知识,类对待成员变量时,对内置类型不处理,而对自定义类型会调用它的默认构造函数。

那么,没有没有默认构造函数的自定义类型,如下:


class A
{
public:
	A(int a )
		:_a(a)
	{}

private:
	int _a;
};

我们可以看到,我们写了构造函数,但不是一个默认构造函数,它需要传参才能正常的初始化,那么在这种情况下,若类B想要成功的初始化这个自定义类型,就必须找到其初始化的方法,可是完全找不着,就会报错

 那么,这种情况下,为什么初始化列表就能解决这个问题呢?

其实在C++的类中,我们就算不写初始化列表,那也是会走的,而且每个成员都一定会走初始化列表。我们相当于为其在这个阶段直接传参,成功的使其初始化成功,这才能解决这个问题。

总结如下:

需要注意的是,初始化列表中的顺序是按照声明的次序来的,比如日期类就是按照年月日的声明顺序来的,初始化列表的顺序也相同。

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

class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}
	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};


explicit关键字

当类的构造函数为单参数的构造函数的时候,我们可以只用一个参数赋值进行构造。

也就是直接Date d2 = 2022;

之所以可以用int类型直接创建一个对象,其原理则是隐式类型转换。

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。

也就是在单参数构造的这个过程之中,2022构造一个无名对象,最后用无名对象给d2对象进行赋值

原理如上图所示,但是当我们想使用引用来创建一个对象的时候,直接使用引用会报错,因为发生任何转换的时候都会产生一个临时变量,而这个临时变量具有常性,在前面加上一个const才可以成立。

当然,这个转换发生的条件也不是那么苛刻的,只需要转递参数的都会发生隐式类型转换。

 static成员

有时候我们不得不使用全局变量来实现某一些操作,但是全局变量本身并不安全,因为它可以被随意更改,所以,为了更加安全的使用全局变量或者说具有全局性质的变量,我们可以使用static去修饰一个类里的成员。

在此之前,我们需要注意的是局部的静态和全局的静态的区别究竟在哪?

局部静态变量与全局静态变量之间的区别在于其作用域的不同,局部的只能在它的作用域里面用,而全局的在哪都能用,他们都存放于静态区,生命周期相同。但是而类里面的静态就属于类的作用域。

 声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

因为作为一个类的成员变量,每一次创建一个类就去初始化一个成员变量天经地义,但是每一次创建一个就初始化一次N个这个变量的静态属性就没有任何意义了,所以静态的成员变量一定要在类外进行初始化,同理,缺省值也是不能初始化静态成员变量的。

 定义初始化要给类型。

当然,作为一个属于这个类的成员变量在里面直接访问也是没有问题的。

总而言之:


旧题再回顾,这个会不会报错?

 不会, 这段依旧可以运行的原因是同成员函数一样存放在对象外头,static放在静态区还是可以被找到的。

由于静态变量的特殊性质,我们一般不会把它放到共有里面,而是放在私有里,为了获取它的值的时候,我们就写一个函数来获取。

 但是这样子的话每一次想要使用这个函数的时候都需要先创建一个对象才行,用起来还是比较蛋疼的,有没有什么办法可以直接拿到它的值呢?这个时候就可以使用静态成员函数来解决这个问题。

 作为静态的成员函数,它没有this指针,结合前面的知识来讲,我们不能越过类直接调用成员函数的原因主要是因为This指针的存在,而它没有,就可以不用创建对象直接调用了。

 但是这样也带来了一些副作用,这个函数将无法访问非静态的成员变量。

 总结下来

静态类型在类和对象中主要是起到打破类的墙的作用,而且其特性很像同类认证,只有都是static的同类才被互相认可。

1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区

2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明

3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问

4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员

5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

友元

友元,我们前面已经接触过,简单来说就是开个后门可以偷家访问到私有的变量,突破私有的封装。

·不过我们之前接触的是友元函数而非友元类,友元类其实也大差不差,主要有以下几种性质。

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

友元关系是单向的,不具有交换性。比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

友元关系不能传递如果C是B的友元, B是A的友元,则不能说明C时A的友元。友元关系不能继承,在继承位置再给大家详细介绍。

内部类

内部类的概念其实就是类中类,在一个类里创建另一个类,这个类就是内部类,内部类有如下几种特征。

1. 内部类可以定义在外部类的public、protected、private都是可以的。

2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。

3. sizeof(外部类)=外部类,和内部类没有任何关系。

在这里内部类的特点还是很奇怪的,B可以随意地访问A的成员,而A却不能访问B的。

 

 匿名对象

 我们正常创建对象的方法有如下两种

 匿名对象就是这种

 匿名对象的特点是:它的生命周期仅仅只有这一行,在某些情况下这个特点将会显得很有用,比方说一个被封装在类里的很简单的某个成员函数

 要创建一个对象,但是匿名对象就可以直接用

 还有一种应用场景:

优化成:

 拷贝对象时的一些编译器优化

 优化场景1:

我们回到单个传参即可创建对象的那一行代码中,也就时explicit关键字的那块,我们可以仅仅使用一个变量触发隐式类型转换然后创建一个对象,生成过程是先拿1创建一个空版临时对象,然后拿着这个临时对象去构造aa1

我们在创建这个对象的时候,在老版本的编译器下,执行的步骤就是:

1.先用1构造一个相同类型的对象

2.拷贝构造到aa1里。

在现代的编译器中,编译器变得更加的“负责”它会优化这一过程变成直接构造


 优化场景2:

 

在这个场景下,我们用A类创建了一个aa1对象,将aa1传参进入f1,在这个过程中,进入函数体内部时发生了拷贝构造

这是一个构造加拷贝构造,但是这并不是一个连续的步骤,为了保证不改变优化后的正确性,编译器不会优化这段过程。换用如下的创建方法即可触发优化。


 优化场景3:

 

这里这个函数的原生执行步骤为构造+拷贝构造

如果是有返回值接收的话,编译器会优化成如下步骤。

 以上的是还没有新建一个对象才能这样优化,如下的就不行了

 


优化场景4:

 

 代码注释部分为非优化写法,在这个非优化写法中,我们先构造了一个aa,然后再拷贝构造出一个临时对象,接着,这个临时对象再次触发拷贝构造给到返回值。

 优化写法如下:

 总而言之:利用好编译器的优化机制,我们能直接返回啊,或者创建变量什么的,不要有中间过程,直接拿来用最佳。


到此,类和对象就概述完毕了!

感谢阅读,希望对你有点帮助!

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

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

相关文章

02 Hadoop概述

Hadoop概述1、Hadoop是什么2、Hadoop版本3、HDFS、YARN、MapReduce&#xff08;1&#xff09; HDFS&#xff08;2&#xff09;YARN&#xff08;3&#xff09;MapReduce&#xff08;3&#xff09;Hadoop模块之间的关系1、Hadoop是什么 是一个由Apache基金会开发的分布式系统基础…

动态规划是个好东西:编辑距离

力扣&#xff1a;72. 编辑距离 这道题目让我狠狠的了解了动态规划&#xff0c;这玩意是真强。 题目描述很简单&#xff1a; 这道题正常来说&#xff0c;我们要考虑这个字符怎么换&#xff0c;长度不一怎么找…等等问题&#xff0c;但是这样做会发现很困难&#xff0c;显然这是…

Vert.x 核心概念及事件模型

Vert.x是基于事件的&#xff0c;提供一个事件驱动编程模型 使用Vert.x作为服务器时&#xff0c;程序员只要编写事件处理器event handler即可。&#xff08;当TCP socket有数据时&#xff0c;event handler被创建调用&#xff09; 另外它还可以在以下几种情况激活&#xff1a; …

反向迭代器

文章目录1. list的反向迭代器2. list的rbegin和rend3. 反向迭代器的实现3.1 复用vector反向迭代器3.2 反向迭代器的变化1. list的反向迭代器 我们先来看一看库里面的list的迭代器是如何写的&#xff1a; 这是list的正向迭代器。 这是list的反向迭代器。 其实大佬们是把正向迭…

【3.1】Eureka注册中心-提供者与消费者/原理分析

【3.1】Eureka-提供者与消费者/原理分析1 提供者与消费者2 服务调用出现的问题3 Eureka的作用3.1 消费者该如何获取服务提供者具体信息&#xff1f;3.2 如果有多个服务提供者&#xff0c;消费者该如何选择&#xff1f;3.3 消费者如何感知服务提供者健康状态&#xff1f;4 总结1…

A. The Enchanted Forest #769 div1

Problem - A - Codeforces 题意&#xff1a; 给你一串序列&#xff0c;任意从什么地方开始&#xff0c;给你k秒时间&#xff0c;让你算最大价值 每一秒时间按顺序你可以做&#xff1a; ①移动到|x-y|<1的地方 ②取走这个位置上所有的数 ③每个位置1 原来是0秒&#x…

opencv-python常用函数解析及参数介绍(七)——边缘检测

边缘检测前言1.基本概念1) 滤波2) 计算梯度3) 非极大值抑制4) 双阈值检测2.opencv中的边缘检测效果展示分析对比3.结尾前言 在之前的文章中我们介绍了使用膨胀和腐蚀、计算图像梯度的方式来获取图像的轮廓&#xff0c;本篇文章将介绍另外一种可以获取图像轮廓的方法——边缘检…

再说原型链

关于原型链&#xff0c;已经被无数次的提起&#xff0c;每次回顾都有新的理解&#xff0c;今天我们再来说说原型链。 我们知道&#xff0c;每一个javascript对象&#xff08;除了null&#xff09;在被创建的时候都会与另一个对象关联起来&#xff0c;这个对象就是我们所说的原型…

【C++进阶】二叉搜索树

文章目录二叉搜索树概念二叉搜索树操作二叉搜索树的实现每个节点的结构插入查找删除二叉搜索树的所有代码(包括测试)版本一版本二test.cpp二叉搜索树概念 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: 若它的左子树不为空&a…

Linux环境下gdb程序调试

目录gdb介绍进入gdb调试环境指令学习l(list)指令b(break)指令info b指令d指令r(run)指令n(next)指令s(step)指令c(continue)指令bt(breaktrace)指令finish指令p(print)指令display指令undisplay指令until指令disable命令enable命令这篇文章将会介绍gdb以及一些常用的gdb调试指令…

3.移动端百分比布局练习-京东首页

访问地址 https://youthddup.gitee.io/myproject/ 1、项目目录结构 2、注意 &#xff08;1&#xff09;设置视口标签以及引入初始化样式 &#xff08;2&#xff09;二倍精灵图缩放 先把精灵图等比缩放原来的一半 然后再测精灵图位置 代码里background-size置为原来的一半 &a…

typescript 数组操作

使用变量来存储值会带来以下限制&#xff1a; 变量本质上是标量。换言之&#xff0c;一个变量声明变量声明一次只能包含一个。这意味着在程序中存储n个值需要n个变量声明。因此&#xff0c;当需要存储更大的值集合时&#xff0c;使用变量是不可行的。 程序中的变量以随机顺序分…

当下一场数字化的浪潮,正在各行各业深刻上演着

一场数字化的浪潮&#xff0c;正在各行各业深刻上演着。在零售领域&#xff0c;亦不例外。以往&#xff0c;提及零售&#xff0c;我们更多地想到的是&#xff0c;各式各样的电商平台&#xff0c;我们看到的是&#xff0c;各式各样的电商模式&#xff1b;现在&#xff0c;提及零…

测试用例设计-淘宝购物车

测试人员和开发人员产生争执了怎么办&#xff1f; ① 先检查自身&#xff0c;是否BUG描述不清楚 ② 站在用户的角度考虑问题 ③ BUG定级要有理有据 ④ 提高自身的技术能力和业务水平&#xff0c;最好同时提出解决方案。 ⑤ 开发人员不接受时&#xff0c;不要争吵&#xff0c;可…

YOLOv5更换骨干网络之 PP-LCNet

论文地址&#xff1a;https://arxiv.org/abs/2109.15099 代码地址&#xff1a;https://github.com/ngnquan/PP-LCNet 我们提出了一种基于MKLDNN加速策略的轻量级CPU网络&#xff0c;名为PP LCNet&#xff0c;它提高了轻量级模型在多个任务上的性能。本文列出了在延迟几乎不变的…

YOLOv5更换骨干网络之 EfficientNet-B0

论文地址&#xff1a;https://arxiv.org/abs/1905.11946 代码地址&#xff1a;https&#xff1a;//githeb.com/TensorFlow/tpu/tree/master/Models/Offical/Efficientnet 卷积神经网络(ConvNet)通常是在固定的资源预算下开发的&#xff0c;如果有更多的资源可用&#xff0c;则…

如何从macOS ventura降级到 macOS Monterey?这两大方法可以帮到你

苹果发布了macOS 13 Ventura的正式版系统&#xff0c;增加了许多实用性的功能&#xff0c;大家纷纷下载更新最新版本的系统。但根据许多已安装ventura的用户反馈&#xff0c;这个版本的MacOS系统还不够成熟&#xff0c;应该有不少bug还没有修复过来&#xff0c;从而求助小编分享…

c#入门-泛型约束

泛型约束 使用泛型时会假设泛型占位符是任何类型。 但因为它被假设是任何类型&#xff0c;所以使用起来有很大的限制。只有所有类型都有的功能&#xff0c;他才能用。 为了满足所有的可能类型&#xff0c;可用的操作非常少。 为此我们可以为泛型占位符添加约束。虽然会让能兼…

大型项目迭代流程

一、回顾目标 总目标&#xff1a; 年底上线完成100% 结果&#xff1a; 年底上线并开量验证过成功&#xff0c;完成率100% 阶段目标A&#xff1a; 10月底项目全流程开发完成&#xff0c;并提测出票前流程 结果&#xff1a;10月21日项目开发完成100%&#xff0c;10月25日前…

基于残差神经网络的交通标志识别算法研究与应用实现

问题&#xff1a; 从图像中识别交通标志对于自动驾驶至关重要。要想实现自动驾驶&#xff0c;车辆必须了解并遵守所有交通规则。当前&#xff0c;特斯拉、谷歌、梅赛德斯-奔驰、丰田、福特、奥迪等许多大公司都在研究自动驾驶。因此&#xff0c;为了实现这项技术的准确性&…