回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

这篇具有很好参考价值的文章主要介绍了回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

💛前情提要💛

本章节是C++深度剖析封装细节&特性的相关知识~

接下来我们即将进入一个全新的空间,对代码有一个全新的视角~

以下的内容一定会让你对C++有一个颠覆性的认识哦!!!

以下内容干货满满,跟上步伐吧~


作者介绍:

🎓 作者: 热爱编程不起眼的小人物🐐
🔎作者的Gitee:代码仓库
📌系列文章&专栏推荐: 《刷题特辑》、 《C语言学习专栏》、《数据结构_初阶》 、《C++轻松学_深度剖析_由0至1》、《Linux - 感受系统美学》

📒我和大家一样都是初次踏入这个美妙的“元”宇宙🌏 希望在输出知识的同时,也能与大家共同进步、无限进步🌟
🌐这里为大家推荐一款很好用的刷题网站呀👉点击跳转



💡本章重点

  • 探索构造函数的奥秘&新型初始化方式

    • 初始化列表

    • 缺省值

  • 探索C++对类的“神奇优化”

  • 了解匿名对象的概念

  • 认识并深入了解“友元”

  • 认识并深入了解“内部类”


🍞一.回炉&剖析构造函数

💡构造函数:

  • 在前文(可>点击<跳转食用呀)对构造函数的铺垫下,我们继续细致深入了解构造函数内部的细节

👉让我们来意义揭晓构造函数还有什么不为人知的秘密吧~


🥐Ⅰ.初始化列表

🔥初始化&赋初值: 在了解新知识前,我们先来回忆一下这部分内容,以便后续的更好深入

  • 初始化:简单来说就是在定义一个变量的时候对其值进行初始化赋值,就叫做变量的初始化,Eg:

    • int a; 为定义了一个变量a,其中初始化的过程则由编译器将其初始化成随机值

    • int a = 0; 为定义了一个变量a,且数值被初始化成数值0

  • 赋初值:简单来说就是人为定义一个变量的初始值是多少,即给一个变量进行赋值

💡构造函数初始化:

  • 在之前的学习中,我们已经了解了构造函数中的其中一种初始化方式:函数体内赋值

    即在构造函数内,我们显示的为成员变量赋上初值

    回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

  • 特别注意: 上述过程只能称作赋初值,并不能称作初始化,这是因为成员变量已经在进入函数体内进行赋值前定义了,即当成员变量进入到函数体内时,已经有一个初值【编译器初始化的随机值】,这也就是为什么函数体内的赋值语句只能称作赋初值,而不是初始化

  • 总结: 初始化只能初始化一次,而构造函数体内可以多次赋值

  • 但有一种独特的方式,可以达到给成员变量初始化:初始化列表初始化

👉初始化列表

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

  • Eg:回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

👆补充:

  • 对于类来说,只要没有被实例化出对象,就没有真正给成员变量开辟空间

  • 也就是说,我们书写的类中的成员变量仅仅是声明而已,并没有真正被被定义出来

  • 只有在用类实例化出一个对象的时候,编译器才真正开辟了这个类的空间,而成员变量也在此时被定义出来且同时完成了初始化【若没有显示定义构造函数(传参构造……),则编译器会调用默认构造函数(即不用传参的构造函数:全缺省构造函数、无参构造函数、编译器默认生成的)】

🌰举个例子:

回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

  • 如上图,若成员为const所修饰的变量的时候,我们是无法实例化出一个对象的

  • 原因就在于: 我们无法通过默认构造函数中的函数体内赋值,去给成员变量const int _n赋上一个值,这是因为变量被const所修饰,我们只有在这个 变量定义的时候才可以初始化它的值 ,否则在后续代码中我们是无法修改它的值的

回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

  • 如上我们就可以知道如果我们使用的是初始化列表进行赋值的话,是可以改变const int _n这个值的

🔥所以:

  • 我们可以认为成员变量的定义阶段是在初始化列表处进行定义且初始化的

  • 这也就是为什么上述类型可以被初始化,是因为这个变量的 定义阶段 就是在初始化列表处进行定义的,所以我们可以在初始化列表(定义)处对其进行初始化

特别注意:

  • 初始化列表仅能在构造函数(构造函数、拷贝构造函数)中使用

  • 每个成员变量在 初始化列表 中只能出现一次【即初始化只能初始化一次】

  • 若类中包含以下成员变量,必须放在初始化列表位置进行初始化【因为以下成员需要在定义时就初始化】:

    • 引用成员变量

    • const成员变量

    • 自定义类型成员(该类没有默认构造函数)

❓有同学可能会疑惑 自定义类型成员 用到初始化列表的是什么情况呢

回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

  • 补充: 对于自定义类型成员变量也一样,在初始化列表阶段其实就是这个自定义成员变量的定义阶段,所以在定义的时候自定义成员变量便会自动调用其 默认构造函数 进行初始化,也就是说:自定义成员变量的默认构造函数其实是在初始化列表初始化阶段就调用了

  • 在以上的这种情况中,我们可以发现是错误的,原因是在于自定义成员变量t2是不存在 默认构造函数的 而是 传参构造函数,即此时编译器是无法自动调用其构造函数去初始化其成员变量的,需要我们传参调用

  • 于是在这种情况下,我们便可以很好的利用初始化列表的特性,在自定义成员变量被定义的时候显示的去调用其构造函数,这样就能达到 传参构造 的效果

回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

🔔易混点: 若同时存在 初始化列表初始化函数体内赋值,最终会采用哪个地方的值呢?

回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

  • 以上便可以看到: 对于 内置类型 来说,最终采取的函数体内赋值作为变量值

  • 总结:

    • 对于 内置类型 来说,在经过初始化列表初始化后,还是会经过函数体内赋值

    • 对于 自定义类型成员(存在默认构函数) 来说,建议使用初始化列表进行初始化,效率更高

🏷️重要内容:

  • 成员变量在类中 声明次序 就是其在初始化列表中的 初始化顺序

  • 简单来说: 就是成员变量的 定义顺序 是由 成员变量在类中的 声明次序 所决定的

  • 与其在初始化列表中的初始化语句先后次序无关

  • 建议: 养成 类中成员变量声明的顺序初始化列表中初始化的顺序 保持一致

综上:

  • 初始化列表是成员变量定义的地方

  • 所以可以尽量多使用初始化列表进行成员的初始化,因为即使我们不写成员也需要进行定义,也还是会经历初始化列表这一步的

  • 上述就是初始化列表的全部内容啦~


🥐 Ⅱ.神奇的构造优化

💡构造函数中的优化:

  • 在C++中,对于对象的构造存在一种神奇的构造优化,接下来就让我们一同揭秘吧~

  • 在此之前,先补充一个小知识点:隐式类型转换

    • 在之前有提到过,只要发生类型偏差,编译器都会自动生成一个临时变量去接收类型变换后的值,再进行赋值,而非直接转换本身变量的类型去进行赋值【这里可以点击>回顾<跳转食用呀】

    • 而对于对象的构造,也存在一种隐式类型转换

🌰举个例子:

回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

  • 上述可以发现,对于aa2这个对象竟然可以支持这种构造方式

  • 本质就是:因为100是整形,而aa2是自定义类型,是不可能直接赋值的,所以会发生隐式类型转换

    • 1️⃣因为发生了类型偏差,编译器会自动产生一个类型与接收对象相同的类型去接收拷贝数据

    • 【即编译器相当于拿100去构造了一个临时对象:A tmp(100);

    • 2️⃣然后再利用这个临时对象去赋值给接收对象

    • 【即编译器再将刚创建的临时对象去赋值:A aa2(tmp);

    • 综上:这里的对象构造看似也只有一条语句,实则编译器做了以上两步操作:构造临时对象➕拷贝构造【语法意义上】

  • 但真实的情况是: 编译器对隐式类型转换其实做了特别的优化

    • 将上述的两步操作优化为一步操作了,优化成A aa2(100);一条语句了

    • 这也就是为什么上述的两步操作被称为语法意义上,因为实际情况是编译器发生优化了,直接进行拿值进行构造了

如果不想让编译器发生如上的隐式类型转换:

  • 可以给构造函数加上explicit关键字修饰

  • explicit修饰构造函数,将会禁止单参构造函数的隐式转换

  • 回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

综上:

  • 以上就是发生在构造函数中的神奇优化的全部内容啦~

🥐 Ⅲ.匿名对象

💡匿名对象:

  • 简单来说:就是没有名字的对象

  • 而且匿名对象的生命周期仅仅只有在构造匿名对象的一行代码中,并不是随着主函数的销毁而销毁的

回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

匿名对象的意义:

  • 在某种场景下,需要调用类中的一个函数去“搞事情”,且只需要调用此函数一次

  • 如果是平常的方法,我们构造一个普通对象,从而去调用函数,但函数我们只需要调用一次,此时这个普通对象就会一直占用空间,直至程序结束而调用析构

  • 而此时,匿名对象就可以起到很大的作用,因为我们仅调用这个函数一次,所以匿名对象就可以在一行代码中帮我们去调用,执行完这行代码去执行下一句的时候,匿名对象生命周期结束,便会自动析构

回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

➡️什么场景使用:

  • 定义一个对象要用,但仅在这一行的代码中使用,其他地方不再使用

综上:

  • 匿名对象的使用可以使程序更加灵活、方便、便捷

  • 上述就是匿名对象的概念啦~


🍞二.static成员

💡静态成员:

  • 声明为 static的类成员 称为 类的静态成员

  • static修饰的 成员变量,称之为 静态成员变量

  • static修饰的 成员函数,称之为 静态成员函数

  • 简单来说:就是被static所修饰的成员都为静态成员,即被修饰的成员不再属于单个对象的,而是属于这个类的,这个类中的所有对象【即所有对象共用此成员,多个对象中的静态成员其实都是在同一块空间上的】,而且静态成员的生命周期是全局的

👉静态成员的特征:

  • 静态成员为所有类对象所共享,不属于某个具体对象的实例

  • 静态成员变量必须在类外定义(初始化),定义时不需要添加static关键字

  • 类静态成员可用类名::静态成员或者对象.静态成员(静态变量为私有成员就不可以了)来访问

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

  • 静态成员和类的普通成员一样,也有publicprotectedprivate这三种访问级别,也可以具有返回值

特别注意:

  • 1️⃣静态成员变量生命周期是全局的,但变量并不能在构造函数内初始化,因为变量存储于静态区【即静态变量属于这个类的成员变量,但并不存储于这个类的存储区域】

  • 2️⃣编译器专门给 静态成员变量的定义初始化,开了个后门:可以在全局中通过::(域作用限定符)去突破类域,初始化静态成员变量(即使静态成员变量是私有成员也是可以的,在这里不受访问限制符限制,因为这是编译器专门留给初始化设置的后门)

  • 3️⃣静态成员函数:只需要突破类域(即告诉编译器要找的函数是属于哪个类的)就可以访问得到,并不需要构造一个对象才能调用

  • 4️⃣静态成员变量:若想访问,会受到访问限定符的限制,若为私有成员,则需要设立一个静态成员函数去获取【所以一般访问静态成员变量的话,会给一个静态成员函数】

🌰面试题: 实现一个类,去计算中程序中创建出了多少个类对象

  • 这个题目,就很好体现了静态成员的存在的意义

    即我们可以创建一个计数器,把每一次类对象的创建都下统计下来,计算一共创建了多少个类对象

  • 有了以上想法,便有了以下的想法实现:

    类外面 创建一个计数器去记录:但此方法有个问题就是如何去设定条件判定类对象创建的时候记录下来,所以对于此方法是 不好实现的

    类里面 创建一个计数器(成员变量)去记录:此方法就 很好实现 了,因为类对象创建时一定会调用构造函数的,于是可以在构造函数内都做一个记录,只要调用就统计一次,但这个计数器是需要属于这个类的,即这个类的所有对象都是共享这个计数器的,而不是分开记录,这样最终才能获得总的类的对象个数

  • 此时就可以利用上静态成员的特性了,将计数器用static修饰,这样计数器就能在这个类中的所有对象中被同时共用

class A
{
public:
	A()
	{
		n++;
	}

	A(const A& a)
	{
		n++;
	}

	static int GetN()
	{
		return n;
	}

private:
	static int n;
};

回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

综上:

  • 就是static成员的全部内容啦~

  • 静态成员函数 不可以 调用非静态成员函数

  • 非静态成员函数 可以 调用类的静态成员函数


🥐Ⅰ.成员初始化新方式

💡C++11成员初始化新方式:

  • C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变量 缺省值

回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节

👆如上:

  • 给了成员变量缺省值后,如果类调用的是默认构造函数的话,就会在 初始化列表阶段缺省值 去初始化自己【若存在函数体内赋值,最终的赋值还是以函数体内赋值为主】

  • 对于自定义类型成员来说,缺省值仅支持单成员变量的自定义类型,且必须是公有成员【本质:这里其实发生了隐式类型转换,被编译器优化成了传值构造】

综上:

  • 就是新型的初始化方式啦~

  • 给缺省值本质就是最后一层预防未初始化的保障,不到万不得已才使用的

  • 在有缺省值初始化列表初始化函数体内赋值的情况下,成员变量最终使用的值是函数体内赋值,若没有函数体内赋值其次使用的是初始化列表初始化的值,最后才是缺省值


🍞三.友元

💡友元:

  • 友元分为:友元类 & 友元函数

  • 友元本质是提供了一种 突破封装 的方式,有时提供了便利

  • 但也存在副作用,友元会增加耦合度,破坏了封装,所以友元不宜多用

👉接下来我们就深入“友元”吧~


🥐Ⅰ.友元函数

💡友元函数:

  • 简单来说:可以让 非本类的函数 访问到 本类的私有成员变量

  • 即友元函数可访问类的私有和保护成员,但不是类的成员函数

  • 友元函数 是定义在类外部的普通函数,不属于任何类,但需要在 类的内部 有一个函数声明:frined ➕ 函数声明

🌰举个例子:

友元函数 就可以适用在如下场景中:

class Date
{
public:
	Date(int year, int month, int day)
		 : _year(year)
		 , _month(month)
		 , _day(day)
	 {}
	
	void operator<<(ostream& out) 
	{
		out << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
  • 我们想重载<<(输出)操作符,对类直接进行输出

  • 但此时我们可以发现,如果运算符重载函数为成员函数的话,默认是自带一个参数Date* this,所以左操作数变为Date* this,右操作数为out

  • 这样就会导致上述的运算符重载函数其实是调不动的,需要_year << out才调得动,但这样写代码就 失去可读性了

  • 于是,我们就可以自己实现一个全局的运算符重载函数,去重新排操作数的顺序(即参数传参的顺序),解决上述问题:

ostream& operator<<(ostream& out,const Date& d ) 
{
	out << d._year << "-" << d._month << "-" << d._day << endl;

	return out;
}
  • 但上述又会出现新的问题,就是在类外无法访问类中的私有成员,而如果这个函数成为类的 友元函数 的话,就可以很好的解决这个问题
class Date
{
public:
	friend ostream& operator<<(ostream& out, const Date& d);

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

	void operator<<(ostream& out)
	{
		out << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;

	return out;
}
  • 此时就相当于把朋友(外部函数)邀请进家(类)里做客,这样朋友就可以无限制访问私有成员等等【因为类里没有访问限定符的限制,可以直接访问】

  • 这也就是为什么说友元函数不是类的成员函数,因为朋友始终是朋友,无法成为家人

特别注意:

  • 友元函数不能用 const修饰

  • 一个函数可以是多个类的友元函数

  • 友元函数的调用与普通函数的调用和原理相同

综上:

  • 就是友元函数的全部内容啦~

  • 友元函数解决了可读性的问题(抢位置问题)

  • 友元函数可以访问到类中的私有成员变量


🥐 Ⅱ.友元类

💡友元类:

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

  • 友元类声明:在想要成为其友元类的类中写上friend ➕ 类的声明

特别注意:

  • 友元关系是单向的,不具有双向性

    AB的友元类,那A中的所有成员函数都是B的友元函数,可以访问B中所有私有成员变量,但B不可以反过来访问A

  • 友元关系不能传递

    如果AB的友元类,B也是C的友元类,不代表AC的友元

综上:

  • 以上就是友元类的全部内容啦~

🥐 Ⅲ.总结

  • 如非迫不得已,不建议使用友元,因为其本质是一种破坏封装的行为

🍞四.内部类

💡内部类:

  • 如果一个类定义在另一个类的内部,这个内部类就叫做 内部类

  • 此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类【因为外部类对内部类没有任何优越的访问权限】

  • 本质:内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员,但是外部类不是内部类的友元

特别注意:

  • 内部类(友元类、函数也可以)可以定义在外部类的publicprotectedprivate都是可以的

  • 注意内部类(友元类也可以)可以直接访问外部类中的static枚举成员,不需要外部类的对象/类名【因为已经突破类域了】

  • sizeof(外部类)=外部类,和内部类没有任何关系

综上:

  • 就是内部类的全部内容啦~

  • 使用起来基本和友元类无差别,因为其本质就是友元类

  • 只不过友元类是声明在类内部,定义在类外部

  • 内部类则直接定义在类内部


🫓总结

综上,我们基本了解了C++中的 “封装细节&特性” 🍭 的知识啦~

恭喜你的内功又双叒叕得到了提高!!!

感谢你们的阅读😆

后续还会继续更新💓,欢迎持续关注📌哟~

💫如果有错误❌,欢迎指正呀💫

✨如果觉得收获满满,可以点点赞👍支持一下哟~✨

回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节文章来源地址https://www.toymoban.com/news/detail-412359.html

到了这里,关于回炉与剖析C++封装特性 - 重新认识C++,完满呈现全部内部细节的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 重新认识小米

    被镁光灯聚焦的企业,总是会被贴上各种标签。 8月14日,小米科技创始人雷军以“成长”为主题的年度演讲,刷遍社交网络。提到小米,你首先想到什么?手机发烧友、极致性价比,还是最年轻的500强? 这些都是外界用自己的视角贴上的标签,或许从作为创始人的雷军的话

    2024年02月12日
    浏览(35)
  • 重新认识Word——页眉页脚

    我们之前已经全面的构建了我们的文章,现在我们来了解一下,我们毕业论文的页眉(页面信息)页脚(页码)的设置。 一份Word文档是由 字,行,段,页,节 组成的,前面几个还比较好理解,那这个 节 是干什么的呢?其实节就是 不同的格式 ,Word里面不同的章节可能对页

    2024年02月01日
    浏览(43)
  • 重新认识Android中的线程

    new Thread:可复写Thread#run方法。也可以传递Runnable对象,更加灵活。 缺点:缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统的资源导致死机或oom。 AsyncTask,轻量级的异步任务工具类,提供任务执行的进度回调给UI线程 场景:需要知晓任务执行的进度

    2024年02月11日
    浏览(31)
  • 通过Nginx重新认识HTTP错误码

    在web开发过程中,通过HTTP错误码快速定位问题是一个非常重要的技能,同时Nginx是非常常用的一个实现HTTP协议的服务,因此本文结合二者谈谈Nginx对HTTP错误码的处理。 在RFC2616对HTTP协议做了定义,其对错误码定义分为5大类,依次分为100-199、200-299、300-399、400-499、500-599。 1

    2024年02月07日
    浏览(36)
  • Kerberos 重新认识 From Oracle安全

    https://docs.oracle.com/cd/E24847_01/html/819-7061/seamtm-1.html#scrolltoc Kerberos服务是一种网络身份认证协议,由麻省理工学院(MIT)开发。它提供了强大的身份验证功能,用于在计算机网络中验证用户和服务之间的身份。Kerberos采用了一种基于密钥的认证机制,通过使用加密票证来证实用户

    2024年02月12日
    浏览(33)
  • Linux安装MySQL 【重新认识MySQL上篇】

    前言 本文章收录在MySQL性能优化+原理+实战专栏,点击此处查看开篇介绍。 本文摘录自 ▪ 小孩子4919《MySQL是怎样运行的:从根儿上理解MySQL》 该篇文章初心是介绍MySQL的安装,但是随着后面不断的学习,遇到的坑越来越多,导致本篇文章不断的更新,敬请谅解! 在深层次的

    2024年02月03日
    浏览(74)
  • 重新认识Elasticsearch-一体化矢量搜索引擎

    2023 哪个网络词最热?我投“生成式人工智能”一票。过去一年大家都在拥抱大模型,所有的行业都在做自己的大模型。就像冬日里不来件美拉德色系的服饰就会跟不上时代一样。这不前段时间接入JES,用上好久为碰的RestHighLevelClient包。心血来潮再次访问Elasticsearch官网,发现

    2024年02月02日
    浏览(42)
  • Claude2 AI实战:重新认识我们自己

    交流源于内心本真的需要,通过交流来降低信息的不对称,今天的交流对象是一个集大成者的老学者,当然是由 Claude2 扮演,相信会有不一样的收获。 角色设定 你是一名集大成者的年迈学者,在哲学、社会学、历史、心理学等方面都有很高的造诣,现在我们针对一些问题进

    2024年02月13日
    浏览(40)
  • Outlook邮箱不简单带你重新认识它

    我们都知道微软公司提供的邮箱产品有两个,开放免费使用Outlook和Hotmail,其实还有一个Live邮箱,只不过现在不对外开放了,尽管如此,Outlook和Hotmail也是Live的二级域名。 二级域名说明:是父级的子级,同属一个服务器。   Outlook邮箱有两大特色: 一、Outlook是跨平台化产品

    2024年02月05日
    浏览(52)
  • CSS 重新认识 !important 肯定有你不知道的

    重新认识 !important 影响级联规则 与 animation 和 transition 的关系 级联层cascade layer 内联样式 !important 与权重 !important 与简写属性 !important 与自定义变量 !important 最佳实践 在开始之前, 先来规范一下文中的用语, 首先看 W3C 中关于 CSS 的一些术语定义吧. 下图来自 W3C 我们将一个完整

    2024年02月04日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包