C++的第一道门坎:类与对象(三)

news2024/11/22 17:48:19

目录

一.再谈构造函数

1.1构造函数体赋值

1.2初始化列表

1.3explicit关键字

二.static成员

2.1概念

​编辑

2.2特性

三.友元

3.1友元函数

3.2友元类

4.内部类


一.再谈构造函数

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初始化列表

如何初始化呢?这就需要用到我们的初始化列表了。

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

作用是会将括号内的内容初始化给成员变量

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

	}

需要注意的是:初始化列表需要写在构造函数的函数体外面,参数列表的后面。 

下面提几个注意点

1.在这里需要注意的是,有一些成员必须放在初始化列表内进行初始化

  • 引用成员变量
  • const成员变量
  • 自定义类型成员且该类没有默认构造函数(即没有不传参就可以用的构造函数)
class A
{
public:
	A(int a)
		:_a(a)
	{

	}
private:
	int _a;
};
class B
{
public:
	B(int a,int ref)
		:_ref(ref)
		,_obj(10)
		,_i(a)
	{}
private:
	int& _ref;//无默认构造
	A _obj;//引用
	const int _i;//静态
};

在这里,它们都需要我们使用初始化列表来进行初始化。 

2.初始化列表是怎么工作的呢?他是按照什么顺序给成员初始化的呢?

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

 猜一下,这段程序会打印出来什么东西?

我们发现,_a2正常打印了,但是_a1却是随机值,这是为什么呢?

这时因为初始化列表是按照声明顺序进行初始化的,也就是说,它会先初始化_a2,再初始化_a1,而由于_a2是用_a1初始化的,而_a1此时还没有初始化,因此_a2打印结果是随机值。

3.尽量使用初始化列表初始化,因为不管是否使用初始化列表初始化,对于自定义类型的成员,一定会优先使用初始化列表初始化。

class Time
{
public:
	Time(int hour = 0)
		:_hour(hour)
	{
		cout << "我用了初始化列表哦" << endl;
	}
private:
	int _hour;
};
class Date
{
public:
	Date(int day)
	{}
private:
	int _day;
	Time _t;
};
int main()
{
	Date a(10);
	return 0;
}

在这段代码中,Date类中含有一个Time类的成员,但是并没有在构造函数中对Time类成员初始化或赋初值,那么这段代码会运行吗?运行结果是什么呢?

 

可以看到,虽然我们没有对Time类成员进行操作,依旧使用了初始化列表对其初始化。 

1.3explicit关键字

构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,还具有类型转换的作用。

class A {
public:
     A(int value) { 
        // 初始化代码
    }
};

void func(A a) {
    // 函数体
}

int main() {
    func(10);
    return 0;
}

在这段代码中,func函数中的调用了A类型的参数,但我们在main函数中传递了一个int类型的参数。但是这段代码是可以通过编译的,这时因为单个参数的构造函数具有隐式类型转换的作用。 

可以从上图看出,单个参数是有类型转换的作用的,这里我们可以将整型的10隐式转化为A类型

那么,如何禁止类型转换呢?

这就需要用到我们的explicit关键字了,它的作用是禁止隐式类型转换。

由于C++设计者认为多个参数的情况下隐式类型转换比较安全,所以一般都在单个参数的情况下使用。

在传递的是一个参数的前提下,我们可以使用explicit关键字,譬如以下场景:

  • 构造函数只有一个参数
  • 构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值
  • 全缺省构造参数

这里我们放出了两种上述情况,请大家注意,这里为了便于大家看就都没有注释掉,如果大家要复制到自己的编译器实践的话,需要注释掉一个。

class Date
{
public:
//注意,以下情况要注释掉一个。
	//1.只有一个参数
		explicit Date(int year)
		:_year(year)
	{}
	// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递
	explicit Date(int year, int month = 1, int day = 1)
	: _year(year)
	, _month(month)
	, _day(day)
	{}

	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2022);
	d1 = 2023;//实际编译器背后会用2023构造一个临时对象,最后用临时对象给d1对象进行赋值
}

注释掉一个的话,也会编译失败,这是因为我们使用了explicit关键字,我们无法将2023隐式转换为Date类型。 

二.static成员

2.1概念

声明时加上static的类成员称为类的静态成员;

用static修饰的成员变量称之为静态成员变量;

用static修饰的成员函数称之为静态成员函数。

静态成员变量要在类外初始化。

做一道题:

现在我们实现一个类,要求计算程序中创建出了多少个类对象。

class A
{
public:
	A()
	{
		++_count;
	}
	~A()
	{
		--_count;
	}
	static int getcount()
	{
		return _count;
	}
private:
	static int _count;
};
int A::_count = 0;
int main()
{
	cout << A::getcount() << endl;
	A a;
	A b;
	cout << A::getcount() << endl;
	return 0;
}
2.2特性
  • 静态成员存放在静态区,不属于某个具体的对象,是所有类对象共享
  • 静态成员变量必须在类外定义定义时不添加static关键字,类中只是声明
  • 类静态成员用类名::静态成员对象.静态成员来访问
  • 静态成员函数中没有隐含的this指针,不可用于访问类内的非静态成员。(没有this)
  • 静态成员也受到访问限定符的限制

三.友元

友元是一种突破封装的方式,有时它很便利,有时会增加耦合度,从而破坏封装,所以友元不宜多用。

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

3.1友元函数

如果我们尝试去重载operator<<操作符,就会发现一个问题,我们无法将其重载为成员函数。

为什么呢?我们根据代码来进行理解:

//<<有两个操作数,分别是cout和d1.
正常使用:cout<<d1;
//但是重载为成员函数,我们的第一个参数就是*this
//我们使用起来就要这么使用了:d1<<cout或者d1.operator<<(&d1,cout)
//这样是不符合我们的使用习惯的

 那么,我们就只能将其实现在类外,那么就又产生了一个新的问题,我们应该如何访问类内的私有成员呢?

这就需要用到我们的friend了,friend是朋友的意思,既然都是朋友了,我用用你的私有物品也没关系的吧?

因此,被friend修饰的函数,可以访问到类内的私有成员

class Date
{
	//友元函数不受访问限定符的限定
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& _cout,const Date& d)
{
	_cout << d._year << d._month << d._day;
	return _cout;
}

istream& operator>>(istream& _cin, Date& d)
{

	_cin >> d._day >> d._month >> d._year;
	return _cin;
}

 说明:

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

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

下面我们用代码来实践一下:

//Time类
class Time
{
	//声明友元于B
	friend class Date; 
public:Time(int hour = 0, int minute = 0, int second = 0)
	: _hour(hour)
	, _minute(minute)
	, _second(second)
{}
private:
	int _hour;
	int _minute;
	int _second;
};
//Date类
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)
	{
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

友元类有以下性质:

  • 友元关系是单向的,不具有交换性。
  • 友元关系不具有传递性
  • 友元关系不可继承

通俗的形容:你把我当朋友,但我不一定把你当朋友我把你当朋友;

                    你把他当朋友,但是我可能不喜欢他,我不会跟他做朋友。

4.内部类

概念:如果一个类定义在另一个类的内部这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

注意点:

  •  内部类可以定义在外部类的public、protected、private都是可以的。
  •  注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  • sizeof(外部类)=外部类,和内部类没有任何关系。

我们可以用如下代码来实践: 

class A
{
private:
	static int k;
	int h;
public:
	class B//B是A的友元
	{
	public:
		void foo(const A& a)
		{
			cout << k << endl;
			cout << a.h << endl;
		}
	};
};
int A::k = 1;//静态
int main()
{
	A::B b;
	b.foo(A());
	return 0;
}

博主的C++专栏:点击进入CPP专栏

码字不易,如果你觉得博主写的不错,给个三联关注评论把!!! 

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

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

相关文章

使用KEPServer连接欧姆龙PLC获取对应标签数据(标签值类型改为字符串型)

1.创建通道&#xff08;通道&#xff09;&#xff0c;&#xff08;选择对应的驱动&#xff0c;跟当前型号PLC型号对应&#xff09;。 2.创建设备&#xff0c;&#xff08;填入IP地址以及欧姆龙的默认端口号&#xff1a;44818&#xff09; 3.创建对应的标签。这里关键讲诉下字…

【VSCode实战】转换大小写快捷键

今天在VSCode Insiders上编码&#xff0c;突然想将某常量转换为大写。按照virtual studio的习惯&#xff0c;我Ctrl Shift U没有效果&#xff0c;Ctrl U也没效果。网上搜了搜&#xff0c;原来VSCode Insiders没有这个默认功能。 而VSCode Insiders这么强大怎么可能没有大小…

Keras深度学习框架实战(1):图像分类识别

1、绪论 1.1 图像分类的定义 图像分类是计算机视觉领域中的一项基本任务&#xff0c;其定义是将输入图像分配给预定义类别中的一个或多个。具体来说&#xff0c;图像分类系统接受一个图像作为输入&#xff0c;并输出一个或多个类别标签&#xff0c;这些标签描述了图像中的内容…

pytorch 指定GPU的几种方法

在使用PyTorch时,你可以通过多种方式指定和使用GPU。以下是一些常见的方法: 1. 使用torch.device torch.device是PyTorch中用于指定设备(CPU或GPU)的对象。你可以通过将张量移动到指定设备来使用GPU。 import torch# 检查是否有可用的GPU device = torch.device("…

光子芯片:突破算力瓶颈的新希望

引言 在现代计算机科学中&#xff0c;计算能力的提升是推动技术进步的核心动力。然而&#xff0c;随着摩尔定律的逐渐失效&#xff0c;传统电子芯片的算力提升面临瓶颈。光子芯片作为一种新兴技术&#xff0c;因其高频率、低损耗和高信噪比的优点&#xff0c;正成为突破算力瓶…

抖音太可怕了,我卸载了

这两天刷短视频&#xff0c;上瘾了&#xff0c;太可怕了。 自己最近一直在研究短视频制作&#xff0c;所以下载了抖音&#xff0c;说实话&#xff0c;我之前手机上并没有抖音&#xff0c;一直在用B站。 用了两天抖音&#xff0c;我发现&#xff0c;这玩意比刷B站还容易上瘾啊…

打工人福音⚡:公牛充电交互协议,建议收藏!

分享《一套免费开源充电桩物联网系统&#xff0c;是可以立马拿去商用的&#xff01;》 协议原文件下载地址&#xff1a; 链接: https://pan.baidu.com/s/1kW15Nfe9cjPDFLGPYJ-zUg?pwdagq2 提取码: agq2 1 总则 1.1 协议概述 本协议适用于公司所有充电产品包括交直流充电桩、…

SDK开发

为什么需要Starter&#xff1f; 理想情况:开发者只需关心调用哪些接口&#xff0c;传递哪些参数就跟调用自己写的代码一样简单。 开发starter的好处&#xff1a;开发者引入之后&#xff0c;可以直接在application.yml中写配置&#xff0c;自动创建客户端。 starter开发流程 …

bhyve:FreeBSD下的原生虚拟机管理器

hbyve简介 自 FreeBSD 10.0-RELEASE 起&#xff0c;BSD 许可的 bhyve 虚拟机管理器已成为底层系统不可或缺的一部分。bhyve 强大而灵活&#xff0c;支持多种客户机操作系统&#xff0c;涵盖 FreeBSD、OpenBSD 以及多个 Linux 发行版。在默认配置下&#xff0c;bhyve 提供对串行…

中建环能 | “农村生活污水治理稳质增效与智能运维技术研究及成套装备应用” 科技成果评价

中华环保联合会组织召开了中建环能科技股份有限公司申请的“农村生活污水治理稳质增效与智能运维技术研究及成套装备应用”技术成果评价会。会议由中华环保联合会水环境治理专业委员会秘书长刘愿军主持。 评审会委员 本次评价会邀请了7位相关专业领域的专家组成专家评价委员会。…

Meterpreter工具使用

Meterpreter属于stage payload&#xff0c;在Metasploit Framework中&#xff0c;Meterpreter是一种后渗透工具&#xff0c;它 属于一种在运行过程中可通过网络进行功能扩展的动态可扩展型Payload。这种工具是基于“内存DLL注 入”理念实现的&#xff0c;它能够通过创建一个新进…

RocketMq broker源码解析

broker 集群工作流程 NameSrv启动成功后&#xff0c;等待broker、Consumer和producer启动后也与NameSrv保持长连接, NameSrv相当于是路由控制中心。启动broker, broker与所有的NameSrv建立长连接, broker&#xff0c;通过定时线程定时向NameSrv发送心跳&#xff0c;broker信息…

mysql中子查询的语法和执行过程

大家好。我们在日常开发过程中&#xff0c;肯定都经常用到了子查询。今天我们就来聊一下mysql中子查询的一些语法以及子查询的执行过程。 一、子查询的语法 首先在开讲之前&#xff0c;我们先创建t1、t2两张表&#xff0c;并分别在表中插入三条数据&#xff0c;方便我们下面内…

JDK环境配置、安装

DK环境配置&#xff08;备注&#xff1a;分32位与64位JDK&#xff0c;32位电脑只能按照32位JDK&#xff0c;64位电脑兼容32、64位JDK&#xff09; 一、检查自己电脑是否安装过JDK 1.在电脑屏幕左下角&#xff0c;输入命令提示符CMD&#xff0c;打开命令提示符应用 2.在打开界…

AI视频下载:ChatGPT数据科学与机器学习课程

ChatGPT是一个基于OpenAI开发的GPT-3.5架构的AI对话代理。作为一种语言模型,ChatGPT能够理解并对各种主题生成类似人类的响应,使其成为聊天机器人开发、客户服务和内容创作的多用途工具。 此外,ChatGPT被设计为高度可扩展和可定制的,允许开发人员对其响应进行微调并将其集成到…

[stm32]——定时器与PWM的LED控制

目录 一、stm32定时器 1、定时器简介 2、定时器分类 3、通用定时器介绍 二、PWM相关介绍 1、工作原理 2、PWM的一般步骤 三、定时器控制LED亮灭 1、工程创建 2、代码编写 3、实现效果 四、采用PWM模式&#xff0c;实现呼吸灯效果 1、工程创建 2、代码编写 3、实现效果 一、stm3…

跨境电商多店铺:怎么管理?风险如何规避?

跨境电商的市场辽阔&#xff0c;有非常多的商业机会。你可能已经在Amazon、eBay、Etsy等在线平台向潜在客户销售产品了。为了赚更多的钱&#xff0c;你可能还在经营多个店铺和品牌。 但是&#xff0c;像Amazon、eBay、Etsy等知名平台会有自己的规则&#xff0c;他们开发了很多…

风电功率预测 | 基于TCN-GRU时间卷积门控循环单元的风电功率预测(附matlab完整源码)

完整代码 clc; clear close allX = xlsread(风电场预测.xlsx); X = X(5665:8640,:); %选取3月份数据 num_samples =

linux系统——性能检测工具glances

在linux系统中&#xff0c;由python开发的glances工具是一个功能强大的性能检测工具 可以通过yum进行安装 安装glances后&#xff0c;进入命令界面 glance支持网站模式&#xff0c;将监控到的数据以网站形式显示出来 这里需要用python包管理命令 使用glances -w开放…

STM32 HAL库USART的接收数据方法实现(STM32Cube_FW_F1_V1.8.5)

目录 概述 1 使用STM32Cube生成项目 1.1 软件版本信息 1.2 配置串口相关参数 1.3 生成工程 2 问题描述 3 解决问题 3.1 修改代码 3.2 编写新的回调函数 4 测试 概述 本文主要介绍STM32 HAL库USART的接收数据方法实现&#xff0c;笔者使用的HAL库为STM32Cube_FW_F1_V1.…