C++细节梳理之STL库

目录

容器库

序列式容器

关联式容器和无序关联式容器

容器适配器

迭代器

算法库

lambda表达式

sort

count_if

accumulate

auto和decltype


STL指的是:容器库,迭代器库,算法库

容器库

容器分为四种

  • 序列式容器
  • 关联式容器
  • 无序关联式容器
  • 容器适配器
序列式容器

序列式容器的构造函数如下:

实例: 

vector<int> ivec1(10); // ivec1包含10个0值元素
vector<int> ivec2(10, 1); // ivec2包含10个值为1的元素
int ia[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
vector<int> ivec3(ia, ia + 10); // ivec3包含10个元素,值分别为0~9
vector<int> ivec4(ivec3); // ivec4包含值为0~9的元素(与ivec3相同)
vector<int> ivec5 = { 1, 2, 3, 4 }; // ivec5包含1~4的元素

 访问元素:

插入元素:

删除元素:

由于erase会返回被删除元素的下一个元素,所以使用erase遍历删除时,调用了erase迭代器就不用向后移位

 auto it = cats.begin();
    while (it!=cats.end()){
        if(it->name==name){
            it = cats.erase(it);//不用移位
        }else ++it;
    }

比较:==, !=, <, <=, >, >=

容器内相同位置的元素来比较 

容量操作:

赋值和交换:

除array是固定长度的数组外,其他的都是动态数量元素的容器

vector<T>是动态的连续数组,动态指长度可变,连续指可用T*访问

可以通过两种元素访问它

  • operator[] (不检查下标是否越界)
  • 函数(返回元素左值):front,at(int),back。其中at会检查下标越界,越界则抛出异常

list(双链表) 不支持下标访问

deque(双端队列)

关联式容器和无序关联式容器

map不会出现重复的键值,而multimap允许出现重复的键值(不支持下标操作),unordered_map不会根据键值自动排序

可以用std::pair<T1, T2> (v1, v2)进行构造,也可以用make_pair构造             

但是最好用的还是用operator[]来插入和修改元素

m["me"] = 100;

若没有找到"me",则会构造一个第一个值为"me",第二个值为"100",若找到了,则会将"me"的第二个值改为100 

通过p.first访问第一个元素(键值),通过p.second访问第二个元素

map中查找元素

删除元素:

multimap的注意事项:

不支持下标操作。insert操作每调用一次都会增加新的元素(multimap容器中,键相同的元素相邻存放)以键值为参数的erase操作删除该键所关联 的所有元素,并返回被删除元素的数目。coun操作返回指定键的出现次数。find操作返回的迭代器指向与被查找键相关联的第一个元素。结合使用countfind操作依次访问multimap容器中与特定键关联的所有元素。

set注意事项:

map 支持的操作 set 基本上都支持,但有区别。如下:
1 )不支持下标操作。
2 )没有定义 mapped_type 类型
3 set 容器定义的 value_type 类型不是 pair 类型,而是与 key_type 相同,指的都 set 中元素的类型
unordered_map会根据构造顺序和更新顺序来排序,类似于栈,最后更新或构造的排在前面
#include <unordered_map>
int main(){
    int total_number = 0;
    cin >> total_number;
    unordered_map<string, string> phone;
    while(total_number--){
        string id, word;
        cin >> id >> word;
        phone[id] = word;
    }
    for(auto i = phone.begin(); i != phone.end(); i++){
        cout << i->first << ": " << i->second << endl;
    }
}

输入为:

7
914670787 SeemsUGetAWrongDoor
418057982 MyNameIsVAN
633425541 NewAssignment
914670787 TakeItBoy
418057982 ImAnArtist
418057982 APerformanceArtist
633425541 FinishItInOneHour

输出为:

633425541: FinishItInOneHour
418057982: APerformanceArtist
914670787: TakeItBoy
容器适配器

标准库中定义的容器适配器都是基于顺序容器建立的,提供顺序容器之上的不同功能接口

stack适配器可以建立在vector、list、deque上

queue适配器只能建立在list、depue上

值得注意的是queue 不支持像 vectorlist 那样直接使用迭代器(iterator)进行遍历和访问。queue 是一个容器适配器(container adapter),它基于另一个标准库容器(默认是 std::deque)实现。因此,它不直接暴露迭代器来访问队列的元素。

所以对队列使用 begin()end() 是错误的做法,因为 std::queue 没有这些成员函数。正确的做法是使用 front() 来访问队列的第一个元素,使用 pop() 来移除队列的第一个元素。

priority_queue适配器只能建立在vector、deque上,优先队列可以根据值自动进行排序,值可以重复

如果不指定基础容器,stack和queue默认采用deque,priority_queue默认采用vector

栈的功能接口:

队列的功能接口

优先队列功能接口:

queue和priority_queue都在头文件<queue>中,stack在头文件<stack>中 

#include <queue>
#include <stack>

迭代器

迭代器是每种容器各自定义的一个或多个不同于容器的类,主要用于访问,修改,增加,删除容器中的元素 

迭代器(Iterator)是用于遍历容器元素的指针对象的包装,单向迭代器实现了以下运算符重载:operator ++, ==, !=, *等

容器会提供一个或多个迭代器实现

vector提供正向和反向迭代器

值得注意的是,end()指向最后一个元素的下一个元素,反向迭代器前面多了一个r,可以看着下标全部反过来然后进行操作 

关于迭代器的种类,一共有5种,每种功能,支持操作都不一样,甚至每个类型的容器都有自行定义的迭代器,如果全部都写下来太繁琐了。好在我们仅关注类别特征,所以我们可以巧妙的借助auto关键字,从而减少对迭代器类型的依赖

需要遍历的话我们可以这么写:

for(auto i = phone.begin(); i != phone.end(); i++){
    cout << *i << endl;
}

进行操作时只需把迭代器看做是指针就可以了 

算法库

lambda表达式

lambda表达式可以看作一个匿名函数,能够捕获作用域中的变量

语法为:

[捕获列表](参数列表)->返回类型{函数体}

[]里面的是捕获列表,用来捕获该匿名函数外的变量,与参数列表不同的是捕获到的列表在该匿名函数中可看做是静态变量,而参数列表就和一般函数的参数列表一样

合理运用lambda表达式可以极大的扩展我们的算法函数

sort

排序函数默认为升序排序

sort(vec.begin(), vec.end());

想要降序则要使用lambda表达式

sort(vec.begin(), vec.end(), [](int a, int b) { return a > b; });

 简单来说,sort函数允许接受一个判别函数,来进行是否交换顺序的判断,所以我们可以借助lambda表达式,里面的a、b就是两个相邻的元素

注意vec.end()不包括在排序范围内(左闭右开)

count_if

想要统计大于20的数的数量可以这么写

auto greater_then_20 = count_if(vec.begin(), vec.end(), [](int i) { return i > 20; });

将大于20的元素的数量赋给了变量greater_then_20

accumulate

该函数可以提供一个累次运算,当我们想要把每一项累加起来时,可以这么写:

#include <numeric>
int main() {
vector<int> v = { 1, 2, 3, 4 };
auto res = accumulate(v.begin(), v.end(), 0, [](decltype(v[0]) i, decltype(v[0]) j) { return i + j ; });
cout << res;
}

 每一项相乘:

int main() {
vector<int> v = { 1, 2, 3, 4 };
auto res = accumulate(v.begin(), v.end(), 1, [](decltype(v[0]) i, decltype(v[0]) j) { return i * j ; });
cout << res;
}

每一项平方相乘:

int main() {
vector<int> v = { 1, 2, 3, 4 };
auto res = accumulate(v.begin(), v.end(), 1, [](decltype(v[0]) i, decltype(v[0]) j) { return i + j * j ; });
cout << res;
}

对于accumulate函数来说,lambda表达式里面的i是一个累计的量,初始值由accumulate函数里面的常数给出(累加时初始值为0,累乘时初始值为1)而j将会遍历vector里的每个元素,在进行lambda表达式的操作后将值赋值给累计量

值得注意的是,在lambda表达式中i,j的类型我们并没有使用auto定义,而是使用了decltype(v[0])定义,表示i,j的类型都与v[0]一致。这样避免了auto推导潜在的问题,如果使用了auto,对于lambda表达式[](auto i, auto j){return i * j}编译器无法确定auto应该被推导为int(累计值类型)还是int(序列元素的类型),最后导致编译错误

知道类型,我们也可以直接写出类型出来而不用编译器猜

void sortRational(vector<rational>& vec){
    sort(vec.begin(), vec.end() , [](rational a, rational b){return a > b;});
}

注意这里如果要升序排序也要用lambda表达式,这是因为rational类是我们自己定义的,使用默认函数时会出现编译错误 

auto和decltype

auto从初始化器推到类型

decltype申明从表达式得到的类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值