学习笔记cppprimer-Part1

在这里插入图片描述一个函数的定义包括四个部分:返回类型函数名、一个括号包围的形参列表以及函数体
cerr:输出警告和错误信息,标准错误
clog:输出程序运行时的一般性信息
endl:操纵符 结束当行,并将与设备关联的缓冲区中的内容刷到设备中
作用域运算符(:?:如需要写出std::cout,使用作用域限定符指出我们想使用定义在命名空间std中的名字cout

item1.isbn():调用名位isbn的成员函数,有时候也被称为方法
数据结构:数据及其上所允许的操作的一种逻辑组合。

第一部分

C++定义了一套包括算数类型空类型在内的基本数据类型
其中算数类型包含了字符、整型数、布尔值和浮点数。
在这里插入图片描述可寻址的最小内存块成为“字节(byte)”,存储的基本单元称为“字(word)”
大多数机器的字节由8比特构成,字则由32或64比特构成,也就是4或8字节

执行浮点数运算时选用double,因为float通常精度不够而且双精度浮点数和单精度浮点数的计算代价相差无几。

类型转换:
unsigned char c = -1 //输出结果为255
当我们赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。例如8比特大小的unsigned char 可以表示0-255区间内的值,赋值-1,则实际结果是该值对256取模后所得的余数。
signed char c2 =256 //c2的值是未定义的。超出范围,则结果是未定义的。

2^32=4 294 967 296

unsigned u=10,u2=42;
std::cout<<u-u2<<std::endl; 
//结果为4294967296-|(10-42)|=4294967264

转义序列
在这里插入图片描述
在这里插入图片描述
std::cout<<“Hi \x4dO\115!\n” //输出Hi MOM!,然后转到新的一行
std::cout<<’\115’<<’\n’; //输出M,转到新一行

注意:如果反斜线\后跟着的八进制数字超过3个,只有前3个数字与\构成转义序列。如"\1234"表示八进制数123对应的字符以及字符4
而\x要使用后面跟着的所有数字。如\x1234表示一个16位的字符

在这里插入图片描述
通常情况下,对象是指一块能存储数据并具有某种类型的内存空间

在这里插入图片描述
这里涉及到的就是默认初始化的问题

如果在定义变量时没有指定初值,那么变量会被默认初始化。

三条性质:1、定义在任何函数体外的变量会被初始化为0。 2、定义在函数体内部的变量不会被初始化。 3、类的对象未被初始化,则初值由类决定。

所以第一个变量是一个空字符串,第二个变量是0,第三个变量不确定,第四个变量因为在std作用域内,而std作用域内有其定义,所以是个空字符串。

关键概念:静态类型C++是一种静态类型语言,其含义是在编译阶段检查类型。
在这里插入图片描述在这里插入图片描述标识符也不能以数字开头

建议:在第一次使用变量时再定义它

#include <iostream>
using namespace std;
int reused = 42; //reused拥有全局作用域
int main()
{
	int unique = 0; //unique拥有块作用域
	int reused = 0;
	//输出0 0 
	cout << reused << " " << unique << endl;
	//输出42 0 ,显式地访问全局变量reused;
	cout << ::reused << " " << unique << endl;

	system("pause");
	return EXIT_SUCCESS;
}

引用

当我们使用术语引用时,指的其实是左值引用
C++11 中新增了一种所谓的右值引用,这种引用主要用于内置类。

引用即别名
int ival=1024;
int &refVal=ival; //refVal指向ival
int &refVal; //错误:引用必须被初始化

refVal=2; //把2赋给refVal指向的对象,此处即是赋给了ival
int ii =refVal ; //与ii=ival执行结果一样

int &refVal3 = refVal ; //正确
int &refVal4 = 10; //错误,引用类型的初始值必须是一个对象
double dval = 3.14;
int &refVal5 = dval; //错误:此处引用类型的初始值必须是int 型对象

在这里插入图片描述bd不合法,因为需要明确引用的对象

在这里插入图片描述都合法。但是进行了强制类型转换
在这里插入图片描述两个10

指针中,判断赋值对象最好的办法就是记住赋值永远改变的是等号左侧的对象。
pi = &ival; //pi的值被改变,现在pi指向了ival
pi = 0; //ival的值被改变,指针pi并没有改变,则pi(也就是指针pi指向的那个对象)发生改变。

void 指针*
void* 是一种特殊的指针类型,可用于存放任意对象的地址。

double obj = 3.14, *pd = &obj;
void * pv = &obj; //obj可以是任意类型的对象
pv = pd; //pv可以存放任意类型的指针

const 限定符
有时候我们希望定义这样一种变量,它的值不能被改变。如
const int bufSize = 512; //输入缓冲区大小
这样就把bufSize定义为一个常量,任何试图为bufSize赋值的行为都将引发错误
bufSize = 512 ; //错误,试图向const对象写值

因为const对象一旦创建后其值就不能再改变,所以const对象必须初始化。

const int i = get_size(); // 正确,运行时初始化
const int j = 42; //正确:编译初始化
const int k; // 错误,k是一个未经初始化的常量

默认状态下,const对象仅在文件内有效
如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字

//file_1.cc 定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
//file_1.h 头文件
extern const int bufSize; //与file_1.cc中定义的bufSize是同一个

常量引用
常量之间才能相互引用
const int ci = 1024;
const int &r1 = ci; //正确
int &r2 = ci; //错误

常量指针必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。

int errNumb = 0;
int *const curErr = &errNumb; //curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = &pi; //pip是一个指向常量对象的常量指针

指针与const
1:指向常量的指针:要想存放变量的地址,必须使用指向常量的指针:const int *p

	int i = -1,&r = 0; //错误,&r需要指向一个对象
    const int i = -1,&r = 0; //正确const int &r = 常量

2:常量指针:指针自身是一个对象,不可变。int *const p = &pi (常量指针必须初始化!!!)

3:指向常量的常量指针。const int *const p (不仅指针本身不可变,指针所指对象也不可变)

const int *const p3 = &i2 ; //正确

顶层const:指针本身是个常量
底层const指针所指的对象是一个常量

int i =0;
int *const p1 = &i;  //顶层const
const int ci = 42;  //顶层const
const int *p2 = &ci;  //底层const
const int *const p3 = p2; //靠右的是顶层const,靠左的是底层const
const int &r = ci; //用于声明引用的const都是底层const

constexpr

常量表达式是指值不会改变并且在编译过程中就能得到计算结果的表达式,如字面值。

一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定

const int max_files = 20; //max_files是常量表达式
const int limit = max_files + 1; //limit是常量表达式
int staff_size = 27; //staff_size不是常量表达式
const int sz = get_size(); //sz不是常量表达式,因为sz的值要到get_size()运行时才能得到,所以不是常量表达式。

声明constexpr的变量一定是一个变量,而且必须用常量表达式初始化。

constexpr int mf = 20; //20是常量表达式
constexpr int limit = mf + 1; //mf+1是常量表达式
constexpr int sz = size(); //只有当size是一个constexpr函数时才是一条正确的声明语句

类型别名

typedef double wags;//wages是double的同义词
typedef wags base,*p; //base是double的同义词,p是double*的同义词

auto (C++11)
auto定义的变量必须有初始值

auto item = val1 + val2;

auto可以在一条语句中声明多个变量,但是一条声明语句只能有一个基本数据类型

auto i=0,*p=&i; //正确,i是整数,p是整型指针
auto sz=0,pi=3.14; // 错误 sz和pi的类型不一致

decltype选择并返回操作数的数据类型

decltype(f()) sum = x; //sum的类型就是函数f的返回类型,sum必须初始化

2.6.3头文件通常包含那些只能被定义一次的实体,如类、const和constexpr变量。

#ifdef仅当变量已定义时为真
#ifndef仅当变量已未定义时为真
#endif一旦检测结果为真,则执行后续操作直至遇到**#endif**指令为止。

#ifndef SALES_DATA_H
#defind SALES_DATA_H
#include <string>
struct Sales_data{
	std::string bookNo;
	unsigned units_sold = 0;
	double revene = 0.0;
}
#endif

第3章 字符串、向量和数组

头文件不应包含using声明
如果声明在头文件中,若是其他文件引用此头文件,而对于某些程序来说,由于不经意间包含的一些名字,反而可能产生始料未及的名字冲突。

在这里插入图片描述使用getline读取一整行

string line;
getline(cin,line);
cout<<line<<endl;

size函数返回的是一个string::size_type类型的值
size函数返回一个无符号整型值。
当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+)的两侧的运算对象至少有一个是string;

string s4 = s1 + ","; //正确 把一个string对象和一个字面值相加
string s5 = "hello" + ","; //错误,两个运算符对象都不是string,不能把字面值直接相加
string s6 = (s1 + ",") + "world"; //正确 因为括号里面的内容组成了一个string对象。
string s7 = ("hello" + ",") + s2; //错误 括号里面的不能把字面值直接相加。

字符串字面值与string是不同的类型

cctype头文件
在这里插入图片描述
C语言中的头文件形如name.h 在C++中这些文件命名为cname
c表示这是一个属于C语言标准库的头文件

范围for(range for) (C++11)

for (declaration: expression)
	statement

如下

string str("some string");
for(auto c : str)  //对于str中的每个字符
	cout<<c<<endl;  //输出当前字符。

想要改变string对象中字符的值,必须把循环变量定义为引用类型

string s ("Hello World!!!");
for(auto &c : s)  //对于s中的每个字符(注意c是引用)
	c = toupper(c);  //c是一个引用,因此赋值语句将改变s中字符的值
cout<<s <<endl;

s[s.size()-1] 为最后一个字符

把遇到的第一个单词改为大写

	for (decltype(s.size())) index = 0;
	index != s.size() && !isspace(s[index]; ++index)
		s[index] = toupper(s[index]);

练习3.10 编写一段程序,读入一个包含标点符号的字符串,将标点符号去除后输出字符串剩余的部分。

	string str("hello@#$world");

	for (int index = 0;index < str.size();index++)
	{
		if (!ispunct(str[index]))
			cout << str[index];
	}

vector

vector<vector<string>> file; //该向量的元素是vector对象

初始化
圆括号构造vector对象
花括号列表初始化

	vector<int> v1(10); //v1有十个元素,每个的值都是0
	vector<int> v1{ 10 }; //v2有一个元素,该元素的值是10

	vector<int> v1(10,1); //10个1
	vector<int> v1{ 10,1 };//10和1两个元素
vector<string> v7{10};//v7有10个默认初始化的元素
vector<string> v8{10,"hi"}; //10个"hi"

迭代器

如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。
在这里插入图片描述改变vector容量,会使vector对象的迭代器失效。

使用迭代器完成二分查找

//text必须是有序的
auto beg = text.begin(),end = text.end();
auto mid = text.begin() + (end - beg)/2; 
while(mid != end && *mid != sought)
{
	if(sought < *mid)
		end = mid;
	else
		beg = mid +  1;
	mid = beg + (end - beg)/2;
}

数组

const char a4[6] = "daniel"; //错误,还有空字符

不允许拷贝和赋值

int a[] = {0,1,2}; 
int a2 = a; //不允许使用一个数组初始化另一个数组
a2 = a; //不能把一个数组直接赋值给另一个数组

Tip要想理解数组声明的含义,最好的办法是从数组的名字开始按照由内向外的顺序阅读。
理解方法:从数组的名字开始,由内向外顺序读取,先左后右,左边为类型。

练习3.28下面数组中元素的值是什么

//string不是内置的数据类型,int是
string sa[10]; //为空
int ia[10];	//10个0
int main()
{
	string sa2[10];	//为空
	int ia2[10];	//随机值,在函数体内不初始化 就是为止
}

在使用数组下标时,通常将其定义为size_t类型

指针和数组

string nums[] = {"one","two","three"};
string *p2 = nums; //等价于string *p2 = &nums[0];


int ia[] = {0,1,2,3,4,5,6,7,8,9};
auto ia2(ia); // ia2是一个整型指针,指向ia的第一个元素
//等价于 auto ia2(&ia[0]);

指针也是迭代器

标准库函数begin和end(C++11)
头文件iterator

  int ia[] = {0,1,2,3,4,5,6,7,8,9};
  int *beg = begin(ia); //指向ia首元素的指针
  int *last = end(ia); //指向arr尾元素的下一位置的指针
int *pbeg=begin(arr),*pend=end(arr);
//如果pbeg确实指向了一个元素,将其解除引用并检查元素值是否为负值。
while(pbeg != pend && *pbeg >= 0) 
	++pbeg;

注意尾后指针不能执行解引用和递增操作。

和迭代器一样,两个指针相减的结果是它们之间的距离

auto n = end(arr) - begin(arr); //n的值是5,也就是arr中元素的数量

两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型。

解引用和指针运算的交互

int ia[] = {0,2,4,6,8};
int last = *(ia + 4); //8
last = *ia + 4 ;//4
int *p = &ia[2]; //p指向索引为2的元素
int j = p[1]; //p[1]等价于*(p+1),也就是ia[3]表示的元素
int k = p[-2]; //p[-2]是ia[0]表示的那个元素

C标准库string函数

在这里插入图片描述

比较字符串

if(strcmp(cal1,cal2)<0); //传入的是指针,比较的是元素内容

C++风格的字符串比较是字符串本身的比较

C风格的字符串比较是字符串首地址的比较

不能用string对象直接初始化指向字符的指针


string s("HelloWorld");

char *str = s; //错误

const char *str = s.c_str(); //正确

如果后续的的操作改变了s的值,可能让之前返回的数组失去效用。

使用数组初始化vector对象指明拷贝区域的首地址和尾后地址节课即可 int int_arr[] = {0,1,2,3,4,5}; //ivec中有6个元素 vector ivec(begin(int_arr),end(int_arr)); //也可以数组的一部分 拷贝int_arr[1]、int_arr[2]、int_arr[3] vector
subVec(int_arr+1,int_arr+4);

多维数组

所谓的多维数组其实是数组的数组。

 int ia[3][4] = { {0,1,2,3},{4,5,6,7},{8,9,10,11} };
 int(*p)[4] = ia; //p是一个含有4个数组的数组 存了0 1 2 3
 p = &ia[2];  //8的地址

一种遍历方式

 int ia[3][4] = { {0,1,2,3},{4,5,6,7},{8,9,10,11} };
 for (auto p = ia;p != ia + 3;++p)
 {
  for (auto q = *p;q != *p + 4;++q)
  {
   cout << *q << " ";
  }
  cout << endl;
 }
 //换成标准库的begin,end也可以。

类型别名简化多维数组的指针

using int_array = int[4]; //新标准下类型别名的声明
typedef int int_array[4]; //等价的typedef声明
int ia[] = {0,2,4,6,8};
int last = *(ia + 4); // last = 8
last = *ia + 4 ; //last = 4
 int i = 0;
 cout << i << " " << ++i << endl; //结果不确定,不知道先++i 还是 先 i
bool b = true;
bool b2 = -b; //b2是true, -1仍是trye

在这里插入图片描述短路求值:当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值

算数>关系>逻辑
i != j<k; //含义 i != (j<k)

在这里插入图片描述
练习4.19在这里插入图片描述由于求值顺序不一定,所以会产生未定义的行为。

点运算符获取对象的一个成员。
ptr->mem 等价于 (*ptr).mem

条件运算符
cond>expr1:expr2;

string finalgrade = (grade < 60) ? "fail" : "pass";
finalgrade = (grade > 90) ? "high pass"
						: (grade < 60) ? "fail":"pass";
 cout << ((grade < 60) ? "fail" : "pass"); //输出pass或fail
 cout << (grade < 60) ? "fail" : "pass"; //输出1 或 0
 cout << grade < 60 ? "fail" : "pass"; //错误 试图比较cout和60

练习4.22
在这里插入图片描述

 int mark = 0;
 cin >> mark;
 string finalgrade;
 finalgrade = (mark > 90) ? "high mark" :
  (mark > 75) ? "pass" : (mark > 60) ? "pass" : "fail";
 cout << finalgrade << endl;

悬垂else:else与其最近的尚未匹配的if相匹配

switch (ch)
{
	case 'a':
	case 'b':
	case 'c':
	...
		++vowelCnt;
		breka;
		//不管是哪种情况 都可以使vowelCnt++
}

异常表达式

1.throw表达式 异常检测部分使用throw表达式来表示它遇到的无法处理的问题 我们说throw引发了异常
2.try语句块 异常处理部分使用try语句块处理异常。try语句块以关键字try开始,并以一个或多个catch子句结束。

	string item1, item2;
	while (cin >> item1 >> item2)
	{
		try {
			//执行添加两个Sales_item对象的代码
			//如果添加失败,代码抛出一个runtime_error异常
		}catch(runtime_error err){
		//提醒用户错误
			cout << err.what()
				<< "\nTry again ? enter y or n" << endl;
			char c;
			cin >> c;
			if (!cin || c == 'n')
				break;
		}
	}

标准异常
头文件stdexcept

在这里插入图片描述
练习
在这里插入图片描述

try {
	if (b == 0) throw runtime_error("除数不能为0");
	cout<<"a/b="<<(a / b);
}catch(runtime_error err){
//提醒用户错误
	cout << err.what()
		<< "\nTry again ? enter y or n" << endl;
}

UNIT6

绝对值函数abs(xxx);
局部静态对象(static)
在程序的执行路径第一次经过对象定义语句时初始化,直到程序终止才销毁,在此期间及时对象所在的函数页数执行也不会对他有影响(值会保留)

size_t count_calls()
{
	static size_t ctr = 0;
	return ++ctr;
}
int main()
{
	for (size_t i = 0;i != 10;++i)
		cout << count_calls() << endl;
	system("pause");
	return EXIT_SUCCESS;
}
//输出1-10 ctr 并不会归零

函数声明中因为不包含函数体 所以无需加上形参的名字。但是加上最好

参数传递两种方式:值传递/引用传递
在这里插入图片描述

void swap1(int& v1, int& v2) //传地址的方式引用
{
	int mid = 0;
	mid = v2;
	v2 = v1;
	v1 = mid;
}

int main()
{
	int v1 = 999, v2 = 1;
	swap1(v1, v2); //传入对象即可 采用传引用方式 它的值被改变
	cout << v1 << " " << v2;
	system("pause");
	return EXIT_SUCCESS;
}
//上述调用中 形参仅仅是实参的又一个名字,对形参操作就是对实参操作。

当函数无须修改时引用形参的值最好使用常量引用

bool isShort(const string &s1,const string &s2)
{
	return s1.size() < s2.size();
}

数组引用形参

void print(int (&arr)[10])  //arr是具有10个整数的整型数组的引用
{
	for(auto elem : arr)
		cout<<elem<<endl;
}
//f(int &arr[10]) //错误 将arr声明成了引用的数组

initializer_list操作和vector类似 但是initializer_list对象中的元素永远是常量值

函数重载如果统一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。
当调用这些函数时,编译器会根据传递的实参类型推断想要的是哪个函数。
默认实参、内联函数、constexpr函数
1.默认实参只能省略尾部的实参
一旦函数的某个形参被赋予了默认值,他后面所有的参数都必须有默认值

int ff(int a,int b=0,int c=0); //正确
char *init(int ht=24,int wd,char backgrnd); //错误

内联函数

inline const string& shorterString(const string& s1, const string& s2)
{
	return s1.size() <= s2.size() ? s1 : s2;
}

int main(int argc,char** argv)
{
	string str1 = "hello";
	string str2 = "help";
	cout << shorterString(str1, str2) << endl;
	//等价于cout<< s1.size() <= s2.size() ? s1 : s2; <<endl;
	system("pause");
	return EXIT_SUCCESS;
}

constexper
常量表达式约定
1.函数的返回类型及所有形参的类型都得是字面值类型
2.函数体中必须有且只有一条return 语句

constexpr int new_sz() {return 42;}
constexpr int foo = new_sz(); //正确 foo是一个常量表达式

assert
assert(expr); //首先对expr求值,如果表达式为假,assert输出信息并终止程序的执行,如果表达式为真,assert什么也不做。

函数指针
当我们把函数名作为一个值使用时,该函数自动地转换成指针

pf = lengthCompare; //pf指向名为lengthCompare的函数
pf = &lengthCompare; //等价于上面的语句 取址符是可选的。

若使用struct关键字,则定义在第一个访问说明符之前的成员是public的,相反 如果使用class 则这些成员是private的

友元 类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。

可变数据成员(mutable) 即使在一个const对象内可能被修改

class Screen {
public:
	void some_member() const;
private:
	mutable size_t access_ctr; //即使在一个const对象内可能被修改
};

void Screen::some_member() const
{
	++access_ctr; //保存一个计数值 用于记录成员函数被调用的次数
}

聚合类
当一个类满足如下条件: 称为一个聚合类,
聚合类使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。
1.所有成员都是public的
2.没有定义任何构造函数
3.没有类内初始值
4.没有基类,也没有virtual函数。

Unit9

vector和string不支持push_front
新标准库中,可以调用shrink_to_fit来要求deque,vector或string退回不需要的内存空间。
看9.5.3

lambda表达式
[捕获列表](parameter list) -> return type{function body}

//使用lambda表达式调用排序
//按长度排序,长度相同的单词维持字典序
stable_sort(words.begin(),words.end(),[](const string &a,const string &b){
	return a.size() < b.size();
});
[sz](const string &a){
	return a.size() > sz;
}

[&] 采用捕获引用方式
[=] 采用值捕获方式

若想修改捕获的变量的值 则在参数列表首加上

mutable [v1]()mutable{return ++v1;};
//transform 前两个迭代器表示输入序列 第三个迭代器表示目的位置。
transform(vi.begin(),vi.end(),vi.begin(),[](int i){return i< 0?-i:i;});

Pair类型
一个pair保存两个数据成员,类似容器,当创建一个pair时,提供两个类型名。
两个成员分别命名为first和second

map第一个成员变量是const的
第二个是可以修改的

向set中添加元素

set.insert({1,2,3,1,2,3}); //只能加入123

向map中添加元素

word_count.insert({word,1});
word_count.insert(make_pair(word,1));
word_count.insert(pair<string,size_t>(word,1));
word_count.insert(map<string,size_t>::value_type(word,1));

lower_bound返回的迭代器指向第一个具有给定关键字的元素
upper_bound返回的迭代器指向最后一个匹配给定关键字的元素
如果元素不在multimap中,则lower_bound和upper_bound会返回相等的迭代器-指向一个不影响排序的关键字插入位置。
equal_range()接受一个关键字,返回一个迭代器pair,若关键字存在,则第一个迭代器指向第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素之后的位置。若未找到匹配元素,则两个迭代器都指向关键字可以插入的位置。

unit12

new,在动态内存中为对象分配空间并返回一个指向该对象的指针
delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
智能指针:类似于常规指针,重要的区别是他负责自动释放所指向的对象。
在这里插入图片描述在这里插入图片描述

int *pi = new int; //pi指向一个动态分配的,未初始化的无名对象
string *ps = new string; //初始化为空string
int *pi = new int(1024); //pi指向的对象值为1024
string *ps = new string(10,‘9’); // *ps为"9999999999"
//vector有十个元素 一次从0到9
vector *pv = new vector{0,1,2,3,4,5,6,7,8,9};

也可以对动态分配的对象进行值初始化,只需在类型名之后跟一对空括号即可
string *ps1 = new string; //默认初始化为空string
string *ps = new string(); //值初始化为空string
int *pi1 = new int; //默认初始化;*pi1的值未定义
int *pi2 = new int(); //值初始化为0;*pi2为0

auto p1 = new auto(obj); //p指向一个与obj类型相同的对象,该对象用obj进行初始化
若obj是一个int 则p1就是int* 若是string 则p1是string*

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值