Cpp类和对象(下)(6)

文章目录

  • 前言
  • 一、初始化列表
    • 概念
    • 使用注意
    • 实际运用
    • explicit关键字
    • 初始化列表的总结
  • 二、static成员
    • static成员的概念
    • static成员的特性
    • static的一个实用场景
  • 三、友元
    • 友元函数
    • 友元类
  • 四、内部类
    • 概念
    • 特性
  • 五、匿名对象
  • 六、再次理解封装和面向对象
  • 总结


前言

  Hello,本篇应该是类和对象系列里面较为轻松的一篇了,尤其是在经历了中篇的洗礼之后(,但是不可掉以轻心,要说这个下篇比上篇简单,那也是没有的
  继续加油!


一、初始化列表

概念

  前文我们讲构造函数的时候,提了一嘴初始化列表,不如我们先来看一下其概念吧:

以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式

我们来对比一下,应该能让你有更加深刻的认识:

首先是我们之前的初始化方式

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

 这种方法赋初值当然有效,可是严格意义上来说并不能称其为初始化,因为初始化只能初始化一次,而函数体内却可以多次赋值

现在再来看看用初始化列表来赋初值

#include <iostream>
using namespace std;class Date
{
public://构造函数Date(int year = 1900, int month = 1, int day = 1):_year(year),_month(month),_day(day){}
private:int _year;int _month;int _day;
};

使用注意

  1. 每个成员变量在初始化列表中只能出现一次
    原因是初始化只能进行一次,所以同一个成员变量在初始化列表中不能多次出现

  2. 类中包含以下成员,必须放在初始化列表进行初始化
    引用成员变量、const成员变量、自定义类型成员(该类没有默认构造函数)
    对于引用成员变量,引用类型的变量在定义时就必须给其一个初始值,所以引用成员变量必须使用初始化列表对其进行初始化
    对于const成员变量,被const修饰的变量也必须在定义时就给其一个初始值,也必须使用初始化列表进行初始化
    对于自定义类型成员(该类没有默认构造函数),若一个类没有默认构造函数,那么我们在实例化该类对象时就需要传参对其进行初始化,否则就会报编译错误

  3. 尽量使用初始化列表初始化
    因为语法理解上初始化列表可以认为是每个成员变量定义初始化的地方,所以无论你是否使用初始化列表,都会走这么一个过程

  4. 成员变量在类中声明的次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后顺序无关
    我后面会举个具体例子说明,但是建议你声明顺序和初始化列表顺序保持⼀致。

实际运用

这个真的很复杂,要梳理清楚很困难

先来看一连串的代码吧:

int a = 10;
int& b = a;// 创建时就初始化const int a = 10;// correct 创建时就初始化
const int b;// error 创建时未初始化class A //该类没有默认构造函数 
{
public:A(int val) //注:这个不叫默认构造函数(需要传参调用){_val = val;}
private:int _val;
};class B
{
public:B():_a(2021) //必须使用初始化列表对其进行初始化{}
private:A _a; //自定义类型成员(该类没有默认构造函数)
};

以上代码很好的解释了为什么三种特殊情况下必须要用初始化列表来初始化

接着看以下代码:

// 使用初始化列表
int a = 10// 在构造函数体内初始化(不使用初始化列表)
int a;
a = 10;// 对于自定义类型使用初始化列表
class Time
{
public:Time(int hour = 0){_hour = hour;}
private:int _hour;
};class Test
{
public:// 使用初始化列表Test(int hour):_t(hour)// 调用一次Time类的构造函数{}// 在构造函数体内初始化(不使用初始化列表)Test(int hour)//初始化列表调用一次Time类的构造函数(不使用初始化列表但也会走这个过程){ Time t(hour);// 调用一次Time类的构造函数_t = t;// 调用一次Time类的赋值运算符重载函数}
private:Time _t;
};

 通过以上代码,你就明白了为什么说尽量使用初始化列表来初始化,其目的就是为了节省效率
 更明确的说对于内置类型,两种方式没差,如上;对于自定义类型,因为不管如何都会走一遍初始化列表,所以直接在初始化列表初始化能最大化地提高效率,如上

再看如下代码:

#include<iostream>
using namespace std;class A
{
public:A(int a):_a1(a),_a2(_a1){}void Print() {cout << _a1 << " " << _a2 << endl;}
private:int _a2 = 2;int _a1 = 2;
};
int main()
{A aa(1); // _a1是1,_a2是随机值aa.Print();
}

 我们分析下为什么会是这个输出,其实,你只要记着上面说的,成员变量的初始化跟定义顺序有关系,跟初始化列表的出现顺序毫无关系,一切就明白了

首先初始化列表里_a1,_a2都有明确的初始化值,于是缺省失效,然后我们再来看定义顺序,_a2先定义,那么_a2先初始化,初始化为_a1的值,可是这时候_a1并没有被赋值,只能是随机值,而紧接着_a1开始初始化,被赋予我们传过去的a,即为1

 其实在这里,我们还提到了缺省值的概念,我们也可以来研究一下,其实我觉得你也可以自己写个程序,跑个调试,看一看缺省值、初始化列表之间的关系

#include<iostream>
using namespace std;class Test
{
public:Test():_a(2){}void GetRet(){cout << _a << endl;}
private:int _a = 1;
};int main()
{Test().GetRet(); // 这个涉及到匿名对象,我们等下会讲return 0;
}

最后输出_a的值为2,其实你调试一下就会发现一些巧妙的地方
_a一开始为1,后面才为2,最后才输出,所以,这其中奥妙就显然了

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

explicit关键字

先来看两行代码:

int a = 1;
double& b = a; // err,内置类型变量在发生类型转换的时候会生成一个临时的常性变量

其实,内置类型也可以转换成自定义类型,这里就和构造函数扯上关系了

一个类的构造函数,不仅起到初始化成员变量的作用,对于单个参数或第一个参数无缺省值的半缺省构造函数来说,它还具有类型转换的作用。(其实多个参数也行,只是要在C++11标准后,用{ }括起来内置类型即可)

在这里插入图片描述
如下,第一个a1构造函数无可厚非,第二个首先1被作为参数构造了一个临时常性变量,再拷贝构造给a2

有什么办法可以禁止构造函数类型转换呢?
有,引入explicit关键字,在构造函数的前面加上它,即可禁止类型转换了

explicit A(int a)

初始化列表的总结

前面讲了那么多,下面用一张表来尝试捋清一下
在这里插入图片描述
一言以蔽之:
无论是否显示写初始化列表,每个构造函数都有初始化列表!
无论是否在初始化列表显式初始化,每个成员变量都要走初始化列表初始化!

二、static成员

static成员的概念

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

static成员的特性

一、静态成员为所以类对象所共享,不属于某个具体的对象
 举例来说,请看以下代码:

#include <iostream>
using namespace std;class Test
{
private:static int _n;
};int main()
{cout << sizeof(Test) << endl; // 1return 0;
}

  结果显示计算Test类的大小为1,因为静态成员 _n 是存储在静态区的,属于整个类,也属于类的所有对象。所以计算类的大小或是类对象的大小时,静态成员并不计入其总大小之和

二、静态成员变量必须在类外定义,定义时不添加static关键字

class Test
{
private:static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;

  这里静态成员变量 _n 虽然是私有,但是我们在类外突破类域直接对其进行了访问。这是一个特例,不受访问限定符的限制,否则就没办法对静态成员变量进行定义和初始化了,本质上是因为_n属于整个类,不单独属于某个类的对象

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

class Test
{
public:static void Fun(){cout << _a << endl; //error不能访问非静态成员cout << _n << endl; //correct}
private:int _a; //非静态成员static int _n; //静态成员
};

其实,含有静态成员变量的类,一般含有一个静态成员函数,用于访问静态成员变量

四、静态成员和类的普通成员一样,也有public、private和protected这三种访问级别
 所以当静态成员变量设置为private时,尽管我们突破了类域,也不能对其进行访问

你不妨思考一下以下两个问题
1、静态成员函数可以调用非静态成员函数吗?
2、非静态成员函数可以调用静态成员函数吗?

答案是:
问题1:不可以。因为非静态成员函数的第一个形参默认为this指针,而静态成员函数中没有this指针,故静态成员函数不可调用非静态成员函数
问题2:可以。因为静态成员函数和非静态成员函数都在类中,在类中不受访问限定符的限制

static的一个实用场景

 请问如何实现⼀个类,计算程序中创建出了多少个类对象?

请看如下代码:

#include<iostream>
using namespace std;class A
{
public:A(){++_scount;}A(const A& t){++_scount;}~A(){--_scount;}static int GetACount(){return _scount;}
private:// 类⾥⾯声明static int _scount;
};// 类外⾯初始化
int A::_scount = 0;int main()
{cout << A::GetACount() << endl;A a1, a2;A a3(a1);cout << A::GetACount() << endl;cout << a1.GetACount() << endl;// 编译报错:error C2248: “A::_scount”: ⽆法访问 private 成员(在“A”类中声明)//cout << A::_scount << endl;return 0;
}

三、友元

  这个我们前面讲解日期类的时候提到过,输出日期类的时候,因为d1 << _cout 与我们的常用习惯不符合,所以我们把<<操作符重载为全局函数,可这样就无法访问到日期类的成员变量,于是,我们采用了友元这一方案,现在我们来详细学习下这一概念

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

友元函数

  友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字

如上,我们再来回顾以下代码:

class Date
{// 友元函数的声明friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
// <<运算符重载
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "-" << d._month << "-" << d._day<< endl;return out;
}
// >>运算符重载
istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}

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

友元类

  和友元函数类似,我们也可以在类中声明一个友元类,友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的非公有成员
在这里插入图片描述

请注意:
友元关系是单向的,不具有交换性 -> 比如上面,Date类是Time类的友元,所以可以直接在Date类中访问Time类的私有成员变量;但是不代表Time类是Date类的友元,不能在Time类中访问Date类的私有成员变量

就像爱情一样,“执子之手,与子偕老”实为可遇不可求
相比之下,“我本将心向明月,奈何明月照沟渠”才是人间常态

友元关系不能传递 -> 例如A是B的友元,B是C的友元,不代表A就是C的友元了
友元关系不能继承 -> 这里在讲到继承后再给大家详细介绍

四、内部类

概念

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

请注意:
1、此时的内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象区调用内部类。
2、外部类对内部类没有任何优越的访问权限。
3、内部类就是外部类的友元类,即内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

特性

一、 内部类受外部类的类域限制
假如我们想创建一个内部类类型的变量,需要用作用域限定符
在这里插入图片描述

二、外部类的大小不包括内部类

在这里插入图片描述

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

五、匿名对象

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

  匿名对象的特点在于,它的生命周期只在这一行,一旦程序走到了下一行,就会自动调用析构函数销毁,且在创建的时候是不用取名字的

在这里插入图片描述
所以对于各种一次性的对象创建,我们都可以使用匿名对象

六、再次理解封装和面向对象

  C++是基于面向对象的程序,面向对象有三大特性:封装、继承、多态

  C++通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起。通过访问限定符的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,外部用户不需要知道,知道了有些情况下也没用,反而增加了使用或者维护的难度,让整个事情复杂化。

所以说,封装的本质是一种更高效的管理

举个例子,我们来看一下火车站
 售票系统:负责售票—用户凭票进入,对号入座。
 工作人员:售票、咨询、安检、保全、卫生等。
 火车:带用户到目的地

乘客不需要知道火车的构造、票务系统是如何运作的,只要能正常方便的应用即可,知道了反而还增加管理成本,而工作人员的协调配合,才能使得让大家坐车井然有序的进行
从这可以看出,面向对象实际上是在模拟世界,这个思路打通了计算机世界和现实世界的桥梁
在这里插入图片描述


总结

  怎么样!是不是感概终于撑过来了,现在终于可以缓口气了,接下来的内存管理会相对较为简单,且能让你在本篇的一些疑惑得到更好的解答,所以休整一下,我们马上继续出发!

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

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

相关文章

『玉竹』基于Laravel 开发的博客、微博客系统和Android App

基于 Laravel 和 Filament 开发, 使用 Filament 开发管理后台&#xff0c;前端比较简洁。 博客大家都清楚是什么东西&#xff0c;微博客类似于微博之类的吧&#xff0c;有时候想要写的东西可能只有几句话&#xff0c;想要起个标题都不好起。 为了是微博客功能更好用&#xff0c…

【小程序】微信小程序课程 -3 快速上手之常用方法

目录 1、 对话框 1.1 模态对话框 1.2 消息对话框 2、 存储 2.1 同步 2.1.1 同步保存数据 2.1.2 同步获取数据 2.1.3 同步删除数据 2.1.4 同步清空数据 2.2 异步 2.2.1 异步保存数据 2.2.2 异步获取数据 2.2.3 异步删除数据 2.2.4 异步清空数据 3、 上拉加载更多…

Java类加载揭秘:从加载过程到双亲委派机制

类的加载 目标 能够理解字节码加载过程 【了解】 路径 类的加载过程类的加载时机 类的加载 当程序在运行后&#xff0c;第一次使用某个类的时候&#xff0c;会将此类的class文件读取到内存&#xff0c;并将此类的所有信息存储到一个Class对象中 说明&#xff1a;Class对象…

金仓数据库 KingbaseES参考手册 (8. 函数(九))

8.299. SCALE 用法&#xff1a; scale(numeric)功能&#xff1a; SCALE返回参数的精度&#xff08;小数点后的位数&#xff09;。 例子&#xff1a; SELECT scale(8.41);8.300. SCORE 用法&#xff1a; SCORE(lable number)输入参数&#xff1a; lable&#xff1a;表示第几个co…

js发送邮件至指定邮箱功能实现方式和技巧?

js发送邮件至指定邮箱的教程&#xff1f;怎么使用Node.js发信&#xff1f; 无论是用户反馈、订单确认还是密码重置&#xff0c;js发送邮件至指定邮箱的需求无处不在。AokSend将深入探讨js发送邮件至指定邮箱的实现方式和技巧&#xff0c;帮助开发者更好地理解和应用这一功能。…

windows桌面管理软件推荐:一键整理桌面!美化电脑桌面小助手!

windows桌面管理软件推荐来咯&#xff01;在繁忙的工作和生活中&#xff0c;一个整洁、有序的电脑桌面不仅能提升工作效率&#xff0c;还能带来愉悦的视觉体验。然而&#xff0c;随着文件的增多&#xff0c;桌面往往变得杂乱无章。幸运的是&#xff0c;市面上有许多优秀的Windo…

推荐一款开源的Redis桌面客户端

TinyRDM 是一个现代化的、轻量级的跨平台 Redis 桌面客户端&#xff0c;能在 Mac、Windows 和 Linux 系统上使用。它有着现代化的设计风格&#xff0c;界面既简洁又清晰&#xff0c;操作起来方便又高效。不管是刚开始接触的新手&#xff0c;还是经验丰富的开发者&#xff0c;都…

C++标准库类——string类

引言 在c中&#xff0c;string类的引用极大地简化了字符串的操作和管理&#xff0c;相比 C 风格字符串&#xff08;char*或cahr[]&#xff09;&#xff0c;std::string 提供了更高效和更安全的字符串操作。接下来让我们一起来深入学习string类吧&#xff01; 1.string 的构造…

Spring Cache的使用

一、简介 1. Spring Cache是Spring提供的一个缓存框架&#xff0c;在Spring3.1版本开始支持将缓存添加到现有的spring应用程序中&#xff0c;在4.1开始&#xff0c;缓存已支持JSR-107注释和更多自定义的选项。 1. Spring Cache利用了**AOP**&#xff0c;实现了基于注解的缓存…

【解密 Kotlin 扩展函数】命名参数和默认值(十三)

导读大纲 1.0.1 命名参数1.0.2 默认参数值 上一节讲述如何自定义 joinToString 函数来代替集合的默认字符串表示 文末遗留下几个待优化问题–传送门 1.0.1 命名参数 我们要解决的第一个问题涉及函数调用的可读性 例如,请看下面的joinToString调用: joinToString(collection,&…

【LLM多模态】Animatediff文生视频大模型

note AnimateDiff框架&#xff1a;核心是一个可插拔的运动模块&#xff0c;它可以从真实世界视频中学习通用的运动先验&#xff0c;并与任何基于相同基础T2I的个性化模型集成&#xff0c;以生成动画。训练策略&#xff1a;AnimateDiff的训练包括三个阶段&#xff1a; 领域适配…

spark之不同序列化对比

一&#xff0c;spark的rdd的序列话不同介绍 下面是使用不同序列化后的占用资源和数据大小 2&#xff0c;sparksql中序列化的区别 sparksql中使用序列化和不使用差别不大&#xff0c;英文sparksql中默认使用了encode自己实现的序列化方法&#xff0c;加上与不加序列化差别不大…

基于真实山地场景下的超多目标优化算法求解无人机三维路径规划,MATLAB代码

超多目标优化算法是一类专门用于解决存在三个以上目标函数的最优化问题的算法。这类问题在现实世界中非常常见&#xff0c;例如在工程设计、资源管理、机器学习等领域。由于目标之间的冲突性&#xff0c;很难找到一个单一的解来同时优化所有目标&#xff0c;因此超多目标优化算…

MQ高级(二):死信交换机--延迟消息及DelayExchange插件--超时订单案例实现

目录 1.延迟消息 1.1.死信交换机和延迟消息 1.1.1.死信交换机 1.1.2.延迟消息 1.2.DelayExchange插件 1.2.1.下载 1.2.2.安装 1.2.3.声明延迟交换机 1.2.4.发送延迟消息 1.3.超时订单问题 1.3.1.定义常量 1.3.2.配置MQ 1.3.3.改造下单业务&#xff0c;发送延迟消息…

【Linux篇】TCP/IP协议(笔记)

目录 一、TCP/IP协议族体系结构 1. 数据链路层 &#xff08;1&#xff09;介绍 &#xff08;2&#xff09;常用协议 ① ARP协议&#xff08;Address Resolve Protocol&#xff0c;地址解析协议&#xff09; ② RARP协议&#xff08;Reverse Address Resolve Protocol&…

详解Web测试和APP测试的区别

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 最近听到有些朋友说&#xff0c;移动端要比web端稍微难一些&#xff0c;涉及到的细节笔记要多&#xff0c;有转去做web测试的想法&#xff0c;看看在具体测试的…

秋招面试准备:《小米2024数字芯片岗面试题》

在数字芯片设计的浪潮中&#xff0c;验证工程师的角色愈发重要。他们如同守门人&#xff0c;确保每一块芯片在投入市场前都能稳定、高效地运行。小米&#xff0c;作为全球知名的智能设备制造商&#xff0c;对数字芯片岗位的人才选拔尤为严格。 本文分享《小米2024数字芯片岗面…

专属文生图助手——SD3+ComfyUI文生图部署步骤

SD3ComfyUI文生图部署步骤 我们使用DAMODEL来实现文生图的部署。 根据提供的操作步骤与代码段落&#xff0c;本文旨在介绍如何下载并部署 Stable Diffusion 3 模型&#xff0c;并通过 ComfyUI 架构实现基于 Web 界面的图像生成应用。本文将剖析各个步骤&#xff0c;并详细解释…

[Redis][Hash]详细讲解

目录 0.前言1.常见命令1.HSET2.HGET3.HEXISTS4.HDEL5.HKEYS6.HVALS7.HGETALL8.HMGET9.HLEN10.HSETNX11.HINCRBY12.HINCRBYFLOAT 2.内部编码1.ziplist(压缩链表)2.hashtable(哈希表) 3.使用场景4.缓存方式对比1.原⽣字符串类型2.序列化字符串类型3.哈希类型 0.前言 在Redis中&am…

同一网络下两台电脑IP一样吗?探究局域网内的IP分配机制

在日常生活和工作中&#xff0c;我们经常会在同一网络环境下使用多台电脑。这时&#xff0c;一个常见的问题就会浮现&#xff1a;同一网络下两台电脑IP一样吗&#xff1f;这个问题看似简单&#xff0c;但实际上涉及到局域网内的IP分配机制。本文将深入探讨这一问题&#xff0c;…