C++核心知识点汇总(面试)

文章目录

前言

本文旨在总结自己在工作学习中总结的C++核心知识点,部分知识点有在面试中涉及。

一 概念

(1) new、delete、malloc、free之间的关系

malloc 和 free 都是 C/C++ 语言的标准库函数, 可以覆盖,C、C++ 中都可以使用

new/delete 是 C++ 的运算符,可以重载,只能在C++ 中使用。

new 调用构造函数,delete 会调用对象的析构函数,而 free 只会释放内存。

它们都可用于申请动态内存和释放内存。但对于非内部数据类型的对象而言,光用 malloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于 malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加给 malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符 delete。注意:new/delete 不是库函数。

(2) delete和delete []的区别

delete 只会调用一次析构函数,而 delete[] 会调用每一个成员函数的析构函数。
对于内建简单数据类型,delete和delete[]功能是相同的。对于自定义的复杂数据类型,delete和delete[]不能互用。简单来说,用new分配的内存用delete删除;用new[]分配的内存用delete[]删除。

(3) C++是不是类型安全的?

不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。

(4) main函数执行之前,还会执行什么代码?

全局对象的构造函数会在main函数之前执行。

(5) const与#define相比,有何优点?

const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

1)const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

2)有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

(6) 引用与指针有什么区别?

1)引用必须被初始化,指针不必。
2)引用初始化以后不能被改变,指针可以改变所指的对象。
3)不存在指向空值的引用,但是存在指向空值的指针。

(7) 全局变量和局部变量有什么区别?是怎么实现的?操作系统和编译器是怎么知道的?

  • 生命周期不同:

全局变量随主程序创建和创建,随主程序销毁而销毁;局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在;

  • 使用方式不同:

通过声明后全局变量程序的各个部分都可以用到;局部变量只能在局部使用;分配在栈区。

  • 内存分配位置不同:

全局变量分配在全局数据段并且在程序开始运行的时候被加载。局部变量则分配在堆栈里面 。

(8) 说出static和const关键字尽可能多的作用?

static关键字至少有下列5个作用:
(1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

const关键字至少有下列5个作用:
(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的 成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。例如:

const classA operator*(const classA& a1,const classA& a2);
operator*的返回结果必须是一个const对象。如果不是,这样的变态代码也不会编译出错:  
classA a, b, c;  
(a * b) = c; // 对a*b的结果赋值  
操作(a * b) = c显然不符合编程者的初衷,也没有任何意义

(9) 子类父类构造析构函数调用顺序

定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数。

(10) 介绍多态、虚函数和纯虚函数

多态:是对于不同对象接收相同消息时产生不同的动作。C++的多态性具体体现在运行和编译两个方面:在程序运行时的多态性通过继承和虚函数来体现;在程序编译时多态性体现在函数和运算符的重载上;

虚函数:在基类中冠以关键字 virtual 的成员函数。它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。

纯虚函数的作用:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在的纯虚函数不具备函数的功能,一般不能直接被调用。

从基类继承来的纯虚函数,在派生类中仍是虚函数。如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstract class)。

抽象类中不仅包括纯虚函数,也可包括虚函数。抽象类是必须用作派生其他类的基类,而不能用于直接创建对象实例。但仍可使用指向抽象类的指针支持运行时多态性。

注:定义一个函数为虚函数,不代表函数为不被实现的函数。定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。定义一个函数为纯虚函数,才代表函数没有被实现。

(11) 什么是“引用”?申明和使用“引用”要注意哪些问题?

引用就是某个目标变量的“别名”,对应用的操作与变量直接操作效果完全相同。声明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因为该引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。

(12) 将“引用”作为函数参数有哪些特点?

(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对相应的目标对象(在主调函数)的操作。

(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

(13) 重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?

从定义上来说:

重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

重写:是指子类重新定义父类虚函数的方法。

从实现原理上来说:

重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定)。

重写:当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。

(14) 描述内存分配方式以及它们的区别?

1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。

2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。

3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

一个C 、C++程序编译时内存分为5大存储区:堆区 、栈区 、全局区 、 文字常量区 、程序代码区 。

(15) 数组与指针的区别?

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
(1)修改内容上的差别

char a[] = "hello";
a[0] = 'X';
char *p = "world"; // 注意p 指向常量字符串
p[0] = 'X'; // 编译器不能发现该错误,运行时错误

(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof§,p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

char a[] = "hello world";
char *p = a;
 
//计算数组和指针的内存容量
cout<< sizeof(a) << endl; // 12 字节
cout<< sizeof(p) << endl; // 4 字节
 
//数组作为函数参数传递
void Func(char a[100])
{
   cout<< sizeof(a) << endl; // 4 字节而不是100 字节
}

(16) 基类的析构函数不是虚函数,会带来什么问题?

派生类的析构函数用不上,会造成资源的泄漏。

(17) 类对象的大小受哪些因素影响?

  • 类的非静态成员变量大小,静态成员不占据类的空间,成员函数也不占据类的空间大小;
  • 内存对齐另外分配的空间大小,类内的数据也是需要进行内存对齐操作的;
  • 虚函数的话,会在类对象插入vptr指针,加上指针大小;
  • 当该该类是某类的派生类,那么派生类继承的基类部分的数据成员也会存在在派生类中的空间中,也会对派生类进行扩展。

(18) 各种情况下 class 的大小各是多少?

class A {};
int main(){
  cout<<sizeof(A)<<endl;// 输出 1;
  A a; 
  cout<<sizeof(a)<<endl;// 输出 1;
  return 0;
}

空类的大小是1, 在C++中空类会占一个字节,这是为了让对象的实例能够相互区别。具体来说,空类同样可以被实例化,并且每个实例在内存中都有独一无二的地址,因此,编译器会给空类隐含加上一个字节,这样空类实例化之后就会拥有独一无二的内存地址。当该空白类作为基类时,该类的大小就优化为0了,子类的大小就是子类本身的大小。这就是所谓的空白基类最优化。

空类的实例大小就是类的大小,所以sizeof(a)=1字节,如果a是指针,则sizeof(a)就是指针的大小,即4字节。

class A { virtual void Fun(){} };
int main(){
  cout<<sizeof(A)<<endl;// 输出 4(32位机器)/8(64位机器);
  A a; 
  cout<<sizeof(a)<<endl;// 输出 4(32位机器)/8(64位机器);
  return 0;
}

因为有虚函数的类对象中都有一个虚函数表指针 __vptr,其大小是4字节

class A { static int a; };
int main(){
  cout<<sizeof(A)<<endl;// 输出 1;
  A a; 
  cout<<sizeof(a)<<endl;// 输出 1;
  return 0;
}

静态成员存放在静态存储区,不占用类的大小, 普通函数也不占用类大小

class A { int a; };
int main(){
  cout<<sizeof(A)<<endl;// 输出 4;
  A a; 
  cout<<sizeof(a)<<endl;// 输出 4;
  return 0;
}

class A { static int a; int b; };;
int main(){
  cout<<sizeof(A)<<endl;// 输出 4;
  A a; 
  cout<<sizeof(a)<<endl;// 输出 4;
  return 0;
}

静态成员a不占用类的大小,所以类的大小就是b变量的大小 即4个字节

(19) sizeof 和 strlen 的区别?

  • sizeof 是一个操作符, strlen 是库函数。
  • sizeof 的参数可以是数据的类型, 也可以是变量; 而 strlen 只能以结尾为 ‘\0’ 的字符串做参数。
  • 数组做 siezeof 的参数不退化,传递给 strlen 就退化为指针了。
  • 编译器在编译时就计算出了 sizeof 的结果, 而 strlen 函数必须在运行时才能计算出来。 而且 sizeof 计算的是数据类型占内存的大小, 而 strlen 计算的是字符串实际的长度。

(20) C语言的指针和引用和c++的有什么区别

  • 指针有自己的一块空间,而引用只是一个别名
  • 使用 sizeof 看一个指针的大小是4,而引用则是被引用对象的大小;
  • 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象;
  • 可以有const指针,但是没有const引用;
  • 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变;
  • 指针可以有多级指针(**p),而引用止于一级;
  • 指针和引用使用++运算符的意义不一样;
  • 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

(21) typedef 和define 有什么区别

  • 用法不同:typedef 用来定义一种数据类型的别名,增强程序的可读性。define 主要用来定义 常量,以及书写复杂使用频繁的宏。
  • 执行时间不同:typedef 是编译过程的一部分,有类型检查的功能。define 是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
  • 作用域不同:typedef 有作用域限定。define 不受作用域约束,只要是在define 声明后的引用 都是正确的。
  • 对指针的操作不同:typedef 和define 定义的指针时有很大的区别。
    注: typedef 定义是语句 ,因为句尾要加上分号 。 而 define 不是语句 ,千万不能在句尾加分号 。

(22) 什么是右值引用,跟左值又有什么区别?

左值和右值的概念 :

  • 左值:能取地址,或者具名对象,表达式结束后依然存在的持久对象;
  • 右值:不能取地址,匿名对象,表达式结束后就不再存在的临时对象;

区别:

  • 左值能寻址,右值不能;
  • 左值能赋值,右值不能;
  • 左值可变,右值不能(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变);

(23) 面向对象的三大特征

  • 封装性:将客观事物抽象成类,每个类对自身的数据和方法实行 protection (private , protected ,public )。
  • 继承性:广义的继承有三种实现形式:实现继承(使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。
  • 多态性:是将父类对象设置成为和一个或更多它的子对象相等的技术。用子类对象给父类对象赋值 之后,父类对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。

(24) c++ 中四种 cast 转换

C++ 中四种类型转换是 : static_cast , dynamic_cast , const_cast , reinterpret_cast.

  1. const_cast
    用于将const变量转为非const.
  • 用于删除 const、volatile 和 __unaligned 特性(如将 const int 类型转换为 int 类型 ) reinterpret_cast用于位的简单重新解释
  • 滥用 reinterpret_cast 运算符可能很容易带来风险。除非所需转换本身是低级别的,否则应- 使用其他强制转换运算符之一。
  • 允许将任何指针转换为任何其他指针类型(如 char* 到 int* 或 One_class* 到 Unrelated_class* 之类的转换,但其本身并不安全)
  • 也允许将任何整数类型转换为任何指针类型以及反向转换。
  • reinterpret_cast 运算符不能丢掉 const、volatile 或 __unaligned 特性。
  • reinterpret_cast 的一个实际用途是在哈希函数中,即,通过让两个不同的值几乎不以相同的索引结尾的方式将值映射到索引。
  1. static_cast
  • 用于非多态类型的转换
  • 不执行运行时类型检查(转换安全性不如 dynamic_cast)
  • 通常用于转换数值数据类型(如 float -> int)
  • 可以在整个类层次结构中移动指针,子类转化为父类安全(向上转换),父类转化为子类不安全(因为子类可能有不在父类的字段或方法)
  • 用于各种隐式转换,比如非const转const,void*转指针等, static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知;
  1. dynamic_cast
  • 用于多态类型的转换
  • 执行行运行时类型检查
  • 只适用于指针或引用
  • 对不明确的指针的转换将失败(返回 nullptr),但不引发异常
  • 可以在整个类层次结构中移动指针,包括向上转换、向下转换
  • 用于动态类型转换 。只能用于含有虚函数的类,用于类层次间的向上和向下转化 。只能转指针或引用 。向下转化时 ,如果是非法的 : 对于指针返回 NULL,对于引用抛异常 。要深入了解内部转换的原理 。
    向上转换:指的是子类向基类的转换
    向下转换:指的是基类向子类的转换
    它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否相同来判断是否能够进行向下转换 。
  1. reinterpret_cast
    几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;
  2. 为什么不使用C的强制转换 ?
    C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错。

(25) 拷贝构造函数和赋值运算符的理解

区别 :

  • 拷贝构造函数生成新的类对象,而赋值运算符不能。
  • 由于拷贝构造函数是直接构造一个新的类对象,所以在初始化这个对象之前不用检验源对象 是否和新建对象相同。而赋值运算符则需要这个操作,另外赋值运算中如果原来的对象中有内存分配要先把内存释放掉。
    注 :当类中有指针类型的成员变量时,一定要重写拷贝构造函数和赋值运算符 ,不要使用默认的。

(26) 正确释放vector的内存(clear(), swap(), shrink_to_fit())

  • vec.clear():清空内容,但是不释放内存。
  • vector().swap(vec):清空内容,且释放内存,想得到一个全新的vector。
  • vec.shrink_to_fit():请求容器降低其capacity和size匹配。
  • vec.clear();vec.shrink_to_fit();:清空内容,且释放内存。

(27) 什么情况下用vector,什么情况下用list,什么情况下用deque

  • vector可以随机存储元素(即可以通过公式直接计算出元素地址,而不需要挨个查找),但在非尾部插入删除数据时,效率很低,适合对象简单,对象数量变化不大,随机访问频繁。除非必要,我们尽可能选择使用vector而非deque,因为deque的迭代器比vector迭代器复杂很多。
  • list不支持随机存储,适用于对象大,对象数量变化频繁,插入和删除频繁,比如写多读少的场景。
  • 需要从首尾两端进行插入或删除操作的时候需要选择deque。

(28) priority_queue的底层原理

priority_queue:优先队列,其底层是用堆来实现的。在优先队列中,队首元素一定是当前队列中优先级最高的那一个

(29) map 、set、multiset、multimap的底层原理

map 、set 、multiset 、multimap 的底层实现都是红黑树 , epoll 模型的底层数据结构也是红黑树 , linux系统中 CFS 进程调度算法 ,也用到红黑树 。
红 黑 树 的 特 性 :

  • 每个结点或是红色或是黑色;
  • 根结点是黑色;
  • 每个叶结点是黑的;
  • 如果一个结点是红的,则它的两个儿子均是黑色;
  • 每个结点到其子孙结点的所有路径上包含相同数目的黑色结点。

(30) map 、set、multiset、multimap的特点

  • set和multiset会根据特定的排序准则自动将元素排序,set中元素不允许重复,multiset可以重复。
  • map和multimap将key和value组成的pair作为元素,根据key的排序准则自动将元素排序(因为红黑树也是二叉搜索树,所以map默认是按key排序的),map中元素的key不允许重复,multimap可以重复。
  • map和set的增删改查速度为都是logn,是比较高效的。

(31) hash_map与map的区别?什么时候用hash_map,什么时候用map?

  • 构造函数:hash_map需要hash function和等于函数,而map需要比较函数(大于或小于)。
  • 存储结构:hash_map以hashtable为底层,而map以RB-TREE为底层。
  • 总的说来,hash_map查找速度比map快,而且查找速度基本和数据量大小无关,属于常数级别。而map的查找速度是logn级别。但不一定常数就比log小,而且hash_map还有hash function耗时。
  • 如果考虑效率,特别当元素达到一定数量级时,用hash_map。
  • 考虑内存,或者元素数量较少时,用map。

(32) STL线程不安全的情况

  • 在对同一个容器进行多线程的读写、写操作时;
  • 在每次调用容器的成员函数期间都要锁定该容器;
  • 在每个容器返回的迭代器(例如通过调用begin或end)的生存期之内都要锁定该容器;
  • 在每个在容器上调用的算法执行期间锁定该容器。

Code

(1) 编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:

class String
{
public:
    String(const char *str = NULL); // 普通构造函数
    String(const String &other); // 拷贝构造函数
    ~ String(void); // 析构函数
    String & operator =(const String &other); // 赋值函数
private:
    char *m_data; // 用于保存字符串
};

//普通构造函数
String::String(const char *str)
{
	if(str == nullptr) {
		m_data = new char[1];   // 得分点:对空字符串自动申请存放结束标志'\0'的空
 		*m_data = '\0';         //加分点:对m_data加NULL 判断
 	} else {
		int length = strlen(str);
		m_data = new char[length+1];    // 若能加 NULL 判断则更好
		strcpy(m_data, str);
	}
}
// String的析构函数
String::~String(void)
{
	delete [] m_data;
}
//拷贝构造函数
String::String(const String &other) // 得分点:输入参数为const型
{
	int length = strlen(other.m_data);
	m_data = new char[length+1];    //加分点:对m_data加NULL 判断
	strcpy(m_data, other.m_data);
}
//赋值函数
String & String::operator =(const String &other) // 得分点:输入参数为const型
{
	if(this == &other)  //得分点:检查自赋值
		return *this;
	delete [] m_data;   //得分点:释放原有的内存资源
	int length = strlen( other.m_data );
	m_data = new char[length+1];    //加分点:对m_data加NULL 判断
	strcpy( m_data, other.m_data );
	return *this;   //得分点:返回本对象的引用
}

(2) 写出完整版的strcpy函数:

//为了实现链式操作,将目的地址返回,加3分! 
char * strcpy( char *strDest, const char *strSrc )
{
    assert( (strDest != NULL) && (strSrc != NULL) );
    char *address = strDest;
    while( (*strDest++ = * strSrc++) != '\0' );
    return address;
}

(3) 设置地址为0x52a1 的整型变量的值为0xaa11:

int *ptr;
ptr = (int *)0x52a1;
*ptr = 0xaa11

(4) 编码实现函数 atoi() ,设计一个程序 ,把一个字符串转化为一个整型数值

int myAtoi(const char * str)
{
	int num = 0; //保存转换后的数值
	int isNegative = 0; //记录字符串中是否有负号
	int n =0;
	char *p = str;
	if(p == NULL) {
		//判断指针的合法性
		return -1;
	}
	while(*p++ != '\0') {
		//计算数字符串度
		n++;
	}
	p = str;
	if(p[0] == '-') {
		//判断数组是否有负号
		isNegative = 1;
	}
	char temp = '0';
	for(int i = 0 ; i < n; i++)
	{
		char temp = *p++;
		if(temp > '9' ||temp < '0') {
			//滤除非数字字符
			continue;
		}
		if(num !=0 || temp != '0')  {
			//滤除字符串开始的0 字符
			temp -= 0x30; //将数字字符转换为数值
			num += temp *int( pow(10 , n - 1 -i) );
		}
	}
	if(isNegative) {
		//如果字符串中有负号,将数值取反
		return (0 - num);
	} else {
		return num; //返回转换后的数值
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值