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

在这里插入图片描述

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻推荐专栏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.hqwc.cn/news/58725.html

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

相关文章

JavaScript中的交互的方式alert,prompt,confirm的用法

一.alert的用法 1.alert 它会显示一条信息,弹出的这个带有信息的小窗口被称为模态窗。“modal” 意味着用户不能与页面的其他部分&#xff08;例如点击其他按钮等&#xff09;进行交互&#xff0c;直到他们处理完窗口。在上面示例这种情况下 —— 直到用户点击“确定”按钮。 …

【TiDB理论知识08】HATP概述

1 HTAP技术 OLTP 在线事务 支付 转账 高并发 每次操作的数据量少 &#xff0c;行存 OLAP 报表分析 每次操作大量数据 列存储 2 传统解决方案 数据抽取到数仓或者数据湖 ETL有延迟 &#xff0c;一般会有T1 T2 数据多副本 3 HTAP的要求 4 TIDB的HTAP架构 TiFlash特点&…

RocketMQ 事务消息

事务消息是 RocketMQ 的高级特性之一 。这篇文章&#xff0c;笔者会从应用场景、功能原理、实战例子三个模块慢慢为你揭开事务消息的神秘面纱。 1 应用场景 举一个电商场景的例子&#xff1a;用户购物车结算时&#xff0c;系统会创建支付订单。 用户支付成功后支付订单的状态…

【刷题笔记8.9】LeetCode题目:两数相加

LeetCode题目2&#xff1a;两数相加 题目及描述 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设…

商城-学习整理-基础-商品服务API-属性分组(七)

目录 一、创建系统菜单二、开发属性分组1、将三级分类功能抽取出来2、编写后端代码3、属性分组新增功能4、属性分组修改回显功能 三、品牌管理1、分页显示有点问题&#xff0c;使用MyBatis-Plus有点问题&#xff0c;需要使用分页插件&#xff0c;给容器中放一个2、修改模糊查询…

支付整体架构

5.4 支付的技术架构 架构即未来&#xff0c;只有建立在技术架构设计良好的体系上&#xff0c;支付机构才能有美好的未来。如果支付的技术体系在架构上存在问题&#xff0c;那么就没有办法实现高可用性、高安全性、高效率和水平可扩展性。 总结多年来在海内外支付机构主持和参与…

Fortinet安全专家问答实录|如何防护暴力破解、撞库攻击

黑客攻防&#xff0c;一个看似神秘&#xff0c;但却必不可缺的领域。近期&#xff0c;全球网络与安全融合领域领导者Fortinet&#xff08;Nasdaq&#xff1a;FTNT&#xff09;&#xff0c;开启了Fortinet DEMO DAY系列实战攻防演练线上直播&#xff0c;让人人都能零距离观摩黑客…

回顾 OWASP 机器学习十大风险

日复一日&#xff0c;越来越多的机器学习 (ML) 模型正在开发中。机器学习模型用于查找训练数据中的模式&#xff0c;可以产生令人印象深刻的检测和分类能力。机器学习已经为人工智能的许多领域提供了动力&#xff0c;包括情感分析、图像分类、面部检测、威胁情报等。 数十亿美…

【C++】开源:ceres和g2o非线性优化库配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍ceres和g2o非线性优化库配置使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&…

文件传输软件的市场现状和未来趋势

文件传输软件是一种能够在不同计算机之间高效、便捷、安全地传送各种类型的文件的应用软件。它是计算机领域中的一项重要技术&#xff0c;涉及到网络通信、数据加密、文件管理等多个方面。随着互联网和移动互联网的发展&#xff0c;文件传输软件的市场需求也日益增大&#xff0…

2.5D游戏是如何做出来的呢,2.5D游戏快速制作教程

前言 【Unity实战篇 】 | 如何制作一款2.5D游戏&#xff0c;2.5D游戏制作案例一、2.5D 游戏概念二、绘制地图三、添加玩家动画和移动等操作四、视角配置4.1 调整摄像机与场景对象的角度4.2 增加镜头旋转功能 五、游戏效果展示 总结 前言 玩过游戏的朋友都知道&#xff0c;市面…

uni-app 封装api请求

前端封装api请求 前端封装 API 请求可以提高代码的可维护性和重用性&#xff0c;同时使得 API 调用更加简洁和易用。 下面是一种常见的前端封装 API 请求的方式&#xff1a; 创建一个 API 封装模块或类&#xff1a;可以使用 JavaScript 或 TypeScript 创建一个独立的模块或类来…