C++中的STL

STL

STL 是“Standard Template Library”的缩写,中文译为“标准模板库”。

STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离

例如,vector 的底层为顺序表(数组),list 的底层为双向链表,deque 的底层为循环队列,set 的底层为红黑树,hash_set 的底层为哈希表。

Iterator

  • 迭代器是一个“可以遍历STL容器内全部或部分元素”的对象
  • 迭代器指出容器中的一个特定位置
  • 迭代器就如同一个指针
  • 迭代器提供对一个容器中的对象的访问方法,并且可以定义容器中对象的范围

迭代器的功能分类

1)正向迭代器

运算符功能
++让迭代器指向下一元素
==,!=判断两个迭代器是否指向同一位置并返回结果
=将右侧的值代入左侧迭代器所引用的元素的位置
*返回该位置的元素

2)双向迭代器

双向迭代器具有正向迭代器的全部功能。除此之外,若 p 是一个双向迭代器,则--pp--都是有定义的。--p使得 p 朝和++p相反的方向移动。

3)随机访问迭代器

随机访问迭代器具有双向迭代器的全部功能。若 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:

  • p+=i:使得 p 往后移动 i 个元素。
  • p-=i:使得 p 往前移动 i 个元素。
  • p+i:返回 p 后面第 i 个元素的迭代器。
  • p-i:返回 p 前面第 i 个元素的迭代器。
  • p[i]:返回 p 后面第 i 个元素的引用。

此外,两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较。p1<p2的含义是:p1 经过若干次(至少一次)++操作后,就会等于 p2。其他比较方式的含义与此类似。

对于两个随机访问迭代器 p1、p2,表达式p2-p1也是有定义的,其返回值是 p2 所指向元素和 p1 所指向元素的序号之差。

容器迭代器功能
vector随机访问
deque随机访问
list双向
set / multiset双向
map / multimap双向
unordered_set / unordered_map正向迭代器
stack不支持迭代器
queue不支持迭代器
priority_queue不支持迭代器

常用函数

  • isalnum()用来判断一个字符是否为数字或者字母,也就是说判断一个字符是否属于a~ z||A~ Z||0~9。
  • isalpha()用来判断一个字符是否为字母
  • isdigit() 用来检测一个字符是否是十进制数字0-9
  • islower()用来判断一个字符是否为小写字母,也就是是否属于a~z。
  • isupper()islower()相反,用来判断一个字符是否为大写字母。

Vector

vector 容器是 STL中最常用的容器之一,它和 array 容器非常类似,都可以看做是对 C++普通数组的“升级版”。不同之处在于,array 实现的是静态数组(容量固定的数组),而 vector 实现的是一个动态数组,即可以进行元素的插入和删除,在此过程中,vector 会动态调整所占用的内存空间,整个过程无需人工干预。

vector 常被称为向量容器,因为该容器擅长在尾部插入或删除元素,在常量时间内就可以完成,时间复杂度为O(1);而对于在容器头部或者中部插入或删除元素,则花费时间要长一些(移动元素需要耗费时间),时间复杂度为线性阶O(n)。

#include <bits/stdc++.h>
using namespace std;

vector <int> a;//存储int的vector
vector <vector <int> > b;//vector里面存vector
vector<vector<int> > c(n, vector<int>(n));//n行n列
vector <int> d[100];//100个vector
函数名功能复杂度
size()返回向量的元素数O(1)
resize(n)调整容器的大小,使其包含n个元素,新增元素调用默认构造函数
resize(n,x)调整容器的大小,使其包含n个元素,新增元素初始化为x
reserve(n)更改容器的容量(capacity)为n
push_back(x)在向量末尾添加元素xO(1)
pop_back(x)删除向量最后一个元素O(1)
emplace_back(x)在向量末尾添加元素x,比push_back()效率高O(1)
begin()返回指向向量开头的迭代器O(1)
end()返回指向向量末尾(最后一个元素的后一个位置)的迭代器O(1)
insert(p,x)在向量位置p处插入元素x,p为指向该位置的迭代器O(n)
erase§删除向量位置p处的元素,p为指向该位置的迭代器O(n)
erase(first,last)删除向量位置 [first ,last) 区间内的所有元素,first 和 last 为迭代器O(n)
clear()删除向量中的所有元素O(n)
assign(const_iterator first,const_iterator last)将一个容器中的 **[first,last)**范围内的元素拷贝到另一个容器中O(n)
reverse(const_iterator first,const_iterator last)反转在**[first,last)**范围内的顺序,reverse函数无返回值O(n)

emplace_back()push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。

emplace_back()C++ 11新增):在容器尾部添加一个元素,调用构造函数原地构造,不需要触发拷贝构造和移动构造。因此比push_back()更加高效。

//#include <bits/stdc++.h>
#include <vector>
#include <iostream>
using namespace std;

vector <int> a;

int main()
{

    a={1,2,3,4,5,6};
    //利用循环直接遍历
    for(int i=0;i<a.size();i++){
        cout<<a[i]<<" ";
    }
    cout<<endl;
    a.pop_back();
    a.emplace_back(7);
    a.erase(a.begin());
    a.insert(a.begin(),0);
    //正向迭代器遍历
    for(vector<int>::iterator it = a.begin(); it != a.end(); it++){
        cout<<*it<<" ";
    }
    cout<<endl;
    //逆向迭代器遍历
    for(vector<int>::reverse_iterator it = a.rbegin(); it != a.rend(); it++){
        cout<<*it<<" ";
    }
    a.clear();
    return 0;
}

输出结果:

1 2 3 4 5 6 
0 2 3 4 5 7
7 5 4 3 2 0

list

底层是以双向链表的形式实现的,插入和删除效率极高,但访问效率低,不能直接访问,需要使用迭代器访问

实际场景中,如何需要对序列进行大量添加或删除元素的操作,而直接访问元素的需求却很少,这种情况建议使用 list 容器存储序列。

list 容器中的元素可以分散存储在内存空间里,而不是必须存储在一整块连续的内存空间中。

// #include <bits/stdc++.h>
#include <list>
#include <iostream>
using namespace std;

list <int> a;
list <list <int> > b;
list <int> c[100];
list <int> d(10);//其中包含 10 个元素,每个元素的值都为相应类型的默认值(int类型的默认值为 0)
list<int> e(10, 5);//包含 10 个元素并且值都为 5
list<int> f(e);//在已有 list 容器的情况下,通过拷贝该容器可以创建新的 list 容器
//通过拷贝其他类型容器(或者普通数组)中指定区域内的元素,可以创建新的 list 容器
int g[] = { 1,2,3,4,5 };
list<int> h(g, g+3);//list容器里有1,2,3
list<int>i(h.begin()+1, h.end());//拷贝h容器中的2,3
函数名功能复杂度
size()返回表的元素数O(1)
begin()返回指向表开头的迭代器O(1)
end()返回指向表末尾(最后一个元素的后一个位置)的迭代器O(1)
push_front(x)在表开头添加元素xO(1)
push_back(x)在表末尾添加元素xO(1)
emplace_front(x)在表开头添加元素xO(1)
emplace_back(x)在表末尾添加元素xO(1)
pop_front(x)删除位于表头的元素O(1)
pop_back()删除位于表尾的元素O(1)
insert(p,x)在表p位置处插入元素x,p为指向该位置的迭代器O(1)
emplace(p,x)在表p位置处插入元素x,p为指向该位置的迭代器,效率比 insert() 方法高O(1)
erase§删除表中p位置的元素,p为指向该位置的迭代器O(1)
clear()删除表中所有元素O(n)
empty()判断容器中是否有元素,若无元素,则返回 true;反之,返回 falseO(1)
sort()通过更改容器中元素的位置,将它们进行排序O(n*logn)
reverse()反转容器中元素的顺序O(n)
// #include <bits/stdc++.h>
#include <list>
#include <iostream>
using namespace std;

list <int> a={6,7,8,9};

bool cmp(int x,int y){
    return x>y;
}

int main()
{
    for(int i=0;i<5;i++) a.push_back(i+1);
    //反向迭代器遍历
    for(list <int>::reverse_iterator it = a.rbegin();it != a.rend();it++)
    cout<<*it<<" ";
    cout<<endl;

    a.pop_front();
    a.pop_back();
    a.sort();

    //迭代器遍历
    for(list <int>::iterator it = a.begin();it != a.end();it++)
    cout<<*it<<" ";
    cout<<endl;

    a.sort(cmp);
    //迭代器遍历
    for(list <int>::iterator it = a.begin();it != a.end();it++)
    cout<<*it<<" ";
    cout<<endl;
}

输出结果:

5 4 3 2 1 9 8 7 6 
1 2 3 4 7 8 9
9 8 7 4 3 2 1

stack

stack不支持Iterator

先进后出

// #include <bits/stdc++.h>
#include <stack>
#include <list>
#include <iostream>
using namespace std;

stack <int> a;
stack <int,list<int> > b;//stack 容器适配器的模板有两个参数。第一个参数是存储对象的类型,第二个参数是底层容器的类型
stack <int> c(a);
函数名功能复杂度
size()返回栈的元素个数O(1)
top()返回栈顶元素O(1)
pop()弹出(删除)栈顶元素O(1)
push(x)向栈中添加元素xO(1)
emplace(x)向栈中添加元素xO(1)
empty()判断栈是否为空,为空时返回trueO(1)

pop()不返回元素

// #include <bits/stdc++.h>
#include <stack>
#include <list>
#include <iostream>
using namespace std;

list <int> a;

int main()
{
    a={1,2,3,4,5};
    stack <int,list <int> > b(a);//用另一个容器来初始化,只要堆栈的底层容器类型和这个容器的类型相同
    //复制一个temp栈用来遍历
    stack <int,list <int> > temp(b);
    while(!temp.empty()){
        cout<<temp.top()<<" ";
        temp.pop();
    }
	cout<<endl;
    //破坏性遍历
    while(!b.empty()){
        cout<<b.top()<<" ";
        b.pop();
    }
}

输出结果:

5 4 3 2 1 
5 4 3 2 1

queue

queue不支持Iterator

先进先出

// #include <bits/stdc++.h>
#include <queue>
#include <iostream>
using namespace std;

queue <int> a;
queue <int> b(a);
函数名功能复杂度
size()返回队列中的元素个数O(1)
front()返回队头元素O(1)
back()返回队尾元素O(1)
pop()队头元素出队O(1)
push(x)向队尾添加元素xO(1)
emplace(x)向队尾添加元素xO(1)
empty()判断队列是否为空,为空返回trueO(1)
swap(queue a,queue b)交换两个队列的元素O(1)
// #include <bits/stdc++.h>
#include <queue>
#include <iostream>
using namespace std;

queue <int> a;

int main()
{
    for(int i=0;i<5;i++)
    a.emplace(i);
    queue <int> b(a);
    while(!b.empty()){
        cout<<b.front()<<" ";
        b.pop();
    }
    cout<<endl;
    cout<<a.front()<<" "<<a.back()<<endl;
    for(int i=5;i>0;i--)
    b.emplace(i);

    swap(a,b);
    while(!b.empty()){
        cout<<b.front()<<" ";
        b.pop();
    }

}
0 1 2 3 4 
0 4
0 1 2 3 4

deque

deque 是 double-ended queue 的缩写,又称双端队列容器。

deque 容器和 vecotr 容器有很多相似之处,比如:

  • deque 容器也擅长在序列尾部添加或删除元素( 时间复杂度为O(1) ),而不擅长在序列中间添加或删除元素。

  • deque 容器也可以根据需要修改自身的容量和大小。

和 vector 不同的是,deque 还擅长在序列头部添加或删除元素,所耗费的时间复杂度也为常数阶O(1)。并且更重要的一点是,deque 容器中存储元素并不能保证所有元素都存储到连续的内存空间中。

当需要向序列两端频繁的添加或删除元素时,应首选 deque 容器。

// #include<bits/stdc++.h>
#include <iostream>
#include <deque>
using namespace std;

int main()
{
    deque <int> a;//创建一个没有任何元素的空 deque 容器

    deque <int> b(10);//创建一个具有 10 个元素(默认都为 0)的 deque 容器

    deque <int> c(10 ,5);//创建了一个包含 10 个元素(值都为 5)的 deque 容器

    deque <int> d(c);//在已有 deque 容器的情况下,可以通过拷贝该容器创建一个新的 deque 容器
                    //采用此方式,必须保证新旧容器存储的元素类型一致

    int num[]={1,2,3,4,5};//通过拷贝其他类型容器中指定区域内的元素(也可以是普通数组),可以创建一个新容器
    deque <int> e(num,num+5);
}

函数名功能复杂度
size()返回元素个数O(1)
begin()返回指向第一个元素的迭代器O(1)
rbegin()返回指向最后一个元素的迭代器O(1)
end()返回指向最后一个元素后一个位置的迭代器O(1)
rend()返回指向第一个元素前一个位置的迭代器O(1)
push_front(x)在容器开头添加元素xO(1)
emplace_front(x)在容器开头添加元素xO(1)
push_back(x)在容器的末尾添加元素xO(1)
emplace_back(x)在容器的末尾添加元素xO(1)
pop_front()删除容器开头元素O(1)
pop_back()删除容器末尾元素O(1)
emplace(p,x)在表p位置处插入元素x,p为指向该位置的迭代器,效率比 insert() 方法高O(1)
insert(p,x)在表p位置处插入元素x,p为指向该位置的迭代器O(n)
erase§删除容器中p位置的元素,p为指向该位置的迭代器O(n)
clear()删除容器中所有元素O(n)
empty()判断容器是否为空,为空返回trueO(1)

priority_queue

底层存储结构采用堆数据结构

底层容器是vector或者deque,vector 或 deque 容器并没有提供实现 priority_queue 容器适配器 “First in,Largest out” 特性的功能,因此 STL 选择使用堆来重新组织 vector 或 deque 容器中存储的数据,从而实现该特性。

priority_queue不支持迭代器

priority_queue 容器适配器定义了一个元素有序排列的队列, **默认队列头部的元素优先级最高。**因为它是一个队列,所以只能访问第一个元素,这也意味着优先级最高的元素总是第一个被处理

// #include <bits/stdc++.h>
#include <queue>
#include <iostream>
using namespace std;

priority_queue <int> a;

//可以用适当类型的对象初始化一个优先级队列:
int num[]={2,4,1,5,3};
priority_queue <int> b {begin(num),end(num)};//初始化列表中的序列可以来自于任何容器,并且不需要有序。优先级队列会对它们进行排序。

priority_queue <int> c(b);//拷贝构造函数会生成一个和现有对象同类型的 priority_queue 对象,它是现有对象的一个副本。

priority_queue是一种能根据元素优先级进行插入,查询,删除操作的队列。操作与queue 相同。

优先队列默认使用operator < 来比较大小,并且大的优先。

priority_queue 模板有 3 个参数,其中两个有默认的参数;第一个参数是存储对象的类型,第二个参数是存储元素的底层容器,第三个参数是函数对象,它定义了一个用来决定元素顺序的断言

// #include <bits/stdc++.h>
#include <queue>
#include <iostream>
using namespace std;


//降序队列(大顶堆):使用 < 比较大小()
priority_queue <int, vector <int>, less<int> > a;

//升序队列(小顶堆):使用 > 比较大小
priority_queue <int, vector <int>, greater<int> > b;
函数名功能复杂度
size()返回队列元素个数O(1)
top()返回队头元素O(1)
push(x)向队列中添加元素xO(logn)
pop()队头元素出队O(logn)
empty()判断队列是否为空,为空时返回trueO(1)

对于结构体,我们需要重载运算符,因为排序要使用 > (或 < )

//#include <bits/stdc++.h>
#include <queue>
#include <iostream>
using namespace std;

struct Point
{
    int x,y;
    friend bool operator<(Point a, Point b){
        return a.x<b.x;
    }
    friend bool operator>(Point a, Point b){
        return a.x>b.x;
    }
};

priority_queue<Point> a;//默认是大顶堆
priority_queue<Point,vector<Point>,greater<Point> >b;

int main()
{
    a.push({1,2});
    a.push({4,5});
    a.push({3,5});
    a.push({2,5});
    while(!a.empty()){
        cout <<a.top().x<<" "<<a.top().y<<endl;
        a.pop();
    }
    cout<<endl;
    b.push({1,2});
    b.push({4,5});
    b.push({3,5});
    b.push({2,5});
    while(!b.empty()){
        cout <<b.top().x<<" "<<b.top().y<<endl;
        b.pop();
    }
}

输出结果:

4 5
3 5
2 5
1 2

1 2
2 5
3 5
4 5

priority_queue 底层使用堆存储结构,大顶堆(小顶堆)中的数据整体依然无序,但是最大(最小)的元素肯定是开头的第一个元素。

第一个元素出队后,剩下的元素重新组织成大顶堆(小顶堆)。

set

set中存储的也是键值对,其键和值完全相同

根据元素的值进行排序的有序集合,集合中不存在重复元素

函数名功能复杂度
size()返回当前 set 容器中存有元素的个数。O(1)
empty()判断当前容器是否为空,为空返回trueO(1)
clear()清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。O(n)
begin()返回指向容器中第一个(注意,是已排好序的第一个)元素的迭代器。O(1)
end()返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的迭代器。O(1)
insert(val)向当前 set 容器中添加新元素。O(logn)
emplace(val)向当前 set 容器中添加新元素。其效果和 insert() 一样,但效率更高。O(logn)
erase(val)删除 set 容器中的元素val。O(logn)
find(val)在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的迭代器反之,则返回和 end() 方法一样的迭代器。O(logn)
lower_bound(val)返回一个指向当前 set 容器中第一个大于或等于 val 的元素的迭代器。O(logn)
upper_bound(val)返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。O(logn)
equal_range(val)该方法返回一个 pair 对象(包含 2 个迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。O(logn)
count(val)在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。O(logn)

set 容器支持随时向内部添加新的元素,因此创建空 set 容器的方法是经常使用的

//#include <bits/stdc++.h>
#include <set>
#include <iostream>
using namespace std;

set <int> a;//默认升序

set <int> b={2,4,3,5,1};//创建的同时初进行始化

set <int> c(b);//拷贝创建

set <int> d(++c.begin(),--c.end());//部分拷贝,[start,end)左闭右开

set <int,greater<int> > e={2,4,3,5,1};//降序

int main()
{
    for(set <int>::iterator it = b.begin();it != b.end();it++)
    cout<<*it<<" ";
    cout<<endl;

    for(set <int>::iterator it = c.begin();it != c.end();it++)
    cout<<*it<<" ";
    cout<<endl;

    for(set <int>::iterator it = d.begin();it != d.end();it++)
    cout<<*it<<" ";
    cout<<endl;

    for(set <int>::iterator it = e.begin();it != e.end();it++)
    cout<<*it<<" ";
    cout<<endl;
}

输出结果:

1 2 3 4 5 
1 2 3 4 5 
2 3 4 
5 4 3 2 1 

bitset

位图的引入

给40亿个不重复的无符号整数,没排过序。给一个无符号整数,如何快速判断一个数是否在这40亿个数中?

要判断一个数是否在某一堆数中,我们可能会想到如下方法:

  • 将这一堆数进行排序,然后通过二分查找的方法判断该数是否在这一堆数中。
  • 将这一堆数插入到unordered_set容器中,然后调用find函数判断该数是否在这一堆数中。

单从方法上来看,这两种方法都是可以,而且效率也不错,第一种方法的时间复杂度是O(NlogN),第二种方法的时间复杂度是O(N)。

但问题是这里有40亿个数,若是我们要将这些数全部加载到内存当中,那么将会占用16G的空间,空间消耗是很大的。因此从空间消耗来看,上面这两种方法实际都是不可行的。

位图解决

实际在这个问题当中,我们只需要判断一个数在或是不在,即只有两种状态,那么我们可以用一个比特位来表示数据是否存在,如果比特位为1则表示存在,比特位为0则表示不存在。比如:

在这里插入图片描述

无符号整数总共有232个,因此记录这些数字就需要232个比特位,也就是512M的内存空间,内存消耗大大减少。

位图的概念

所谓位图,就是用每一位来存放某种状态,适用于海量数据,数据无重复的场景。通常是用来判断某个数据存不存在的。

bitset的定义方式

方式一: 构造一个16位的位图,所有位都初始化为0。

bitset<16> bs1; //0000 0000 0000 0000

方式二: 构造一个16位的位图,根据所给值初始化位图的前n位。

bitset<16> bs2(0xfa5); //0000 1111 1010 0101

方式三: 构造一个16位的位图,根据字符串中的0/1序列初始化位图的前n位。

bitset<16> bs3(string("10111001")); //0000 0000 1011 1001

bitset成员函数的使用

bitset中常用的成员函数如下:

成员函数功能
set()设置指定位 或 所有位
reset()清空指定位 或 所有位
flip()反转指定位 或 所有位
test()获取指定位的状态
count()获取被设置位的个数
size()获取可以容纳的位的个数
any()如果有任何一个位被设置则返回true
none()如果没有位被设置则返回true
all()如果所有位都被设置则返回true

注意: 使用成员函数set、reset、flip时,若指定了某一位则操作该位,若未指定位则操作所有位。

#include <bits/stdc++.h>
using namespace std;

int main()
{
	bitset<8> bs;
	bs.set(2); //设置第2位
	bs.set(4); //设置第4位
	cout << bs << endl; //00010100
	
	bs.flip(); //反转所有位
	cout << bs << endl; //11101011
	cout << bs.count() << endl; //6

	cout << bs.test(3) << endl; //1

	bs.reset(0); //清空第0位
	cout << bs << endl; //11101010

	bs.flip(7); //反转第7位
	cout << bs << endl; //01101010

	cout << bs.size() << endl; //8

	cout << bs.any() << endl; //1

	bs.reset(); //清空所有位
	cout << bs.none() << endl; //1

	bs.set(); //设置所有位
	cout << bs.all() << endl; //1
	return 0;
}

输出结果:

00010100
11101011
6
1
11101010
01101010
8
1
1
1

bitset运算符的使用

一、bitset中 >> ,<< 运算符的使用。
bitset容器对>>、<<运算符进行了重载,我们可以直接 使用>>、<<运算符对biset容器定义出来的对象进行输入输出操作。

#include <bits/stdc++.h>
using namespace std;

int main()
{
	bitset<8> bs;
	cin >> bs; //10110
	cout << bs << endl; //00010110
	return 0;
}

输出结果:

00010110

二、bitset中赋值运算符、关系运算符、复合赋值运算符、单目运算符的使用。
bitset容器中不仅对赋值运算符和一些关系运算符进行了重载,而且对一些复合赋值运算符和单目运算符也进行了重载,我们可以直接使用这些运算符对各个位图进行操作。

<<  	//二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。
>>		//二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。

包括如下运算符:

  • 赋值运算符:=
  • 关系运算符:==、!=
  • 复合赋值运算符:&=、|=、^=、<<=、>>=
  • 单目运算符:~
#include <bits/stdc++.h>
using namespace std;

int main()
{
	bitset<8> bs1(string("10101010"));
	bitset<8> bs2(string("10101010"));
	bs1 >>= 1;
	cout << bs1 << endl; //01010101

	bs2 |= bs1;
	cout << bs2 << endl; //11111111

    bs1 = ~bs1;
    cout << bs1 << endl; //10101010

    cout<<(bs1 == bs2)<<endl; //false

	return 0;
}

输出结果:

01010101
11111111
10101010
0

三、bitset中位运算符的使用。
bitset容器中同时也对三个位运算符进行了重载,我们可以直接使用&、|、^(按位与,按位或,按位异或)对各个位图进行操作。

#include <bits/stdc++.h>
using namespace std;

int main()
{
	bitset<8> bs1(string("10101010"));
	bitset<8> bs2(string("01010101"));
	
	cout << (bs1 & bs2) << endl; //00000000
	cout << (bs1 | bs2) << endl; //11111111
	cout << (bs1 ^ bs2) << endl; //11111111
	return 0;
}

输出结果:

00000000
11111111
11111111

四、bitset中[ ]运算符的使用。
bitset容器中对[ ]运算符进行了重载,我们可以直接使用[ ]对指定位进行访问或修改。

#include <bits/stdc++.h>
using namespace std;

int main()
{
	bitset<8> bs(string("00110101"));
	cout << bs[0] << endl; //1
	bs[0] = 0;
	cout << bs << endl; //00110100
	return 0;
}

输出结果:

1
00110100

map

map 容器存储的都是 pair 对象,也就是用 pair 类模板创建的键值对。其中,各个键值对的键和值可以是任意数据类型,包括 C++基本数据类型(int、double 等)、使用结构体或类自定义的类型。(使用 map 容器存储的各个键值对,键的值既不能重复也不能被修改)

前面提到,map 容器存储的都是 pair 类型的键值对元素,更确切的说,该容器存储的都是 pair<const K, T> 类型(其中 K 和 T 分别表示键和值的数据类型)的键值对元素。

//#include <bits/stdc++.h>
#include <map>
#include <iostream>
using namespace std;

//创建出一个空的 map 容器
map<string,int> a;

//创建的同时初始化
map<string,int> b={{"c",3},{"d",4},{"a",1},{"b",2}};//默认情况下,调用 std::less<T> 规则,根据容器内各键值对的键的大小,做升序排序。

//拷贝构造
map<string,int> c(b);

//部分拷贝,[start,end)左闭右开
map<string,int> d(++c.begin(),--c.end());

map<string,int,greater<string> > e={{"c",3},{"d",4},{"a",1},{"b",2}};//调用 std::greater<T>,做降序排序。

int main()
{
    for(map<string,int>::iterator it = b.begin();it != b.end();it++)
    cout <<it->first<<" "<<it->second<<endl;
    cout<<endl;
    for(map<string,int>::iterator it = c.begin();it != c.end();it++)
    cout <<it->first<<" "<<it->second<<endl;
    cout<<endl;
    for(map<string,int>::iterator it = d.begin();it != d.end();it++)
    cout <<it->first<<" "<<it->second<<endl;
    cout<<endl;
    for(map<string,int>::iterator it = e.begin();it != e.end();it++)
    cout <<it->first<<" "<<it->second<<endl;
}

输出结果:

a 1
b 2
c 3
d 4

a 1
b 2
c 3
d 4

b 2
c 3

d 4
c 3
b 2
a 1
函数名功能复杂度
size()返回当前 map 容器中存有键值对的个数。O(1)
clear()清空 map 容器中所有的键值对。O(n)
begin()返回指向容器中第一个(注意,是已排好序的第一个)键值对的迭代器。O(1)
end()返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的迭代器。O(1)
insert(make_pair(key,val))向 map 容器中插入键值对。O(logn)
emplace(make_pair(key,val))在当前 map 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。O(logn)
erase()删除 map 容器指定位置、指定键(key)值或者指定区域内的键值对。O(logn)
find(key)在 map 容器中查找键为 key 的键值对,如果成功找到,则返回指向该键值对的迭代器;反之,则返回和 end() 方法一样的迭代器。O(logn)
count(key)在当前 map 容器中,查找键为 key 的键值对的个数并返回。注意,由于 map 容器中各键值对的键的值是唯一的,因此该函数的返回值最大为 1。O(logn)
empty()若容器为空,则返回 true;否则 false。O(1)

注:

operator[]

map容器重载了 [] 运算符,只要知道 map 容器中某个键值对的键的值,就可以向获取数组中元素那样,通过键直接获取对应的值

可以通过 a[key]=val 来修改元素(赋值),若key不存在,相当于新增了一个元素。

unordered_map和unordered_set的底层实现都是hash table,内部存储并不保证顺序,也不排序,也就是说你插入多个元素时的插入顺序和你从begin到end遍历时的顺序未必是一致的,这个顺序性它是不保证的。再加上,在需要的时候比如装载因子达到某一个值的时候,所有元素会重新hash,重新hash后,各个元素的位置都会重新计算,都可能都会发生移动,就更加不保证顺序了。

unordered_set

unordered_set 容器,可直译为“无序 set 容器”,即 unordered_set 容器和 set 容器很像,唯一的区别就在于 set 容器会自行对存储的数据进行排序,而 unordered_set 容器不会。

顺序和插入顺序无关

底层实现为哈希表,建立比较消耗时间,而查询,插入,删除的速度都在常数级别,可以看出O(1),但如果出现hash冲突,性能会下降很多,最坏情况下会退化成O(n)

总的来说,unordered_set 容器具有以下几个特性:

  1. 不再以键值对的形式存储数据,而是直接存储数据的值。
  2. 容器内部存储的各个元素的值都互不相等,且不能被修改。
  3. 不会对内部存储的数据进行排序。(这和该容器底层采用哈希表结构存储数据有关)

unordered_set 容器的类模板定义如下:

template < class Key,            //容器中存储元素的类型
           class Hash = hash<Key>,    //确定元素存储位置所用的哈希函数
           class Pred = equal_to<Key>,   //判断各个元素是否相等所用的函数
           class Alloc = allocator<Key>   //指定分配器对象的类型
           > class unordered_set;
参数含义
Key确定容器存储元素的类型,如果将 unordered_set 看做是存储键和值相同的键值对的容器,则此参数则用于确定各个键值对的键和值的类型,因为它们是完全相同的,因此一定是同一数据类型的数据。
Hash = hash指定 unordered_set 容器底层存储各个元素时,所使用的哈希函数。需要注意的是,默认哈希函数 hash 只适用于基本数据类型(包括 string 类型),而不适用于自定义的结构体或者类。
Pred = equal_tounordered_set 容器内部不能存储相等的元素,而衡量 2 个元素是否相等的标准,取决于该参数指定的函数。 默认情况下,使用 STL 标准库中提供的 equal_to 规则,该规则仅支持可直接用 == 运算符做比较的数据类型。

如果 unordered_set 容器中存储的元素为自定义的数据类型,则默认的哈希函数 hash 以及比较函数 equal_to 将不再适用,只能自己设计适用该类型的哈希函数和比较函数,并显式传递给 Hash 参数和 Pred 参数。

//#include <bits/stdc++.h>
#include <unordered_set>
#include <iostream>
using namespace std;

//创建空的 unordered_set 容器
unordered_set<string> a;

//在创建 unordered_set 容器的同时,可以完成初始化操作
unordered_set<string> b={"hello","world","ni","hao"};

//拷贝构造
unordered_set<string> c(b);

//部分拷贝
unordered_set<string> d(++c.begin(), c.end());//unordered_set支持的迭代器为正向迭代器

int main()
{
    for(unordered_set<string>::iterator it = a.begin(); it != a.end();it++)
    cout<<*it<<" ";
    cout<<endl;
    for(unordered_set<string>::iterator it = b.begin(); it != b.end();it++)
    cout<<*it<<" ";
    cout<<endl;
    for(unordered_set<string>::iterator it = c.begin(); it != c.end();it++)
    cout<<*it<<" ";
    cout<<endl;
    for(unordered_set<string>::iterator it = d.begin(); it != d.end();it++)
    cout<<*it<<" ";
    cout<<endl;
}

输出结果:


hao ni world hello 
hao ni world hello 
hello world ni 
函数名功能复杂度
size()返回当前容器中存有元素的个数。O(1)
begin()返回指向容器中第一个元素的正向迭代器。O(1)
end()返回指向容器中最后一个元素之后位置的正向迭代器。O(1)
empty()若容器为空,则返回 true;否则 false。O(1)
find(key)查找以值为 key 的元素,如果找到,则返回一个指向该元素的正向迭代器;反之,则返回一个指向容器中最后一个元素之后位置的迭代器(如果 end() 方法返回的迭代器)。O(1)
count(key)在容器中查找值为 key 的元素的个数。O(logn)
insert()向容器中添加新元素。O(1)
emplace()向容器中添加新元素,效率比 insert() 方法高。O(1)
erase(val)删除指定元素。O(1)
clear()清空容器,即删除容器中存储的所有元素。O(n)

unordered_map

unordered_map 容器和 map 容器一样,以键值对(pair类型)的形式存储数据,存储的各个键值对的键互不相同且不允许被修改。但由于 unordered_map 容器底层采用的是哈希表存储结构,该结构本身不具有对数据的排序功能,所以此容器内部不会自行对存储的键值对进行排序。

建立比较消耗时间,而查询,插入,删除的速度都在常数级别,可以看出O(1),但如果出现hash冲突,性能会下降很多,最坏情况下会退化成O(n)

unordered_map 容器模板的定义如下所示:

template < class Key,                        //键值对中键的类型
           class T,                          //键值对中值的类型
           class Hash = hash<Key>,           //容器内部存储键值对所用的哈希函数
           class Pred = equal_to<Key>,       //判断各个键值对键相同的规则
           class Alloc = allocator< pair<const Key,T> >  // 指定分配器对象的类型
           > class unordered_map;
参数含义
<key,T>前 2 个参数分别用于确定键值对中键和值的类型,也就是存储键值对的类型。
Hash = hash用于指明容器在存储各个键值对时要使用的哈希函数,默认使用 STL 标准库提供的 hash 哈希函数。注意,默认哈希函数只适用于基本数据类型(包括 string 类型),而不适用于自定义的结构体或者类。
Pred = equal_to要知道,unordered_map 容器中存储的各个键值对的键是不能相等的,而判断是否相等的规则,就由此参数指定。默认情况下,使用 STL 标准库中提供的 equal_to 规则,该规则仅支持可直接用 == 运算符做比较的数据类型。

当无序容器中存储键值对的键为自定义类型时,默认的哈希函数 hash 以及比较函数 equal_to 将不再适用,只能自己设计适用该类型的哈希函数和比较函数,并显式传递给 Hash 参数和 Pred 参数。

//#include <bits/stdc++.h>
#include <unordered_map>
#include <iostream>
using namespace std;

//创建空的 unordered_map 容器
unordered_map<string,int> a;

//在创建 unordered_map 容器的同时,可以完成初始化操作
unordered_map<string,int> b={{"hello",1},{"world",2},{"ni",3},{"hao",4}};

//拷贝构造
unordered_map<string,int> c(b);

//部分拷贝
unordered_map<string,int> d(++c.begin(),c.end());

int main()
{
    for(unordered_map<string,int>::iterator it = a.begin();it != a.end();it++)
    cout<<it->first<<" "<<it->second<<endl;
    cout<<endl;
    for(unordered_map<string,int>::iterator it = b.begin();it != b.end();it++)
    cout<<it->first<<" "<<it->second<<endl;
    cout<<endl;
    for(unordered_map<string,int>::iterator it = c.begin();it != c.end();it++)
    cout<<it->first<<" "<<it->second<<endl;
    cout<<endl;
    for(unordered_map<string,int>::iterator it = d.begin();it != d.end();it++)
    cout<<it->first<<" "<<it->second<<endl;
    cout<<endl;
}

输出结果:


hao 4
ni 3
world 2
hello 1

hao 4
ni 3
world 2
hello 1

hello 1
world 2
ni 3

函数名功能复杂度
size()返回当前容器中存有键值对的个数。O(1)
begin()返回指向容器中第一个键值对的正向迭代器。O(1)
end()返回指向容器中最后一个键值对之后位置的正向迭代器。O(1)
empty()若容器为空,则返回 true;否则 false。O(1)
find(key)查找以 key 为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果 end() 方法返回的迭代器)。O(1)
count(key)在容器中查找以 key 键的键值对的个数。O(logn)
insert()向容器中添加新键值对。O(1)
emplace()向容器中添加新键值对,效率比 insert() 方法高。O(1)
erase(key)通过键值,删除指定键值对。O(1)
clear()清空容器,即删除容器中存储的所有键值对。O(n)

STL算法

STL算法存在 < algorithm >库中,主要有sortupper_boundlower_boundbinary_search

sort

使用的排序方法为经过优化的快速排序,复杂度比较稳定,为 O(nlogn)

//#include <bits/stdc++.h>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

struct Point
{
    int x, y;
};

Point a[100];
vector <Point> b;

bool cmp(Point a,Point b){
    return a.x > b.x;
}

int main()
{
    sort(a,a+100);//默认使用 <,升序排列
    sort(b.begin(),b.end(),cmp);//定义cmp,降序排列
}

count,find

//count()
template <class InputIterator, class T>  
typename iterator_traits<InputIterator>::difference_type 
count (
InputIterator first, 
InputIterator last, 
const T& val
);
//find()
InputIterator find (InputIterator first, InputIterator last, const T& val);
//因为 first 和 last 的类型为输入迭代器,因此该函数适用于所有的序列式容器。

first:查询的起始位置,为一个迭代器

last: 查询的结束位置,为一个迭代器

通过比较是否等于 val 返回[first,last]与 val相等的数值的个数。

注意本函数与find的区别:

count 返回值为查找的个数

find 返回值为一个迭代器

find() 函数的底层实现,其实就是用==运算符将 val[first, last) 区域内的元素逐个进行比对。这也就意味着,[first, last) 区域内的元素必须支持==运算符。

find()会返回一个输入迭代器,当 find() 函数查找成功时,其指向的是在 [first, last) 区域内查找到的第一个目标元素;如果查找失败,则该迭代器的指向和 last 相同。

#include <bits/stdc++.h>
using namespace std;

int main(){
    vector<int> a = {1, 1, 1, 2, 2, 3, 4, 4, 5};
    for(int i = 1; i <= 5; ++i) {
        cout << i << "的个数为:" << count(a.begin(), a.end(), i) << endl;
    }
    cout << "第一个1在第" << find(a.begin(), a.end(), 1) - a.begin() << "位" << endl;
}

输出结果

1的个数为:3
2的个数为:2
3的个数为:1
4的个数为:2
5的个数为:1
第一个1在第0位

accumulate

accumulate函数将一段数字从头到尾累加起来,或者使用指定的运算符进行运算
accumulate函数的前两个参数指定累加的范围,第三个参数为累加的初值,第四个参数为进行的操作,默认为累加

template<class InputIterator, class Type>
   Type accumulate(
      InputIterator _First, 
      InputIterator _Last, 
      Type _Val
   );
template<class InputIterator, class Type, class Fn2>
   Type accumulate(
      InputIterator _First, 
      InputIterator _Last, 
      Type _Val, 
      BinaryOperation _Binary_op //自定义二进制操作
   );

!!! accumulate()的返回值类型与第三个参数的类型一致

#include<iostream>
#include<vector>
#include<numeric>
using namespace std;

int main() {

    vector<int> nums = {1, 2, 3, 4, 5};
    int result = accumulate(nums.begin(), nums.end(), 0);
    cout << result << endl;
    
    return 0;
}

输出结果

15

inner_product

template <class InputIterator1, class InputIterator2, class T>
   T inner_product (InputIterator1 first1, InputIterator1 last1,
                    InputIterator2 first2, T init);

template <class InputIterator1, class InputIterator2, class T,
          class BinaryOperation1, class BinaryOperation2>
   T inner_product (InputIterator1 first1, InputIterator1 last1,
                    InputIterator2 first2, T init,
                    BinaryOperation1 binary_op1,
                    BinaryOperation2 binary_op2);
//第一个op1是对init最后返回结果的操作,版本1中默认为+,第二个op2是对内积操作的自定义,版本1中默认为乘
参数说明
first1第一个容器的某一个迭代器
last1第一个容器的某一个迭代器,在first后面
first2第二个容器的某一个迭代器
init初始值

作用:将first1到last1之间的对象(左闭右开),与first2及其对应位置的对象相乘,并且加上init

如果first2长度不够,会提前结束,first1同理

// inner_product example
#include <iostream>     // std::cout
#include <functional>   // std::minus, std::divides
#include <numeric>      // std::inner_product
 
int main () {
  int init = 100;
  int series1[] = {10,20,30};
  int series2[] = {1,2,3};
 
  std::cout << "using default inner_product: ";
  std::cout << std::inner_product(series1,series1+3,series2,init);
  std::cout << '\n';
  return 0;
}

输出结果

using default inner_product: 240

merge和inplace_merge

将 2 个有序序列合并为 1 个有序序列

C++ STL 标准库的开发人员考虑到用户可能需要自定义排序规则,因此为 merge() 函数设计了以下 2 种语法格式:

//以默认的升序排序作为排序规则
OutputIterator merge (InputIterator1 first1, InputIterator1 last1,
                      InputIterator2 first2, InputIterator2 last2,
                      OutputIterator result);
//以自定义的 comp 规则作为排序规则
OutputIterator merge (InputIterator1 first1, InputIterator1 last1,
                      InputIterator2 first2, InputIterator2 last2,
                      OutputIterator result, Compare comp);

first1last1first2 以及 last2 都为输入迭代器,[first1, last1) 和 [first2, last2) 各用来指定一个有序序列;result 为输出迭代器,用于为最终生成的新有序序列指定存储位置;comp 用于自定义排序规则。同时,该函数会返回一个输出迭代器,其指向的是新有序序列中最后一个元素之后的位置。

注意,当采用第一种语法格式时,[first1, last1)[first2, last2) 指定区域内的元素必须支持 < 小于运算符;同样当采用第二种语法格式时,[first1, last1)[first2, last2) 指定区域内的元素必须支持 comp 排序规则内的比较运算符。

注意,merge() 函数底层是通过拷贝的方式实现合并操作的。换句话说,上面程序在采用 merge() 函数实现合并操作的同时,并不会对 firstsecond 数组有任何影响。

当 2 个有序序列存储在同一个数组或容器中时,如果想将它们合并为 1 个有序序列,除了使用 merge() 函数,更推荐使用 inplace_merge() 函数。和 merge() 函数相比,inplace_merge() 函数的语法格式要简单很多:

//默认采用升序的排序规则
void inplace_merge (BidirectionalIterator first, BidirectionalIterator middle,
                    BidirectionalIterator last);
//采用自定义的 comp 排序规则
void inplace_merge (BidirectionalIterator first, BidirectionalIterator middle,
                    BidirectionalIterator last, Compare comp);

其中,firstmiddlelast 都为双向迭代器,[first, middle)[middle, last) 各表示一个有序序列。返回值为空。

merge() 函数一样,inplace_merge() 函数也要求 [first, middle)[middle, last) 指定的这 2 个序列必须遵循相同的排序规则,且当采用第一种语法格式时,这 2 个序列中的元素必须支持 < 小于运算符;同样,当采用第二种语法格式时,这 2 个序列中的元素必须支持 comp 排序规则内部的比较运算符。不同之处在于,merge() 函数会将最终合并的有序序列存储在其它数组或容器中,而 inplace_merge() 函数则将最终合并的有序序列存储在 [first, last) 区域中。

min_element和max_element

min_element(iterator start, iterator end, compare comp)
  • iterator start, iterator end 这些是指向容器中范围的迭代器位置。
  • compare comp 它是一个可选参数(一个函数),是比较函数(比较大小的规则)。

返回值: 它返回一个迭代器,指向给定范围内具有最小值的元素。

max_element用法与min_element类似

distance

distance() 函数用于计算两个迭代器表示的范围内包含元素的个数,其语法格式如下:

template<class InputIterator>
typename iterator_traits<InputIterator>::difference_type distance (InputIterator first, InputIterator last);

其中,firstlast 都为迭代器,其类型可以是输入迭代器、前向迭代器、双向迭代器以及随机访问迭代器;该函数会返回[first, last)范围内包含的元素的个数。

注意,firstlast 的迭代器类型,直接决定了 distance() 函数底层的实现机制:

  • firstlast 为随机访问迭代器时,distance() 底层直接采用 last - first 求得 [first, last) 范围内包含元素的个数,其时间复杂度为O(1)常数阶;
  • firstlast 为非随机访问迭代器时,distance() 底层通过不断执行 ++first(或者 first++)直到 first==last,由此来获取 [first, last) 范围内包含元素的个数,其时间复杂度为O(n)线性阶。

upper_bound和lower_bound

upper_bound:返回序列中第一个大于查找值的位置的指针或迭代器

lower_bound:返回序列中第一个大于等于查找值的位置的指针或迭代器

二者都需要原序列按一定规则排好序,复杂度为O(logn)

同时,该函数会返回一个正向迭代器,当查找成功时,迭代器指向找到的元素;反之,如果查找失败,迭代器的指向和 last 迭代器相同。

//#include <bits/stdc++.h>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

int a[5]={1, 2, 3, 4, 5};
vector<int> b={1, 2, 3, 4, 5};

int main()
{
    int px = upper_bound(a, a+5, 3) - a;
    int x = *upper_bound(a, a+5, 3);

    int py = lower_bound(b.begin(), b.end(), 3) - b.begin();
    int y = *lower_bound(b.begin(), b.end(), 3);

    cout << px << " " << x << endl;
    cout << py << " " << y << endl;
}

输出结果:

3 4
2 3

binary_search

二分查找,如果找到返回true,没找到就返回false,复杂度为O(logn),要求序列升序

//查找 [first, last) 区域内是否包含 val
binary_search(ForwardIterator first, ForwardIterator last, val)//first 和 last 都为正向迭代器

prev_permutation和next_permutation

prev_permutation

prev_permutation()函数功能是输出所有比当前排列小的排列,顺序是从大到小。

//#include <bits/stdc++.h>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

int a[3]={1, 2, 3};
int b[3]={2, 1, 3 };
vector<char> c={'A','B','C'};
vector<char> d={'B','A','C'};

int main()
{
    do{
        cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<endl;
    }while (prev_permutation(a,a+3));
    cout<<"--------"<<endl;
    do{
        cout<<b[0]<<" "<<b[1]<<" "<<b[2]<<endl;
    }while (prev_permutation(b,b+3));
    cout<<"--------"<<endl;
    do{
        cout<<c[0]<<" "<<c[1]<<" "<<c[2]<<endl;
    }while (prev_permutation(c.begin(),c.end()));
    cout<<"--------"<<endl;
    do{
        cout<<d[0]<<" "<<d[1]<<" "<<d[2]<<endl;
    }while (prev_permutation(d.begin(),d.end()));

    return 0;
}

输出结果:

1 2 3
--------
2 1 3
1 3 2
1 2 3
--------
A B C
--------
B A C
A C B
A B C

next_permutation

产生全排列,当序列存在下一个全排列时返回true,否则返回false,要求原序列升序

**为什么要升序:**这个函数在下一个排列大于上一个排列时返回 true,如果上一个排列是序列中最大的,它返回 false

//#include <bits/stdc++.h>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;

int a[3]={1, 2, 3};
int b[3]={2, 1, 3 };
vector<char> c={'A','B','C'};
vector<char> d={'B','A','C'};

int main()
{
    do{
        cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<endl;
    }while (next_permutation(a,a+3));
    cout<<"--------"<<endl;
    do{
        cout<<b[0]<<" "<<b[1]<<" "<<b[2]<<endl;
    }while (next_permutation(b,b+3));
    cout<<"--------"<<endl;
    do{
        cout<<c[0]<<" "<<c[1]<<" "<<c[2]<<endl;
    }while (next_permutation(c.begin(),c.end()));
    cout<<"--------"<<endl;
    do{
        cout<<d[0]<<" "<<d[1]<<" "<<d[2]<<endl;
    }while (next_permutation(d.begin(),d.end()));

    return 0;
}

输出结果:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
--------
2 1 3
2 3 1
3 1 2
3 2 1
--------
A B C
A C B
B A C
B C A
C A B
C B A
--------
B A C
B C A
C A B
C B A

对于不是一定顺序的排列,使用时将prev_permutationnext_permutation结合使用。

  • 10
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值