C++入门基础语法

前言

C++是一门面向对象语言,与面向过程的C语言有着很大的差别,C++语法还是比较难的,可以说是主流语言中最难的一门语言(只是语法层面),所以说建议先学C语言,学的比较扎实了以后再学习C++,但是C++毕竟是在C语言基础之上扩充的,所以C++还是支持C语言的语法的。

命名空间

为什么要有命名空间

假设我们在一个项目中的两个源文件都创建了一个名为a的全局变量,那么在代码链接中就会报一个错误:找到一个或多个多重定义的符号

在这里插入图片描述

那我们可以用两个方法解决,第一种方法:改变其中一个全局变量的名字(可以但不推荐),但是你以后到了公司,在和别人合作时,你们俩出现这种情况,要去叫他修改或者自己修改,这是很麻烦的。第二种方法:就是定义一块属于你的命名空间,命名空间相当于一块只有你知道的宝藏地,并且只有知道这块宝藏地位置的人才可以去那里拿宝藏。

在完成大项目的过程中,变量、函数和以后要学的类都是大量存在的,并且它们的名称将可能存在于全局作用域中,就可能会发生命名冲突,使用命名空间的目的就是对标识符进行本地化,以避免命名冲突或者命名污染

命名空间的定义

定义命名空间,需要使用到关键字namespace,然后加上一对"{}",在括号中声明变量、函数、类,甚至是再嵌套一个命名空间,接下来我们边写代码边注释上命名空间的使用方法。

// 命名空间需要使用关键字namespace定义
// shl是这块命名空间的名称
namespace shl
{
	// 命名空间中可以定义普通变量
	// rand是声明在这块命名空间的成员
	int rand = 0;
	// 不可以在命名空间中给变量赋值,只可以进行初始化
	// rand = 10; 错误使用方法
	// 命名空间可以定义一个结构体
	struct List
	{
		int data;
		int size;
		int capacity;
	};
	// 命名空间可以定义一个函数
	int Add(int num1, int num2)
	{
		return num1 + num2;
	}
	// 命名空间可以再嵌套一个结构体
	namespace n1
	{
		int n2 = 0;
	};
};

/*
	同一个工程允许存在多个名字一样的命名空间,编译器最后会将所有名字一样的命名空间
	整合到一起
*/
namespace shl
{

};

命名空间的使用

命名空间的使用方法分为三种:

1.命名空间 + 作用域限定符

2.使用using将命名空间中成员引入

3.使用using namespace + 命名空间名称引入(注意:慎用!!!)

// 2.使用using将命名空间中成员引入
using shl::Add;

// 3.使用using namespace命名空间名称引入 注意:慎用!!!
// using namespace shl;

int main()
{
	// 1.这些使用方法为:命名空间+作用域限定符
	// 命名空间的普通变量使用方法
	printf("%d\n", shl::rand);
	// 命名空间的函数使用方法
	printf("%d\n", shl::Add(1, 2));
	// 命名空间的结构体使用方法
	struct shl::List L;
	// 命名空间嵌套命名空间的使用方法
	printf("%d\n", shl::n1::n2);

	// 2.使用using将命名空间中成员引入
	printf("%d\n", Add(1,2));

	return 0;
}

C++的输入输出

在C语言中我们都知道想要输入输出都需要引一个头文件#include <stdio.h>,在C++中也可以引这个头文件,也可以引一个C++特有的头文件#include <iostream>(注意:C++引头文件不需要写.h的后缀),接下来我们边写代码边注释上C++的输入输出使用。

// i-input o-output stream-流 翻译过来相当于输入输出流
// 这是C++的头文件,为了与C语言头文件区分,也为了正确使用命名空间,已经不需要.h的后缀
#include <iostream>

// C++中cout、cin函数是实现在名为std的命名空间中的
/*
	注意:在平常自己练习编写代码的时候,可以将std命名空间引入,
	但是在编写大型项目时最好不要这样做!!!
*/
using namespace std;

int main()
{
	int a = 0;
	// cout是C++特有的输出函数,类似于C语言中的printf()函数
	// <<是流插入运算符,<<后面写你要输出到屏幕上的变量值、字符串等
    /*
    	在C语言中,如果你要输出变量的值,你还要写上变量对应的转换说明符,而在C++中,
    	你可以直接写变量的名字,编译器会自动识别并输出变量存储的内容
    */
	// endl的英语意思是end of line,作用是换行作用,相当于C语言中的'\n'
	cout << "hello world!" << endl;
    // cin是C++特有的输入函数,类似于C语言中的scanf()函数
    // >>是流提取运算符,>>写你要从键盘上输入的变量的值等
    /*
    	和cout规则相同,你可以直接在>>后面写你要输入的变量的名字,且与scanf函数相同,
    	当你输入空格或换行,cin就会结束输入
    */
	cin >> a;
	cout << a << endl;
	return 0;
}

缺省参数

什么是缺省参数

缺省参数:你可以在声明定义函数时为形式参数指定一个默认值,当你调用参数并且不传实参时,编译器就会使用这个默认值。

缺省参数的规则

  1. 缺省参数不能同时在函数的声明和定义中同时出现,这也是上面为什么或要加粗的原因。
  2. 缺省值必须是常量或者是全局变量
  3. C语言是不支持这个语法的
// 缺省参数不能同时在函数的声明和定义中出现,尽管缺省参数的值是一样的也不行

// 错误的写法
// 应该将其中一个地方的默认值删掉才行
// 函数的声明
// int Add(int A = 10, int B = 20);

// 函数的定义
/*int Add(int A = 10, int B = 20)
{
    return A + B;
}*/

缺省参数的分类

全缺省参数

全缺省参数:即所有的形式参数都有一个指定的默认值。

// 以下即全缺省参数
int Add(int a = 10, int b = 20)
{
	return a + b;
}
int main()
{
	// 不传实参即用默认值
	cout << Add() << endl;
	// 传实参即使用实参
	cout << Add(1, 2) << endl;

	return 0;
}

半缺省参数

半缺省参数:即部分形式参数有指定的默认值,但谁有默认值谁没有默认值,C++是有制定规则的,规则:半缺省参数只能从右向左给出,不能间隔着给,这是C++的语法规则。

// 错误的写法
// int Add(int A = 10, int B, int C = 20);

// 正确的写法
int Add(int A, int B = 20, int C = 10);

函数重载

函数重载的定义

在C++中,是允许在同一作用域声明名字相同的函数的,但是函数的参数列表(形参的个数类型顺序)必须不同,重载函数常用来实现功能类似但数据类型不同的问题。

函数重载的分类

  1. 参数类型不同
  2. 参数个数不同
  3. 参数顺序不同(顺序是指参数类型顺序的不同)
// 1.参数类型不同
int Add(int a, int b)
{
	return a + b;
}

double Add(double a, double b)
{
	return a + b;
}
int main()
{
	// 只需要传对应数据类型的数值,编译器就会自动识别
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 2.2) << endl;

	return 0;
}

// 2.参数个数不同
int Add(int a, int b)
{
	return a + b;
}

int Add(int a, int b, int c)
{
	return a + b + c;
}

int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1, 2, 3) << endl;

	return 0;
}

// 3.参数顺序不同(顺序是指参数类型顺序的不同)
void  f(int a, double b)
{
	cout << "Add(int,char)" << endl;
}

void  f(double b, int a)
{
	cout << "Add(char,int)" << endl;
}
int main()
{
	f(1, 2.2);
	f(2.2, 1);

	return 0;
}

名字修饰

我们来想一下为什么C语言没有重载函数而C++有重载函数呢?答案也挺简单的,因为C语言使用的编译器与C++使用的编译器不同。那我们用linux系统来浅探一下究竟。

首先,我们在linux系统下写了一个名为test.c的C语言文件,如下图所示。

在这里插入图片描述

接下我们使用gcc(linux中C语言的编译器)进行编译,很显然以下的报错就是有两个同名的函数:
在这里插入图片描述
然后我们将其中一个函数注释掉,进行编译,肯定会编译成功,然后生成"a.out"文件,我们在linux使用指令"objdump -S a.out",就可以看到链接时,函数func修饰后函数名为:
在这里插入图片描述

接下来我们使用g++(linux中C++的编译器)对刚刚编写的test.c进行编译(在linux中尽管你写的是C语言文件,但是你可以主动调用编译器对你的代码进行编译,所以在linux中可以让c语言文件进行C++或C的方式编译,但是在Vs平台下,.c就默认自动用C语言编译器编译,.cpp就默认自动使用C++编译器编译),将刚刚的注释符号去掉后,并没有报错:
在这里插入图片描述

我们再linux使用指令"objdump -S",就可以看到链接时,函数func修饰后函数名为:

在这里插入图片描述

看我们写的两个函数的名字被g++编译器编译器成"_Z4funcv"和"_Z4funci",看,这就是为什么C++支持函数重载的原因。

C++和C语言编译器都有各自名字修饰的规则,只不过他们各自的规则不同,C语言的名字规则很直白,你给函数取什么名字,编译器会取相同的名字,而C++的名字修饰规则是"_Z(函数名长度)(函数名)(各参数首字母小写)"。所以说在C++中,你可以取相同的函数名、相同类型的返回值,但是参数列表中不能有完全一样类型的形参变量。

总结来说:C语言只会将你的双胞胎函数取同一个名字,而C++把你的双胞胎函数按照特点取不同的名字。

引用

引用的概念

概念:

引用就是你对你定义过的变量取别名,编译器不会为你这个引用变量新开辟一块空间,而是与引用实体(即你引用的变量)共用一块空间

语法规则:

类型& 引用变量名 = 引用实体;

代码:

int main()
{
	int a = 10;
	// 类型& 引用变量名 = 引用实体;
	int& b = a;

	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	cout << "变量a的地址" << &a << endl;
	/*
		要对引用变量取地址,依旧是在引用变量名前使用取地址符& ,
		但是& (变量名)是取地址,(类型)& 是定义一个引用变量,千万不要混淆
	*/
	cout << "引用变量a的地址" << &b << endl;

	return 0;
}

运行后窗口:

在这里插入图片描述

引用的特性

  1. 引用在定义时就必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,就不能再引用其他实体

代码:

int main()
{
	int a = 10;
	int b = 20;
	// 1.引用在定义时就必须初始化
	int& ra = a;
	// err
	// int& ra;

	// 2.一个变量可以有多个引用
	int& ra1 = a;
	int& ra2 = a;

	// 3.引用一旦引用一个实体,就不能再引用其他实体
	/*
		"ra = b;"的意思是将b的值赋给引用变量ra,不是ra变成b的别名,
		因为对ra取地址发现还是与a的地址相同
	*/
	ra = b;
	cout << "ra=" << ra << endl;
	cout << "a的地址为" << &a << endl;
	cout << "ra的地址为" << &ra << endl;
	cout << "b的地址为" << &b << endl;

	return 0;
}

运行窗口:

在这里插入图片描述

常引用

int main()
{
	// 平常创建的普通类型变量,如int、char等,他们是可以读可以修改值
	// 被const修饰过的变量,就不能修改值,只剩下读的权限了
	// 如果你想引用一个被const修饰过的常变量,那么你也只能用常引用的方式
	const int a = 10;
	// int& ra1 = a; error
	const int& ra2 = a;
	// 10是个常量,本身就只有读的权限,所以也只能用常引用
	// int& b = 10; error
	const int& b = 10;
	double d = 12.1;
	// 编译器会创建一个临时变量存储d的值,然后再赋给变量i
	int i = d;
	//int& rd = d; error
	// 与上同理
	const int& rd = d;

	return 0;
}

**double类型为什么要赋给常引用的原因:**double类型的变量赋给int类型的变量,会发生类型转换,转换的过程中是编译器会将double类型的变量先放到一个临时变量中,这个临时变量具有常属性,可读不可写,所以只能用const int& 类型名接收。而int给int是直接给的,不需要经过临时变量。所以严格上来讲,这个int&引用不是double的别名,而是这个中间变量的别名。

建议:使用传引用传参时,如果不需要改变形参的值,则建议使用const (类型)&。

结论:const Type& 可以接收各种类型的对象。

引用的应用场景

引用可以用来做参数和做返回值,并且运用到引用的特点。

  1. 做参数

我们平常给函数传参的时候有两种方法:传值、传址,现在就有第三种方法了:传引用。具体使用如下:

// 两数交换函数
void Swap(int& ra, int& rb)
{
	int tmp = ra;
	ra = rb;
	rb = tmp;
}

int main()
{
	int a = 10;
	int b = 20;
	Swap(a, b);
	cout << a << endl;
	cout << b << endl;

	return 0;
}

我们都知道在C语言中,如果你想写一个两数交换的函数,你必须传变量的地址,然后函数的形参得是指针,否则你就无法真正地改变交换两个变量的值,但是我们现在可以让形参为引用变量,而我们只要传变量的值给它,就也可以达到真正改变两数交换的效果。因此我们可以知道传引用的本质是:形参和实参是同一个东西。函数形参列表定义的引用变量,相当于是实参的别名。

我们再提一点,之前在学习链表时,只要接口函数调用完头指针可能发生变化的函数,我们都是传头指针的地址,然后形参用二级指针接收,但是二级指针还是有点难度的,当初都给我绕晕了,理了半天才理清楚,但是现在有传引用,我们就不需要用二级指针了,这就是传引用的优点之一,具体看以下代码:

代码:

// 单链表的头插
// 这样相当于给头指针取个别名"phead",所以这个"phead"就是"plist",根本不需要用二级指针,phead改变,plist就跟着改变
// 因为phead的类型就是SLNode*,所以在后面加个"&"就是引用了,可以与int&做个对比
void SListPushFront(SLNode*& phead, SLTDataType x)
{
	// 创建一个新节点
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		printf("create fail");
		exit(-1);
	}
	else
	{
		newnode->data = x;
		newnode->next = NULL;
	}
	// 第一种情况:单链表为空
	if (phead == NULL)
	{
		phead = newnode;
	}
	// 第二种情况:单链表有一个及以上的结点
	else
	{
		newnode->next = phead;
		phead = newnode;
	}
}

int main()
{
	SLNode* plist = NULL;
    // 只需要传指针就好
	SListPushFront(plist, 1);
	SListPushFront(plist, 2);
	SListPushFront(plist, 3);
	SListPushFront(plist, 4);
	SListPrint(plist);
	

	return 0;
}

运行窗口:

在这里插入图片描述

  1. 做返回值

我们也可以让函数的返回值为引用型,具体写法为:(类型)& 函数名(形参列表),很显然我们返回值类型为引用型,那我也得定义一个引用变量接收返回值。但是我们不能随随便便让引用做返回值,我们接下来用代码来演示一下:

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}

int main()
{
    int a = 10;
   	int b = 20;
	int& ret = Add(a, b);
	Add(1, 2);
	// 会发现ret的值不是30,是3
	cout << ret << endl;

	return 0;
}

运行窗口:

在这里插入图片描述

我们打印ret的值为3而不是30,我们来一探下究竟,我们来编译一下,我们发现不管哪次调用函数Add,引用变量ret的地址与函数Add的返回值c的地址一模一样,所以说函数Add返回值是引用型的,也就是说c就是引用型的,而引用变量ret引用了函数Add的返回值,ret就相当于返回值c的别名了,所以第二次调用了函数Add,返回值c的值变成了3,而ret也会变成3,因为ret是c的别名,c的值改变ret就跟着改变。

在这里插入图片描述

在这里插入图片描述

那我们要在什么情况下让引用做返回值呢?比如说以下代码的场景,变量n被static修饰过后,作用域就变了,尽管函数Count结束,变量n也不会被销毁,所以这种情况我们就可以让引用做返回值。

int& Count()
{
	static int n = 0;
	n++;
	
	return  n;
}

int main()
{
	int& ret = Count();
	cout << ret << endl;
	ret = Count();
	cout << ret << endl;

	return 0;
}

总结:如果函数返回时,出了函数的作用域,如果返回对象没有还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

做参数与做返回值价值体现

  1. 做参数:a.提高效率 b.形参改变,实参也会改变
  2. 做返回值:a.提升效率 b.修改返回值

传参和传引用的效率差

我们知道,当我们使用传值的传参方式时,编译器就会临时拷贝一份你传的值,而这是需要占用空间和需要时间的,特别是你传一份类型非常大的值时,代码执行效率会大打折扣。而且如果你以传值的方式传参,并且有返回值时,系统不是直接给你返回这份值,而是有一份返回值的临时拷贝,然后把这份临时拷贝返回,如果返回值类型很大的话,效率也会大打折扣。

引用与指针的区别

引用就是为一个变量取一个外号,和被引用变量共用一块空间,而创建指针,编译器是需要开辟一块空间存储这个指针的,然后指针存储的是其他变量的地址。

引用和指针的区别:

  1. 引用是定义一个变量的别名,而指针是存储一个变量的地址。
  2. 引用必须初始化,而指针是可以不用初始化的。
  3. 引用在引用一个实体后,就不能再引用其他实体了,而指针像个渣男一样,想指谁就指谁。
  4. 没有NULL引用,有NULL指针。
  5. 在sizeof中含义不同,引用的结果为被引用变量的大小,而指针的结果始终是4/8个字节。
  6. 引用变量加一,相当于被引用的变量值加一,指针加一则是指针向后偏移一个类型的大小。
  7. 有多级指针,没有多级引用。
  8. 访问实体方式不同,引用编译器自己可以处理,而指针需要使用一个解引用符号。
  9. 引用相比指针来说更安全。

内联函数

前言

调用函数,是要建立栈帧的,就代表着调用函数是有消耗的,那如果你编写了一个函数,这个函数只要几行,但是使用频繁,这样消耗岂不是很大!所以我们可以想想怎么优化,第一种方法:定义宏,但是定义宏又很麻烦,比如说:**不方便调试,因为在编译时,会直接替换掉,你都没办法看到具体细节,这样就导致代码的可读性差,可维护性差。**所以C++就定义了一个内联函数。

概念

使用关键字inline修饰的函数就是内联函数,内联函数的意义就是在编译时,编译器会在调用内联函数的位置展开内联函数,这样就没有函数压栈的开销了,可以提升代码运行的效率。我们写一段简单的代码,然后进入调试状态,看一下汇编代码,浅探一下究竟。

inline int Add(int a, int b)
{
	int c = a + b;

	return c;
}

int main()
{
	int a = 10;
	int b = 20;
	Add(1, 2);

	return 0;
}

在Debug模式下调试我们还需要做一下其他准备,因为Debug模式对你的代码没有像release模式下那样优化,所以我们要做一些操作。(我使用的是Vs2019,其他版本的vs可能操作有些不同)。

在这里插入图片描述

步骤1

在这里插入图片描述

步骤2

属性更改完成后,我们先对普通的没有被inline修饰的Add函数进行调试,查看它的汇编代码,出现了一句call指令,很明显编译器调用了函数Add:

在这里插入图片描述

我们再改成内联函数的形式,进行调试,然后查看它的汇编代码,我们可以发现根本没有call指令,说明并没有调用代码,而是把内联函数Add展开了:

在这里插入图片描述

特性

  1. inline是一种空间换时间的写法,会省去调用函数额外的开销。所以代码很长(大概10行以上,当然不同的编译器标准不同)的话,或者函数中使用了递归或循环,就不适合使用内联函数。
  2. inline对于编译器来说只是一种建议,所以如果函数里面使用了递归或循环,编译器就不会听从你这个建议,会继续使用调用函数的方法。
  3. inline不建议声明和定义时分离,分离会导致链接错误。因为inline会使调用函数处展开函数,链接就找不到函数的地址。

auto(C++11新语法)

概念

你可以用auto定义一个变量,这个变量的类型是你赋值然后由编译器判断这个值的类型来的,所以说你使用auto定义一个变量,必须初始化,否则编译器无法判断这个变量的类型。但是auto不是一种类型,更具体的说auto是一个类型声明时的“占位符”,在编译器编译代码时,会自动将auto替换成变量实际的类型。

代码:

int main()
{
	auto a = 10;
	auto b = 10.11;
	auto c = 'a';

	cout << "变量a的类型是" << typeid(a).name() << endl;
	cout << "变量b的类型是" << typeid(b).name() << endl;
	cout << "变量c的类型是" << typeid(c).name() << endl;

	return 0;
}

运行截图:

在这里插入图片描述

auto的使用规则

  1. auto与指针引用结合使用

    1. 用auto声明指针变量时,可以写auto或auto*。
    int x = 10;
    auto ax = &x;
    auto* px = &x;
    
    1. 用auto声明引用类型时,必须写&
    int x = 10;
    auto& rx = x;
    // 混淆写法,你是想声明一个int类型的变量还是一个引用类型的变量?编译器猜不出来
    // auto rx = x;
    
  2. 使用auto在同一行定义多个变量

    在一行使用auto声明多个变量时,这些变量必须是同一类型的。不然编译器就会报错,因为编译器实际是只对第一个类型进行推导的,然后用推导出来的类型去定义其他变量。

    auto a = 1, b = 2;
    // 错误的写法
    // auto c = 1, d = 1.2;
    
  3. auto不能推导的场景

    1. 不能作为函数的参数

      // 错误的写法
      // 编译器不知道x的类型,无法对a做出实际类型的推导
      void Test(auto x)
      {
          
      }
      
    2. auto不能直接用来声明数组

      // 错误的写法
      auto a[] = { 1,2,3 };
      

基于范围的for循环(C++11新语法)

  1. 范围for的语法

    在C++中如果我们想遍历一个数组,我们就可以使用C++11的新语法基于范围的for循环,这个for循环分为两部分,使用":“隔开两部分,”:"左边用于迭代的变量,右边是被迭代的范围。具体使用如以下代码所示:

    int main()
    {
    	int arr[10] = { 0 };
    	int i = 1;
        // 要使用引用的方式才能改变数组的值
    	for (auto& x : arr)
    	{
    		x += i;
    		i++;
    	}
       	/*
       	自动取数组的每个元素赋给x,所以x只是一份复制,对x加一,
       	数组的元素并不会发生改变
       	*/
    	for (auto x : arr)
    	{
    		cout << x << endl;
    	}
    	return 0;
    }
    
  2. 范围for的使用条件

    1. for循环迭代的范围必须是确定的

      如以下代码,就是错误的使用:

      // 函数传参实际传的是数组首元素的地址,所以编译器并不确定数组的范围
      void Test(int arr[])
      {
          for(auto& x : arr)
          {
              cout << x + 1 << endl;
          }
      }
      

指针空值nullptr(C++11新语法)

概念

我们在编写C语言都知道,定义一个变量的时候最好初始化以下,指针变量也是,尽管不指向其他变量,也最好置空,置空的方法有两种:

int* p1 =NULL;
int* p2 = 0;

注意:给指针置0也相当于NULL,这是定义。

那假设我们在C++中写了以下两个函数:

void f(int)
{
	cout << "f(int)" << endl;
}

void f(int*)
{
	cout << "f(int*)" << endl;
}

int main()
{
	f(0);
	f(NULL);
	f((int*)NULL);

	return 0;
}

运行截图:

在这里插入图片描述

我们发现,只有第三次,我们传了一个被强制转换为(int*)的NULL,才调用了"void f(int*)",但假设我们传NULL也是想调用函数"void f(int*)"呢?这时就会产生歧义,所以C++11就引入了一个新概念——nullptr,他的作用也是空值指针的作用,只是与NULL效果不一样,它就是很明确的是一个空值指针,所以在C++中如果指针变量要置空,建议将nullptr赋给指针变量,而不去使用NULL。

void f(int)
{
	cout << "f(int)" << endl;
}

void f(int*)
{
	cout << "f(int*)" << endl;
}

int main()
{
	f(0);
	f(nullptr);
	f((int*)nullptr);

	return 0;
}

运行截图:

在这里插入图片描述

注意:

  1. nullptr是C++11新引入的关键字,所以可以直接使用nullptr。
  2. nullptr的大小与(void*)0相同,在32位系统下是4个字节,64位系统是8个字节。
  3. 为了提高代码的健壮性,表示指针空值的话建议都使用nullptr。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值