C++中的STL

C++

3. STL

3.1 STL介绍

概念:STL(Standard Template Library, 标准模板库),是C++标准库的一部分,提供了一些通用的模板类和模板函数,用于实现各种数据结构,如链表、向量、栈、队列、集合、映射等。STL提供了丰富的数据结构和算法,可以减少开发人员编写代码的时间和空间开销,提高开发效率。
STL从广义上分为:容器(Container)、迭代器(Iterator)、算法(Algorithm),容器和算法之间通过迭代器进行无缝连接。STL几乎所有的代码都采用了模板类或者模板函数,这相比传统的由函数和类组成的库来说提供了更好的代码复用机会。
STL六大组件

  1. 容器:各种数据结构。STL提供了多种容器,如vector、list、deque、set、map、stack、queue等,这些容器都实现了一些基本的数据结构操作,如插入、删除、查找、排序等。
  2. 算法:各种常用的算法,如sort、find、copy、for_each、transform、remove、replace等。
  3. 迭代器:用于遍历容器的指针,扮演了容器和算法之间的胶合作用。共有五种类型,从实现角度看,迭代器是一种将operator*、operator->、operator++、operator–等指针相关操作予以重载的class template,所有STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native opinter)也是一种迭代器。
  4. 仿函数:行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了operator()的class或者class template。
  5. 适配器:一种用于修饰容器或者仿函数或者迭代器接口的东西。
  6. 空间配置器:负责空间的配置与管理。从实现角度看,容器是一个实现了动态空间配置、空间管理、空间释放的class template。

优点
STL是C++标准库的一部分,不需要额外安装。
STL的一个重要特性是将数据和操作分离。数据由容器类别加以管理,操作则由可定制的算法定义。迭代器在两者之间进行无缝连接,以使算法可以和容器交互运作。程序员不需要思考STL具体地实现过程,只要熟练使用即可。

高可重用性:STL中几乎所有的代码都采用了模板类和模板函数的方式实现。
高性能:如map可以高效地从十万条记录里面查找出指定的记录,因为map是采用红黑树的变体实现的。
高移植性:如在项目A上用STL编写的模块,可以直接移植到项目B上。

3.2 容器、算法、迭代器介绍

3.2.1 容器

STL容器就是将运用最广泛的一些数据结构实现出来。
常用的数据结构:数组(array)、链表(list)、树(tree)、栈(stack)、队列(queue)、集合(set)、映射表(map),根据数据在容器中的排列特性,可以分为序列式容器和关联式容器。

序列式容器强调值的排序,序列式容器中的每个元素都有固定的位置,除非用删除或者插入的操作改变这个位置。Vector容器、Deque容器、List容器等。
关联式容器是非线性的树结构,更准确地说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。在值中选择一个值作为关键字key,这个关键字对值起到索引的作用,方便查找。Set/multiset容器、Map/multimap容器。

容器可以嵌套容器。

3.2.2 算法

STL收录的算法经过了数学上的效能分析与证明,是极具复用价值的,包括常用的排序、查找等。特定的算法往往搭配特定的数据结构,算法与数据结构相辅相成。
算法分为:质变算法非质变算法

质变算法:算法操作的元素会改变,例如sort、reverse、rotate、unique、replace、remove等。
非质变算法:算法操作的元素不会改变,例如find、count、copy、for_each、transform、remove_copy等。

3.2.3 迭代器

迭代器:提供一种方法,使之能够依序寻访某个容器所含的元素,而又无需关心这些元素的底层存储方式。
迭代器种类:

类型描述支持的操作
输入迭代器提供对数据的只读访问只读,支持++、==、!=
输出迭代器提供对数据的只写访问只写,支持++
前向迭代器提供读写操作,并能向前推进迭代器读写,支持++、==、!=
双向迭代器提供读写操作,并能向前和向后操作读写,支持++、–
随机访问迭代器提供读写操作,并能任意移动迭代器读写,支持++、–、[n]、-n、<、<=、>、>=

3.3 STL中的容器

3.3.1 string容器

C风格字符串(以空格符结尾的字符数组)太过复杂,不适合大程序开发,所以C++标准库定义了一种string类。
C++的字符串与C语言的字符串比较:

  • C语言:char*是一个指针。
  • C++:
    • string是一个类,内部封装了char*,用来管理这个容器。
    • string类中封装了很多的功能函数,例如:find、copy、delete、replace、insert等。
    • 不用考虑内存释放和越界的问题。

string管理char*所分配的内存。每一个string的复制,取值都由string类负责维护,不用担心复制越界和取值越界等。

3.3.1.1 string容器的构造
// string构造
void test1(){
    // 无参构造,创建一个空字符串
    string str1; // 等于string str = string();

    //通过一个string去构造另一个string
    string str2 = string("hello world");

    //通过一个char(字符)数组去构造一个string
    const char* array = "hello world";
    string str3 = string(array);

    // 通过指定数量的指定字符,构造一个字符串
    string str4 = string(5,'A'); // 内容是:AAAAA
};
3.3.1.2 string的赋值

// string赋值

void test2(){
    string str1;
    // 通过等号赋值,等号已经被string进行了运算符重载
    // 通过字符串赋值
    str1 = "hello world";

    // 通过字符数组赋值
    const char* arr = "abc";
    str1 = arr;

    // 通过字符赋值
    str1 = 'a';

    // assign函数。都是&引用,即可以链式操作。
    str1.assign("hello world");
    str1.assign(arr);
    str1.assign(5,'A');
    // string& assign(const char* s, int n); 把字符串s的前n个字符赋值给string
    str1.assign("hello world",5);
    cout << str1 << endl;
    // string& assign(const char* s, int start, int n); 将s从start开始n个字符赋值给字符串
    str1.assign("hello world",3,4); // llo w

}
3.3.1.3 string存取字符操作
// string的存取字符操作
void test3(){
    // 通过下标访问
    // char& operator[](int n); 通过[]访问
    // char& at(int n); 通过at访问
    string str1 = "hello world";
    cout << str1[2] << endl;
    cout << str1.at(4) << endl;
    str1[2] = 'a';
    str1.at(4) = 'a';
    cout << str1 << endl;

    // 使用了&引用(取别名)
    // 使用字符引用返回值,存储字符串中指定下标位字符的引用。
    char& c = str1[4];
    // 修改引用的值,这里引用的是字符串中的字符数组中的指定下标位的元素,所以c改变,数组中也会对应改变。
    c = '!';
    cout << str1 << endl;

    // 注意:
    //      一旦字符串中的字符数组内存重新分配了,使用之前的引用再进行空间访问,可能会出现问题。
    // 重写分配的情况:c++中,字符串长度小于16位时,空间开辟在栈上,超过16位,重新分配内存。
    // c_str: 将C++风格的字符串转换成C风格的字符串(返回的是C++的string类中维护的那个字符数组的指针)
    cout << (int*)str1.c_str() << endl; // 原来地址
    str1 = "12345678901234"; // 重新赋值,长度未超过16位,不会重新分配内存,地址不发生改变
    cout << c << endl; // 输出引用
    str1 = "12345678901234567"; // 重新赋值,长度超过16位,会重新分配内存,地址发生改变
    cout << (int*)str1.c_str() << endl; // 新地址
//    cout << c << endl; //地址改变,引用的c地址已经被释放了
}
3.3.1.4 string拼接
// string拼接
void test4(){
    string str = "hello";
    // +
    string str1 = str + "world";
    // += 后面跟需要加上的字符或字符串或者字符数组
    str += "world";
    // append函数
    // string& append(const char* s);
    str.append("world");
    // string& append(const char* s, int n); 把字符串前n个字符拼接到当前字符串结尾
    str.append("world",5);
    // string& append(const string& str); 把字符串拼接到当前字符串结尾,同 +=
    // string& append(const string& str, int start, int n); 把字符串从start开始n个字符拼接到当前字符串结尾
    str.append("world",3,4);
    // string& append(int n, char c); 把n个字符c拼接到当前字符串结尾
    str.append(5,'A');
    cout << str << endl;

}
3.3.1.5 string查找和替换
// string查找和替换
void test5(){
    string str = "hello world! hello1 world1!";
    // 查找 find:第一次出现的位置,返回首字符的位置下标
    // int find(const string& str, int pos); 从pos位置开始查找字符串str第一次出现的位置,pos默认是0
    // int find(const char* s, int pos); 从pos位置开始查找字符串s第一次出现的位置
    // int find(const char* s, int pos, int n); 从pos位置开始查找字符串s前n个字符第一次出现的位置
    // int find(char c, int pos); 从pos位置开始查找字符c第一次出现的位置
    cout << str.find("world") << endl;
    cout << str.find("world1", 10) << endl;
    cout << str.find("world1", 5, 5) << endl;
    cout << str.find('1') << endl;
    // 如果找不到,返回-1
    cout << str.find("hello2") << endl; //这个却并不是-1
    int res = str.find("hello2"); // 现在是-1
    cout << res << endl;

    // 查找rfind :最后一次出现的位置,从后往前找
    // int rfind(const string& str, int pos) const; // 查找字符串str最后一次出现的位置,pos默认最大值
    // int rfind(const char* s, int pos, int n) const; // 查找字符串s的前n个字符最后一次出现的位置
    // int rfind(const char c, int pos) const; // 查找字符c最后一次出现的位置
    cout << str.rfind("world") << endl;
    cout << str.rfind("world1", 10) << endl; // 从后往前找,从pos往前找,即从10->0的位置找

    // 替换
    // string& replace(int start, int n, const string& str); 从start位置开始,删除n个字符,并用字符串str替换
    // string& replace(int start, int n, const char* s); 从start位置开始,删除n个字符,并用字符串s替换
    cout << str.replace(0,5,"no") << endl; // 返回的是这个string对象本身,即str也会发生改变
    cout << str << endl;
}

3.3.1.6 string的比较
// string比较
void test6(){
    // 字符串大小比较规则:比的是字典顺序(更加深入来说,就是字符在字符集中的映射的数字)
    // 依次比较字符串中的每一位字符,如果字符相同,继续比较后面的一位字符,直到某一位的比较可以确定大小关系,返回大小关系
    // 仍然可以使用> < >= <= == !=来比较,但是有一定局限性。
    //      局限性:比较结果是bool类型,无法充足的表示每一种比较结果(大于,小于,等于)。
    //      因此string提供了compare函数,返回一个int类型:
    //      0:相等;
    //      1:str1大于str2;
    //      -1:str1小于str2
    string str1 = "abe";
    string str2 = "abc";
    cout << str1.compare(str2) << endl;
}
3.3.1.7 string的子串获取
// string子串的获取
void test7(){
    // pos:起始下标,从pos开始获取子串,默认0,不要越界,越界报错。
    // n:长度,获取多少个字符,可以越界,将剩余的字符串全部获取。
    // string substr(int pos, int n) const; // 返回从pos开始的n个字符组成的新字符串
    string str = "hello world";
    cout << str.substr(0,5) << endl;
    cout << str.substr(6) << endl;
    cout << str.substr(6,20) << endl;
    cout << str << endl; // 不会对原字符串产生影响
}
3.3.1.8 string的插入和删除
// string的插入和删除
void test8(){
    // string& insert(int pos, const char* s); // 在pos位置插入字符串s
    // string& insert(int pos, const string& str); // 在pos位置插入字符串str
    // string& insert(int pos, int n, char c); // 在pos位置插入n个字符c
    // string& erase(int pos, int n = npos); // 删除从pos开始的n个字符,默认所有npos
    string str = "hello world";
    str.insert(0,"123");
    cout << str << endl;
    str.insert(1,"123",2); // 在第1个位置开始,插入字符串的前两个字符
    cout << str << endl;
    str.insert(1,"123456",3,2); // 在第1个位置,插入字符串的从第3个字符开始的2个字符
    cout << str << endl;

    // 删除
    str.erase(0,5);
    cout << str << endl;
}

3.3.2 vector容器

array是静态空间,一旦配置了就不可以更改,要更改则需要配置新空间,然后进行赋值。而vector容器是一个动态数组,可以动态的增加和删除元素,内部机制会自动扩充空间以容纳新元素。使用时需要引入#include "vector"

3.3.2.1 vector容器的遍历
// vector容器的遍历
void test1() {
    // 1. 构造一个vector对象,通过vector的无参构造,构造一个空的vector容器
    vector<int> v;
    // 2. 添加元素
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    // 3. 遍历vector容器,使用迭代器
    // 迭代器:使用普通指针,依次指向vector中的每一个元素
    // begin():获取到的是vector容器中的首元素的地址
    // end():获取到的是vector容器中的最后一位元素的下一位指针
//    vector<int>::iterator it = v.begin();
//    cout << "vector容器的遍历:" << *it << endl;
//    it++; // 指针后移,注意:越界输出会报错
//    cout << "vector容器的遍历:" << *it << endl;
    for (vector<int>::iterator it = v.begin(); it != v.end(); it++){
        cout << "vector容器的遍历:" << *it << endl;
    };

    for (vector<int>::iterator it = v.begin(); it != v.end(); it++){
        if (*it == 20){
            *it = 200;
        };
        cout << "vector遍历时修改值:" << *it << endl;
    };

    // 迭代器遍历时缩写
    // 依次将v容器中的每一个元素,给ele进行赋值
    // 注意:是赋值,所以改变ele的值,不会影响v容器中的元素,如果要修改v容器中的元素,要引用&
    for (int ele:v){
        cout << ele << endl;
    }
    // 遍历、缩写、引用、修改值
    for (int &ele:v){
        if(ele == 10){
            ele = 100; // 这样修改的值才会影响v容器中的元素
        }
    }

    // 倒序遍历
    for (vector<int>::iterator it = v.end();it != v.begin();){
        it--;
        cout << "vector容器的遍历倒序:" << *it << endl;
    }
}
3.3.2.2 vector容器的构造函数
// vector容器的构造函数
void test2(){
    // 1. 通过vector的无参构造,构造一个空的vector容器
    vector<int> v1;
    // 2. vector(n, ele) 使用n个ele填充容器
    vector<int> v2(5, 100);
    printVector(v2);
    // 3. vector(const vector& v), 拷贝构造函数

    // 4. vector(v.begin(), v.end()) 左闭右开
    vector<int> v3(v2.begin(), v2.end());  // 将v2容器中从begin->end中的元素拷贝到v3容器中
    printVector(v3);// 注意:end()指向的是最后一个元素的下一个位置,元素从0开始
    vector<int> v4(v2.begin(), v2.begin()+4); // 将v2容器中前4个元素拷贝到v4容器中
    printVector(v4);

    int array[] = {1, 2, 3, 4, 5};
    vector<int> v5(array, array+3);
    // array是0位置的地址,array+3是3位置的地址,由于end()指向的是最后一个元素的下一个元素,所以v5容器中实际有的是0 1 2这三个位置的元素
    // 也可以说成是将array数组的前3个元素拷贝到v5容器中,上面的同理
    printVector(v5);

    // 注意:指针加1,指的是指针向后偏移一个位置,不是单纯的地址的值加1,减同理。
}
3.3.2.3 vector容器的赋值操作
// vector的常用赋值操作
void test3(){
    // assign(beg, end); 将[beg, end)区间中的数据拷贝赋值给本身
    // assign(n, ele); 将n个ele拷贝赋值给本身
    // vector& operator = (const vector & vec); // 重载的等号赋值运算符
    // swap(vec); 交换两个vector容器的元素
    int array[] = {1, 2, 3, 4, 5};
    // vector构建
    vector<int> v1;
    v1.assign(array, array+3); // array是数组的首元素的地址,array+3是数组的第4个元素的地址,左闭右开
    printVector(v1);

    vector<int> v2;
    v2.assign(3,10);
    printVector(v2);

    vector<int> v3;
    v3 = v2; // 使用重载后的等号运算符=
    printVector(v3);

    cout << "交换前:" << endl;
    printVector(v1);
    printVector(v2);
    v1.swap(v2);
    cout << "交换后:" << endl;
    printVector(v1);
    printVector(v2);
}
3.3.2.4 vector的大小操作
//vector 的大小操作
void test4(){
vector<int> v(10,5);
// 返回容器中元素个数
cout << "容器中元素个数:" << v.size() << endl;
// 判断容器是否为空
cout << "容器是否为空:" << v.empty() << endl;
// 返回容器容量
cout << "容器容量:" << v.capacity() << endl;

// 重新指定长度,不是指定容器的容量,而是容器中元素的个数,如果容器长度不够,会进行扩容
v.resize(20); // 如果新长度大于原来长度,则用默认值填充,也可以指定填充值,如v.resize(20, 100);
cout << "resize,新的长度>原来长度" << endl;
cout << "容器中元素个数:" << v.size() << endl;
cout << "容器容量:" << v.capacity() << endl;
// 如果新长度小于原来长度,则多余的元素被删除;
v.resize(4); // 如果新长度大于原来长度,则用默认值填充,也可以指定填充值,如v.resize(20, 100);
cout << "resize,新的长度<原来长度" << endl;
cout << "容器中元素个数:" << v.size() << endl;
cout << "容器容量:" << v.capacity() << endl;

// 运行发现,容器中元素是4个,容量是20,怎么让它一致呢?
// 创建一个新的空容器,将v容器中的元素拷贝到新容器中,然后交换两个容器,v指向新容器,匿名函数指向旧容器,运行完会自动回收空间
// 拷贝构造新容器时,新容器的容量是跟元素数量一致的,所以容量=元素个数
vector<int>(v).swap(v); // swap收缩空间
cout << "容器中元素个数:" << v.size() << endl;
cout << "容器容量:" << v.capacity() << endl;
}
3.3.2.5 vector的数据存取
// vector的存取
void test5(){
    // at(int idx); // 返回索引idx所指的数据,如果idx大于容器中实际元素个数,则抛出异常
    // operator[]; 返回索引idx所指的数据,如果idx大于容器中实际元素个数,则抛出异常
    // front(); 返回容器中第一个数据元素,即0号位元素
    // back();返回容器中最后一个数据元素

    int array[] = {1, 2, 3, 4, 5,6,7,8,9,10};
    vector<int> v(array,array+sizeof(array)/sizeof(array[0]));

    int& ele = v.at(2); // 引用
    cout << v.at(2) << endl;
    ele = 100; // 修改会对v容器中的元素进行修改
    cout << v.at(2) << endl;
    printVector(v);

    int& ele2 = v[2];
    cout << v[2] << endl;
    ele2 = 200;
    printVector(v);

    cout << v.front() << endl;
    cout << v.back() << endl;
}
3.3.2.6 vector的插入和删除
// vector的插入和删除
void test6(){
    int array[] = {1, 2, 3, 4, 5,6,7,8,9,10};
    vector<int> v(array,array+sizeof(array)/sizeof(array[0]));
    // insert(const_iterator pos, int count, ele); // 迭代器指向位置pos插入count个元素ele
    // push_back(ele); // 在容器尾部插入元素ele
    // pop_back(); // 删除容器中最后一个元素
    // erase(const_iterator start, const_iterator end); // 删除迭代器[start, end)区间的元素
    // erase(const_iterator pos); // 删除迭代器指向的元素
    // clear(); // 删除容器中所有元素
    v.insert(v.begin()+3,2, 100); // 在容器的3位置处插入2个100,变成了1, 2, 3, 100, 100, 4, 5……
    printVector(v);

    v.push_back(100); // 在容器尾部插入元素100
    printVector(v);

    v.pop_back(); // 删除容器中最后一个元素
    printVector(v);

    v.erase(v.begin()+3, v.begin()+5); // 删除容器中3和4位置的元素,不包括5,先闭后开
    printVector(v);

    v.erase(v.begin()+3); // 删除容器中3位置的元素
    printVector(v);

    v.clear(); // 删除容器中所有元素
//    printVector();

}

3.3.3 deque容器

depeque容器是一种双端队列,允许在容器的开头和结尾进行插入和删除操作。操作与vector容器类似,不同的是,deque容器允许在容器的开头和结尾进行插入和删除操作。同样的,使用deque容器时,需要引入#include "deque"

void printDeque(deque<int>& deq){
//    for (auto it  = deq.begin(); it != deq.end(); it++) {
//        cout << *it << " ";
//    };
    for (int& ele: deq) {
        cout << ele << " ";
    };
    cout << endl;
}
int main(){
    int array[] = {1,2,3,4,5,6,7,8,9,10};
    // 构造
    deque<int> deq1 = deque<int>(array, array+10);
    printDeque(deq1);

    deque<int> deq2(5,2);
    printDeque(deq2);

    // 赋值
    deq2.assign(array, array+3);
    printDeque(deq2);

    deq2.swap(deq1);
    printDeque(deq2);
    printDeque(deq1);

    // 大小,deque没有capacity,它没有预先分配容量
    cout << deq1.size() << endl;
    cout << deq1.empty() << endl;
    deq1.resize(8);
    printDeque(deq1);
    deq1.resize(10, 9);
    printDeque(deq1);

    //
    deq1.push_back(999);
    deq1.push_front(888);
    printDeque(deq1);
    deq1.pop_back();
    deq1.pop_front();
    printDeque(deq1);

    // 访问
    int& ele = deq1.at(4); // 访问指定位置元素,下标
    cout << ele << endl;
    ele = 666;
    cout << deq1[4];
    printDeque(deq1);

    return 0;
}
3.3.4 stack容器(栈)

stack容器是一种先进后出的结构,栈。stack容器允许新增元素,移除元素,取得栈顶元素。入栈push,出栈pop。

#include "iostream"
#include "stack"
using namespace std;
int main(){
    // 构造函数:
    // 1.无参构造 2.拷贝构造
    stack<int> s1;

    // 压栈/入栈
    s1.push(10); // 最先入栈的是栈底
    s1.push(20);
    s1.push(30); // 最后入栈的是栈顶
    // 获取栈顶的元素
    int& ele = s1.top();
    cout << ele << endl;
    ele = 100;
    cout << s1.top() << endl;

    // 出栈
    s1.pop(); // 栈顶元素出栈,pop()返回值为void
    cout << s1.top() << endl;

    // 大小
    cout << s1.size() << endl;
    cout << s1.empty() << endl;

    cout << "------------------输出所有元素---------------------------" << endl;
    while (not s1.empty()) {
        cout << s1.top() << ", ";
        s1.pop();
    }

    return 0;
}

3.3.5 queue容器(队列)

queue容器是一种先进先出结构,队列。queue容器允许新增元素,移除元素,取得队列头元素。

#include "iostream"
#include "queue"
using namespace std;

int main(){
    queue<int> q;

    q.push(10);
    q.push(20);
    q.push(30);

    // 获取队头元素
    cout << "队头元素:" << q.front() << endl;

    // 获取队尾元素
    cout << "队尾元素:" << q.back() << endl;

    // 从队列中移出元素(队头元素)
    q.pop();
    cout << q.front() << endl;

    // 大小
    cout << "队列大小:" << q.size() << endl;
    cout << "队列是否为空:" << q.empty() << endl;
    
    return 0;
}

3.3.6 list容器

list容器是一个双向链表,由多个节点组成,节点内容分为数据域和指针域。
list提供的迭代器是双向迭代器Bidirectional Iterator,可以进行前移和后移。
插入和删除操作不会对原有的list迭代器有影响,list元素的删除也只有被删除的那个元素的迭代器失效,其它迭代器不受影响。

#include "iostream"
#include "list"
using namespace std;

void printList(list<int> &l){
    for(list<int>::iterator it = l.begin(); it != l.end(); it++){ // list<int>::iterator可以换为auto
        cout << *it << ", ";
    }
    cout << endl;
//    for (int& i: l){
//        cout << i << ", ";
//    }
//    cout << endl;
}
int main(){
    // 构造函数:
    int arr[] = {1,2,3,4,5};
    list<int> l1(arr, arr+5);
    // 元素的插入和删除:
    l1.push_back(6); // 尾部插入
    l1.push_front(0); // 头部插入
    printList(l1);

    l1.pop_back(); // 移除尾部元素
    l1.pop_front(); // 移除头部元素
    printList(l1);

    list<int>::iterator it = l1.begin();
    l1.insert(it, 100); // it只能++或者++,由于地址不是连续了,+n不允许
    // 如果想在指定位置插入元素,需要先将指针it移到指定位置,通过++和--移动。
    printList(l1);

    l1.insert(it, 3, 200); // 在it位置插入2个200
    printList(l1); // 由于已经在it的位置插入了元素,所以it已经不是第一个元素(第0位元素)了,而是第1位元素

    list<int>::iterator it2 = l1.begin();
    l1.insert(++it2, arr+3, arr+5); // 在it2位置插入arr[3]、arr[4],左闭右开
    printList(l1);

    // 删除
    auto start = l1.begin();
//    l1.erase(++start); // 删除it3位置的元素
    // 删除[3,6)
    for(int i = 0;i < 3;i++){
        start++;
    }
    auto end = l1.begin();
    for(int i = 0;i < 6;i++){
        end++;
    }
    l1.erase(start, end);
    printList(l1);

    // l1.clear(); // 清空list
    l1.remove(200); // 删除所有值为200的元素
    printList(l1);

    // 大小
    cout << l1.size() << endl;
    cout << l1.empty() << endl;
    // l1.resize(13);

    // 数据存取
    cout << l1.front() << endl; // 获取第一个元素
    cout << l1.back() << endl; // 获取最后一个元素
    // 只是获取,不是弹出后返回,不影响l1
    // 获取其余位置元素:迭代器,自增即可
    auto it3 = l1.begin();
    for(int i = 0;i < 3;i++){
        it3++;
    }
    cout << *it3 << endl;

    // 反转
    l1.reverse();
    printList(l1);

    // 排序
    l1.sort();
    printList(l1);

    return 0;
}

3.3.7 set/multiset容器

set不允许有重复元素,所有元素都会根据元素的值自动排序。set的迭代器是const_iterator。
set特性:当对容器中的元素进行插入和删除操作时,操作之前的所有迭代器,在操作完成之后依然有效,但不包括被删除的那个元素。
multiset的特性以及用法跟set一致,但是multiset允许有重复值。
set和multiset的底层实现都是红黑树,即平衡二叉树的一种。
平衡二叉树:左子树和右子树的高度差不超过1的二叉搜索树,遍历使用中序遍历(左、中、右,即从小到大)。

#include "iostream"
#include "set" // set和multiset都是这个包
using namespace std;

void printSet(set<int> &s){
    // set的迭代器是一个只读迭代器,只能读取,不能修改
    for (auto it = s.begin(); it != s.end(); it++) {
        cout << *it << " ";
    }
    cout << endl;
//    for(int ele : s){ // 简化的遍历,不能int& ele进行引用,因为set的元素是const的,只读,不能修改
//        cout << ele << " ";
//    }
//    cout << endl;
}
int main(){
    // 构造函数
    set<int> s;
    multiplies<int> ms;

    // 插入元素
    s.insert(1);
    s.insert(4);
    s.insert(2);
    s.insert(3);
    s.insert(3);
    printSet(s); // 结果中自动排序和去重了

    // 删除元素,使用迭代器删除
    auto it = s.begin();
    s.erase(it); // 删除当前迭代器指向的元素,并返回下一个元素的指针
    printSet(s);
    // s.erase(s.begin(), s.end()); 区间删除
    // s.clear(); // 清空set

    // 查找元素:set容器没有下标
    set<int>::iterator target = s.find(3); // 如果存在,返回指向元素的迭代器(指针),否则返回end()
    // set<int>::iterator可以换成auto
    cout << s.count(3) << endl; // 查找元素的数量,set去重了,所以结果是1或0。multiset不去重,结果大于等于0
    auto res1 = s.lower_bound(3); // 查找第一个大于等于3的元素的迭代器
    auto res2 = s.upper_bound(3); // 查找第一个大于3的元素的迭代器

    // multiset与set用法一致,但可以插入重复元素
    return 0;
}

3.3.8 map/multimap容器

pair<key, value>:将两个数据整合到一起,成为一个整体,这两个数据类型可以不同,一个是key,一个是value。

void test_pair(){
    // 构建pair
    pair<int, int> p1(1, 2);
    cout << p1.first << " " << p1.second << endl;

    pair<string, int> p2 = make_pair("hello", 1);
    cout << p2.first << " " << p2.second << endl;
}
  1. map中存储的元素是一个个的pair,pair中的第一个元素是key,第二个元素是value。
  2. map中存储的所有键值对,会按照key进行排序。
  3. map中键不能重复,但值可以重复。(multimap的key可以重复)
  4. 可以通过迭代器修改map中pair的值,但是不能修改键。
#include "map" // 同样的需要引入map
void printMap(map<string, int> &m){
    for (map<string, int>::iterator it = m.begin(); it != m.end(); it++) {
        cout << it->first << " " << it->second << endl;
    };
    cout << endl;
}
int main(){
    map<string, int> m;
    // 插入
    m.insert(make_pair("math", 98));
    m.insert(pair<string, int>("chinese", 99));
    m.insert(map<string, int>::value_type ("english", 89));
    m["history"] = 90;

    printMap(m);
    m["history"] = 100; // 若不存在,添加值,已经存在就修改值
    printMap(m);

    // 删除
//    m.erase(m.begin()); // 删除第一位
//    m.erase("english"); // 根据键删除键值对pair
//    printMap(m);

    // 查找
    map<string ,int>::iterator it = m.find("math"); // 查找指定键的键值对的迭代器
    auto it2 = m.find("math");
    cout << m.count("english") << endl; // 查找有多少个指定键的键值对

    // 大小操作
    cout << m.size() << endl;
    cout << m.empty() << endl;

    m.clear();

    // multimap允许键重复
    return 0;
}

3.4 STL中的算法

3.4.1 函数对象(仿函数)与谓语

重载函数调用操作符的类,其对象称为函数对象,即它们是行为类似的对象,也叫仿函数(functor),其实就是重载了“()”操作符,使得类对象可以像函数那样调用。

函数对象(仿函数)是一个类,不是一个函数。
函数对象(仿函数)重载了”()”操作符,可以像函数那样调用。
根据参数数量不同,分为一元仿函数和二元仿函数。

仿函数

// 定义自己的仿函数
class MyPrint{
public:
    int num;
    MyPrint(){
        num = 0;
    };

    void operator()(int n){ // 重载()
        cout << "MyPrint: " << n << endl;
        num++;
    }
};
int main(){
    MyPrint my;
    my(1); // 类似函数调用,这种对象叫函数对象
    my(2);
    cout << "num: " << my.num << endl; // 调用了num次
    return 0;
}

谓语:如果一个函数对象(仿函数)中,重载的()的返回值类型是bool,那么这个函数对象(仿函数)就是一个谓语Predicate。

#include <algorithm> // 算法的包
#include "iostream"
#include "vector"
using namespace std;

// 谓语Predicate
class Student{
private:
    string _name;
    int _age;
public:
    Student(){};
    Student(string name, int age):_name(name), _age(age){};
    int age(){
        return _age;
    }
    void desc(){
        cout << "name: " << _name << ", age: " << _age << endl;
    }


};
// 一元谓语
class Yonger{ // 判断未成年的谓语
public:
    bool operator()(Student& s) const{
        return s.age() < 18;
    }
};
//
class MyComparetor{
public:
    bool operator()(Student& s1, Student& s2){
        return s1.age() < s2.age();
    }
};
void test1(){
    vector<Student> v;
    // 建议使用emplace_back代替push_back,因为emplace_back可以在就地构造对象的同时将其添加到容器中,避免了额外的对象拷贝或移动开销,从而提高了效率。
    v.push_back(Student("huang", 18));
    v.emplace_back("li", 19);
    v.emplace_back("wang", 20);
    v.emplace_back("zhang", 17);
    v.emplace_back("zhao", 25);
    v.emplace_back("chen", 22);
    v.emplace_back("zhou", 17);
    v.emplace_back("zho", 18);

    // 需求:从容器中找到第一个未成年的学生
    // find_if(start, end ,predicate):从给定范围中,查询满足条件的元素,返回指向第一个满足条件的元素的迭代器,
    // 如果没有找到满足条件的元素,则返回end迭代器。
    auto it = find_if(v.begin(), v.end(), Yonger());
    if (it == v.end()){
        cout << "no yonger" << endl;
    }else{
        it->desc(); // (*it).desc()
    }

    // 需求:将容器中的元素进行排序(按照年龄,升序)
    // sort(start, end, compare):将给定范围内的元素进行排序,排序规则由compare函数决定。
    sort(v.begin(), v.end(), MyComparetor());
    for (auto& s: v){
        s.desc();
    }

}
int main(){
    test1();
    return 0;
}

STL内置函数对象:分为算数类、关系运算类、逻辑运算类,使用需要引入#include <functional>
算数类:除了negate是一元,其余都为二元

template<class T>T plus:加法运算
template<class T>T minus:减法运算
template<class T>T multiplies:乘法运算
*template<class T>T divides:除法运算
*template<class T>T modulus:取余运算
*template<class T>T negate:取负运算,一元

关系运算类:都是二元

template<class T>bool equal_to:等于运算
template<class T>bool not_equal_to:不等于运算
template<class T>bool greater:大于运算
template<class T>bool less:小于运算
template<class T>bool greater_equal:大于等于运算
template<class T>bool less_equal:小于等于运算

逻辑运算类:not为一元,其余为二元

template<class T>bool logical_and:逻辑与运算
template<class T>bool logical_or:逻辑或运算
template<class T>bool logical_not:逻辑非运算

#include <functional>
#include <iostream>
using namespace std;
int main(){
    plus<int> p;
    cout << p(1, 2) << endl;
    
    negate<int> n;
    cout << n(1) << endl;
    return 0;
}

3.4.2 算法

算法主要由头文件#include <algorithm>提供。

3.4.2.1 遍历算法

这里用了for_each和for_transform,如果只需要对元素进行操作而不关心结果,或者操作是就地进行的,使用 std::for_each如果需要将操作的结果保存到另一个容器中,使用 std::transform

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;

vector<int> getVector(){ // 获取一个vector容器,填充10个元素
    vector<int> v;
    for (int i = 0; i < 10; ++i) {
        v.push_back(i);
    };
    return v;
}
// 遍历算法 遍历容器元素
// @param begin 起始迭代器
// @param end 结束迭代器
// @param _callback 回调函数或函数对象
// for_each(iterator begin, iterator end, _callback);

class MyPrint{ // 用于输出的函数对象(仿函数)
public:
    void operator()(int i){
        cout << i << ", ";
    }
};
void print(int i){ // 普通函数
    cout << i << ", ";
};
void test1(){  // 使用for_each遍历容器元素
    vector<int> v = getVector();
    // 使用for_each遍历容器元素
    for_each(v.begin(), v.end(), MyPrint()); // 使用函数对象(仿函数)
    for_each(v.begin(), v.end(), print); // 使用普通函数引用

}

// transform算法 将指定容器区间的元素搬运到另一个容器中
// 注意:transform不会给目标容器分配内存,所以需要我们提前分配好内存
// @param begin 起始迭代器
// @param end 结束迭代器
// @param dest 目标容器起始迭代器
// @param _callback 回调函数或函数对象
// @ return _callback返回值
// transform(iterator begin, iterator end, iterator dest, _callback);

class NumberOperator{ // 复制元素的函数对象(仿函数)
public:
    int operator() (int i){
        return i;
    }
};
class SumOperator{ // 相加的函数对象(仿函数)
public:
    int operator()(int i, int j){
        return i+j;
    }
};
void test2(){ // 使用transform将v1容器的元素搬运到v2容器中
    vector<int> v1 = getVector();
    vector<int> v2;
    v2.resize(v1.size()); // transform不会给目标容器分配内存,所以需要我们提前分配好内存
    // 将v1容器的元素搬运到v2容器中
    transform(v1.begin(), v1.end(), v2.begin(), NumberOperator());
    cout << "v2容器元素:";
    for_each(v2.begin(), v2.end(), MyPrint());
    cout << endl;

    vector<int> v3;
    v3.resize(10);
    // 将v1和v2容器的元素相加后搬运到v3
    transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), SumOperator());
    cout << "v3容器元素:";
    for_each(v3.begin(), v3.end(), MyPrint());
}
int main(){
//    test1();
    test2();

    return 0;
}
3.4.2.2 查找算法

findfind_if:查找容器中是否有指定元素,返回的是一个迭代器,如果没找到,返回end迭代器。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;


vector<int> getVector(){ // 获取一个vector容器,填充10个元素
    vector<int> v;
    for (int i = 0; i < 10; ++i) {
        v.push_back(i);
    };
    return v;
}
// 1. find函数
void test1(){
    vector<int> v = getVector();
    // 从这个容器中查找5
    auto it = find(v.begin(), v.end(), 5); // find返回的是一个迭代器
    if(it != v.end()){
        cout << "找到元素" << endl;
    }else{
        cout << "未找到元素" << endl;
    }
}


class Person{
public:
    string name;
    int age;

    Person() = default;
    Person(string name, int age):name(name), age(age){};
    bool operator==(const Person& p){ // 重载==运算符
        return this->name == p.name && this->age == p.age;
    }
};
void test2(){
    vector<Person> v;
    v.push_back(Person("张三", 10));
    v.push_back(Person("李四", 20));
    v.push_back(Person("王五", 30));
    v.push_back(Person("赵六", 40));
    // 两个Person对象不能直接==,所以需要重载==运算符,重载见Person类
    vector<Person>::iterator it = find(v.begin(), v.end(), Person("李四", 20));
    if (it != v.end()){
        cout << "找到元素" << endl;
    }else{
        cout << "未找到元素" << endl;
    }
}

// 2. find_if函数
class Greater7{
public:
    bool operator()(int x){
        return x > 7;
    }
};
void test3(){
    vector<int> v = getVector();
    // 找到第一个大于7的元素,通过Greater7这个谓语(返回为bool的函数对象)作为条件进行查找
    vector<int>::iterator it = find_if(v.begin(), v.end(), Greater7());
    if (it != v.end()){
        cout << "找到元素" << *it << endl;
    }else{
        cout << "未找到元素" << endl;
    }
}
int main(){
//    test1();
//    test2();
    test3();
    return 0;
}

adjacent_find:查找相邻重复元素,返回相邻元素的第一个迭代器。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;
void test1(){
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(2);
    v.push_back(4);
    v.push_back(4);
    v.push_back(5);
    v.push_back(4);
    v.push_back(1);

    auto it = adjacent_find(v.begin(), v.end());
    if (it != v.end()){
        cout << "找到相邻重复元素" << *it << endl;
    }else{
        cout << "未找到相邻重复元素" << endl;
    }
}

class Person{
public:
    string name;
    int age;

    Person() = default;
    Person(string name, int age):name(name), age(age){};
    bool operator==(const Person& p){ // 重载==运算符
        return this->name == p.name && this->age == p.age;
    }
};

void test2(){
    vector<Person> v;
    v.push_back(Person("张三", 10));
    v.push_back(Person("李四", 20));
    v.push_back(Person("王五", 30));
    v.push_back(Person("王五", 30));
    v.push_back(Person("赵六", 40));
    // 默认使用==进行比较,需要在Person类中重载==运算符
    auto it = adjacent_find(v.begin(), v.end());
    if (it != v.end()){
        cout << "找到相邻重复元素" << endl;
    }else{
        cout << "未找到相邻重复元素" << endl;
    }
    // 写一个谓词进行比较
    class MyCompare{
    public:
        bool operator()(Person p1, Person p2){
            return p1.name == p2.name && p1.age == p2.age;
        }
    };
    auto it2 = adjacent_find(v.begin(), v.end(), MyCompare());
    if (it2 != v.end()){
        cout << "找到相邻重复元素" << endl;
    }else{
        cout << "未找到相邻重复元素" << endl;
    }
}
int main(){
//    test1();
    test2();
    return 0;
}

binary_search:二分查找法,查找指定元素是否在容器中,返回bool值。注意:只能在有序的容器中使用。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;
int main(){
    vector<int> v;
    v.push_back(1);
    v.push_back(4);
    v.push_back(12);
    v.push_back(20);
    v.push_back(22);
    cout << binary_search(v.begin(), v.end(), 20) << endl;
    cout << binary_search(v.begin(), v.end(), 21);
    return 0;
}
// 也可以重载仿函数,使用自定义的谓词进行查找

countcount_if:统计容器中指定元素的个数。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;
struct JishuPridicate{ // 判断奇数
    bool operator()(int x){
        return x % 2 == 1;
    }
};
int main(){
    vector<int> v = {1, 7, 9, 2, 3, 3, 3, 4, 5, 3, 6, 7, 7, 8, 9, 3, 10};
    // 查找3的个数
    cout << count(v.begin(), v.end(), 3) << endl;
    // 查找有多少个奇数
    cout << count_if(v.begin(), v.end(), JishuPridicate()) << endl;
    return 0;
}
3.4.2.3 排序算法

sort:对容器进行排序,默认是升序。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;
struct Print{ // 输出
    void operator()(int i){
        cout << i << ", ";
    };

};
void test1(){
    vector<int> v = {1, 5, 6, 7, 2, 3, 4, 8, 9, 10};
    // 排序,默认升序
    sort(v.begin(), v.end());
    for_each(v.begin(), v.end(), Print());
    cout << endl;

    // 降序
    sort(v.begin(), v.end(), greater<int>());
    for_each(v.begin(), v.end(), Print());
};

class Person{
public:
    string name;
    int age;
};
void test2(){
    vector<Person> v;
    v.push_back(Person{"li", 19});
    v.push_back(Person{"zhao", 22});
    v.push_back(Person{"zhou", 23});
    v.push_back(Person{"wang", 20});
    v.push_back(Person{"zhang", 21});
    v.push_back(Person{"huang", 18});
    // 根据年龄排序
    sort(v.begin(), v.end(), [](Person &p1, Person &p2){ // 匿名函数充当谓语
        return p1.age < p2.age;
    });
    for (auto &p : v) {
        cout << p.name << " " << p.age << endl;
    }
}
int main(){
//    test1();
    test2();
    return 0;
}

merge算法:合并两个有序的容器,并保存到一个新的容器中。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;
int main(){
    vector<int> v1 = {1,3,5,7,9,11,13};
    vector<int> v2 = {0,2,3,4,6,8,10,12,14,16};
    vector<int> v3;
    v3.resize(v1.size() + v2.size());
    merge(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
    for(auto &i : v3){
        cout << i << " ";
    }
    // 合并后的结果依旧有序
    return 0;
}

shuffle算法:打乱容器元素的顺序,需要使用随机数生成器。

//#include "iostream"
//#include "algorithm"
//#include "vector"
//using namespace std;
//int main(){
//    stand(time(0)); // 随机种子,避免每次允许结果一致
//    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
//    random_shuffle(v.begin(), v.end()); // random_shuffle()函数打乱顺序,在C++14中被弃用
//    for (auto &i : v) {
//        cout << i << " ";
//    }
//    return 0;
//}
#include <random>
#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;
int main(){
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    shuffle(v.begin(), v.end(), default_random_engine());
    for (auto &i: v)
        cout << i << " ";
    return 0;
}

reverse:反转容器的元素顺序。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;
int main(){
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    reverse(v.begin(), v.end());
    for (auto &i : v) {
        cout << i << " ";
    }
}
3.4.2.4 拷贝与替换

copy:将容器中的元素拷贝到另一个容器中。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;
int main(){
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    vector<int> v2;
    v2.resize(v.size());
    copy(v.begin(), v.end(), v2.begin());
    for (auto &i : v2) {
        cout << i << " ";
    }
    return 0;
}

replacereplace_if:将容器中的指定元素替换为另一个元素。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;

class IsOdd{ // 判断奇数的谓语
public:
    bool operator()(int i){
        return i % 2 == 1;
    }
};
int main(){
    vector<int> v = {1, 2, 3, 4, 5, 6, 2, 1, 2};
    replace(v.begin(), v.end(), 2, 100);
    for (auto &i : v) {
        cout << i << " ";
    }
    cout << endl;

    // 将所以奇数替换成0
    replace_if(v.begin(), v.end(), IsOdd(), 0);
    for (auto &i : v) {
        cout << i << " ";
    }
    cout << endl;
    return 0;
}

swap:互换两个容器的元素。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;
int main(){
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    vector<int> v2 = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
    swap(v, v2);
    for (auto &i : v) {
        cout << i << " ";
    }
    cout << endl;
    for (auto &i : v2) {
        cout << i << " ";
    }
}
3.4.2.5 算术生成

accumulate:对容器中的元素进行求和。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;
int main(){
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    cout << accumulate(v.begin(), v.end(), 0) << endl;
    return 0;
}

fill:将容器中的元素填充为指定值。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;
int main(){
    vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    fill(v.begin(), v.end(), 0);
    for (auto &i : v) {
        cout << i << " ";
    }
    return 0;
}
3.4.2.6 set集合算法

要求两个集合必须是有序的,求交集(set_intersection),求并集(set_union),求差集(set_difference)。

#include "iostream"
#include "algorithm"
#include "vector"
using namespace std;

void print(int i){
    cout << i << " ";
}
int main(){
    vector<int> v1 = {1, 2, 3, 4, 5, 6, 7, 8};
    vector<int> v2 = {4, 5, 6, 7, 8, 9, 10};
    vector<int> v3;
    v3.resize(min(v1.size(), v2.size()));
    // 交集,返回的是交集中最后一个元素的下一位的迭代器
    auto it = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
    for_each(v3.begin(), it, print);
    cout << endl;

    // 并集,会自动去重
    v3.resize(v1.size()+v2.size()); // 并集后最大的长度
    auto it2 = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
    for_each(v3.begin(), it2, print);
    cout << endl;

    // 差集
    v3.resize(max(v1.size(),v2.size()));
    auto it3 = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
    for_each(v3.begin(), it3, print);
    cout << endl;
}

C++面向对象部分见C++面向对象。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值