04 黑马C++_STL技术

本文将接着上文(03 黑马C++_泛型编程-CSDN博客),继续更新本人在学习黑马C++过程所整理的笔记(本文序号将继续承接上文)。

本文的使用方法:可以将本文作为一个工具书来使用,当编写程序或者阅读程序遇到不熟悉的知识点的时候,可以用 CTRL+F 按键 搜索关键词,查看该知识点。如:当忘记结构体定义方法,需不需要在括号后加分号,就可以 CTRL+F 搜索 “结构体”关键字,即可跳转到所需内容处。相较于使用 C++ Primer 感觉更加快速、便捷,希望对大家有所帮助。

为了建立数据结构和算法的一套标准,诞生了 STL,提高代码复用性
STL (Standard Template Library)

  • STL 分为三大类 (容器和算法之间通过迭代器进行无缝连接
  1. 容器(container)
    1. string:字符串
    2. vector:数组
    3. deque:双端数组、双向队列
    4. stack:栈(先进后出)
    5. queue:队列(先进先出)
    6. list:链表(随意插入)
    7. set/multiset:集合(自动排序)
    8. map/multimap:映射(键,值)高性能高效率
  2. 算法(algorithm)
  3. 迭代器(iterator)

![[Pasted image 20230818211247.png]]

6.1 基本概念

6.1.1 定义

  • 容器
    ![[Pasted image 20230818212637.png]]

  • 算法
    ![[Pasted image 20230818212703.png]]

  • 迭代器 (便于访问元素,常用双向迭代器、随机迭代器
    ![[Pasted image 20230818212750.png]]

![[Pasted image 20230818212931.png]]

6.1.2 迭代器

#include <vector>
// 创建容器
vector<int> v;
v.push_back(1);
v.push_back(2);
// 创建迭代器
vector<int>::iterator itBegin = v.begin();	// 指向第一个元素
vector<int>::iterator itEnd = v.end();		// 指向最后一个元素的下一个位置

6.1.3 输出显示

  • for循环
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
	cout << *it << endl;    // 技巧:*it解出来的数据类型就是迭代器定义尖括号里的数据类型
}
  • STL遍历算法
#include <algorithm>       // 需要包含头文件
// 输出回调函数
void myPrint(int val)
{
	cout << val << endl;
}
for_each(v.begin(), v.end(), myPrint); 

6.1.4 容器的嵌套

// 定义二维容器
vector<int> a, b, c;
for (int i = 0; i < 3; i++) {
	a.push_back(i+1);
	b.push_back(i+2);
	c.push_back(i+3);
}
vector<vector<int>> aa;
aa.push_back(a);
aa.push_back(b);
aa.push_back(c);
// 输出显示
for (vector<vector<int>>::iterator it = aa.begin(); it != aa.end(); it++) {
	// 此时,(*it) 相当于 vector<int>
	// 法一
	for_each((*it).begin(), (*it).end(), myPrint);
	// 法二
	for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) {
		cout << *vit << " ";
	}
	cout << endl;
}

6.1.5 容器作为形参

  • 函数传入参数为容器时,如果要防止传入实参在函数中被修改,可以加上 const,但是加上 const 的容器,在使用迭代器时需要使用 const_iterator,例:
void printVector(const vector<int>& v)	
{
	// const 防止实参被修改,此时 v.begin() 返回 const_iterator,所以需要定义 const_iterator 类型的迭代器
	for (vector<int>::const_iterator it = v.begin(); it != v.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;
}

6.2 string 容器

![[Pasted image 20230819092549.png]]

6.2.1 构造函数

![[Pasted image 20230819093840.png]]

// 1. 默认构造
string s1;
// 2. 有参构造
const char* str = "hello world";   // 注意,字符串赋值的方法,前面加个 const
string s2(str);
// 3. 拷贝构造
string s3(s2);
// 4. n 个字符初始化
string s4(5, 'a');

注意:给 char* str 直接赋值字符串,需要加上 const !!!

  • 给 char* 赋值的另外一种方法
char* str1 = NULL;
str1 = (char*)malloc(1024);    // 申请一块 1024 的连续内存空间,并返回 char* 指针
for (int i = 0; i < 5; i++) {  // 赋值
	str1[i] = 'a'+i;
}

6.2.2 赋值操作

![[Pasted image 20230819100140.png]]

6.2.3 字符串拼接

![[Pasted image 20230819100718.png]]

6.2.4 string 查找和替换

![[Pasted image 20230819101741.png]]

  • find :从左往右查找 ; rfind : 从右往左查找
  • 若 find 没找到,则返回 string::npos
  • 注意,替换输入了多少都会全替换上
string str3("hello");
// 从 1号位置起 3 个字符,都替换成 1111
str3.replace(1, 3, "1111"); // hello -> h1111o,四个1都加入

6.2.5 字符串比较

按照 ASCLL 码进行对比,相等:return 0; 大于: return 1; 小于: return -1;

![[Pasted image 20230819102625.png]]

6.2.6 字符存取

  • 重载了[ ],可以通过 [ ] 进行索引
  • 也可以通过 at 访问获取字符
    ![[Pasted image 20230819102852.png]]

注意:二者的区别在于,operator []不做边界检查, 哪怕越界了也会返回一个引用,当然这个引用是错误的引用,如果不小心调用了这个引用对象的方法,会直接导致应用退出。 而由于at会做边界检查,如果越界,会提示异常错误

6.2.7 插入和删除

![[Pasted image 20230819103234.png]]

6.2.8 子串获取

![[Pasted image 20230819103435.png]]

6.3 vector 容器

也称为单端数组,不同于数组的是vector空间可以动态拓展(并不是在原有的空间之后继续接新空间,而是找更大的内存空间,然后将原数据拷贝到新空间

![[Pasted image 20230819104955.png]]

  • 迭代器是支持随机访问的迭代器

6.3.1 构造函数

![[Pasted image 20230819105101.png]]

// 默认构造
vector<int> v1;
// 利用 v1 区间中的元素拷贝
vector<int> v2(v1.begin(), v1.end());
// 10 个 100
vector<int> v3(10, 100);
// 拷贝构造
vector<int> v4(v3);

6.3.2 赋值操作

![[Pasted image 20230819110203.png]]

6.3.3 容量和大小

![[Pasted image 20230819110458.png]]

6.3.4 插入和删除

![[Pasted image 20230819110733.png]]

6.3.5 数据存取

![[Pasted image 20230819111136.png]]

6.3.6 互换容器

swap( vec ) 将 vec 与本身元素互换

实际用途:可以收缩内存空间

vector<int> v;
for (int i = 0; i < 100000; i++) {	// 存入100000个数
	v.push_back(i);
}
cout << v.size() << endl;        // 100000
cout << v.capacity() << endl;    // 138255
// 重新指定大小为3
v.resize(3);					 
cout << v.size() << endl;        // 3
cout << v.capacity() << endl;    // 138255,此时容量还是13万多,造成内存浪费
// 巧用 swap 收缩内存
vector<int>(v).swap(v);
// vector<int>(v) 利用v拷贝构造创建一个匿名对象,此时的匿名对象容量、大小均为 3 ;
// 再调用swap交换,使得v的大小和容量都为匿名对象的3、3;而匿名对象则为3、138255,但是匿名对象在当前行代码执行完自动回收
cout << v.size() << endl;        // 3
cout << v.capacity() << endl;    // 3

6.3.7 预留空间

减少 vector 在动态拓展容量时的拓展次数

![[Pasted image 20230819113138.png]]

  • 统计 vector 开辟了多少次内存才把 10万 个数据存入
vector<int> v;
int count = 0;			// 统计开辟次数
int* p = NULL;
for (int i = 0; i < 100000; i++) {
	v.push_back(i);
	// 当指针不指向第一个位置,即增大容量开辟了内存
	if (p != &v[0]) {
		count++;
		p = &v[0];		// 指向新地址
	}
}
cout << count << endl;	// 30次

若直接在 v.reserve(100000); 则 count = 1,只需一开始开辟一次即可。

6.4 deque 容器

双端数组,可以对头端、尾端进行插入删除操作

![[Pasted image 20230819115143.png]]

![[Pasted image 20230819115500.png]]

  • 迭代器是支持随机访问的迭代器

6.4.1 构造函数

![[Pasted image 20230819115538.png]]

6.4.2 赋值操作

![[Pasted image 20230819125921.png]]

6.4.3 容量和大小

  • deque 没有容量的概念

![[Pasted image 20230819130303.png]]

6.4.4 插入和删除

![[Pasted image 20230819130500.png]]

6.4.5 数据存取

![[Pasted image 20230819131339.png]]

6.4.6 排序操作

![[Pasted image 20230819131455.png]]

  • 需要包含标准算法头文件 algorithm
  • 默认为升序,从小到大排序
  • 对于支持随机访问的迭代器容器,都可以用 sort 排序,比如 vector 容器也有sort

6.5 stack 容器

6.5.1 基本概念

  • 先进后出
  • 栈不允许有遍历的行为,只能访问栈顶元素

![[Pasted image 20230819135516.png]]

6.5.2 常用接口

![[Pasted image 20230819135923.png]]

6.6 queue 容器

6.6.1 基本改概念

  • 先进先出
  • 队列中只有队头和队尾才可以被外界访问,不允许有遍历行为

![[Pasted image 20230819140322.png]]

6.6.2 常用接口

![[Pasted image 20230819141145.png]]

6.7 list 容器

  • 物理存储非连续,数据元素的逻辑顺序是通过链表中的指针链接实现
  • 由一系列结点组成,结点的组成:存储数据元素的数据域 + 存储下一个结点地址的指针域

![[Pasted image 20230819142810.png]]

  • 单链表:指针域只能指向节点的下一个节点;
  • 双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点;
  • 循环链表:链表首尾相连(是属于单链表,只有next)

6.7.1 基本概念

  • 由于链表的存储方式并不是连续的内存空间,因此链表中的迭代器只支持前移和后移,不能随机访问,属于 双向迭代器

![[Pasted image 20230819143108.png]]

  • 因为 vector 容器插入删除会重新分配内存空间,所以迭代器会失效, 而list不会
  • STL 中 listvector 是两个最常用的容器
    C/C++的定义链表节点方式,如下所示:
// 单链表
struct ListNode {
    int val;  // 节点上存储的元素
    ListNode *next;  // 指向下一个节点的指针
    ListNode(int x) : val(x), next(NULL) {}  // 节点的构造函数
};

6.7.2 构造函数

![[Pasted image 20230819143538.png]]

6.7.3 赋值和交换

![[Pasted image 20230819144435.png]]

6.7.4 容量和大小

![[Pasted image 20230819144809.png]]

6.7.5 插入和删除

![[Pasted image 20230819144824.png]]

  • 新增了一个 remove,可以删除所有匹配的值

6.7.6 数据存取

![[Pasted image 20230819145329.png]]

  • 注意:内存存放不是在连续的物理空间,且双向迭代器不支持随机访问,此时不可以通过 [ ] 和 at 跳跃性地访问元素
  • 验证迭代器是否可以随机访问的方法
list<int> l;
list<int>::iterator it = l.begin();
it++;it--;       // 可以运行,双向迭代器支持前移后移
it = it + 1;     // 报错,不可以随机访问

6.7.7 反转和排序

![[Pasted image 20230819151206.png]]

  • 注意,只能使用 list 类的排序成员函数sort,而不能使用 algorithm 头文件中的 sort 排序函数,所有不支持随机访问迭代器的容器,不可以使用标准算法
vector<int> v;
sort(v.begin(), v.end());     // 调用 algorithm 标准函数

list<int> l;
//sort(l.begin(), l.end());   // 报错,不支持双向迭代器使用标准算法
l.sort();                     // 只能使用内置的成员函数
  • list 的 sort 函数默认升序排列, 若想改为降序, 可以提供函数或者仿函数来改变排序规则
// 排序的回调函数
bool myCompare(int v1, int v2)
{
	return v1 > v2;   // 让第一个数 大于 第二个数
}
l.sort(myCompare);    // 此时排序结果为降序

6.7.8 排序案例

  • 自定义数据类型的高级排序
// 排序回调函数
bool comparePerson(Person p1, Person p2)
{
	if (p1.age != p2.age)
		return p1.age < p2.age;		    // 年龄升序
	else
		return p1.height > p2.height;   // 年龄相同则身高降序
}

list<Person> L;
Person p1("张三", 35, 175);
Person p2("李四", 32, 180);
Person p3("王五", 33, 170);
Person p4("赵六", 35, 195);

L.push_back(p1);
L.push_back(p2);
L.push_back(p3);
L.push_back(p4);

L.sort(comparePerson);   // 排序

6.8 set/multiset 容器

关联式容器,在插入时自动排序(升序),底层结构式用二叉树实现的
set 容器配置的迭代器类型为==双向迭代器==

6.8.1 构造和复制

![[Pasted image 20230819163826.png]]

6.8.2 容量和大小

  • 没有 resize,不允许重新指定大小
    ![[Pasted image 20230819164354.png]]

6.8.3 插入和删除

  • 没有尾插 push_back 等等,只有一个 insert

![[Pasted image 20230819164559.png]]

6.8.4 查找和统计

  • count 对于 set 而言没有意义,结果只能是 0/1

![[Pasted image 20230819165317.png]]

6.8.5 set 和 multiset 的区别

  • 在使用时,直接包含** set 头文件**,即可使用这两个

![[Pasted image 20230819165646.png]]

  • s.insert(10) 的返回值为对组 pair<iterator, bool>。第一个值为迭代器,表示插入值的位置;第二个值为插入数据是否成功的标志
set<int> s;
// 定义一个对组来接收 insert 的返回值,从而判断是否插入成功
pair<set<int>::iterator, bool> ret = s.insert(10);  
if (ret.second) {                  // 使用 second 索引第二个参数
	cout << "插入成功!" << endl;
}
else {
	cout << "插入失败!" << endl;
}

6.8.6 排序

利用仿函数(重载函数调用运算符),可以改变排序规则

  • 内置类型排序
// 指定排序规则仿函数
class myCompare
{
public:
	bool operator()(int v1, int v2) const { // 注意,要加上const
		return v1 > v2;                     // 降序
	}
};

// 修改排序规则从大到小,需要在插入数据前、创建容器时指定
set<int, myCompare> s1;		// 传入仿函数
s1.insert(10);
s1.insert(30);
s1.insert(20);
// 声明迭代器,也需要传入仿函数创建专属迭代器
for (set<int, myCompare>::iterator it = s1.begin(); it != s1.end(); it++) {
	cout << (*it) << " ";    // 输出显示
}
  • 自定义类型排序
// 自定义数据类型
class Person
{
public:
	Person(string name, int age) {
		this->name = name;
		this->age = age;
	}
	string name;
	int age;
};
// 指定排序规则仿函数
class comparePerson
{
public:
	bool operator()(const Person& p1, const Person& p2) const { // 注意要加个 const
		return p1.age > p2.age;			       // 年龄降序
	}
};

// 自定义数据类型,需要指定排序规则,否则 insert 时报错
set<Person, comparePerson> s1;     // 传入仿函数
Person p1("张三", 24);
Person p2("李四", 22);
Person p3("王五", 25);
Person p4("赵六", 12);
// 插入数据
s1.insert(p1);
s1.insert(p2);
s1.insert(p3);
s1.insert(p4);
// 输出显示 - 声明迭代器,也需要传入仿函数创建专属迭代器
for (set<Person, comparePerson>::iterator it = s1.begin(); it != s1.end(); it++) {
	cout << it->name << " " << it->age << endl;
}
  • 注意:自定义数据类型插入 set 容器中时,需要指定排序规则,否则 insert 时报错

6.9 map/multimap 容器

  • 所有的元素都是 pair(对组),第一个元素为key(键值),起到索引作用;第二个元素为value,为实值
  • 所有的元素都会根据元素的key键值自动排序
  • 本质: 属于关联式容器,底层结构采用 二叉树 实现
  • 优点:可以根据 key 值快速找到 value 值,实现快速索引
  • map/multimap 区别:map 不允许有重复的 key 值

6.9.1 构造和赋值

![[Pasted image 20230819200500.png]]

void printMap(const map<int, int>& m)
{
	for (map<int, int>::const_iterator it = m.begin(); it != m.end(); it++) {     // 用 first 和 second 索引
		cout << it->first << " " << it->second << endl;
	}
}

map<int, int> m;
// 1. 创建对组对象
pair<int, int> x1(1, 10);		   
m.insert(x1);
// 2. 匿名对组
m.insert(pair<int, int>(2, 20));   
// 3. 直接用 mak_pair
m.insert(make_pair(3, 30));        
// 4. value_type
m.insert(map<int, int>::value_type(4, 40));
// 5. []  - 不建议使用
m[4] = 40;
// 输出显示
printMap(m);

6.9.2 大小和容量

![[Pasted image 20230819201742.png]]

6.9.3 插入和删除

![[Pasted image 20230819202005.png]]

erase 之后,返回迭代器指向的下一个值 iter = vecInt.erase(iter);

6.9.4 查找和统计

![[Pasted image 20230819202750.png]]

6.9.5 排序


class myCompare
{
public:
	bool operator()(int v1, int v2) const {
		return v1 > v2;        // 按照降序排列
	}
};

map<int, int, myCompare> m;    // 需要传入排序仿函数
m.insert(make_pair(1, 10));
m.insert(make_pair(2, 20));
m.insert(make_pair(3, 30));
m.insert(make_pair(4, 40));
// 输出显示
for (map<int, int, myCompare>::const_iterator it = m.begin(); it != m.end(); it++) {
	cout << it->first << " " << it->second << endl;
}

6.10 其他

6.10.1 pair 对组

成对出现的数据,利用对组可以返回两个数据

  • 创建及使用
// 1. 创建方法一
pair<int, double> p1(14, 3.14);
// 2. 创建方法二
pair<string, int> p2 = make_pair("Tom", 18);
// 使用方法
cout << p2.first << " " << p2.second << endl;

6.10.2 函数对象

函数重载调用操作符的(本质是一个类),其对象成为函数对象
函数对象使用重载()时,行为类似函数调用,也叫仿函数

特点

  • 函数对象不同于普通函数的地方在于,函数对象可以有成员属性,可以用来记录一些内部状态,可以有自己的状态
  • 函数对象作为一个类,还可以作为参数进行传递
class myPrint
{
public:
	int operator()(string test) {    // 重载 ()
		cout << test << endl;
		this->count++;
		return count;
	}
	int count = 0;
};

6.10.3 谓词

返回 bool 类型的仿函数成为 谓词

  • operator() 只有一个参数,称为一元谓词;
  • operator() 由两个参数,称为二元谓词
  • 一元谓词 - 用于查找 vector 容器 find_if 中大于 5 的值
// 一元谓词
class greaterFive
{
public:
	bool operator()(int val) {
		return val > 5;           // 查找大于 5 的值
	}
};

vector<int> v;
for (int i = 0; i < 10; i++) {
	v.push_back(i);
}
// 查找容器中大于 5 的数据,若查找到返回迭代器
vector<int>::iterator it = find_if(v.begin(), v.end(), greaterFive()); 
if (it != v.end())
	cout << "找到了大于 5 的数据:" << *it << endl;
  • 二元谓词 - 用于 vector 容器中 sort 降序排列
// 二元谓词
class sortDown
{
public:
	bool operator()(int v1, int v2) {
		return v1 > v2;                // 降序
	}
};

vector<int> v;
v.push_back(1);
v.push_back(4);
v.push_back(2);
v.push_back(3);
v.push_back(5);
// 降序排流
sort(v.begin(), v.end(), sortDown());

6.10.4 内建函数对象

需要引入头文件 functional

  • 算数仿函数

![[Pasted image 20230822094026.png]]

例:

#include <functional>
negate<int> n;
cout << n(50) << endl;     // 取反仿函数(一元),输出 -50

plus<int> p;
cout << p(10, 20) << endl; // 相加函数(二元),输出 30
  • 关系仿函数

![[Pasted image 20230822095101.png]]

例:(使用内建函数对象改变 sort 排序

sort(v.begin(), v.end(), greater<int>());   // 使用内建函数对象实现降序排序
  • 逻辑仿函数
    ![[Pasted image 20230822102915.png]]

例:(把 v1 容器取反放到 v2容器中

vector<bool> v;
v.push_back(true);
v.push_back(false);
v.push_back(true);
v.push_back(false);
v.push_back(true);

vector<bool> v2;
v2.resize(v.size());   // 搬运前需要先指定 v2 大小空间
// 使用搬运函数
transform(v.begin(), v.end(), v2.begin(), logical_not<bool>());

6.11 常用算法

![[Pasted image 20230823092927.png]]

6.11.1 遍历算法

for_each

for_each(iterator beg, iterator end, _func);

例(遍历输出 vector 容器):

// 普通函数
void print01(int val)
{
	cout << val << " ";
}
// 仿函数
class print02
{
public:
	void operator()(int val) {
		cout << val << " ";
	}
};

vector<int> v;
for (int i = 0; i < 10; i++) {
	v.push_back(i);
}
// 调用普通函数遍历输出
for_each(v.begin(), v.end(), print01);
// 调用仿函数遍历输出
for_each(v.begin(), v.end(), print02());

transform

搬运容器数据
![[Pasted image 20230823104150.png]]

例:

class Transform
{
public:
	int operator()(int val) {
		return val;				// 把数据原封不动地返回去
	}                           // 也可以加个100或者取个反啥的再搬过去
};

vector<int> v;
for (int i = 0; i < 10; i++) {
	v.push_back(i);
}
vector<int> v2;
v2.resize(v.size());              // 在搬运前目标容器需要开辟空间
transform(v.begin(), v.end(), v2.begin(), Transform());

6.11.3 查找算法

![[Pasted image 20230823104917.png]]

find
![[Pasted image 20230823105257.png]]

例(查找自定义类型):

// 自定义数据类型
class Person
{
public:
	Person(string name, int age) {
		this->name = name;
		this->age = age;
	}
	// 需要重载 ==,让底层的 find 知道如何对比数据
	bool operator==(const Person& p) {  
		if ((this->name == p.name) && (this->age == p.age))
			return true;
		else
			return false;
	}
	string name;
	int age;
};

vector<Person> v2;
Person p1("张三", 18);
Person p2("李四", 15);
Person p3("王五", 20);

v2.push_back(p1);
v2.push_back(p2);
v2.push_back(p3);
// 查找,注意在自定义类中重载 == 
vector<Person>::iterator itt = find(v2.begin(), v2.end(), p3);
if (itt != v2.end())
	cout << "找到了" << itt->name << endl;

find_if
![[Pasted image 20230823192012.png]]

例:

vector<int> v;
for (int i = 0; i < 10; i++) {
	v.push_back(i);
}
// 找出所有大于 5 的数
vector<int>::iterator it;
vector<int>::iterator start = v.begin();
do {
	it = find_if(start, v.end(), greaterFive());
	if (it != v.end()) {
		cout << "找到了大于5的数:" << *it << endl;
		start = it+1;   // 从该位置的下一个位置开始查找
	}
} while (it != v.end());

adjacent_find
![[Pasted image 20230823194018.png]]

binary_search

二分查找

![[Pasted image 20230823194233.png]]

count
![[Pasted image 20230823194555.png]]

count_if
![[Pasted image 20230823194838.png]]

6.11.4 排序算法

![[Pasted image 20230823195107.png]]

sort
![[Pasted image 20230823195313.png]]

random_shuffle
![[Pasted image 20230823195323.png]]

merge

合并后的容器也是有序的
注意:在合并之前,需要为目标容器开辟内存空间(可以用resize函数)
![[Pasted image 20230823195334.png]]

reverse
![[Pasted image 20230823195556.png]]

6.11.5 拷贝和替换算法

![[Pasted image 20230823195618.png]]

copy

注意,拷贝前需要为目标容器开辟空间

![[Pasted image 20230823195700.png]]

replace
![[Pasted image 20230823195837.png]]

replace_if
![[Pasted image 20230823195944.png]]

swap
![[Pasted image 20230823200041.png]]

6.11.6 算术生成算法

属于小型算法,使用时包含头文件 numeric

accumulate
![[Pasted image 20230823212816.png]]

fill
![[Pasted image 20230823212924.png]]

6.11.7 集合算法

注意,目标容器需要提前开辟空间

![[Pasted image 20230823213053.png]]

set_intersection

#include <algorithm>     // 使用 min 需要包含头文件
v.resize(min(v1.size(), v2.size()));   // 开辟两个容器中的最小大小

![[Pasted image 20230823213901.png]]

![[Pasted image 20230823213839.png]]

set_union
![[Pasted image 20230823213912.png]]

set_difference
![[Pasted image 20230823214204.png]]
至此,黑马C++相关文章已更新完毕。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_小猪沉塘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值