STL + string类的使用

目录

一 STL简介

1.什么是STL

2.STL的版本

3.STL的六大组件

1. 容器(Containers)

2. 算法(Algorithms)

3. 迭代器(Iterators)

4. 仿函数(Functors,函数对象)

5. 适配器(Adapters,配接器)

6. 空间配置器(Allocators)

六大组件的协作关系

4.STL的重要性

5.如何学习STL

二 String类

1 为什么学习 String 类

1.1 C语言中的字符串

1.2 面试中的题目 

2 标准库中的String类

2.1 String类

2.2 auto 和 范围for 

auto

范围for

2.3 String类的常用接口说明

1.string类对象常见构造

1.1 string()

1.2 string (const string& str)

1.3 string (const string& str, size_t pos, size_t len = npos);

2. string 类对象的访问和遍历操作

1.下标+[ ]

2.迭代器

begin() / end()

rbegin() / rend()

3.范围for() 

4.at() 与 [ ]

5.back()  / front() 

3.String类对象的容量操作

3.1 size() / length()

3.2 capacity()

3.3 empty()

3.4 clear()

3.5 shrink_to_fit()

3.6 reserve()

3.7 resize() 

4. string类对象的修改操作

1. push_back() 

2.append()

3.operator+=()

4.c_str()

5.find()

6.rfind()

7.substr()

5 string类的非成员函数

1.getline()

2.relational operators


一 STL简介

1.什么是STL

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

         STL并不是C++官方进行编写管理的内容,而是官方给出一个标准,由其他企业或实验室进行完成。所以不同编译器或者不同操作系统底层下实现可能并不相同,例如 String类中的扩容 capaccity(),在VS编译器下是1.5倍扩容,但是在Linux的g++下却是两倍扩容。

        STL的使用在日常中的使用很频繁,比如 vector,string 等容器,sort(),reverse(),swap()等函数。使用过的同学能感受到这些工具的便利性,比如在算法比赛中STL库便是经常会被使用。灵活的使用STL无论是对于工作还是算法竞赛来说都是非常重要的。

2.STL的版本

原始版本                                                                                                                                               Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许 任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本--所有STL实现版本的始祖。

P.J.版本

        由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。

RW版本

        由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。

SGI版本

        由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码,主要参考的就是这个版本。

3.STL的六大组件

1. 容器(Containers)

定义:用于存储和管理数据的数据结构模板类。

分类:

顺序容器:元素按线性顺序排列(如vector,list,duque)。

关联容器:通过键(Key)高效查找元素(如 map,set,unordered_set)。

容器适配器:基于其他容器实现的特定接口(如 stack,queue)。

特点:提供统一的数据管理接口(如 push_back(), insert(), erase())

2. 算法(Algorithms)

定义:独立于容器的通用函数模板,用于操作数据(如排序、查找、遍历)。

常见算法

sort():对元素排序。

find():查找指定元素。

transform():对元素进行转换。

copy():复制元素到另一容器。

特点:通过迭代器与容器解耦,不直接操作容器本身。  

3. 迭代器(Iterators)

定义:类似指针的对象,用于遍历容器中的元素,是算法与容器之间的桥梁。

分类(按功能递增):

输入迭代器:只读,单向移动(如 istream_iterator)。

输出迭代器:只写,单向移动(如 ostream_iterator)。

前向迭代器:可读写,单向移动(如 forward_list 的迭代器)。

双向迭代器:可双向移动(如 list, map 的迭代器)。

随机访问迭代器:支持随机访问(如 vector, deque 的迭代器)。

作用:统一容器的访问方式,使算法无需关心容器底层结构。

4. 仿函数(Functors,函数对象)

定义:重载了 operator() 的类对象,可像函数一样调用。

用途

作为算法的策略(如自定义排序规则)。

与适配器结合(如 bind2nd 绑定参数)。

5. 适配器(Adapters,配接器)

定义:通过封装现有组件,改变其接口或行为。

分类

容器适配器:如 stack(基于 deque 或 list 实现栈)、queue。

迭代器适配器:如 reverse_iterator(反向遍历容器)。

函数适配器:如 bind1st、bind2nd(绑定函数参数)。

6. 空间配置器(Allocators)

定义:负责内存分配与释放的模板类,封装底层内存管理细节。

作用:控制容器的内存分配策略(如内存池优化)。

支持自定义内存管理(如共享内存、对齐分配)

默认配置器:std::allocator<T>。

六大组件的协作关系

容器通过空间配置器管理内存。

算法通过迭代器操作容器的元素。

仿函数和适配器定制算法或容器的行为。

适配器基于现有组件扩展功能(如将 deque 适配为 stack)

4.STL的重要性

1.笔试中经常会用到STL,使用STL对于写代码来说会有很大便利性,STL里帮你实现了很多现成的功能,STL中的算法已经经过了优化,会比手写的代码效率更高。

2.在面试中通常面试官也会考察STL相关的知识点。

3.网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。

5.如何学习STL

学习STL可以分为三个境界

第一境界:熟用STL。

第二境界:了解STL底层是如何实现的,了解泛型技术的内涵。

第三境界:扩充STL,即能自己编写STL库内容。


二 String类

讲解之前先推荐一个很使用的工具  cplusplus.com 。对于我们日常使用的函数等,这个网站里能很系统的给出分类和讲解。

1 为什么学习 String 类

1.1 C语言中的字符串

        C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。        

1.2 面试中的题目 

        字符串转整形数字

2 标准库中的String类

2.1 String类

               string类的文档介绍  这个文档就是从前面的 cplusplus 中找出来的,依次往下翻看能看到对于每一类功能都有进行系统的分类,对于系统性的学习和比较 来说更加友好。

std::string的本质是标准库提供的一个类模板的特化版本,它的底层实现动态字符数组。

内部通过动态分配的字符数组 char[] 存储字符串内容

2.2 auto 和 范围for 

        在学习String 类之前先来学习下这两个内容,auto 和 范围for 在C++中也被叫做语法糖,可以简单理解为使用较为友好,便利性很高。语法糖让更加复杂冗长的代码变得更为简洁并且更易于理解,但是底层的逻辑保持不变。就像糖衣一样,让代码变得更甜,但本质上来说,并没有增加任何功能。比如 += 来说也是一个语法糖。

auto

在早期的C/C++中auto的含义是:使用 auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

用auto声明指针类型时,用autoauto*没有任何区别,但用auto声明引用类型时则必须加&。

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

auto不能作为函数的参数,可以做返回值,但是建议谨慎使用。

auto不能直接用来声明数组。 

1.auto能自动推导参数类型。

void text()
{
    // auto会自动推导 a 参数类型
	auto a = 10;
}

        但是参数必须要初始化,否则编译器无法得知参数的类型到底是什么。

        // 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项

2. auto可以作为返回值,但不能作为参数。

int func1()
{
	return 10;
}

// 不能做参数
void func2(auto a)
{
}

// 可以做返回值,但是建议谨慎使用,此时编译器会根据返回值自动推导返回值类型
auto func3()
{
	return 3;
}

3.auto声明指针类型

int x = 10;    // x 已经指定为 int 类型
auto y = &x;   // auto 推导类型,引用类型


auto* z = &x;  // 此时指定为指针类型,右边必须是指针
auto& m = x;   // 指定为引用类型

 

         在声明指针类型时,使用auto 与 auto* 是一样的,形式上auto*可以更明显的告诉你这是个指针类型。可以理解为auto* 的右边必须是指针。

4.auto必须始终推导为同一类型

auto x = 1, y = 2;
// 类型相同,编译通过


auto cc = 3, dd = 4.0;
// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型

5.auto不能直接用来声明数组

// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
auto array[] = { 4, 5, 6 };

范围for

 对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11中引入了基于范围的for循环。for循环后的括号由冒号分为两部分:第一部分是范围 内用于迭代的变量,第二部分则表示被迭代的范围自动迭代,自动取数据,自动判断结束

范围for可以作用到数组和容器对象上进行遍历。

范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

范围for()表示

for(元素类型 element : 容器) {
    // 迭代代码
}

// 容器是想要遍历的目标,可以是数组,字符串等
// 元素类型 是容器中一个元素的类型
// element可以任意命名,表示为容器中的一个元素

范围for()的使用

#include<iostream>
#include <string>
using namespace std;
int main()
{
    int array[] = { 1, 2, 3, 4, 5 };
// C++98的遍历
    for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
    {
        array[i] *= 2;
    }
    for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
    {
        cout << array[i] << endl;
    }

// C++11的遍历
    for (auto e : array)  
    cout << e << " " << endl;

    string str("hello world");
    for (auto ch : str)
    {
        cout << ch << " ";
    }
    cout << endl;

}

加&与不加&的区别

假设对于数组内的内容我们只需要进行读操作,就不需要加&。

如果还要进行写操作,需要加&。

同时这点还与下面的是否调用拷贝构造有关。

int arr[]={1,2,3,4,5};

// 只对数据进行读操作
for(auto e : arr)
{
    cout << e << " " ;   
}

cout << endl;

//改变数组内的数据值,加引用
for(auto& e : arr)
{
    e *= 2;
}

是否调用拷贝构造

值类型声明       

        当循环变量声明是值类型时, 每次迭代时,容器中的元素会被 拷贝 到循环变量中这会调用元素类型的拷贝构造函数(若存在且未被优化)。

int arr[] = {1,2,3};

for(auto e : arr)
{
    ....
}

数组arr的元素会被拷贝到e中去,这时会调用拷贝构造。

引用类型声明

当循环变量声明为引用类型时:循环变量直接引用容器中的元素不会触发拷贝操作。不会调用拷贝构造函数。

这点原理是类的默认成员函数中拷贝构造函数内容。类的默认成员函数-优快云博客

这里拷贝构造的好处就是对象比较大的时候,节省空间。

2.3 String类的常用接口说明

1.string类对象常见构造

先了解什么是构造​​​​​​函数,不清楚的可以看这篇博客  ​类的默认成员函数-优快云博客 

这里根据cplusplus.com - The C++ Resources Network的内容来讲解:

 

1.1 string()

        string()是默认构造,可以不传任何参数。

#include<iostream>
using namespace std;
int main()
{
	string a;
	return 0;
}
1.2 string (const string& str)

         string (const string& str) 可以传入一个string类型的对象。

#include<iostream>
using namespace std;
int main()
{
	string a = "hello";
	string b(a);
	return 0;
}
1.3 string (const string& str, size_t pos, size_t len = npos);

        这个构造的功能是 从str 串的pos 开始,往后截取len个字符长度的元素。

#include<iostream>
using namespace std;
int main()
{
	string s = "hello";
	string b(s, 1, 2);
	return 0;
}

如果len的长度超出了字符串原有的长度会如何?

string b(s, 2, 1000);

显然字符串s肯定没有1000个字符的,遇到这样的情况,会读取到字符串s的最后一个字符。

假若不填最后一个参数也是没问题的,size_t len = npos是一个缺省值,它也会读取到最后一个字符停止。

npos 

         这时可能有人会问,这个 npos 是什么,结合官方文档来看 

表面上它是一个静态类型的 -1,实则并不然。size_t是无符号类型的整数,但是-1是有符号整数,我们可以知道-1的补码是全1,全1在无符号整数中是整数的最大值,所以这里的npos是整数所能表示的最大值,所以当npos作为缺省值时,他必定会读取到字符的最后一个位置。

总结

 

void Teststring()
{
    string s1; // 构造空的string类对象s1
    string s2("hello bit"); // 用C格式字符串构造string类对象s2
    string s3(s2); // 拷贝构造s3
}

2. string 类对象的访问和遍历操作

遍历总结来说共有三类:1.下标+[ ]    2.迭代器    3.范围for() 

1.下标+[ ]

 对于数组元素的读取,我们通常使用 数组名[下标] 的方式进行访问,这样访问的便捷性不用多说,而C++又有运算符重载的功能,因此String类中给出了 [] 的重载,使得字符串能够像数组一样使用下标进行访问,这无疑大大增加了便利性。

string::operator[ ]重载了两种类型,第一种是能进行读写,第二种是只读的

	// 1、下标+[]
	for (size_t i = 0; i < s2.size(); i++)
	{
		s2[i] += 1;
		//s2.operator[](i) += 1;
	}



	//for (size_t i = 0; i < s2.length(); i++)
	for (size_t i = 0; i < s2.size(); i++)
	{
		cout << s2[i] << " ";
	}



	const string s5("xxxxxx");
	//s5[0] = 'y';
    // s5是const 类型的,只读,因此不能进行修改

2.迭代器

迭代器是一个左闭右开的区间 :[ ) 

在行为上可以将迭代器理解为像指针一样的对象,它有 begin() 和 end() 两个值,分别指向开始到结束的位置,使用迭代器时它会自动从 begin 进行遍历,到 end 时结束。

注意它是一个左闭右开的空间,end()指向最后一个元素的下一个位置。

 

如何使用

迭代器的类型根据你使用的容器来定 ,比如这里使用的string类型的字符串

        string::iterator it = s.begin()

但是这样来说类型的名称就太冗长了,可以使用 auto 来自动生成类型 

        auto it = s2.begin()

	auto it = s2.begin();
	while (it != s2.end())
	{
		*it += 1;
		cout << *it << " ";
		++it;
	}
	cout << endl;

因为迭代器是行为类似于指针的对象,所以要访问内容时要使用 * (注意这里的 * 不一定是解引用的意思,C++可以对 * 进行重载) 

还有一点要注意的是,这里的便利方式使用的是 while,而不是 for()循环,字符串和数组这里也可以使用for 循环来遍历,因为它的底层是数组,后一个元素的地址肯定要大于前一个,但是对于链表等来说,它的物理地址并不是连续的,因此建议使用while()遍历。

list<int> lt1 = { 1,2,3,4,5 };
	list<int>::iterator it1 = lt1.begin();
	while (it1 != lt1.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;

begin() / end()

前面已经说过这两个分别指向开始和结束。

 

 C++里给出了两个重载,可以根据const来进行区分,const这里缩小了权限,只能进行读操作。

这里会使用即可。

rbegin() / rend()

rbegin()/rend()是用来反向遍历的。

	string::reverse_iterator rit = s2.rbegin();
	while (rit != s2.rend())
	{
		//*rit += 1;
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

注意观察这里使用的是反向迭代器,但是往后遍历的时候,使用的是 ++it,这也说明了 * 不是指针的解引用。 

3.范围for() 

这个前面已经讲解过,这里不做过多阐述,范围for()的底层是迭代器 

	// 范围for
	for (auto ch : s)
	{
		cout << ch << " ";
	}
4.at() 与 [ ]

at()与 opreator[ ] 的功能相同,他们的不同点在于二者对于越界访问的处理不同

operator[ ] 是断言处理,会直接终止程序

at() 是try-catch捕获异常  

5.back()  / front() 

 front()返回字符串的首字符,back()返回字符串的结尾字符。

3.String类对象的容量操作

3.1 size() / length()

        size() 与 lenght() 都是返回字符串有效字符的长度,即从字符串开始到 '/0' 前的有效字符个数,注意这两个都不包含 '/0' 。 

	string s = "abcdef";
	cout << s.size() << endl;
	cout << s.length() << endl;

运行结果,显而易见二者的功能相同。 

size()和length() 是等价的,二者可以用来获取字符串的长度。二者返回相同的结果,但是size()更符合标准库的容器类。 

string这里可以使用length(),但是STL中的容器类有很多,根据字面意思,length更适合长度,但是后面有树这样的非线性的数据结果,使用length多少有点不合适,所以size()更符合标准库的容器类。 

3.2 capacity()

capacity()用于返回当前字符串分配的空间大小。即其能够容纳的字符数量而不必立即重新分配内存

         字符串当前分配的空间大小,并不一定等于字符串当前的字符长度。同时二者均不计算最后一个字符后的 '/0' 。capacity() 返回字符串当前分配的内存空间大小,单位为字符数。即使字符串为空,该值也可能大于零,因为字符串通常预先分配内存以提高效率。

	string s = "abcdef";
	cout << s.size() << endl;
	cout << s.capacity() << endl;

自动扩容

当size()达到capacity()时,字符串会自动扩展内存,此时的capacity()会增加。但是不同的编译器实现的机制不同,扩容的方式也不同。比如 VS 是 1. 5 倍扩容,但是Linux下的g++是 2 倍扩容。

 

3.3 empty()

用于判断字符串是否为空。如果字符串中没有任何字符,包括没有终止空字符('\0'),则返回true。不需要参数直接调用。

这里只了解如何使用即可。 

3.4 clear()

 用于清空字符串内容,但一般情况下不会清理内存空间。

	string s = "abcdef";
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	s.clear();
	cout << s.size() << endl;
	cout << s.capacity() << endl;

3.5 shrink_to_fit()

shrink_to_fit() 会尝试将字符串的内存容量(capacity())调整为刚好等于当前字符串的大小(size())。这样可以释放之前预留的未使用的内存空间,优化内存使用。

同时shrink_to_fit 是 异地缩容,即它缩容时会另外开辟一块新的空间。因为释放空间不能从字符串中间开始释放,只能从起始指针处释放。

3.6 reserve()

reserve(n) 请求将字符串的容量(capacity())调整为至少 n 个字符,以容纳未来的扩展,避免频繁的内存重新分配。调用此函数不会改变字符串的内容或长度(size()),仅调整底层内存的容量。

reserve()扩容分为三种情况:

1. n < size() < capacity()

2. size() < n < capacity()

        这两种情况不一定会缩容,主要看编译器如何解释。

3.size() < capacity() < n 

        一定会扩容。·

注意:无论是哪种情况,都不会改变字符串的内容。

    string s = "abcdef";	
    cout << s.capacity() << endl;
	s.reserve(1);
	cout << s.capacity() << endl;
	cout << s << endl;

3.7 resize() 

resize()会将字符串的长度size()显式调整为 n 。

// resizing string
#include <iostream>
#include <string>

int main ()
{
  std::string str ("I like to code in C");
  std::cout << str << '\n';

  unsigned sz = str.size();

  str.resize (sz+2,'+');
  std::cout << str << '\n';

  str.resize (14);
  std::cout << str << '\n';
  return 0;
}



COUT
I like to code in C
I like to code in C++
I like to code

这里跟reserve()一样分多种情况。

4. string类对象的修改操作

 

1. push_back() 

在字符串后尾插一个元素。

 代码演示

	string s = "abcdef";
	s.push_back('1');
	cout << s << endl;

注意这里的参数只有一个char 类型的字符,写入 '12'只会保留最后一个字符。

2.append()

将指定内容追加到字符串的末尾,它会修改字符串的长度,返回自身(*this)的引用。

这里的append()有多种类型重载,使用时查看文档即可

 代码演示

// appending to string
#include <iostream>
#include <string>

int main ()
{
  std::string str;
  std::string str2="Writing ";
  std::string str3="print 10 and then 5 more";

  // used in the same order as described above:
  str.append(str2);                       // "Writing "
  str.append(str3,6,3);                   // "10 "
  str.append("dots are cool",5);          // "dots "
  str.append("here: ");                   // "here: "
  str.append(10u,'.');                    // ".........."
  str.append(str3.begin()+8,str3.end());  // " and then 5 more"
  str.append<int>(5,0x2E);                // "....."

  std::cout << str << '\n';
  return 0;
}

3.operator+=()

在字符串后追加内容,这个使用比较常见。

比如我们常见的数值 += 操作,此处功能相同。

 

// string::operator+=
#include <iostream>
#include <string>

int main ()
{
  std::string name ("John");
  std::string family ("Smith");
  name += " K. ";         // c-string
  name += family;         // string
  name += '\n';           // character

  std::cout << name;
  return 0;
}
4.c_str()

返回C格式的字符串。

        这里的功能主要与语言的兼容有关,比如平时完成C++项目工程时,一般来说不是全部使用C++完成,会有一部分内容使用C语言完成 ,如果C内容处想从C++内容处获取某些字符串,直接获取就不行的,这里便要使用c_str()来完成转换。

5.find()

查看指定内容在字符串中的位置。根据重载函数不同功能也不同。

代码演示

	// 从pos位置开始查看 s1 在 s中的位置
	string s = "abcdefgh";
	string s1 = "ab";
	cout << s.find(s1, 0);

如果说指定内容没找到会怎么样?

	// 从pos位置开始查看 s1 在 s中的位置
	string s = "abcdefgh";
	string s1 = "ab";
	cout << s.find(s1, 1);

返回了  npos。

6.rfind()

与find()功能相同,不过是从后往前进行查找。

        rfind()的使用场景举例:要得到某个文件的后缀,这时从后往前查找更快。

7.substr()

从指定位置开始,截取字符串 len长度的目标串

 代码演示

// string::substr
#include <iostream>
#include <string>

int main ()
{
  std::string str="We think in generalities, but we live in details.";
                                           // (quoting Alfred N. Whitehead)

  std::string str2 = str.substr (3,5);     // "think"

  std::size_t pos = str.find("live");      // position of "live" in str

  std::string str3 = str.substr (pos);     // get from "live" to the end

  std::cout << str2 << ' ' << str3 << '\n';

  return 0;
}

注意:

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差

不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可

以连接字符串。

2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留

好。

5 string类的非成员函数

1.getline()

获取一行字符串。

        平时我们使用 cin  时,遇到空格和换行就是遇到了终止符,如果我们想要输入的字符串带有空格等就会比较麻烦。

        getline()就解决了这一问题,两个函数的第一个参数都是输入流对象,即 cin ,第二个是指定字符串,第一个式子中的第三个参数是指定终止符,不填的话默认是 ' /n'。

// extract to string
#include <iostream>
#include <string>

int main ()
{
  std::string name;

  std::cout << "Please, enter your full name: ";
  std::getline (std::cin,name);
  std::cout << "Hello, " << name << "!\n";

  return 0;
}
2.relational operators

        即是把字符串相关的运算符进行了全局类型的重载,这里不重载为成员函数的原因是成员函数自带一个自身的this指针,这样就定死了一个操作数是string类对象,而且使用时格式上也会比较怪异

想了解这方面的知识 类的默认成员函数-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值