那些你不知道的类和对象的知识

news2024/11/19 4:15:48

在这里插入图片描述

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻推荐专栏1: 🍔🍟🌯C语言初阶
🐻推荐专栏2: 🍔🍟🌯C语言进阶
🔑个人信条: 🌵知行合一
🍉本篇简介:>:深入理解构造函数,介绍友元函数,内部类等等
金句分享:
✨努力不一定是为了钱,还有骨子里的自信与淡定✨

目录

  • 一、构造函数的深入理解
    • 1.1 初始化列表
    • 1.2 关键字:`explicit`
  • 二、`Static`成员变量/函数
    • (1)定义
    • (2)静态成员函数为什么一定要在类外面初始化:
    • 总结:
  • 三、 友元
    • (1) 友元函数
    • (2)友元类
  • 四、内部类(天生友元)

一、构造函数的深入理解

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;
};

在这里插入图片描述

答案:
并不是初始化操作,因为初始化只能初始化一次,是指变量在创建的时候被赋予的初始值.而构造函数体内可以进行多次赋值.

那成员变量是在哪里初始化的呢?
在这里插入图片描述
运行结果:

2023-2-1

类的成员变量会先经过初始化列表,再走函数体内赋值,所以month初始化为了1,后又在函数体内被重新赋值.为了2.

在构造函数的函数名参数后与{}中间的区域是成员变量初始化的地方.

初始化列表:

以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。(如上图)

(1) 初始化列表的作用:
我们未使用初始化列表之前,一直都是在函数体内赋值,那初始化列表有什么用呢?
试着看一下下面这段代码.
在这里插入图片描述
对于下列成员变量,只能使用初始化列表进行初始化,因为这些成员变量只能在定义时就给出初始化的值:

  1. const成员变量
  2. 引用成员变量
  3. 没有默认构造函数的自定义类型成员

正确写法:

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
		, pa(day)			//在初始化列表对这些特殊的成员变量初始化
		, b(2)
		,t1(6,15,20)
	{
		_month = 2;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	int& pa;
	const int b;
	Time t1;
};

(2) 初始化列表的初始化顺序与成员变量的声明有关,与写在初始化列表的顺序无关.

示例;
并不会先个c赋值,而是按a,b,c的顺序进行初始化,此时a是使用未初始化的b进行初始化,b是使用未初始化的c来初始化,最后c使用66初始化.

故结果ab都是随机值.
在这里插入图片描述

1.2 关键字:explicit

构造函数不仅可以构造与初始化对象,对于以下三种构造函数,还具有类型转换的作用。

  1. 单个参数构造函数
    示例:Test(int a )
  2. 除第一个参数无默认值其余均有默认值的构造函数.
    示例:Test(int a, int b = 66, int c = 88)
  3. 全缺省的构造函数.
    示例:Test(int a=20, int b = 66, int c = 88)

类型转换的情况展示:
在这里插入图片描述
t1=num,num将会被赋值给第一个参数.
使用 explicit后,编译器会报错.
在这里插入图片描述
C++中,关键字explicit用来修饰类的构造函数,它的作用是防止隐式类型转换。当一个类的构造函数被声明为explicit时,编译器将不会自动执行隐式类型转换,而只能进行显式类型转换。这样会提高代码的可读性,隐式类型转换可读性不好.

显示类型转换:↓
在这里插入图片描述
附上对应代码:

class Test
{
public:
	//1. 单参数构造
	//Test(int a )
	//{
	//	_a = a;
	//}
	
	//2. 除第一个参数无默认值其余均有默认值的构造函数
	//Test(int a, int b = 66, int c = 88)
	//	: _a(a)
	//	, _b(b)
	//	,_c(c)		
	//{

	//}
	
	//3. 全缺省构造
	//Test(int a = 20, int b = 66, int c = 88)
	//	: _a(a)
	//	, _b(b)
	//	, _c(c)
	//{

	//}
	explicit Test(int a=20, int b = 66, int c = 88)
		: _a(a)
		, _b(b)
		,_c(c)		
	{

	}
	void Print()
	{
		cout << _a << endl;
		cout << _b << endl;
		cout << _c << endl;
	}
private:
	int _a;
	int _b;
	int _c;
};

void test1()
{
	Test t1;
	t1.Print();
	cout << endl;
	int num = 99;
	t1 =(Test) num;
	t1.Print();
}
int main()
{
	test1();
	return 0;
}

二、Static成员变量/函数

(1)定义

静态成员变量静态成员函数是属于类而不是对象的成员。它们与类的实例对象无关,而是与整个类相关联。

静态成员变量(static member variable)是在类中使用关键字static声明的成员变量。它不属于类的任何特定实例对象,而是属于整个类。只会有一个静态成员变量的副本被共享给所有的类的实例对象。可以直接通过类名访问静态成员变量,也可以通过类的对象进行访问。

静态成员函数(static member function)是通过关键字static声明的类成员函数。与普通成员函数不同,静态成员函数不依赖于类的实例对象。它只能访问类的静态成员,不能访问非静态成员。静态成员函数可以直接通过类名进行调用,而不需要创建类的实例对象。

(2)静态成员函数为什么一定要在类外面初始化:

  1. 存储空间分配静态成员变量需要在内存中分配存储空间,类的定义只是描述了该成员变量的类型和访问方式,只是图纸,并没有分配空间。所以在类外进行初始化方便为其分配存储空间。

  2. 只能初始化一次静态成员变量属于整个类,不属于某个对象,静态成员变量在整个类的生命周期中只能被初始化一次。如果在类的定义中进行初始化,那么每个包含该类定义的文件都会进行初始化,这违背了静态成员变量只应初始化一次的原则。将初始化操作移到类外,可以确保只有一次初始化。

  3. 存储空间的链接性:将静态成员变量的初始化放在类外,可以保持存储空间的链接性。如果多个不同的源文件都包含了该类的定义并进行了初始化,链接器无法确定使用哪个初始化值,从而导致链接错误。将初始化放在类的实现文件中,可以避免链接错误。

总结:

静态成员变量静态成员函数特点如下:

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

  2. 静态成员变量必须在类外定义,类中只是声明,定义时指定类域,并且不需要static 关键字.

  3. 访问方式(前提是公有,如果是私有,需要在类中定义一个函数去返回):
    (1)类名::静态成员
    (2)对象.静态成员 (不推荐)

  4. 静态成员函数不属于某个对象,所以没有隐藏的this指针,不能访问任何非静态成员.
    在这里插入图片描述

  5. 静态成员也是类的成员,受publicprotectedprivate 访问限定符的限制

静态成员变量和静态成员函数的主要用途包括:

  1. 对象计数器:可以使用静态成员变量来实现某个类的对象的计数功能。
class Test
{
public:
	Test()//构造函数
	{
		++_count;
	}
	Test(Test& t)//拷贝构造
	{
		++_count;
	}
	~Test()
	{
		--_count;
	}

	static int GetCount()
	{
		return _count;
	}
private:
	static int _count;
};
int Test::_count = 0;

void test1()
{
	cout << Test::GetCount() << endl;
	Test t1,t2;
	Test t3(t1);
	Test t4;
	cout << Test::GetCount() << endl;
}
  1. 共享数据:静态成员变量可以用于在类的所有实例对象之间共享某些数据。
  2. 工具函数:静态成员函数可以作为工具函数,独立于对象的操作,提供一些辅助功能。

静态成员变量静态成员函数为类提供了与类相关的特性和功能,并且可以在不创建类的实例对象的情况下进行访问和使用。

  1. 静态成员函数可以调用非静态成员函数吗?

不可以,静态成员函数不能直接调用非静态成员函数。因为静态成员函数是属于类的,而非静态成员函数是属于对象的。静态成员函数没有指向具体对象的指针,因此不能访问对象的非静态成员函数和非静态成员变量。如果需要在静态成员函数中调用非静态成员函数,可以先创建一个对象,然后通过对象调用非静态成员函数。

  1. 非静态成员函数可以调用类的静态成员函数吗?

可以,非静态成员函数可以调用类的静态成员函数。静态成员函数是与类相关联的函数,而不是与类的任何特定对象相关联的函数。因此,非静态成员函数可以使用类的静态成员函数,因为静态成员函数不依赖于特定对象的存在。

在这里插入图片描述

三、 友元

(1) 友元函数

当我们需要实现流运算符重载时,会出现一个比较尴尬的问题,那就是第一个参数被this指针占据,且无法改变,这就造成左操作数是对象,调用起来十分别扭.

示例:

class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		_month = 2;
	}
	//第一个参数被this指针占据了,所以ostream& _cout只能作为右操作数,则调用起来就很别扭.
	ostream& operator<<(ostream& _cout)
	{
		_cout << _year << "-" << _month << "-" << _day << endl;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};
void test1()
{
	Date d1;
	d1 << cout;//别扭的调用
}
int main()
{
	test1();
	return 0;
}

由于类的成员函数第一个参数被this指针占据,所以流运算符重载只能写成全局函数,但是全局函数无法访问类中的私有成员,为了能够在类的外面也可以访问类中的私有成员.
友元函数的出现,以朋友的身份,去家(类)里参观.

class Date
{
public:
	friend ostream& operator<<(ostream& _cout, const Date& d);//友元函数只是一个声明,不受public,private等访问限定符影响,是在类外面的定义的.
	Date(int year = 2023, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		_month = 2;
	}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)//这是类外面的函数,没有this指针
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

void test1()
{
	Date d1;
	cout<<d1<<endl;//顺眼的调用
}

这么说吧.友元函数是类的关系户,类外面别的函数都受类域的限制,不能访问类中的私有成员和保护成员,但是友元函数却可以,一个特殊的存在,由于这样操作破坏了类的封装性,我们建议少使用友元.

小结:

  • 友元函数可访问类的私有(private)和保护(protect)成员,但友元函数不属于类,不是类的成员函数.
  • 友元函数不能用const修饰
  • 因为友元函数不属于类,所以不受public,private等访问限定符影响,只是一个声明,在类中的哪出现都可以.
  • 友元函数的调用与普通函数的调用原理相同

(2)友元类

前面介绍了友元函数,那类也可以是类的友元.

  1. 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
  2. 但是友元关系是单向的,不具有交换性。
    示例:如果Date类是Time类的友元,即在Time类中声明,Date是他的朋友.
    则可以在Date类中直接访问Time类的私有成员变量,但是在Time类中是无法访问Date类中的私有成员的.
  3. 友元关系不能传递.
    如果B是A的友元,C是B的友元,则不能说明C时A的友元.就比如.
    你朋友的朋友不一定是你的朋友.
class Time
{
public:
	friend class Date;//友元类只是一个声明,不受public,private等访问限定符影响,是在类外面的定义的.
	Time(int hour=6, int minute=30, int second=30)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}
	void Test()
	{
		cout << d1._year;//报错,无法访问,因为Date类并没有声明Time是自己的友元类
	}
private:
	int _hour;
	int _minute;
	int _second;
	Date d1;
};
class Date
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		//可以访问,因为Time类声明了Date是它的友元类
		cout << t1._hour << "-" << t1._minute << "-" << t1._second << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time t1;
};
void test1()
{
	Date d1;
	d1.Print();
}

四、内部类(天生友元)

如果一个类A它定义在另外一个类B的里面(内部),则类A是类B的内部类.

外部类对内部类没有任何特权,但是内部类却是外部类的天生友元.

class Date//外部类
{
public:
	Date(int year = 2023, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
	}

private:
	int _year;
	int _month;
	int _day;
	static int a;
public:
	class Time//内部类
	{
	public:
		void Test(const Date& d1)
		{
			cout << d1._year << "-" << d1._month << "-" << d1._day << endl;//是外部类的天生友元,可以访问外部类的私有成员
			a = 5;//可以直接访问外部类的静态成员变量
		}
	};
};
int Date::a = 3;

内部类的特点:

  1. 内部类可以定义在外部类的publicprotectedprivate中皆可,访问时受域作用限定符的限制.
  2. 外部类并不是包括内部类,即sizeof(外部类)=外部类,内部类只是在外部类的类域中定义,并不占空间.
  3. 内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。

C++中内部类用的并不多.

本篇到此结束,觉得不错的小伙伴可以三连支持一下.谢谢.
在这里插入图片描述

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

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

相关文章

vscode运行python报错:ModuleNotFoundError:No module named ‘xxx‘

在乌班图上使用pycharm的时候&#xff0c;pycharm总是莫名其妙卡死&#xff0c;又说是搜狗输入法的锅&#xff0c;又说别的原因&#xff0c;一气之下不用pycharm,转到vscode上&#xff0c;没想到出现了如下报错。 就是vscode在运行python的时候&#xff0c;自定义模块的调用无…

springboot配置文件的使用

目录 1.application.properties是springboot默认的配置文件&#xff0c;但是比较繁琐&#xff0c;一般用.yml文件 2. 配置文件的作用 3.配置文件的使用 1.application.properties是springboot默认的配置文件&#xff0c;但是比较繁琐&#xff0c;一般用.yml文件 ①、properti…

【设计模式】模板模式

什么是模板模式&#xff1f; 模板方法模式&#xff08;Template Method Pattern&#xff09;&#xff0c;又叫模板模式(Template Pattern)&#xff0c;在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现&#xff0c;但调用将以抽象类中定义的方式进行…

LeetCode--HOT100题(25)

目录 题目描述&#xff1a;141. 环形链表&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;141. 环形链表&#xff08;简单&#xff09; 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连…

SpringBoot集成极光推送完整实现代码(建议收藏)

工作中经常会遇到服务器向App推送消息的需求&#xff0c;一般企业中选择用极光推送的比较多&#xff0c;在集成极光时发现极光的文档并不完整&#xff0c;网上的文章也很多不能直接使用&#xff0c;这里列出我在工作中集成极光的全部代码&#xff0c;只需要按照如下代码保证一次…

Error: Design has unresolved cell reference

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 所有的unresolved cell reference问题都是cell信息没读到引起的&#xff0c;在dc/pt里就是db没读到&#xff0c;在ICC2里就是ndm没读。 ICC2中午饭这个问题可以report_design_…

MyBatisPlus解决逻辑删除与唯一索引的兼容问题

需求背景 比如有张用户表,在插入或者更新数据的时候&#xff0c;我们需要 用户名称(username),不能重复。 我们首先考虑的是给该字段创建唯一索引 <span style"color:#4b4b4b"><span style"background-color:#ffffff"><code class"…

JavaWeb-能制作中小型网站企业管理系统(适合快速梳理)

导言 第一章 Web前端开发 一、初始web前端 二、HTML、CSS介绍 三、VsCode安装 四、标签 1 实现标题--排版 2 实现标题--样式 颜色表示 CSS选择器 3 实现标题--超链接 4 实现标签--正文 5 页面布局 盒子模型 布局标签 6 表格、表单标签 表格标签 表单标签 表单项 …

Electron学习1 安装环境与第一个程序

Electron学习1 安装环境与第一个程序 一、 Electron 简介二、安装 nvm三、安装nodejs四、安装nrm五、安装electron1. npm 初始化2. 创建 package.json3. 安装electron4. 创建一个页面5. 创建文件main.js6. 创建预加载器文件 preload.js7. 启动程序 六、打包 一、 Electron 简介…

大模型在金融医疗、生命系统和物理仿真领域的创新应用探索

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 在当今迅速发展的科技领域&#xff0c;大模型技术正日益成为金融医疗、生命系统和物理仿真等领域中的重要工具。2023年6月16日&#xff0c;AI TIME举办的青年科学家大模型专场活动邀请了国防科技大学理学院数学…

springboot+vue智能化网络电子相册图片管理系统_84ds3

随着计算机技术发展&#xff0c;计算机系统的应用已延伸到社会的各个领域&#xff0c;大量基于网络的广泛应用给生活带来了十分的便利。所以把智能化电子相册与现在网络相结合&#xff0c;利用计算机搭建智能化电子相册系统&#xff0c;实现智能化电子相册的信息化。则对于进一…

超级浏览器与指纹浏览器:功能与特点的比较

导语&#xff1a;随着互联网的快速发展&#xff0c;隐私和安全问题日益受到关注。在这个背景下&#xff0c;超级浏览器和指纹浏览器作为定制化浏览器的两个重要类型&#xff0c;各自具有独特的功能和特点。本文将对超级浏览器和指纹浏览器进行比较&#xff0c;帮助读者更好地理…

微信小程序nodejs+vue+uniapp校运会高校运动会报名管理系统

3.1小程序端 小程序登录页面&#xff0c;用户也可以在此页面进行注册并且登录等。 登录成功后可以在我的个人中心查看自己的个人信息或者修改信息等 在广播信息中我们可以查看校运会发布的一些信息情况。 在首页我们可以看到校运会具体有什么项目运动。 在查看具体有什么活动我…

linux I/O性能优化

Linux 文件系统 磁盘和文件系统的关系&#xff1a; 磁盘为系统提供了最基本的持久化存储。 文件系统则在磁盘的基础上&#xff0c;提供了一个用来管理文件的树状结构。 文件系统工作原理 索引节点和目录项 文件系统&#xff0c;本身是对存储设备上的文件&#xff0c;进行组织…

【腾讯云 Cloud Studio 实战训练营】使用 Cloud Studio 快速构建 Vue + Vite 完成律师 H5 页面

【腾讯云 Cloud Studio 实战训练营】使用 Cloud Studio 快速构建 Vue Vite 完成律师 H5 页面 前言一、基本介绍1.应用场景2.产品优势 二、准备工作1.注册 Cloud Studio2.进入 Vue 预置开发环境 三、使用 Cloud Studio 快速构建 Vue Vite 完成律师 H5 页面1.安装相关依赖包2.主…

【FAQ】安防监控视频EasyCVR平台分发的FLV视频流在VLC中无法播放

众所周知&#xff0c;TSINGSEE青犀视频汇聚平台EasyCVR可支持多协议方式接入&#xff0c;包括主流标准协议国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。在视频流的处理与分发上&#xff0c;视频监控…

C语言---数据结构实验---哈夫曼树及哈夫曼编码的算法实现---图的基本操作

文章目录 写在前面哈夫曼树及哈夫曼编码的算法实现实验内容代码实现 图的基本操作实验内容代码实现 写在前面 本篇实验代码非本人写&#xff0c;代码源自外部&#xff0c;经调试解决了部分warning和error后在本地vs上可以正常运行&#xff0c;如有运行失败可换至vs 未来会重构…

将朴素矩阵乘法在共享内存中分块,每个线程只计算结果矩阵中的单个元素

kenel的block中的每个线程用于计算共享内存中矩阵Pd中的一个元素Pd_(i&#xff0c;j)&#xff0c;每个线程都读取Md的一行和Nd的一列。Pd_(0&#xff0c;0)和Pd_(1,0)两个结果是由两个线程完成的。这里一开始只有Pd被加载进共享内存&#xff0c;Md和Nd还在全局内存中&#xff1…

嵌入式软件测试方法-质量模型

软件测试评估质量的时候用到的很多测试度量项 质量大师朱兰提出了“质量管理三部曲”&#xff0c;来对企业质量进行管理。 第一部曲&#xff1a;质量策划&#xff0c;致力于制定质量目标并规定必要的运行过程、准备相关资源以实现质量目标。 第二部曲&#xff1a;质量控制&am…

【java】【经验】java: 错误: 不支持发行版本 6

前言&#xff1a;配置过maven之后&#xff0c;发现原来的一些项目运行提示java: 错误: 不支持发行版本 6或者java: 错误: 不支持发行版本 5&#xff0c;主要原因&#xff1a;是因为项目使用的Java版本和安装的Java版本不符合 目录 1 设置项目java版本 2 设置模块版本 3 set…