类和对象(下)--- 初始化列表、explicit、友元、static、匿名对象和内部类

        本篇将会对类和对象的主要知识收尾,先会对构造函数进行补充,分别补充了构造函数体赋值、初始化列表、explicit 关键字,然后介绍 static 成员知识以及友元、内部类还有匿名对象等知识点,目录如下:

目录

1. 构造函数补充

1.1 构造函数体赋值

1.2 初始化列表

1.3 explicit 关键字

2. static 成员

3. 友元

3.1 友元函数

3.2 友元类

4. 内部类

5. 匿名对象

1. 构造函数补充

        前面由于对于默认函数的知识还不够充分,所以就没有将构造函数的剩余部分加上,接下来将会把构造函数的所有知识都进行补充完毕。

1.1 构造函数体赋值

        在创建对象时,编译器通过调用构造函数,给对象中的各个成员变量一个合适的初始值。虽然通过调用构造函数对象就已经存在一个初始值,但是我们不能将其称为对对象中的成员变量进行初始化构造函数体中的语句只能将其称之为赋初值,而不能称为初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

        如下:

class Date {
public:Date(int year, int month, int day) {_year = year;_month = month;_day = day;// 在调用一次构造函数体时确实将函数进行了多次赋值_year = 1;}
private:int _year;int _month;int _day;
};

1.2 初始化列表

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

        首先我们先需要探讨为什么需要初始化列表:

        如上,当我们在成员变量中定义了一个由 const 修饰的成员变量的时候,我们可以发现,不管是编译器默认生成的构造函数还是由自己定义的成员函数来说,都编译不成功。这是因为有些成员变量必须在定义的时候初始化,所以我们需要在某个地方将这些必须在定义的时候初始化的变量进行初始化,这就诞生出了初始化列表

        初始化列表的形式如下:

class A
{
public:// 定义以下的构造函数,使得不会存在默认的构造函数A(int a):_a(a){}
private:int _a;
};class Date {
public:Date(int year, int month, int day, int& x):_year(year),_month(month),_day(day),_p((int*)malloc(sizeof(int)))  // 同样在初始化列表中对指针进行分配空间,_num(1),_ref(x),_a(1){ //... 函数体中也可以在次进行赋值_year = 1;}
private:int _year = 1;  // _year的缺省值,其实是初始化列表的缺失值,//当初始化列表对_year进行处理int _month;		// 那么就不会走_year = 1int _day;int* _p;// 必须走初始化列表const int _num;int& _ref;A _a;  // 没有默认构造的类,有默认构造的类可以不初始化
};

        以上就是初始化列表的一般使用形式了,其中,仍然存在一些需要注意的点:

        1. 每个成员变量只能在初始化列表中出现一次,但是可以在函数体中可以出现多次

        2. 类中包含这些成员,必须放在初始化列表中:引用成员变量、const 成员变量、自定义类型成员(无默认构造函数)

        对于初始化列表的使用

        尽量选择在初始化列表出就将所有成员变量进行初始化,因为无论构造函数是否存在初始化列表,都是会进行初始化列表的操作,虽然成员变量未定义在初始化列表中,但还是会进行操作,不管给的是一些随机值。

        初始化列表进行初始化的顺序,在初始化列表的初始化中,并不是根据从上而下的初始化顺序,而是按照成员变量声明的顺序进行初始化的,所以建议在初始化列表中,按照成员变量声明的顺序写下来

1.3 explicit 关键字

        构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造参数,还具有类型转换的作用(C++11标准后对于多参数同时具有类型转换的作用)。对于这句话的解释如下:

        如上图,对于 a1 来说,我们将3传入进去进行拷贝构造,然后 a1 中的 _a 变量就被赋值为了3,但是当我们将 4 直接赋值给 a2,又是如何进行赋值的呢,因为2作为一个常量,并不属于 A 类型的对象,为什么可以调用类似拷贝构造一样的赋值呢?

        这是因为在对 a2 进行赋值的时候,先会对2进行类型转换,也就是将2构造成一个a的零时对象,然后将 a2 与该临时对象进行调用拷贝构造(也许在Debug的时候,发现并不会调用拷贝构造,那是因为编译器将其优化了,同一个表达式连续步骤的构造,很可能会合二为一),所以最后 a2 中的成员变量被拷贝成功。(但是最好将a2需要赋值的常量数字设置为与a2成员对象相同类型的变量,比如a2的成员变量为int型,那么最好传入一个int型的对象,最好不要传入其他类型的参数,有些编译器可能会报错)

        对于 a3 来说,当成员变量为什么常引用也可以赋值成功呢,这是因为对2进行类型转化的时候生成的是一个临时变量,临时变量具有常性

        当一个类的成员变量有多个时,该如何进行构造呢?如下:

        当类中的成员变量有多个时,我们可以使用类似数组的形式将其赋值给对象(该形式是C++11标准之后才有的形式)。

        当我们不想使用常量直接赋值给一个对象的时候,我们可以使用 explicit 关键字修饰构造函数,这样就不会进行类型转换后调用拷贝构造。如下:

        所以,使用 explicit 修饰的构造函数,将会禁止构造函数的隐式转化

2. static 成员

static 成员的概念:

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

        静态成员变量所具有的特点之一就是为所有成员共享,不属于某个具体的对象,那我们如何计算一个类在程序中创建了多少个类对象呢。如下程序:

class A {
public:A(int x = 0, int z = 0):_a(x),_ca(z){_num++;}A(const A& a) {_a = a._a;_ca = a._ca;_num++;}static int GetNum() {return _num;}
private:int _a;int _ca;static int _num;
};int A::_num = 0;int main() {A a1(3,5);A a2 = {4, 6};const A& a3 = {5, 7};cout << a1.GetNum() << endl;return 0;
}

        如上程序,就可以计算出一个类构造了多少个对象。

        那么 static 成员的特点又有哪一些呢:

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

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

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

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

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

3. 友元

        友元提供了一种突破封装的方式,可以让我们在类外也使用被 private 修饰的变量,但是友元会增加耦合度,破坏封装,所以在很多时候并不建议使用友元。

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

3.1 友元函数

        当我们尝试使用 operator 操作符去构造 operator<< 重载成员函数。虽然确实可以做到,但是因为 cout 的输出流对象和隐含的 this 指针在抢占第一个参数的位置(若使用 operator<< ,那么就是 this 指针占据第一个参数),但实际使用的过程中,我们应该使用 cout 作为第一个参数,所以我们不得不将 operator 重载成全局函数。虽然将 operator 重载成全局函数,但会导致类外没有办法访问类中被 private 修饰的成员,这个时候就需要使用友元来解决问题。

        如下:

        当我们在类域中定义时,就只能将其写成这个样子,但是这并不是 cout 的使用风格,这个时候我们就需要将 operator<< 在类外定义,然后在类域中进行友元声明,如下:

        如上,当我们使用友元修饰一个函数之后,友元函数就可以直接访问类的私有成员了,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加 friend 关键字

        对于友元函数的说明:

        1. 友元函数可以访问类的私有保护成员,但不是类的成员函数

        2. 友元函数不可以使用 const 修饰

        3. 友元函数可以在类定义的任何地方声明不受类访问限定符限制

        4. 一个函数可以是多个类的友元函数

        5. 友元函数的调用和普通函数的调用相同

3.2 友元类

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

        但是对于友元类来说,存在以下几点特性:

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

        2. 友元关系不具有传递性,如 a 是 b 的友元,b 是 c 的友元,a 不是 c 的友元

        3. 友元关系不能继承。

如下:

class Time
{friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){}
private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接访问时间类私有的成员变量,想要访问,需要在 Date 类中存在 Time 类的对象_t._hour = hour;_t._minute = minute;_t._second = second;}
private:int _year;int _month;int _day;Time _t;
};

        对于如上的函数而言,当我们想要在 Date 类中定义 Time 类的友元时,那么 Time 中也可以访问 Date 类中的成员对象了,但是我们需要注意一个先后的问题,假若我们想要在 Time 中定义与 Date 对象有关的函数时,只能在类中进行声明,而不能进行定义,因为在使用函数时,会在函数以上找有关 Date 对象的定义,而不会向下寻找,所以这时候会报错,解决办法就是将声明与定义分离。

4. 内部类

        内部类:若一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

如下就是一个内部类:

class A {
public:A(int y =0, int yy = 0):_a(y),_aa(yy){}// 内部类class B {public:void Print(){ // ...}B(int x = 0):_b(x){}private:int _b;};private:int _a;int _aa;
};int main() {A::B b(1);A a(1,2);b.Print();return 0;
}

        如上所示,A 中 B 就是一个内部类,将 B 定义在 A 类中,若想要定义一个 B 类型的对象,我们就需要先通过类 A 加上作用域限定符然后才能访问到类 B。但是若我们想要计算一下 A 类占据的内存大小呢?如下:

        如上图,我们计算出来的结果显示为 8,仅仅只是 A 的两个成员变量的大小。这是因为在 A 中即使定义了一个类,计算大小时,也并不会计算这个类的大小,因为 B 只是在 A 中的一个声明,当 B 被编译器编译后,其实什么也不会产生。

        内部类的特性:

        1. 内部类天生就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的成员。

        2. 内部类可以定义在外部类的 public、private、protected

        3. 内部类可以直接访问外部类中的 static 成员,不需要外部类的对象。

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

5. 匿名对象

        在Cpp存在的匿名对象就是只定义一次,并且匿名对象的生命周期也是只在定义的这一行中,走过这一行之后声明周期也就结束了。如下:

        当我们 Debug 该程序的时候,发现走过匿名对象之后,该对象就被销毁了。匿名对象的定义方式如上两种,类(),或类(成员变量的参数)。

        匿名函数的作用为:当我们只想使用类中的某个成员函数时,我们就可以直接创建一个匿名对象,然后通过该匿名对象调用该函数

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

免费AI开源引擎:在电力电网工单系统中智能解析的应用场景

电力系统作为国家基础设施的重要组成部分&#xff0c;其稳定性和可靠性对经济社会发展具有至关重要的影响。随着智能电网技术的发展&#xff0c;如何高效、准确地处理电网工单&#xff0c;成为提升电网运维管理水平的关键。自然语言处理&#xff08;NLP&#xff09;技术在电力工…

想学电子维修,没基础怎么学?

如果你没有电子维修的基础&#xff0c;学习起来可能会有些挑战&#xff0c;但并不意味着不能学会。以下是一些建议&#xff0c;帮助你从零开始学习电子维修&#xff1a; 从了解基本概念 首先&#xff0c;要补充电子学的基本概念&#xff0c;比如电流、电压、电阻、电容、电感等…

扫码预约登记怎么做?表单二维码在线制作技巧

现在提交预约信息时&#xff0c;很多场景下已不再使用原来传统的纸质登记方式&#xff0c;而采用扫码填写信息的方式来登记预约信息。这种方式除了能够有效的降低成本之外&#xff0c;还能够让更多用户能够同时填写自己的信息&#xff0c;解决需要需要排队填写的复杂操作&#…

Clickhouse-表引擎探索之MergeTree

引言 前文曾说过&#xff0c;Clickhouse是一个强大的数据库Clickhouse-一个潜力无限的大数据分析数据库系统 其中一个强大的点就在于支持各类表引擎以用于不同的业务场景。 MergeTree MergeTree系列的引擎被设计用于插入极大量的数据到一张表当中。数据可以以数据片段的形式一…

kubernetes用户权限管理详解——普通用户[kubeconfig]

原文&#xff1a; 学一下https://suxueit.com/article_detail/tdVymI4BWZdDRfKqnv1y K8s 的用户分为两类 普通用户&#xff1a;普通用户是指集群外部的人或系统管理&#xff0c;它们不由 Kubernetes 直接管理。普通用户的证书、密钥和权限管理通常由外部系统&#xff08;如企业…

科创新格局·共赢双循环“2024上海智能科技与创新展览会”

2024上海智能科技与创新展览会&#xff0c;将于6月中旬在上海新国际博览中心隆重召开。作为一场盛大的科技盛会&#xff0c;此次展览会将汇聚科技前瞻趋势&#xff0c;融合产业贸易优势&#xff0c;布局初创投资赛道&#xff0c;提供全方位场景生态的跨界合作&#xff0c;构建“…

算法之美:B+树原理、应用及Mysql索引底层原理剖析

B树的一种变种形式&#xff0c;B树上的叶子结点存储关键字以及相应记录的地址&#xff0c;同等存储空间下比B-Tree存储更多Key。非叶子节点不对关键字记录的指针进行保存&#xff0c;只进行数据索引 , 树的层级会更少 , 所有叶子节点都在同一层, 叶子节点的关键字从小到大有序排…

Linux基本指令篇

在前边&#xff0c;我们已经了解过了Linux操作系统的发展和应用&#xff0c;从该篇起&#xff0c;就正式进入对Linux的学习。 今天我们就来在Xshell上远程登录我们的云服务器。首先我们要知道自己云服务器的公网ip&#xff0c;然后修改一下密码。 点击跳转 修改完密码之后我们…

跑通飞浆平台的MTMCT 跨镜跟踪示例

想跑通飞浆平台的MTMCT跨镜跟踪示例&#xff0c;真的是难上加难啊&#xff01; 改了几处代码&#xff0c;可以顺利跑通了&#xff0c;特此记录&#xff1a; 第一处&#xff1a;不要拉主线的代码&#xff0c;改成 !git clone https://gitee.com/paddlepaddle/PaddleDetection…

【unity】如何汉化unity Hub

相信大家下载安装unity后看着满操作栏的英文&#xff0c;英文不好的小伙伴们会一头雾水。但是没关系你要记住你要怎么高速运转的机器进入中国&#xff0c;请记住我给出的原理&#xff0c;不懂不代表不会用啊。现在我们就来把编译器给进行汉化。 第一步&#xff1a;我们打开Uni…

pytorch-tpu/llama推理优化之input prompt bucketing

数据更新&#xff1a; python脚本&#xff08;注意分支&#xff09;&#xff1a; HLO图分析KV-Cache更新&#xff1a; KV-Cache作为HLO图的输入输出&#xff1a;bf16[1,2048,32,128]{3,2,1,0} 128x, 2x32x2 参考链接 notes for transformer introduction by an Italian t…

引领向量数据库技术新变革,Milvus 2.4 正式上线

备受关注的 Milvus 2.4 正式上线! 作为向量数据库赛道的领军者,Zilliz 一直致力于推动向量技术的进步与创新。本次发布中,Milvus 新增支持基于 NVIDIA 的 GPU 索引—— CUDA 加速图形索引(CAGRA),突破了现有向量搜索的能力。 GPU 索引是向量数据库技术中的重要里程碑,…