C++入门第一篇----基础关键字总结和C++一些优于C语言的总结

前言:

C++,作为C语言的升级语言,它自然有着优于C语言本身的语法特性和语言特点,并且在大家以后进入到工程项目开发的时候,C++的很多兼容性要远远优于C语言,但C++相比C语言到底有哪些好处呢?我想仅仅光说C++强于C语言本身是没有说服力的,所以下面让我们看一看从最基本的几个问题出发,C++强于C语言的几个基础方面。
首先让我们先来看C++最基本的第一个代码:

#include<iostream>
using namespace std;
int main()
{
   cout<<"hello world!"<<endl;
   return 0;
}

然后再让我们看看C语言是如何实现这样的代码的:

#include<stdio.h>
int main()
{
   printf("hello world!");
   return 0;
}

和我们熟知的C语言相比,C++多了很多东西,namespace是什么?iostream是什么?cout是什么?…接下来让我们一个一个来探究这些问题。在接下来的很多情况中,我基本都利用C语言和C++的特点对比来导入C++的知识点,这样更好理解。

1.命名空间 namespace

作为C++的63个关键字之一,我们变从这第一个namespace关键字开始。
我们在C语言书写时有时候会面临这样一个问题:

#include<stdio.h>
#include<stdlib.h>
int rand=30;
int main()
{
    printf("%d\n",rand);
    return 0;
}

乍一看这行代码没有明显错误,但编译器会出现这样的一行报错
在这里插入图片描述
rand不明确,这是因为我们的库文件stdlib.h里面也有一个rand函数或者变量之类的东西,而我们在这里又使用了一个rand,计算机编译的时候不知道你调用的是哪个rand,这就产生了错误,所以这便引出了C语言的第一个问题:命名冲突,C语言本身是无法解决命名冲突的问题的,它的主要冲突主要包括与库函数之间的冲突或者在一个长项目中重复变量名字的冲突。这是很烦人的问题,这导致我们创建变量命名的时候不得不谨小慎微,看看是否有冲突问题,利用大小写和各种不同的标识符号区分开。
但C++便为此提供了一个更好的方法:namespace—命名空间
我们可以这样改:

namespace hbw
{
    int rand=30;
}
#include<stdio.h>
#include<stdlib.h>
int rand=30;
int main()
{
    printf("%d\n",rand);
    printf("%d\n,hbw::rand);
    return 0;
}

这时,代码就不会报错了,程序运行的结果如下:
在这里插入图片描述
我们加上namespace命名空间的效果就是,命名空间内部定义的变量被namespace如同高墙一样围起来,我们接下来的程序再使用相同名字,它会默认指向名字对应的namespace hbw 命名空间之外的变量,这样hbw里面的rand和外面的rand就被一个namespace区分了开来而不会发生命名冲突的问题,倘若我们想访问hb内部的rand,只需要hbw::rand即可,其中::符号为域作用限定符的意思,他就代表我们必须要访问hbw里面的rand而不是其他地方的。而此时变量的属性是不变的。

1.namespace的几点特性:

首先明确写法:
namespace 名字(注意这里不要加分号)
{
在里面写变量或者函数定义都可以
}

特性1:
命名空间内部也存在冲突问题,而我们的命名空间内部是可以通过嵌套命名空间从而解决这个问题的,例如:

namespace hbwpersonal
{
    int q=30;
	int arr1[10] = { 0 };
	namespace Swap
	{
	    int q=50;
		int sz = sizeof(hbwpersonal::arr1) / sizeof(hbwpersonal::arr1[0]);
		void swap(int* x, int* y)//交换函数
		{
			int tmp = *x;
			*x = *y;
			*y = tmp;
		}
	}

当我们访问q的时候,也会由于两个命名空间里面都有q,我们倘若仅仅是hbwpersonal::q,计算机无法识别,故我们要这样区分:

printf("%d",hbwpersonal::q);
printf("%d",hbwpersonal::Swap::q);

这样两个q就不会发生冲突问题,故我们得出结论:
命名空间也是可以不断嵌套的,如同if语句一个道理。
特性2:
对于实践过程中定义和声明分离的问题
对于不同位置命名空间,倘若名字相同,在计算机编译的时候是可以把同名的命名空间自动按照同一个命名空间去合并为一个的命名空间去处理的。
所以,我们对于处理头文件和函数文件分离的情况的时候,我们完全可以将其封装在相同名字的命名空间内部,这样就相当于定义和声明串联在了一起,和之前的C语言的函数定义和声明就没有本质区别了。这样当我们把函数文件和头文件同时发给别人的时候,它就可以不在乎命名冲突直接用命名空间里面的内容即可。

2.namespace命名空间的展开:

就像小的时候我去乡下,看到村子里的农民有时候要装大棚,有的时候又要拆大棚,我们的namespace也是同理,也可以通过展开来开放使用权限,C++语法的很多内容都涉及到权限,包括后面我们要学的引用等知识,这要是为什么C加加支持各种C语言做不到的东西,它对于权限的划分是更加细致的。
using namespace +对应空间的名字,这样我们对应的空间的权限便全部打开了,就不用我们使用::限定这个命名空间里面的变量或者函数等东西了。
当然,盲目的全部打开风险是很大的,而且很多时候在实际工程中我们可能根本用不到命名空间里面的全部元素,所以C++为我们提供了一个可以指定打开的方式:
using +对应空间的名字+里面的元素名称
通过这样的方式,我们便指定只打开我们想要打开的元素,而不是全展开,这样既方便又降低了风险。
当我们using展开后,我们就不用使用对应的::符号才能调用对应的变量了,只需要像往常一样直接写名字即可。
所以让我们回到我们最开始的第一个C++程序:
using namespace std;
现在我们就能理解它的含义了,其中的我们实际上就是展开了名为std的命名空间,std是C++库里面的官方命名空间,很多C++的关键字都包含其中,加上这句话后,我们就能跟C语言一样直接使用这些关键字,而不是std::cin,std::cout…这样麻烦的操作了。

2.C++的输入输出

C语言和C++在程序上最直观的区别是输入和输出,对于即使看不懂代码或者刚学编程的人(我就是这样)来说,C++的cin cout似乎就是C语言对应的scanf和printf,没错,所以接下来让我们讲讲C++的输入和输出。

1.io流:

我们的第一个C++代码有这么一个引用:

#include<iostream>

我们从这个引用中看出两点,1.C++的头文件引用是不用加.h的 2.我们为什么要引用这个iostream呢?
现在让我们来说说什么是iostream:
iostream即io流的头文件,io流就是in out的缩写,从字面意义上来理解就是进行数据流入和数据流出的文件,我们这里姑且先这样理解,你可以把他理解为C语言我们像用printf就要引用头文件#include<stdio.h>,同理**,我们想要进行cin cout的输入输出,这个引用也是必不可少的。**

2.cin cout关键字:

cin为流提取关键字,即输入关键字,相当于scanf但比scanf使用起来更加方便,不过这也导致了cin没法控制精度,但C++兼容C语言,故原来的C语法在C++也是成立的。
cin最大的特点在于可以自动识别类型,输入什么类型,C++的编译器就按照什么类型处理,不用像scanf那样去指定占位符。
cout为流插入关键字,即输出关键字,相当于printf但同理也更加方便,不过同样也没法控制精度,控制精度还需要C语言。
cout的特点也是自动识别类型,识别什么类型就输出什么类型
cin cout一般共同使用,如下:

int a=0;
cin>>a;
cout<<a<<endl;

注意,在C++语法中,<< >>不再表示位移操作符,而是代表一种指向和流向,cin流向内存,同理cout<<即从内存中再流出去,在C++中换行一般用endl,就和"\n"道理相同。
<< >>符号可以反复使用搭配换行和变量,可以同时输入和输出多个变量。
注意:
在这里插入图片描述
注意这个错误,cin不要写endl,就像scanf不要写\n一样。

3.缺省参数

1.概念:

缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。
例如下面的例子:

void Func(int a = 0)
 {
     cout<<a<<endl; 
 }
int main()
{
 Func();     // 没有传参时,使用参数的默认值
 Func(10);   // 传参时,使用指定的实参
return 0; 
}

结果为:
第一个结果为:0
第二个结果为:10
我总结为:
没传参就使用参数的默认值,即我们赋给的0,倘若传参了就是用对应的参数值。也就是你没有就用我的,你要是有那就用你的

2.分类

1.全缺省参数:

即所有的参数都有缺省值,因此我们在传参的时候可以什么值也不传,默认缺省值进入函数执行。
如下:

void Func(int a = 10, int b = 20, int c = 30)
 {
     cout<<"a = "<<a<<endl;
     cout<<"b = "<<b<<endl;
     cout<<"c = "<<c<<endl;
 }
 Func();
 

这样写输出的结果就是:10 20 30.

2.半缺省参数:

即部分参数有缺省值,有些参数没有缺省值,我们传参的时候要注意非缺省参数一定要为其赋值,缺省参数则可以不用赋值
如下:

void Func(int a, int b = 10, int c = 20)
 {
     cout<<"a = "<<a<<endl;
     cout<<"b = "<<b<<endl;
     cout<<"c = "<<c<<endl;
 }

注意缺省参数的几个注意的点:
1. 半缺省参数必须从右往左依次来给出,不能间隔着给
2. 缺省参数不能在函数声明和定义中同时出现
3. 缺省值必须是常量或者全局变量,不能int a=b,这样是不符合语法的
4. C语言不支持(编译器不支持)

这里特别强调一下第一点的理解,为什么缺省参数部分赋缺省值的时候是从右向左赋值的呢?这是因为我们给非缺省的参数传参是从左向右顺序进行的,故我们也会在赋参数的过程中按左到右的顺序赋予值,为了防止非缺省参数和缺省参数在赋值的时候出现混乱,故我们缺省赋值从右向左进行。
第二点的不能同时出现的原因就是倘若声明和定义所给的缺省值不同,编译器不知道应该识别哪个缺省值,这样就会出现错误。

4.函数重载:

在解释函数重载之前,让我们先想想C语言中一种比较头疼的情况:
假如我想编写一个支持不同类型进行相同处理的函数,我们知道函数的特性是规定参数类型,规定函数的返回值类型,那样就导致我们处理浮点型需要写一个函数,处理整型又需要写另一个函数,但本质上他们的函数操作时相同的,但我们使用函数的时候又必须得让其叫不同的函数名,处理函数少的程序还好,倘若是几百个函数组成的大程序,我们基本通过名字知道其功能,但频繁的改动名字就容易造成混乱,故C++为我们提供了一种良好的方式,让我们可以对相同功能但参数类型和返回值类型不同的情况使用同一个名字,这便是函数重载。

1.概念:

函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这
些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型
不同的问题。

如下:

#include<iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right) 
{
 cout << "int Add(int left, int right)" << endl;
 return left + right; 
 }
double Add(double left, double right) 
{
 cout << "double Add(double left, double right)" << endl;
 return left + right; 
 }
// 2、参数个数不同
void f()
{
 cout << "f()" << endl; 
 }
void f(int a)
{
 cout << "f(int a)" << endl; 
 }
// 3、参数类型顺序不同
void f(int a, char b) 
{
 cout << "f(int a,char b)" << endl; 
 }
void f(char b, int a)
 {
 cout << "f(char b, int a)" << endl; 
 }
int main()
{
 Add(10, 20);
 Add(10.1, 20.2);
 f();
 f(10);
 f(10, 'a');
 f('a', 10);
 return 0;
 }

观察上面的程序你会发现:虽然参数数量不同种类不同返回值不同,但他们都可以叫一个名字且不会报错,这就是函数重载的效果,运行结果如下:
在这里插入图片描述
那为什么C++可以支持函数重载呢?C++是如何支持函数重载的呢?C语言为何不支持函数重载呢?我们来探究一下:

2.函数重载原理:

在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。
由我们前面已经学到的C语言的编译链接的知识点可知,

  1. 实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们
    可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标
    文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么
    怎么办呢?
  2. 所以链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就
    会到b.o的符号表中找Add的地址,然后链接到一起。(老师要带同学们回顾一下)
  3. 那么链接时,面对Add函数,链接接器会使用哪个名字去找呢?这里每个编译器都有自己的
    函数名修饰规则。
    也就是说,实际上编译阶段编译器对函数的处理是首先识别函数的名字,但仅仅是识别名字,类似:—我知道你要用这个函数了,我清楚这件事情了----,然后最关键的是在链接阶段,由我们之前学过的知识点知道,链接阶段是程序去链接各种库函数和各种程序文件脚本的阶段,在这个阶段计算机将真正去识别相应的函数名字并且为其链接相应的函数的地址和库的地址,而我们C与C++编译器也是在这一阶段产生了不同。
    . 由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使
    用了g++演示了这个修饰后的名字。
    如下:
    首先写一个函数:
int Add(int a,int b)
{
    return a+b;
}
void Func(int a,double b,int*p)
{

}
int main()
{
      Add(1,2);
      func(1,2,0);
      return 0;
}

C语言是如何识别链接函数的呢?
在这里插入图片描述
我们发现C语言是直接去识别函数名字分配地址,即看我们写的函数名字
C++呢?

在这里插入图片描述
我们发现C++识别的函数名与原函数名字有很大不同,加了一些字母和其他字符
是的,我想到这里,你应该知道为什么C++支持函数重载的原因了。
. 我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】,也就是说,C++识别的不是函数的名字,而是函数对应的参数的类型,参数的个数,通过参数的信息在链接阶段用对应的一些修饰规则修饰函数名字,这样就可以让同名函数通过修饰变得可以被分辨出来,这就是C++支持函数重载的原理。而反观C语言,只能识别函数名且不会修饰,这导致C语言写函数名字必须不同,否则无法识别。

一个关键的细节:
注意:函数修饰主要看的就是参数,是不看返回值的,返回值不是函数修饰的原因,返回类型是任意的都可
即:如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办法区分。

5.内联函数:(关键字inline)

看到名字或许我们会感到很抽象和陌生,内联是什么意思呢?
不要着急,首先让我们再一次回想C语言的两个知识点:第一个是我们熟知的函数,第二个是我们有时候很少用的
函数和宏的优缺点是什么呢?
宏的优缺点
优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。

函数的优缺点
优点:
1.可以实现复杂的程序功能
2.方便调试
3.代码逻辑清楚
缺点:
1.代码冗长,影响性能
2.实现简单功能时比较麻烦

1.概念:

那么,倘若有一种东西,它弥补了函数和宏的缺点,同时又保留了函数和宏的优点,这样的东西一定十分受欢迎,这便是C++的内敛函数。
例如:宏书写两数相加:

#define hbw(x,y) x+y;
printf("%d",hbw(1,2)

如果用内联函数来写:

inline int hbw(int x,int y)
{
    return x+y;
}

我们会发现:内联函数的代码格式更类似函数,这意味着内敛函数是可以调试的,但它又与函数不同,以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

2.内联函数的特点:

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
    用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
    行效率。
  2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建
    议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
    是递归、且频繁调用的函数采用inline修饰
    ,否则编译器会忽略inline特性。
    也就是说,inline更多是向计算机提出一种想要使用内联函数的建议,但倘若计算机发现函数过大,就会无视这种建议依旧按照函数开辟栈帧。

注意一点:!!!!
inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到

6.引用(最为重要的一个知识点!)

1.引用的概念:

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
使用的格式:类型& 引用变量名(对象名) = 引用实体;
例如:我已经创建了一个整型变量a,即 int a=100;接下来我使用引用,int&b=a;这样之后,b这个名字就相当于是变量a的别名,对b的影响本质上也就是对a的影响,这就是引用。
注意:引用类型必须和引用实体是同种类型的
例如:

void TestRef()
{
    int a = 10;
    int& ra = a;//<====定义引用类型
    printf("%p\n", &a);
    printf("%p\n", &ra);
}

这里,打印a的地址和pa的地址,我们发现他们两个的地址是相同的,这也让我们思考到一个底层问题:引用的底层可能也是通过操纵指针去执行的。

2.引用的几点特性:

1. 引用在定义时必须初始化,即使用了引用就必须让其对应一个实体,不能像指针那样置空
2. 一个变量可以有多个引用,但一个引用不能对应多个实体
3. 引用一旦引用一个实体,再不能引用其他实体

3.常引用:权限的严格检查

看下段代码:

void TestConstRef()
{
    const int a = 10;
    int& ra = a;   // 该语句编译时会出错,a为常量
    const int& ra = a;
    int& b = 10; // 该语句编译时会出错,b为常量
    const int& b = 10;
    double d = 12.34;
    int& rd = d; // 该语句编译时会出错,类型不同
    const int& rd = d; }

1.从第一个看起,我们定义了一个const int不可改变的整型,那我们接下来是不能使用int&来引用a的,这是由于计算机的一个底层逻辑:即权限是只能缩小或者同层,但权限是不能放大的,我们已经const int来赋予a的权限,倘若我们不加int引用a,反而导致了不可改变这一条权限被放大了,这是违反了基层逻辑的,故第二条编译会出错。
2.接着看第二个,我们直接int&b=10,但10本身是常量,相当于const int类型的,这样我们的问题就跟上一个一样了,故会报错,故我们也要加const
3.再看第三个,对于double类型的d,我们用int&来引用double,这样按理来说没错呀?**但这里我们就要提到一个知识点:对于包括存在类型转换的赋值情况,计算机首先会把右值拷贝一份出来,利用这个拷贝的右值来进行下面的赋值和操作,我们称这个拷贝的右值为临时变量,临时变量默认是常属性,即const 类型的,所以说到这里我们就知道了,依旧是权限的放大问题,故我们要加const **

4.引用与指针的区别和相对应的特点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
    3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何
    一个同类型实体

    4. 没有NULL引用,但有NULL指针,引用必须初始化
  3. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32
    位平台下占4个字节,64位8字节)
    6. 引用自加即引用的实体增加1,即对应的变量去加1,指针自加即指针向后偏移一个类型的大小,相当于位置的改变
    7. 有多级指针,但是没有多级引用,一个引用只能对应一个实体
  4. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  5. 引用比指针使用起来相对更安全
    我们在语法概念的表层上可能感觉引用是不占用空间的,但其实本质上在底层引用是占用空间的,不过,我们现在的理解,就把引用理解为不占用空间就好

5.引用的应用场景:

1. 做参数

我们以冒泡函数的交换为例子:
倘若我们用C语言想要交换,就只能使用传指针的方式进行,这样我们还要反复写解引用符号*,有的时候容易忘记还麻烦,但倘若C++我们就可以用引用来操作,如下:

void Swap(int& left,int& right)
{
  int temp=left;
  left=right;
  right=temp;
}
int main()
{
    int a=0;
    int b=2;
    Swap(a,b);
}

引用传参,最关键的是可以提升效率,且操作简便
这里我们的参数left right,实际上就是 a和b,我们就相当于直接对a b直接交换,而不是之前的传地址解引用后指代a b本身。

2. !!!做返回值(易错)!!!

我们之前已经见过2种返回值的情况:
1.传值返回
2.传址返回

而今天我们学完引用后我们就接触到一个新的返回方式:3.引用返回
首先要清楚一个问题:在函数中开辟的栈区变量,最终终会被系统销毁回收,而且函数的空间是可以重复利用的,一块空间销毁的位置下一次再开辟空间也会利用相同的位置,甚至变量的开辟也会从原来的位置开辟,注意,我说的是栈区空间,对于堆区和静态区变量,即使函跳出函数作用域,函数被销毁,这两部分空间也不会被销毁
既然清楚上面的特点,我们就要知道三种返回值的特点:传值返回实际上就是将要返回的变量的数值临时拷贝一份返回,而不是之前函数里面的变量了,传地址返回也是返回的就是拷贝到一份地址,而引用是将返回的变量本身返回回来,它是不经过拷贝过程的,返回什么就是它本身。
故我们必须要强调:引用返回应该作用于那些堆区和静态区的变量,因为他们在函数结束后不会被销毁,不会导致引用到非法空间的情况
而传值和传地址返回则不需要特别强调这个情况,因为本身有拷贝的过程,这也是引用返回容易出现错误的关键原因

如下面的情况:

int& Add(int a, int b)
 {
    int c = a + b;
    return c;
  }
int main()
{
    int& ret = Add(1, 2);
    Add(3, 4);
    cout << "Add(1, 2) is :"<< ret <<endl;
    return 0; 
}

那么我们这段代码的结果是什么呢?
在这里插入图片描述

我们发现,第一个数字是打出来的,而第二个则是一堆乱码,这是为什么呢?就像我之前所说的,函数作用域跳出后就会自动销毁函数栈帧,相应的函数里面的变量也不见了,故我们的引用指向的位置非法,但还是返回回来了,故其实我们的第一个3的位置就应该已经是一堆乱码了,但VS编译器可能仍然帮助保存了起来,但我只要改一下就会变成乱码,如下:

int& Add(int a, int b)
 {
    int c = a + b;
    return c;
  }
int main()
{
    int& ret = Add(1, 2);
    cout<<"sssss    "<<ret<<endl;
    Add(3, 4);
   cout <<"ssssss   " << ret << endl;
    return 0; 
}

在这里插入图片描述
虽然是乱码,但我们发现两个乱码却是相同的,这说明了函数的开辟是可重复性的,引用对应的位置没发生改变,对应的的变量都开辟在同一个位置。
不信我们再这样写:

using namespace std;
int& Add(int a, int b) 
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);
	cout << ret << endl;
	Add(3, 4);
	cout << ret << endl;
	return 0;
}

在这里插入图片描述
同样的位置,值是不同的,且符合函数的返回结果,正好证实了我的结论:这说明了函数的开辟是可重复性的,引用对应的位置没发生改变,对应的的变量都开辟在同一个位置。**
综上:我们要再一次强调,引用返回应该适用于跳出函数作用域后不会被销毁的变量,比如堆区和静态区,它是不适用于栈区变量的!!!!!!!

7.总结:

以上便是我初入C++掌握到的一些知识点,你会发现很多知识点在展开的时候都和C语言有联系,没错,要知道任何事物的产生都有其意义,倘若C语言是完美的,那么C++便没有诞生的可能性了,C++就是为了优化C语言的语法和错误而诞生的语言,我们接下来的学习也应该参考C语言的语法去思考,去举一反三,从而了解C++语法为何而诞生。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值