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.xdnf.cn/news/1544552.html

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

相关文章

【Oauth2整合gateway网关实现微服务单点登录】

文章目录 一.什么是单点登录&#xff1f;二.Oauth2整合网关实现微服务单点登录三.时序图四.代码实现思路1.基于OAuth2独立一个认证中心服务出来2.网关微服务3产品微服务4.订单微服务5.开始测试单点登录 一.什么是单点登录&#xff1f; 单点登录&#xff08;Single Sign On&…

权威期刊Cell Discovery新成果!上海交大洪亮团队提出CPDiffusion模型,超低成本、全自动设计功能型蛋白质

蛋白质是生命活动的主要执行者&#xff0c;其结构与功能之间的关系一直是生命科学领域研究的核心议题。近年来&#xff0c;随着深度学习的兴起&#xff0c;借助其强大的数据处理能力&#xff0c;让模型学习蛋白质序列、结构及其功能之间的映射关系&#xff0c;设计出具备更高稳…

prithvi WxC气象模型

NASA发布了prithvi WxC气象模型发布 Prithvi是NASA开源的模型&#xff0c;被誉为全球最大的开源地理空间大模型。昨天晚上逛X平台&#xff0c;我看到Prithvi模型又来了新成员&#xff1a;prithvi WxC。 NASA和IBM创建了一个基于MERRA-2数据的天气和气候AI基础模型—Prithvi Wx…

数据库事务索引视图、存储过程

目录 文章目录 一、数据库事务 事务概述&#xff1a; 事务特征&#xff1a; 提交回滚 隔离级别 二、索引 索引创建原则&#xff1a; 存储引擎 单列索引 组合索引 全文索引 空间索引 三、视图 四、函数和存储过程 1.函数的创建 2.存储过程 3.调用格式 4.两者…

初识爬虫8

1.selenium的作用和工作原理 2. 使用selenium&#xff0c;完成web浏览器调用 # -*- coding: utf-8 -*- # 自动化测试工具&#xff0c;降低难度&#xff0c;性能也降低 from selenium import webdriverdriver webdriver.Edge()driver.get("https://www.itcast.cn/")…

高德2024全民出行节启动,联合生态伙伴发放百亿补贴

临近十一黄金周&#xff0c;高德地图宣布启动“2024全民出行节” &#xff0c;致力于打造出门好生活体验&#xff0c;让用户十一出行“和囧途说ByeBye”。今年十一黄金周期间&#xff0c;高德地图联合生态伙伴在百大行业推出百亿补贴&#xff0c;涵盖吃、住、行、游、购、娱。同…

netty编程之那么多的网络框架为啥非选你?

写在前面 java nio框架不止一种&#xff0c;为啥非选netty&#xff1f;本文来看下。 1&#xff1a;正文 网络io框架&#xff0c;除了netty外&#xff0c;还有mina&#xff0c;sun grizzly&#xff0c;cindy等&#xff0c;为啥独选netty。 mina netty和mina作者同属一人&…

CAN协议一致性测试——深入浅出理解CAN协议(四)

本系列是在同公司硬件设计、验证的同事1、在完成了CANFD硬件接口IP开发 2、熟悉ISO-11898系列、ISO16845、CAN2.0协议、CANFD协议等以及大量学习资料 3、深入研究其他家CANFD IP&#xff08;NXP、BOSCH&#xff09;4、独立开发了对应底层驱动 5、通过CANoe和周立功CAN分析仪完成…

类的难疑点

一、知识点 1、类的属性和对象属性&#xff08;实例属性&#xff09; shuxing"123" self.shuxing"123" 2、类的对象 self.loginMyclass() loginMyclass() 3、访问类属性和方法的操作 通过“类名.属性”访问&#xff1a;Myclass.shuxing 通…

筋膜炎能自愈吗

筋膜炎能否自愈&#xff0c;主要取决于病情的严重程度以及患者的自我管理和治疗情况。 一、轻微筋膜炎的自愈可能性 对于轻微筋膜炎患者&#xff0c;通过合理的日常护理和适当的运动锻炼&#xff0c;有很高的自愈可能性。这包括&#xff1a; 1、充分休息&#xff1a;避免过度…

【BetterBench博士】2024华为杯C题:数据驱动下磁性元件的磁芯损耗建模 Python代码实现

题目 【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析 【BetterBench博士】2024年中国研究生数学建模竞赛 E题&#xff1a;高速公路应急车道紧急启用模型 问题分析 【BetterBench博士】2024年中国研究生数学建模竞赛 C题&#xff1a;数据驱动…

React 理解 re-render 的作用、概念,并提供详细的例子解释

一、什么是 re-render 在 React 中 re-render&#xff08;重新渲染&#xff09; 是经常发生的行为&#xff0c;主要确保视图要时刻保持最新的数据来呈现。 但每次发生 re-render 也是有代价的&#xff0c;比如数据状态、focus 焦点、表单数据、都得重置&#xff0c; 遇到代码…

Linux ping c实现

linux下ping程序的c实现 #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdint.h> #include <netdb.h> #include <arpa/inet.h> #include <unistd.h> #include <st…

LoongArch 个人赛一级评测(前递旁路+load阻塞)

目录 LoongArch 个人赛一级评测&#xff08;前递旁路load阻塞&#xff09;声明测试说明 代码修改thinpad_top.vconver_ram.vIF_stage 测试自动评测在线实验 踩坑记录读写使能控制inout类端口的使用方法Vivado生成Bit流文件时出现[Synth 8-91] ambiguous clock in event control…

file zilla server安装以后,client连接,账号登录成功,但是读取目录失败的处理

1、下载 2、连接 开始一直报错&#xff0c;server1.9.1的版本&#xff0c;后来直接下载了汉化版本进行安装&#xff1a; 服务端 - FileZilla中文网 3、报错&#xff1a;登录正常&#xff0c;但是读取目录失败&#xff0c;一开始也是这个错误&#xff0c;后来改成安装低版本的&a…

python之装饰器、迭代器、生成器

装饰器 什么是装饰器&#xff1f; 用来装饰其他函数&#xff0c;即为其他函数添加特定功能的函数。 装饰器的两个基本原则&#xff1a; 装饰器不能修改被装饰函数的源码 装饰器不能修改被装饰函数的调用方式 什么是可迭代对象&#xff1f; 在python的任意对象中&#xff…

Mac使用技巧-来自苹果专人在线辅导服务3

真的太喜欢上苹果专人在线辅导课程了&#xff01;每次感觉都满满收获&#xff01; 目录 一、手势 1.1三指拖移 1.2四指上推 1.3五指快速进入启动台 二、程序坞里的app 1.如何将程序坞中不需要/不常用的app移除&#xff1f; 2.当鼠标放在将程序坞中app时&#xff0c;图标…

到底该不该做副业?这些真相你必须知道!零基础入门到精通,收藏这一篇就够了

天哪&#xff01;根据最新调查显示&#xff0c;超过60%的职场人士正在考虑或已经开始从事副业。 那么&#xff0c;究竟该不该做副业&#xff1f; 第一部分&#xff1a;副业的好处 1. 增加收入 做副业最直接的好处就是可以增加收入。 在经济压力日益增大的今天&#xff0c;…

STMCubeMx——C8T6的串口调试、接收与发送

一、stmCubeMX串口通信的步骤 1、新建一个文件&#xff0c;选择自己的芯片 2、配置时钟 3、配置串口 串口的模式解析可以跳转到以下文章查看 stmCubemx——配置串口时的几种模式-CSDN博客在STM32CubeMX中配置串口&#xff08;USART或UART&#xff09;时&#xff0c;可以设置…

Python连接Kafka并收发数据

目录 一、Kafka 二、发送端&#xff08;生产者&#xff09; 三、接收端&#xff08;消费者&#xff09; 一、Kafka Apache Kafka 是一个开源流处理平台&#xff0c;由 LinkedIn 开发&#xff0c;并于 2011 年成为 Apache 软件基金会的一部分。Kafka 广泛用于构建实时的数据…