C++ STL库的介绍和使用(上)

简介: C++ STL库的介绍和使用

C++ STL库的介绍和使用


STL(标准模板库),是惠普实验室开发的一系列软件的统称。现在主要出现在C++中,但是在引入C++之前该技术已经存在了很长时间了。STL从广义上分为:容器(container) 算法(algorithm) 迭代器(iterator),容器和算法之间通过迭代器进行无缝衔接。STL几乎所有的代码都采用了模板类或者是模板函数,这相比于创痛的由函数和类组成的库来说提供了更好的代码重用机会。STL标准模板库,在我们C++标准程序库中隶属于STL的占到了80%以上。


STL六大组件


  • 容器:各种数据结构 vector list deque set map等,存放数据
  • 算法:如sort find copy for_each等,操作数据
  • 迭代器:容器和算法的桥梁
  • 仿函数:为算法提供更多的策略
  • 适配器:为算法提供更多的参数接口
  • 空间配置器:管容器和算法的空间


STL六大组件的交互关系:容器通过空间配置器获取数据存储空间,算法通过迭代器获取存储器的内容,仿函数可以协助算法完成不同的策略变化,适配器可以修饰仿函数。


算法的分类


质变算法:运算过程中会更好区间内的数据,如拷贝、替换、删除等

非质变算法:运算过程中不会更改区间内容,如查找、计数、遍历等


迭代器


迭代器是一种抽象的概念,提供一种方法,使之能够依顺序访问某个容器所含的各个元素,而无需暴露该容器的内部表示方式。


迭代器的设计思维是STL的关键所在,STL中心思想是把容器和算法分开,彼此设计独立。


一个简单的例子

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void print(int it)
{
    cout << it << " ";
}
void test()
{
    vector<int> vv;
    vv.push_back(1);
    vv.push_back(2);
    vv.push_back(3);

    // 开始迭代器,指向开始位置
    vector<int>::iterator itBegin = vv.begin();
    // 结束迭代器,指向尾元素的下一个位置
    vector<int>::iterator itEnd = vv.end();
    for(auto it = itBegin; it!= itEnd; it++)
    {
        cout << *it << " ";
    }
    cout << endl;
    // 需要了解,可用vs查看对应的实现源码,可以看到整体实现思路是上面的for
    for_each(itBegin, itEnd, print);
    cout << endl;
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}


容器和自定义类型

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Person
{
public:
    Person(int a, int n): age(a), num(n) {}
    int age;
    int num;
};
void print(const Person &it)
{
    cout << it.age << " " << it.num << endl;
}

void test()
{
    vector<Person> vv;
    vv.push_back(Person(1,1));
    vv.push_back(Person(2,2));
    vv.push_back(Person(3,3));
    for_each(vv.begin(), vv.end(), print);
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}


容器嵌套容器

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void print2(int a)
{
    cout << a << " " ;
}
void print(const vector<int> &a)
{
    for_each(a.begin(), a.end(), print2);
}
void test()
{
    vector<int> a;
    a.push_back(1);
    a.push_back(2);
    a.push_back(3);
    vector<int> b;
    b.push_back(4);
    b.push_back(5);
    b.push_back(6);
    vector<int> c;
    c.push_back(7);
    c.push_back(8);
    c.push_back(9);
    vector<vector<int>> aa;
    aa.push_back(a);
    aa.push_back(b);
    aa.push_back(c);
    for_each(aa.begin(), aa.end(), print);
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}


常用容器


string


C风格的字符串太过复杂,难以掌握,不太适合大程序的开发,所以C++标准库定义了一种string类,定义在头文件中。


string和C风格字符串对比:


  • char是一个指针,string是一个类,string封装了char,管理了这个字符串,是一个char类型的容器。
  • string封装了很多实用的成员方法,查找find、拷贝copy、删除delete、替换replace、插入insert
  • 不用考虑内存的释放和越界,string管理了char*所分配的内存,每一次string的赋值,取值都由string类负责维护,不用担心复制越界和取值越界等算法


下面的例子列举了一些简单使用:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 字符串赋值
void test()
{
    string str("5152");
    string str3;
    str3 = str;
    cout << str3 << endl;
    string str2;
    str2 = "hello world";
    cout << str2 << endl;
    string str1;
    str1 = 'H';
    cout << str1 << endl;
    string str4;
    str4.assign(str);
    cout << str4 << endl;
}
// 字符串存取
void test02()
{
    string str = "hello world";
    cout << str[1] << endl;
    cout << str.at(1) << endl;
    str[1] = 'E';
    str[7] = 'O';
    cout << str << endl;
    try{
//        str[100] = 'H';
        str.at(100) = 'H';
    }
    catch (exception &e)
    {
        cout << e.what() << endl;
    }
}
// 字符串拼接
void test03()
{
    string str1 = "hello ";
    string str2 = "world";
    str1+=str2;
    cout << str1 << endl;
    string str3 = "hello ";
    str3+="world";
    cout << str3 << endl;
    str3.append(" mmmm");
    cout << str3 << endl;
    str3.append(" mmmm", 3);
    cout << str3 << endl;
    str3.append(str2, 2, 3);
    cout << str3 << endl;
}
// 字符串查找和替换
void test04()
{
    string str1 = "hello world";
    int a = str1.find('e');
    cout << a << endl;
    a = str1.find('e', 3);
    cout << a << endl;
    a = str1.find("worl");
    cout << a << endl;
    // 表示从0开始替换5个字符,替换成后面的
    str1.replace(0, 5, "mmmmm");
    cout << str1 << endl;
}
// 字符串比较和子串的提取
void test05()
{
    string str1 = "hello world";
    string str2 = "hello";
    int a = str1.compare(str2);
    cout << a << endl;
    a = str1.compare("oooo");
    cout << a << endl;
    a = str1.compare("hello world");
    cout << a << endl;

    string str = str1.substr(0, 8);
    cout << str << endl;
    string str11 = "asd:asdas:asdasdasd:dadssad:dasdsa";
    int pos = 0;
    while(1)
    {
        int ret = str11.find(":", pos);
        if(ret < 0)
        {
            string tmp = str11.substr(pos, str11.size() - pos);
            cout << tmp << endl;
            break;
        }
        string tmp = str11.substr(pos, ret - pos);
        cout << tmp << endl;
        pos = ret + 1;
    }
}
// 字符串的插入和删除
void test06()
{
    string str = "hello world";
    str.insert(5, " hehe");
    cout << str << endl;
    str.erase(5, 5);
    cout << str << endl;
    str.clear();
    cout << str << endl;
}
// 字符串和C风格字符串转换
void test07()
{
    char *aa = "hello world";
    string str = aa;
    const char *cc = str.c_str();
    cout << str << endl;
    cout << cc << endl;
}
int main(int argc, char* argv[])
{
    test07();
    return 0;
}


vector


vector的数据安排以及操作方式与数组相似,两者的唯一差别在于运用的灵活性。数组是静态空间,一旦配置了就不能改变,要更换大一点或者小一点的空间,可以,但是需要自己去做空间分配和数据拷贝,然后释放原先的空间。vector是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。因此vector的运用对于内存的合理利用与利用的灵活性有很大的帮助,我们不必担心空间不足而开辟一块很大的array。


vector的迭代器:随机访问迭代器


随机访问迭代器:迭代器+n可以通过编译器编译,就是随机访问迭代器。


vector的容量(capacity)和大小(size)是有区别的。


  • capacity:是空间能容纳元素的最大个数
  • size:是空间中实际存放的个数
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void print(const vector<int> &aa)
{
    for(auto it = aa.begin(); it != aa.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
    fflush(stdout);
}
void test()
{
    vector<int> v;
    for(int i = 0; i < 100; ++i)
    {
        v.push_back(i);
    }
    cout << v.size() << endl;
    cout << v.capacity() << endl;
}
// 另寻空间的次数
void test02()
{
    int count = 0;
    vector<int> v;
    int *p = nullptr;
    for(int i = 0; i < 1000; ++i)
    {
        v.push_back(i);
        if(p!= &v[0])
        {
            count++;
            p = &v[0];
        }
    }
    cout << count << endl;
}
// vector的构造、赋值和交换
void test03()
{
    vector<int> v(10, 5);
    print(v);
    vector<int> v1(v.begin() + 2, v.end() -2);
    print(v1);
    vector<int> v2(v);
    print(v2);
    vector<int> v3;
    v3.assign(v.begin() + 1, v.end() - 1);
    print(v3);
    vector<int> v4;
    v4 = v3;
    print(v4);
    vector<int> v5;
    v5.assign(5, 10);
    print(v5);
    vector<int> v6(6, 20);
    print(v6);
    v5.swap(v6);
    print(v5);
    print(v6);
}
// vector大小操作
void test04()
{
    vector<int> v(10, 5);
    if(v.empty())
    {
        cout << "v is empty" << endl;
    }
    else
    {
        cout << v.capacity() << endl;
        cout << v.size() << endl;
    }
    print(v);
    v.resize(16);
    print(v);
    v.resize(1024, 5);
    print(v);
    v.resize(2);
    print(v);
    cout << v.capacity() << endl;
    cout << v.size() << endl;
    // 使用swap收缩容量空间,通过交换函数重新申请大小
    vector<int>(v).swap(v);
    cout << v.capacity() << endl;
    cout << v.size() << endl;
    vector<int> vv(10, 5);
    cout << "vv:" << vv.capacity() << endl;
    cout << "vv:" << vv.size() << endl;
    // 空间预留
    vv.reserve(50);
    cout << "vv:" << vv.capacity() << endl;
    cout << "vv:" << vv.size() << endl;
}
// vector的数据操作
void test05()
{
    vector<int> v;
    for(int i = 0; i < 6; i++)
    {
        v.push_back(i);
    }
    cout << v[5] << endl;
    cout << v.at(5) << endl;
    cout << v.front() << endl;
    cout << v.back() << endl;
    v.insert(v.begin() + 3, 2, 100);
    print(v);
    v.pop_back();
    print(v);
    v.erase(v.begin()+1,v.begin()+3);
    print(v);
    v.erase(v.begin());
    print(v);
    v.clear();
    print(v);
}
int main(int argc, char* argv[])
{
    test05();
    return 0;
}

注意: resize只能修改size,不能修改容量。


deque


vector容器时一段连续的内存空间,dequeue则是一种双向开口的联系线性空间。所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作。vector也可以在头尾进行插入操作 ,但是vector的头部插入操作效率奇差,不可以被接受。


deque容器是由一段一段的定量连续空间构成的,一旦有必要在deque前端或者是尾端增加新的空间,便配置一段连续定量的空间,串接在deque的头端或者是尾端。deque最大的工作就是维护这些分段连续的空间的整体性的假象,并提供随机访问接口,避免了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。

#include <iostream>
#include <algorithm>
#include <deque>
using namespace std;
void print(const deque<int> &aa)
{
    for(auto it = aa.begin(); it != aa.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
    fflush(stdout);
}
// 初始化和赋值
void test()
{
    deque<int> v(5, 10);
    print(v);
    deque<int> v1;
    v1.assign(v.begin()+1, v.end()-1);
    print(v1);
    deque<int> v2 = v;
    print(v2);
    v2.swap(v1);
    print(v1);
    print(v2);
}
// 插入和存取
void test02()
{
    deque<int> v(5, 10);
    v.resize(10,3);
    print(v);
    v.resize(3);
    print(v);
    v.push_front(6);
    print(v);
    v.push_back(6);
    print(v);
    v.pop_front();
    print(v);
    v.pop_back();
    print(v);
    cout << v[1] << endl;
    cout << v.at(1) << endl;
    cout << v.size() << endl;
    cout << v.empty() << endl;
    v.clear();
    cout << v.empty() << endl;
}
int main(int argc, char* argv[])
{
    test02();
    return 0;
}

stack


stack是一种先进后出的数据结构,它只有一个出口。stack允许新增元素,移除元素,取得栈顶元素,但是除了顶端外,没有任何办法 存取stack元素。换言之,stack不允许有遍历行为。有元素推入栈的操作称为push, 将元素推出栈的操作称为pop。


stack没有迭代器。

#include <iostream>
#include <algorithm>
#include <stack>
using namespace std;
void test()
{
    stack<int> ss;
    ss.push(1);
    ss.push(2);
    ss.push(3);
    ss.push(4);
    ss.push(5);
    cout << ss.top() << endl;
    ss.pop();
    cout << ss.top() << endl;
    ss.pop();
    cout << ss.top() << endl;
    ss.pop();
    cout << ss.top() << endl;
    ss.pop();
    cout << ss.top() << endl;
    ss.pop();
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}

queue


队列是一种先进先出的数据结构,他有一个出口一个入口,queue容器允许从一端新增元素,从另外一端移除元素。


队列没有迭代器。

#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
void test()
{
    queue<int> qq;
    qq.push(1);
    qq.push(2);
    qq.push(3);
    qq.push(4);
    qq.push(5);
    cout << qq.size() << endl;
    cout << qq.empty() << endl;
    cout << qq.front() << endl;
    qq.pop();
    cout << qq.front() << endl;
    qq.pop();
    cout << qq.front() << endl;
    qq.pop();
    cout << qq.front() << endl;
    qq.pop();
    cout << qq.front() << endl;
    qq.pop();
    cout << qq.empty() << endl;
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}


list


链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序实现的。链表由一系列节点组成,节点在运行时动态生成。每个节点包括两个部分:一个是存储数据元素的数据域,一个是存储下一个节点地址的指针域。相较于vector的连续线性空间,list就显得负责许多,它的每次插入或者是删除一个元素,就是配置或者释放一个元素的空间。因此,list对于空间的运用具有绝对的精准,一点也不浪费,而且,对于任何位置的元素插入或者是删除,list永远是常数时间。list和vector是两个最长被使用的容器。list是一个双向链表。


链表采用的是动态存储分配,不会造成内存浪费和溢出,链表执行插入和 删除操作十分方便,修改指针即可,不需要移动大量的元素。俩表灵活但是空间和时间额外耗费比较大。


list的迭代器是 双向访问迭代器

#include <iostream>
#include <algorithm>
#include <list>
#include <vector>
using namespace std;
void print(const list<int> &ll)
{
    for(auto & a : ll)
    {
        cout << a << " ";
    }
    cout << endl;
    fflush(stdout);
}
class Person
{
public:
    Person(int a) : age(a){}
    bool operator<(const Person &right)
    {
        return this->age < right.age;
    }
    int age;
};
class Compare
{
public:
    bool operator()(const Person &left, const Person &right)
    {
        return left.age < right.age;
    }
};
bool operator==(const Person &left, const Person &right)
{
    return left.age == right.age;
}
bool compare(const Person &left, const Person &right)
{
    return left.age > right.age;
}
void print(const list<Person> &ll)
{
    for(auto & a : ll)
    {
        cout << a.age << " ";
    }
    cout << endl;
    fflush(stdout);
}
void print(const vector<Person> &ll)
{
    for(auto & a : ll)
    {
        cout << a.age << " ";
    }
    cout << endl;
    fflush(stdout);
}
void test()
{
    list<int> ll;
    ll.push_back(10);
    ll.push_back(20);
    ll.push_back(30);
    ll.push_back(40);
    print(ll);
    auto it = find(ll.begin(), ll.end(), 20);
    ll.insert(++it, 3 ,100);
    print(ll);
    ll.reverse();
    print(ll);
    ll.sort();
    print(ll);
}
void test01()
{
    list<Person> ll;
    ll.push_back(10);
    ll.push_back(20);
    ll.push_back(30);
    ll.push_back(40);
    print(ll);
    ll.remove(Person(20));
    print(ll);
    ll.sort();
    print(ll);
    ll.sort(compare);
    print(ll);
}
void test02()
{
    vector<Person> ll;
    ll.push_back(20);
    ll.push_back(10);
    ll.push_back(30);
    ll.push_back(40);
    sort(ll.begin(), ll.end(), compare);
    print(ll);
    sort(ll.begin(), ll.end(), Compare());
    print(ll);
    sort(ll.begin(), ll.end(), [&](const Person &left, const Person &right) -> bool {
        return left.age > right.age;
    });
    print(ll);
}
int main(int argc, char* argv[])
{
    test02();
    return 0;
}

注意: list删除自定义数据,必须重载==运算符,否则无法匹配是否相等。


C++ STL库的介绍和使用(下):https://developer.aliyun.com/article/1459455

目录
相关文章
|
3月前
|
算法 C++ 容器
C++标准库(速查)总结
C++标准库(速查)总结
84 6
|
4天前
|
编译器 C语言 C++
【c++丨STL】list模拟实现(附源码)
本文介绍了如何模拟实现C++中的`list`容器。`list`底层采用双向带头循环链表结构,相较于`vector`和`string`更为复杂。文章首先回顾了`list`的基本结构和常用接口,然后详细讲解了节点、迭代器及容器的实现过程。 最终,通过这些步骤,我们成功模拟实现了`list`容器的功能。文章最后提供了完整的代码实现,并简要总结了实现过程中的关键点。 如果你对双向链表或`list`的底层实现感兴趣,建议先掌握相关基础知识后再阅读本文,以便更好地理解内容。
14 1
|
17天前
|
算法 C语言 C++
【c++丨STL】list的使用
本文介绍了STL容器`list`的使用方法及其主要功能。`list`是一种双向链表结构,适用于频繁的插入和删除操作。文章详细讲解了`list`的构造函数、析构函数、赋值重载、迭代器、容量接口、元素访问接口、增删查改操作以及一些特有的操作接口如`splice`、`remove_if`、`unique`、`merge`、`sort`和`reverse`。通过示例代码,读者可以更好地理解如何使用这些接口。最后,作者总结了`list`的特点和适用场景,并预告了后续关于`list`模拟实现的文章。
33 7
|
2月前
|
存储 编译器 C语言
【c++丨STL】vector的使用
本文介绍了C++ STL中的`vector`容器,包括其基本概念、主要接口及其使用方法。`vector`是一种动态数组,能够根据需要自动调整大小,提供了丰富的操作接口,如增删查改等。文章详细解释了`vector`的构造函数、赋值运算符、容量接口、迭代器接口、元素访问接口以及一些常用的增删操作函数。最后,还展示了如何使用`vector`创建字符串数组,体现了`vector`在实际编程中的灵活性和实用性。
64 4
|
2月前
|
C语言 C++ 容器
【c++丨STL】string模拟实现(附源码)
本文详细介绍了如何模拟实现C++ STL中的`string`类,包括其构造函数、拷贝构造、赋值重载、析构函数等基本功能,以及字符串的插入、删除、查找、比较等操作。文章还展示了如何实现输入输出流操作符,使自定义的`string`类能够方便地与`cin`和`cout`配合使用。通过这些实现,读者不仅能加深对`string`类的理解,还能提升对C++编程技巧的掌握。
77 5
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
57 2
|
2月前
|
存储 算法 Linux
【c++】STL简介
本文介绍了C++标准模板库(STL)的基本概念、组成部分及学习方法,强调了STL在提高编程效率和代码复用性方面的重要性。文章详细解析了STL的六大组件:容器、算法、迭代器、仿函数、配接器和空间配置器,并提出了学习STL的三个层次,旨在帮助读者深入理解和掌握STL。
55 0
|
20天前
|
存储 编译器 C语言
【c++丨STL】vector模拟实现
本文深入探讨了 `vector` 的底层实现原理,并尝试模拟实现其结构及常用接口。首先介绍了 `vector` 的底层是动态顺序表,使用三个迭代器(指针)来维护数组,分别为 `start`、`finish` 和 `end_of_storage`。接着详细讲解了如何实现 `vector` 的各种构造函数、析构函数、容量接口、迭代器接口、插入和删除操作等。最后提供了完整的模拟实现代码,帮助读者更好地理解和掌握 `vector` 的实现细节。
30 0
|
3月前
|
存储 程序员 C++
C++常用基础知识—STL库(2)
C++常用基础知识—STL库(2)
89 5
|
3月前
|
存储 自然语言处理 程序员
C++常用基础知识—STL库(1)
C++常用基础知识—STL库(1)
79 1