【复习整理归纳】| C++面经(面向对象)

面向对象

在这里插入图片描述

1 对象模型

C++【对象模型】| 虚函数表 & 多态如何调用虚函数
C++【对象模型】| 【01】简单了解C++对象模型的布局

2 对象的生命周期

2.1 C++编译器的优化

- 【1】用临时对象生成新对象时,临时对象就不产生了,直接构造新对象;
A a1 = A(10);  // 与A a1(10);没有区别
- 函数参数传递过程中,对象优先按引用传递,不要按值传递;
- 函数对象返回时,优先返回一个临时对象由于【1】;
- 接收返回值时对象的函数调用时,优先按初始化的方式接收,不要按赋值的方式接收;
// 未优化
Test GetObj(Test t) {
	int val = t.,getData();
	Test tmp(val);
	return tmp;
}

int main() {
	Test t1;
	Test t2;
	t2 = GetObj(t1);
	return 0;
}
// 优化
Test GetObj(Test t) {
	int val = t.,getData();
	return tmp(val);
}

int main() {
	Test t1;
	Test t2 = GetObj(t1);
	return 0;
}

2.2 拷贝赋值和拷贝构造的区别

Test a1;
a1 = Test(10);	// 为赋值操作,调用operator=,由于a1是已存在的,会产生临时对象
a1 = 10;	// 隐式生成临时对象,10会被转换为Test(10)

2.3 指针和引用指向临时对象

  • 不能返回局部的或者临时对象的指针或引用;
Test *p = &A(10);	// A在构造好后,在当前语句就会执行析构函数,故p为nullptr
Test &ref = A(10);	// 而引用指向的不会,生命周期被延长

在这里插入图片描述

3 多态

类的类型

  • 若类有虚函数,则*class是运行时期的类型;
  • 若类没有虚函数,则*class是编译器的类型;

3.1 动态多态(运行期)

  • 【多态】基类指针(引用)指向派生类对象,通过指针(引用)调用同名覆盖虚函数,基类指针指向哪个派生类对象,就会调用哪个派生类对象的同名覆盖方法;
  • 子类型多态(运行期):虚函数
  • 强制多态(编译期/运行期):基本类型转换、自定义类型转换

底层

  • 通过动态绑定,找到虚表指针->虚函数表->对应的函数;

3.2 静态多态(编译期/早绑定)

  • 静态多态:函数重载、模板(函数、类);

注意

  • 可以将派生类的对象赋值给基类的指针或引用,反之不可;
  • 普通函数(非类成员函数)不能是虚函数;
  • 静态函数(static)不能是虚函数;
  • 构造函数不能是虚函数(因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成后才会形成虚表指针);
  • 内联函数不能是表现多态性时的虚函数

3.3 多态应用情况

3.3.1 当基类的虚函数和子类的同名函数都有默认参数时

子类的默认参数会被覆盖;由于基类的默认参数会在编译期就被压栈;

在这里插入图片描述

3.3.2 交换虚表指针

访问类空间的前4个字节,即虚表指针;

在这里插入图片描述
3.3.3 基类指针调用子类private属性的虚函数

成员方法是否能调用,是在编译期就决定的,而基类中的虚函数为public,所有编译通过;而基类指针调用子类的
虚函数是运行期才确定;

在这里插入图片描述

4 默认参数

形参待默认值的函数

- 给默认值的时候从右向左给;
- 调用效率问题,默认参数直接压栈,否则需要找到变量,在将他压栈;
- 定义出形参默认值,声明也可给形参默认值,但只能出现一次;

在这里插入图片描述

5 虚析构函数

虚析构函数是需要通过基类指针来调用子类的析构;

5.1 什么时候把基类的析构函数必须实现成虚函数

  • 基类的指针(引用)指向new出来的派生累对象时,delete基类指针它调用析构时,必须发生动态绑定,否则导致派生类的析构无法调用;

6 纯虚函数

  • 纯虚函数只是一个接口,是个函数的声明而已,它要留到子类必须实现
  • 特殊的虚函数,在基类中不能对虚函数给出有意义的实现,它的实现留给该基类的派生类去做
  • 带纯虚函数的类叫抽象类,这种类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。抽象类被继承后,子类可以继续是抽象类,也可以是普通类

7 虚函数

  • 类里如果声明了虚函数,这个函数是实现的,它的作用就是为了能让这个函数在它的子类里面可以被覆盖(override),编译器就可以使用后期绑定来达到多态了。
  • 虚函数在子类里面可以不重写
  • 虚函数的类用于 “实作继承”,继承接口的同时也继承了父类的实现;

7.1 虚函数、静态绑定、动态绑定

  • 一个类定义了虚函数,则编译阶段编译器给该类类型产生一个唯一的虚函数表;主要存储RTTI指针和虚函数地址;当程序运行时,每张表都会加载到内存的.rodata区;
  • 一个类内定义了虚函数,则该类定义的对象运行时,内存中起始位置多了个虚表指针,指向相应类型的虚函数表,一个类型定义n个对象都指向同一张虚函数表;
  • 一个类内虚函数的个数,不影响对象的内存大小,影响的是虚函数表的大小;
  • 若派生类中的方法和基类方法、返回值、函数名、参数列表都相同,则基类的方法是virtual虚函数,则派生类的该方法,自动处理成虚函数;

在这里插入图片描述
7.2 成为虚函数

- 虚函数能产生地址,存储在虚表中;
- 对象必须存在;

7.3 不能为虚函数

- 构造函数,不可以;由于构造函数调用任何函数都是静态绑定,不会发生静态绑定派生类对象构造过程;
	- 先调用的是基类的构造函数,在调用派生类的构造函数;
- 静态函数;
- 内联函数;

7.4 析构为什么可以为虚函数

- 在析构函数中,对象依旧存在;

7.5 是不是虚函数的调用一定式动态绑定

不是,类构造中调用虚函数,就是静态绑定;
若不是通过指针或引用来调用虚函数,就是静态绑定;

9 继承、虚继承

9.1 继承

- 代码复用;
- 派生类从基类继承所有成员,除构造和析构;
- 通过继承,在基类里给所有派生类可以保留统一的纯虚函数,等待派生类进行重写,通过使用多态,可以通过基类的指针访问
不同派生类对象的同名覆盖方法;

若不想要被继承

将构造函数设置为private

派生类如何初始化从基类继承来的成员变量

- 通过调用基类相应的构造函数来初始化;
- 通过基类析构来负责清理;

派生类对象构造和析构过程

- 派生类调用基类的构造函数,初始化继承成员;
- 调用派生类自己的构造函数,初始化自己的成员;
- 调用派生类的析构函数,释放派生类成员;
- 调用基类的析构,派生类中从基类继承来的成员;

基类和派生类的转换

- 从下到上是安全的;
- 从上到下是不安全;

9.2 虚继承

虚继承用于解决多继承条件下的菱形继承问题(浪费存储空间存在二义性);

  • 当虚继承的子类被当做父类继承时,虚基类指针也会被继承。
  • 出现虚继承时会讲将基类继承的部分放置到类的最尾部,并在头部添加一个vbptr指向一个虚基类表,虚基类表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持着公共基类(虚基类)的两份同样的拷贝,节省了存储空间;

在这里插入图片描述

基类指针指向子类时,出现的问题

  • 故在内存释放的时候会出现错误
    在这里插入图片描述

虚继承:

  • 虚基类依旧存在继承类中,只占用存储空间
  • 虚基类表存储的是虚基类相对直接继承类的偏移

9.3 菱形继承

A: m_a
B:public A{m_a, m_b}
C:public C{m_a, mc}
D:public B, public C {m_a, m_b, m_a, m_c, m_d}
  • 以上简述了菱形继承;D中继承会存在两份的m_a,浪费空间;
  • 故使用虚继承来解决,B,C中的都放置了虚基类表指针,而m_a被移动到D中保存一份,需要注意此时m_a的初始化需要交给D,若A中没有默认构造,D需要手动初始化;

10 仿函数

参考

把operator()进行函数重载的对象;

【好处】:
- 通过函数指针调用函数,是没有办法内联,效率低,有函数的开销;
- 函数对象是用类生成的,可以添加相关的成员变量,用来记录函数对象使用时更多的信息;

【一般使用在】:
- 泛型算法参数传递;
- 比较;
- 优先级队列;
- 智能指针;

11 模板类、成员模板

  • 模板类中可以使用虚函数
  • 一个类(无论是普通类还是类模板)的成员模板(本身是模板的成员函数)不能是虚函数

12 抽象类、接口类、聚合类

  • 抽象类:含有纯虚函数的类;
    • 让所有子类复用该熟悉;
    • 给所有子类保留统一的重写接口;
    • 不可被实例化,但可定义指针和引用变量;
  • 接口类:仅含有纯虚函数的抽象类
  • 聚合类:用户可以直接访问其成员,并且具有特殊的初始化语法形式。满足如下特点:
    • 所有成员都是 public;
    • 没有定义任何构造函数;
    • 没有类内初始化;
    • 没有基类,也没有 virtual 函数;

14 final和override关键字

- override,避免子类在重写基类函数的时候,防止写错被当成新函数,若写错编译不通过;
- final不希望被子类重写;

15 拷贝初始化和直接初始化

- 直接初始化调用与实参匹配的构造函数;
- 拷贝初始化总是调用拷贝构造函数;
- 拷贝初始化使用指定的构造函数创建一个临时对象,在将临时对象拷贝到正在创建的对象; 

16 public、protected、private权限

- public:外部可以访问;
- protected:可被子类访问;
- private:外部不能访问;

继承权限

- 外部只能访问对象public成员;
- 在继承结构中,派生从基类可继承private成员,但无法访问;
- 若派生类和外部不打算访问,则用private继承;
- 若派生类要访问,不被外部访问用protected;

默认继承权限

- 要看是class还是struct;
- class则为private;struct则为public;

17 对象复用的了解,零拷贝的了解

对象复用

- 创建一个对象池,对对象重复利用,避免多次创建销毁;

零拷贝

- 一种避免CPU将数据从一块存储拷贝到另一块中;
- 减少了数据拷贝和共享总线操作的次数;
	- 如:【vector的emplace_back()实现零拷贝,直接将元素原地构造插入,而push_back需要使用拷贝构造和移动构造】

18 知道C++中的组合吗?它与继承相比有什么优缺点吗?

继承

- 优点:子类可以重写对非类的扩张;
- 缺点:父类的内部细节对子类的是可见的;	
	- 无法在运行期改变父类继承方法的行为;
	- 子类和父类是高耦合;

组合

- 类作为另一个类的成员变量;
- 被包含的对象对于当前对象是不可见的;
- 当前对象和包含对象是一个低耦合关系;
- 当前对象在运行时可动态绑定包含对象;

19 this 指针

this 指针是一个隐含于每一个非静态成员函数中的特殊指针,它指向调用该成员函数的那个对象

  • 一个类型有很多个对象,它们的成员变量是私有的,而成员方法是共享的,我们可以通过this指针来区分是那个对象调用该成员方法;
  • 当对一个对象调用成员函数时,编译程序先将对象的地址赋给 this 指针,然后调用成员函数将this指针传入该成员函数,每次成员函数存取数据成员时,都隐式使用 this 指针

this 指针被隐含地声明为: ClassName *const this,不能给 this 指针赋值;在 ClassName 类的 const 成员函数中,this 指针的类型为:const ClassName* const,不能对 this 指针所指向的这种对象是不可修改的(即不能对这种对象的数据成员进行赋值操作);

this 并不是一个常规变量,而是个右值,所以不能取得 this 的地址(不能 &this);

在以下场景中,经常需要显式引用 this 指针:

  • 为实现对象的链式引用;
  • 为避免对同一对象进行赋值操作;
  • 在实现一些数据结构时,如 list。

20 C 实现 C++ 类

C 实现 C++ 的面向对象特性(封装、继承、多态

  • 封装:使用函数指针属性方法封装到结构体中;
  • 继承:结构体嵌套
  • 多态:父类与子类方法的函数指针不同;

21 friend 友元类和友元函数

  • 能访问私有成员;
  • 破坏封装性
  • 友元关系不可传递
  • 友元关系的单向性
  • 友元声明的形式及数量不受限制

22 成员初始化列表

  • 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面;
  • 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面;
  • 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化;
  • 当调用一个基类的构造函数,拥有一组参数时;
  • 当调用一个成员类的构造函数,它拥有一组参数时;

为什么用成员初始化列表会快一些?

- 函数体中的初始化,是所有在数据成员被分配空间后才进行的;
- 列表初始化是数据成员分配空间的时候就进行初始化;
- 对于类型,少了一次调用构造函数的方法,在函数内赋值会调用拷贝构造;

23 运算符重载

  • 其优先级和结合律与内置类型一致才可以,不能改变运算符操作数个数;
  • 重载方式:成员、非成员运算符,成员运算符少一个参数;
  • 下标、箭头运算符必须是成员函数运算符;
=,[],()和->操作符只能通过成员函数进行重载;
<<和>>只能通过全局函数配合友元函数进行重载;
不要重载&&和||操作符,因为无法实现短路规则。

24 C++中的重载、重写(覆盖)和隐藏的区别

重载

编译器做对象运算时,会调用优先调用成员方法的重载运算符,若没有会在全局中查找;
并且若成员方法没有定义该重载
同一作用域下,对同名函数,函数名相同,其参数的类型、顺序、个数不同、static、virtua、const、volatile不同;
不同函数之间水平关系;
调用时根据实参列表对应关系来找到对应的函数;
【C中为什么没有重载】:
- 由于C++有名变换机制,产生函数符号结合参数等,C函数符号只由函数名来决定;

重写

基类和派生类的方法,返回值、函数名一级参数列表都相同,切基类的方法是虚函数,则派生类的方法就是自动处理成虚函数;
	- 是父类和子类的垂直关系;
	- 要求参数列表都相同;
	- 调用方法根据对象的类型;

隐藏

(隐藏子类函数):派生类中的函数屏蔽了基类中的同名函数(和重写的区别在于基类该函数是否为虚函数);
	- 两个参数相同,若基类不是虚函数;
	- 两个参数不同,无论基类函数是否为虚函数都会被隐藏;
- 基类指针指向派生类时可以调用派生类的覆盖函数;
- 基类指针只能调用基类的被隐藏函数,无法识别派生类的隐藏函数;

25 构造函数

继承、委派构造、右值引用及其他类型

25.1 默认构造函数

编译器合成:

  • 类中内含带有默认构造的类成员;
  • 带有默认构造的基类;
  • 带有虚函数的类:
    • 当class声明/继承一个virtual function;
    • 当class派生自一个继承串链,其中一个或更多的virtual base classes;

编译期间,必须给vptr设定初值,且放置地址,这些都在构造函数中完成;
在编译期不能够确定真正的类;

  • 带有一个虚基类的类
    • 编译器必须让虚基类在每个子类中的位置,能够在执行期准备好,在编译期不能够确定真正的类;

程序员是否有提供构造函数,若有则在前头插入默认构造;

25.2 移动拷贝构造以及完美转发

在这里插入图片描述

函数传递中:
	CMyString& &&str  本质上为  CMyString&;
	CMyString&& &&str  本质上为  CMyString&&;
而上述参数传递中:&&str会被当作一个左值,并不是一个右值;
故需要通过forward来传递依次来保证不会被改变;

【forward】:识别左值还是很右值,防止右值被当作成左值;
在这里插入图片描述
保证高效的string赋值方法
在这里插入图片描述

25.3 拷贝构造函数

产生拷贝动作

  • 当以一个对象的内容作为另一个对象的初值时;
  • 当对象被当作参数传递时;
  • 当函数返回一个对象时

一般会导致一个临时类对象的产生;

  • 拷贝构造可以带参数,但需要给默认参数

为什么传引用不会传值
-由于形参的生产需要调用到拷贝构造,会自己调用自己,导致编译错误;

编译器何时自动生成拷贝构造

没有bitwise copy semantics时,编译器将要合成拷贝构造

  • 当类内含一个成员类,而成员类中声明一个拷贝构造
  • 当类继承一个基类,而该基类存在一个拷贝构造时;

以上两种情况必须将后者的拷贝构造插入到类中的拷贝构造;

  • 当类声明一个或多个虚函数
  • 当类派生一个继承串链,其中一个或多个虚基类时;

拷贝构造的作用

重新设定virtual table指针,当类中出现虚函数即会执行该操作:

  • 增加vtbl,内含每一个有作用的虚函数地址;
  • 一个指向vtbl的vptr,安插在每一个类对象内;

当使用拷贝构造发生在带虚函数的类上时,该vptr的初始化将会交给它;

在这里插入图片描述

25.4 何时需要自定义拷贝构造函数

  • ①对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数;
  • ②在提供拷贝构造函数的同时,还应该考虑重载"="赋值操作符号。

什么情况下会调用拷贝构造函数

- 用一个类初始化另一个类,会产生临时对象,把对象拷贝给临时对象,函数执行完先析构局部变量,在析构临时对象;
- 函数的参数时类对象时;
- 函数返回值时局部对象;

26 编译器转化语句

转化阶段

  • 重写每一个定义(占用内存的行为),其中的初始化操作会被剥除;
  • class的拷贝构造会被安插;

参数初始化

  • 当一个类对象作为参数,会导入临时性的对象,并调用拷贝构造将其初始化,在将临时的对象交给函数,当离开函数时,将会调用其析构函数释放;
  • 方法二:以拷贝建构方式,把实际参数建构在位置上,记录在堆栈中,在函数返回之前将其析构;
X xx;       // X __temp0
            // __temp0.X::X(xx);
foo(xx);    // foo(__temp0);

返回值优化

返回值从局部对象中拷贝返回一般做一个双阶段转换:

  • 先加上一个额外参数(reference),用来放置拷贝建构的返回值;
  • 在return前插入拷贝构造调用的操作,将要返回的内容拷贝到reference中;
X bar() {   // void bar(X& __result)   添加额外参数
    X xx;   // X xx;
            // xx.X::X();  默认构造
            // __result.X::XX(xx);  拷贝构造
    return xx; // return;  无返回值
}

编译器层面优化

  • 编译器将result参数取代named return value(NRV);
  • 该优化能大大的提供程序效率,但由于时编译器完成,故不能明确知道是否有被优化,且当函数较为复杂时,也难以被优化;

return class(xxx)

27 空类为啥不为0

由于编译器确保这个类在内存中能配置到独一无二的地址,故安插一个char进去;

【win下vs13 15 17 19 20】:C语言不允许定义空类的struct结构体;
【gcc linux/unix】:sizeof(struct{}) = 0;
【C++中】:为1,由于它是一个对象需要内存,需要构造;

28 类的大小

  • 语言本身所造成的额外负担,由于虚基类的支持,需要在类中增加一个指针大小;
  • 编译器对特殊情况所提供的优化处理,如上述两个编译器的差异;
  • Alignment的限制,一般会将数值调整到整数倍,以此来提供程序的效率;

29 data member的布局

非静态数据成员在类中的排序顺序和声明一样;排序顺序只符合出现较晚的成员具有较高的地址;

  • 数据成员地址并不一定连续,由于可能需要边界的调整;且可能在class内部插入vptr来支持对象模型;

静态数据成员

  • 静态数据成员的存取不会招致空间或时间上的额外负担
  • 其存取方式不需要通过类对象
  • 若对于一个静态数据成员的地址,会得到一个指向数据类型的指针,而不是一个指向类成员的指针;
  • 如果两个类中都有声明同名的静态数据成员,则会导致命名冲突,而编译器会对其进行编码让其成为独一无二的名称

非静态数据成员

一般通过类的起始位置,在加上该数据成员的偏移位置(在编译时期可知),其效率和struct 成员一样;
如:origin._y ==>&origin + (&Point3d::_y - 1);
该-1操作,是由于指向数据成员的指针,offset值总是被加1;
用于区分一个指向数据成员的指针,用以指出类的第一个成员一个指向数据成员的指针,没有指出任何成员的两种情况;

30 vptr放在类对象中的哪里最好?

【将ptr放在头部】
C++2.0后由于支持虚拟继承以及抽象基类,故开始将vptr放置在头部,但该做法丧失了C兼容性;

31 继承中地址转换

pv = pv3d ? (Vertex*)((char*)pv3d) + sizeof(Point3d) : 0;

32 成员函数

  • 静态成员不能为const;

不能用const的原因:一个静态成员函数访问的值是其参数、静态数据成员和全局变量,而这些数据都不是对象状态的一部分。 而对成员函数中使用关键字const是表明:函数不会修改该函数访问的目标对象的数据成员。 既然一个静态成员函数根本不访问非静态数据成员,那么就没必要使用const了

成员函数与非成员函数
float func(const Point3d *_this) {} == float Point3d::func() const {}

静态成员函数

  • 不能直接存取class中的非静态数据成员object_count((Point3d*)0) =>0强转为指针,提供this指针
  • 不能被声明为const、volatile、virtual
  • 不需要经由class obj才被调用;
  • 可用于回调函数;

33 mangling

  • 对于成员函数,为了区分一般会加上当前类的名称,避免派生类中出现重名函数;
  • 为了放置函数重载的同名,为此加上了参数的类型和参数个数,来加以区分;
  • 如果禁止使用该方法对函数进行别名,那么请使用extern"C";

34 多个基类

vcall thunk用处

  • 以适当的offset调整this指针;
  • 跳到virtual function;

主次实例

当一个子类内含n-1个虚表时,n为上一层基类的个数,此时子类将会有两个虚表产生:

  • 主要实例,与Base1共享
  • 次要实例,与base2有关
    将多个virtual tables连锁成一个,形成次要表格,若要获取可通过主要表格名称加上一个offset即可获得;

第二个base class 会影响对虚函数的支持有三种情况

【通过一个指向第二个base class的指针,调用子类虚函数

  • 当调用时,该指针必须调整以指向子类的起始处;

【一个指向子类的指针,调用第二个基类中一个继承而来的虚函数

  • 子类指针必须再次调整,以指向第二个基类子对象;

【允许一个虚函数的返回值有所变化】:

  • 当我们指向第二个基类的指针来调用clone时,该指针也需要被调整,否则返回一个指向子类的对象;

35 函数效能

  • 建议不要再一个虚基类中声明非静态数据成员
  • 经过测试非成员函数、静态成员函数、非静态成员函数都被转换为完全相同形式,效率相同;
  • 虚函数再继承中,每多一层继承其执行时间将会明显增加,由于没增加一层久会多增加一个额外的vptr设定

36 成员初始化列表

  • 为public才可用;
  • 只能指定常量;
    初始化可能性高些;

37 继承下的构造扩充

构造内可能扩充:

  • 成员初始化列表中的数据成员初始化操作将会放入构造中;
  • 调用类成员的默认构造
  • 若类有vptr,需要设定初值
  • 调用上一层的基类构造,若基类时多重继承下的第二或后续的基类,则this指针必须要被调整;
  • 虚基类构造被调用,从左到右,由深到浅;
  • 类中的每一个虚基类子对象的偏移位置要在执行期被存取

38 函数模板

C++ | 【05】函数模板及类模板

39 munch方法

它是如何工作的呢?

  • 为每一个需要静态初始化的文件产生一个_sti()函数,含有构造或inline expansions;
    __sti()[__sti__matrix_c__identity()] { identity.Matrux::Matrix(); }
  • 与之对应的产生一个__std()函数,内含析构函数;
  • 提供runtime library munch函数:一个_main()【作为main的第一个指令】调用__sti(),一个_exit()调用__std();

有了上述方法,那么该如何收集各个对象的sti和std函数呢】

  • 使用mn命令,该命令将用于可执行文件,将对象的符号表格项目导到munch中;
  • 此时munch会搜索以_sti或__std开头的名称将该函数名称添加到__sti或__std函数的跳离表格中;
  • 在将这个表格写到一个小的程序文本文件;
  • 在将编译重新激活,将该表格的文件加以编译,整个可执行文件被重新链接;

例题

在这里插入图片描述

任意C++编译器,在用临时对象构造新对象时,则临时对象就不产生了,直接构造新对象;
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jxiepc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值