程序设计学习笔记(C++、STL)2023.5.1

一、概述

在程序设计中常用的数据结构包含线性结构、树状结构、图结构、散列结构等。针对不同结构常见的操作包括构建、插入、查找、删除等。

二、数据结构以及容器

在线性结构中常用的是数组和字符串,字符串是一种特殊的数组,但是具有一些特殊的使用场景和方法。通用的方法包括查找、删除、插入,字符串包含正则表达式、子串等。

1. 数组

在容器存储的数组中实现折半查找,使用迭代器实现折半查找。

#include<iostream>
#include<vector>

template<class T1, class T2>
bool binary_search(T1 first, T1 last, const T2 value){
    if(first > last){
        return false;
    }
    T1 middle = first + (last - first)/2;
    if(*middle < value){
        return binary_search(middle+1, last, value);
    }else if (value < *middle){
        return binary_search(first, middle-1, value);
    }
    return true;
}

int main(){
    std::vector<int>arr = {1, 2, 5, 9, 10, 20, 30};
    std::cout<<binary_search(arr.begin(), arr.end(), 6)<<std::endl;
    return 0;
}

使用传统的数组实现折半查找。

#include<iostream>

template<class T>
bool binary_search(T* first, T* last, const T& value){
    if(first > last){
        return false;
    }
    T* middle = first + (last - first)/2;
    if(*middle < value){
        return binary_search(middle+1, last, value);
    }else if (value < *middle){
        return binary_search(first, middle-1, value);
    }
    return true;
}

int main(){
    int arr[] = {1, 2, 5, 9, 10, 20, 30};
    std::cout<<binary_search(arr, arr+5, 2)<<std::endl;
    return 0;
}

2. 字符串

2.1 使用C++ string类

2.1.1 创建字符串

std::string str;

2.1.2 字符串求子串

string substr(size_t pos=0, size_t len=npos) const

2.1.3 字符串延伸

在string类中重载了与string,char*,char的字符串拼接。

2.1.4 任意位置插入字符串

string& insert(size_t pos, const string& str)

2.1.5 字符串查找

size_t find(const string& str, size_t pos=0) const

size_t rfind(const string& str, size_t pos=0) const

size_t find_first_of(const string& str, size_t pos=0) const

2.1.6 示例

#include<iostream>
#include<string>
#include<exception>

int main(){
    std::string str = "test";
    // 获取字符串的长度使用`length()`函数,直接获取实际的字符串长度
    std::cout<<str.length()<<std::endl;
    // 将字符串与另外的字符串拼接,string
    std::string new_str = str + "xx";
    std::cout<<new_str<<std::endl;
    // 在字符串中进行查找
    std::cout<<str.find('e', 2)<<std::endl;
    std::cout<<str.rfind('s', 1)<<std::endl;
    std::cout<<str.find_first_of('e')<<std::endl;
    // 插入字符
    try{
        std::cout<<str.insert(10,"hhh")<<std::endl;
    } catch(std::out_of_range e){
        std::cout<< e.what()<<std::endl;
    }
    
    // 求字符串的子串
    std::cout<<str.substr(1,3)<<std::endl;
    // 字符串的随机读取
    std::cout<<str[2]<<std::endl;
    // 字符串替换
    str[2]='x';
    str.replace(str.begin(), str.begin()+1, "HHH");
    std::cout<<str<<std::endl;

    return 0;
}

2.2 使用C类型实现

2.2.1 创建字符串

C语言中字符串是以字符数组的形式实现的,每个字符串以'\0'结尾。

创建字符串的方式有两种,一是按照数组的方式对字符数组赋值,第二种是直接将字符串赋值给数组。

char str1[6] = {'H','e','l','l','o','\0'};
char str2[] = "Hello";

2.2.2 字符串长度

字符串长度可以使用size_t strlen(const char* str);

使用已有的函数需要使用string.h库或者在C++中使用ctring

#include<iostream>
#include<cstring>

int my_str_len(char* str){
    if(str == nullptr){
        return 0;
    }
    int ret = 0;
    char *p = str;
    while ((*p)!='\0'){
        ret+=1;
        ++p;
    }
    return ret;
    
}

int main(){
    char str[] = "Hello";
    std::cout<<strlen(str)<<std::endl;
    std::cout<<my_str_len(str)<<std::endl;
    return 0;
}

2.2.3 字符串复制

字符串复制可以直接使用char* strcpy(char* str1, const char* str2);

#include<iostream>
#include<cstring>
#include<cassert>

char* my_strcpy(char* str1, const char* str2){
    assert(str1 != nullptr && str2 != nullptr);
    char* ret = str1;
    while ((*str1++ = *str2++)!='\0');
    return ret;
}

int main(){
    char str[] = "Hello";
    char *new_str = new char[strlen(str)+1];
    std::cout<<strlen(new_str)<<std::endl;
    strcpy(new_str, str);
    std::cout<<new_str<<std::endl;
    delete[] new_str;
    new_str = new char[strlen(str)+1];
    my_strcpy(new_str, str);
    std::cout<<new_str<<std::endl;
    delete[] new_str;
    return 0;
}

2.2.4 字符串拼接

字符串的拼接可以直接使用char* strcat(char* str1, const char* str2);

在C语言中无法探测分配空间的大小,应该开发者考虑是否越界。

#include<iostream>
#include<cstring>
#include<cassert>

char* my_strcat(char* str1, const char* str2){
    assert(str1 != nullptr && str2 != nullptr);
    char* ret = str1;
    while (*str1!='\0'){
        ++str1;
    }
    while ((*str1++ = *str2++)!='\0');
    return ret;
}

int main(){
    char str1[30] = "Hello";
    char str2[] = "World!";
    strcat(str1, str2);
    std::cout<<str1<<std::endl;
    my_strcat(str1, str2);
    std::cout<<str1<<std::endl;
    return 0;
}

2.2.5 字符串比较

字符串比较使用int strcmp(const char* str1, const char* str2)

#include<iostream>
#include<cstring>
#include<cassert>

int my_str_cmp(const char* str1, const char* str2){
    assert(str1!=nullptr && str2!=nullptr);
    while ((*str1!='\0')&&(*str1++ == *str2++));
    int t = *str1-*str2;
    if (t == 0){
        return 0;
    }else{
        return (t>0)?1:-1;
    }
    
}

int main(){
    char str1[] = "abcde";
    char str2[] = "abcde";
    std::cout<<strcmp(str1, str2)<<std::endl;
    std::cout<<my_str_cmp(str1, str2)<<std::endl;
    return 0;
}

2.2.6 子串查找

从字符串中查找字符使用直接的查找,直接查找子串的时间复杂度较大,KMP算法。

#include<iostream>
#include<cstring>

int find_str(const char* str1, const char* str2){
    if(str1 == nullptr || str2 == nullptr){
        return -1;
    }
    int n = strlen(str1);
    int m = strlen(str2);
    for(int i = 0; i < n - m + 1; ++i){
        int j = 0;
        for(; j < m; ++j){
            if(str1[i+j] != str2[j]){
                break;
            }
        }
        if(j == m){
            return i;
        }
    }
    return -1;
}

int main(){
    char str1[] = "abcdefghijk";
    char str2[] = "efg";
    std::cout<<find_str(str1, str2)<<std::endl;
    return 0;
}

三、常用容器

在编程中经常遇到对线性结构的组织以及key-value对的组织,并且在一些场景下需要数值进行自动排序。

1. array

array是一个长度固定不变的容器。

2. vector

vector是一个长度可变的序列容器,支持随机访问,在尾部插入或者删除的效率高,当内存不足会重新分配空间并转移数据。

#include<iostream>
#include<vector>
#include<algorithm>

int main(){
    std::vector<int> vec;
    // 增
    for(int i=0; i<10; ++i){
        // vec.push_back(i);
        vec.emplace_back(i);
    }
    for(auto v: vec){
        std::cout<<v<<" ";
    }
    std::cout<<std::endl;
    // vec.insert(vec.begin(), 10);
    vec.emplace(vec.begin(), 10);
    for(auto v: vec){
        std::cout<<v<<" ";
    }
    std::cout<<std::endl;
    // 删
    vec.pop_back();
    std::cout<<"Erase last one."<<std::endl;
    for(auto v: vec){
        std::cout<<v<<" ";
    }
    std::cout<<std::endl;
    std::vector<int>::iterator rm_iter = std::remove(vec.begin(), vec.end(), 3);
    std::cout<<"Remove 3."<<std::endl;
    for(auto v: vec){
        std::cout<<v<<" ";
    }
    std::cout<<std::endl;
    vec.erase(rm_iter, vec.end());
    std::cout<<"Erase."<<std::endl;
    for(auto v: vec){
        std::cout<<v<<" ";
    }
    std::cout<<std::endl;
    for(auto iter = vec.begin(); iter != vec.end(); ){
        // iter = vec.erase(iter);
        std::swap(*iter, *(vec.end()-1));
        vec.pop_back();
    }
    // vec.clear();
    // vec.erase(vec.begin(), vec.end());
    std::cout<<"Erase all."<<std::endl;
    for(auto v: vec){
        std::cout<<v<<" ";
    }
    std::cout<<std::endl;
    // 改
    for(int i=0; i<10; ++i){
        // vec.push_back(i);
        vec.emplace_back(i);
    }
    std::cout<<"New push."<<std::endl;
    for(auto v: vec){
        std::cout<<v<<" ";
    }
    std::cout<<std::endl;
    vec[5] = 10;
    std::cout<<"Update."<<std::endl;
    for(auto v: vec){
        std::cout<<v<<" ";
    }
    std::cout<<std::endl;
    *(vec.begin()) = 20;
    std::cout<<"Update."<<std::endl;
    for(auto v: vec){
        std::cout<<v<<" ";
    }
    std::cout<<std::endl;
    // 查
    std::vector<int>::iterator iter = std::find(vec.begin(), vec.end(), 30);
    if(iter!=vec.end()){
        std::cout<<"Find:"<<*iter<<std::endl;
    }else{
        std::cout<<"Not found."<<std::endl;
    }
    return 0;
}

3. deque

deque是双端序列容器,在双端处增删数据效率高。支持数据的随机访问。

4. list

list是一个以双向链表形式组织的容器,在首尾可以高效插入或者删除元素,不支持数据的随机访问。

5. forward_list

forward_list是一个单链表形式组织的容器,因此只能从一个方向进行元素遍历,比list节省空间。

4. pair

pair表示一对数据first-second

#include<iostream>
#include<vector>
#include<algorithm>

int main(){
    std::vector<std::pair<int, int>> vec;
    for(int i=0; i<10; ++i){
        vec.emplace_back(std::pair<int, int>(10-i, i));
    }
    std::sort(vec.begin(), vec.end(), [](std::pair<int, int>& a, std::pair<int, int>& b){return a.first<b.first;});
    for(auto v:vec){
        std::cout<<v.first<<" "<<v.second<<std::endl;
    }
    
    return 0;
}

5. map

map是映射容器,提供一对一的映射,内部自动排序。键的值不能重复也不能修改。

#include<iostream>
#include<string>
#include<map>

int main(){
    std::map<std::string, int> my_map;
    // 增
    for(int i=0; i<10;++i){
        std::pair<std::map<std::string, int>::iterator, bool> tag = my_map.emplace("str_"+std::to_string(9-i), i);
        if (!tag.second){
            tag.first->second += 1;
        }
    }
    // my_map["str_81"]=20;
    for(auto m: my_map){
        std::cout<<m.first<<" "<<m.second<<std::endl;
    }
    for(int i=0; i<10;++i){
        std::pair<std::map<std::string, int>::iterator, bool> tag = my_map.emplace("str_"+std::to_string(9-i), i);
        if (!tag.second){
            tag.first->second += 1;
        }
    }
    std::cout<<"New map."<<std::endl;
    for(auto m: my_map){
        std::cout<<m.first<<" "<<m.second<<std::endl;
    }
    // 删
    std::map<std::string, int>::iterator iter = my_map.find("str_81");
    if(iter != my_map.end()){
        my_map.erase(iter);
    }
    std::cout<<"Delete."<<std::endl;
    for(auto m: my_map){
        std::cout<<m.first<<" "<<m.second<<std::endl;
    }
    // 改
    my_map["str_23"] += 20;
    std::cout<<my_map["str_23"]<<std::endl;
    // 查
    iter = my_map.find("str_5");
    if(iter != my_map.end()){
        std::cout<<iter->first<<" "<<iter->second<<std::endl;
    }
    return 0;
}

6. multimap

multimap多重映射容器,存储键值对,键的值不能修改,可以存储多个键相同的数据。

7. set

set是集合容器,不允许有相同的元素,因此可用于去重。底层实现为红黑树。

在元素之外额外配置一个键值,键值与元素值相同。默认按照从小到大排序。

#include<iostream>
#include<string>
#include<set>

int main(){
    std::set<std::string> my_set;
    // 增
    for(int i=0; i<10; ++i){
        my_set.emplace("str_"+std::to_string(10-i));
    }
    for(auto s: my_set){
        std::cout<<s<<" ";
    }
    std::cout<<std::endl;
    // 删
    std::set<std::string>::iterator iter = my_set.find("str_2");
    if(iter != my_set.end()){
        my_set.erase(iter);
    }
    my_set.erase("str_3");
    for(auto s: my_set){
        std::cout<<s<<" ";
    }
    std::cout<<std::endl;
    return 0;
}

8. multiset

multiset是多重集合容器,容器能够对加入的数值或者对象自动排序,可以存在重复值。底层实现为红黑树。

9. unordered_map

unordered_map是一种哈希容器,或者称为无序关联容器。底层采用哈希表的存储结构。

相比map容器的差别在于底层的实现结构不同,并且不会对元素进行排序。

使用迭代器遍历哈希容器,效率不如关联式容器。

在操作unordered_map容器过程(尤其是向容器中添加新键值对)中,一旦当前容器的负载因子超过最大负载因子(默认值为 1.0),该容器就会适当增加桶的数量(通常是翻一倍),并自动执行rehash()成员方法,重新调整各个键值对的存储位置(此过程又称“重哈希”),此过程很可能导致之前创建的迭代器失效。

#include <iostream>
#include <unordered_map>

int main()
{
    //创建 umap 容器
    std::unordered_map<int, int> umap;
    //向 umap 容器添加 50 个键值对
    for (int i = 1; i <= 50; i++) {
        umap.emplace(i, i);
    }
    //获取键为 49 的键值对所在的范围
    auto pair = umap.equal_range(49);
    //输出 pair 范围内的每个键值对的键的值
    for (auto iter = pair.first; iter != pair.second; ++iter) {
        std::cout << iter->first <<" ";
    }
    std::cout << std::endl;
    //手动调整最大负载因子数
    umap.max_load_factor(3.0);
    //手动调用 rehash() 函数重哈希
    umap.rehash(10);
    //重哈希之后,pair 的范围可能会发生变化
    for (auto iter = pair.first; iter != pair.second; ++iter) {
        std::cout << iter->first << " ";
    }
    return 0;
}

10. unordered_multimap

unordered_multimap是哈希多重映射。

11. unordered_set

unordered_set是一个哈希容器,或者叫无序关联容器。

12. unordered_multiset

unordered_multiset是哈希多重集合。

表 1 不同容器的迭代器

容器对应的迭代器类型
array随机访问迭代器
vector随机访问迭代器
deque随机访问迭代器
list双向迭代器
set / multiset双向迭代器
map / multimap双向迭代器
forward_list前向迭代器
unordered_map / unordered_multimap前向迭代器
unordered_set / unordered_multiset前向迭代器
stack不支持迭代器
queue不支持迭代器

表 2 迭代器的 4 种定义方式

迭代器定义方式具体格式
正向迭代器容器类名::iterator 迭代器名;
常量正向迭代器容器类名::const_iterator 迭代器名;
反向迭代器容器类名::reverse_iterator 迭代器名;
常量反向迭代器容器类名::const_reverse_iterator 迭代器名;

三、C++算法

1. 排序

对容器中元素的排序是一个常用的操作,对对象的排序需要首先定义对象的大小判断规则,重载operator<或者使用Lambda函数

函数名用法
sort (first, last)对容器或普通数组中 [first, last) 范围内的元素进行排序,默认进行升序排序。
stable_sort (first, last)和 sort() 函数功能相似,不同之处在于,对于 [first, last) 范围内值相同的元素,该函数不会改变它们的相对位置。
partial_sort (first, middle, last)从 [first,last) 范围内,筛选出 middle-first 个最小的元素并排序存放在 [first,middle) 区间中。
partial_sort_copy (first, last, result_first, result_last)从 [first, last) 范围内筛选出 result_last-result_first 个元素排序并存储到 [result_first, result_last) 指定的范围中。
is_sorted (first, last)检测 [first, last) 范围内是否已经排好序,默认检测是否按升序排序。
is_sorted_until (first, last)和 is_sorted() 函数功能类似,唯一的区别在于,如果 [first, last) 范围的元素没有排好序,则该函数会返回一个指向首个不遵循排序规则的元素的迭代器。
void nth_element (first, nth, last)找到 [first, last) 范围内按照排序规则(默认按照升序排序)应该位于第 nth 个位置处的元素,并将其放置到此位置。同时使该位置左侧的所有元素都比其存放的元素小,该位置右侧的所有元素都比其存放的元素大。

1.1 sort()

sort()函数使用快速排序实现,要求排序的迭代器为随机访问迭代器,因此支持的容器为arrayvectordeque

在排序中需要交换容器中元素的位置,因此对对象的排序需要对类定义移动构造函数和移动赋值函数。

sort()函数包含在algorithm头文件中。

#include <iostream>     // std::cout
#include <algorithm>    // std::sort
#include <vector>       // std::vector
//以普通函数的方式实现自定义排序规则
bool mycomp(int i, int j) {
    return (i < j);
}
//以函数对象的方式实现自定义排序规则
class mycomp2 {
public:
    bool operator() (int i, int j) {
        return (i < j);
    }
};
int main() {
    std::vector<int> myvector{ 32, 71, 12, 45, 26, 80, 53, 33 };
    // 升序
    std::sort(myvector.begin(), myvector.end());
    for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) {
        std::cout << *it << ' ';
    }
    std::cout<<std::endl;
    // 降序
    std::sort(myvector.begin(), myvector.end(), std::greater<int>());
    for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) {
        std::cout << *it << ' ';
    }
    std::cout<<std::endl;
    // 自定义规则
    // 通过定义函数
    // std::sort(myvector.begin(), myvector.end(), mycomp);
    // 通过定义函数对象
    // std::sort(myvector.begin(), myvector.end(), mycomp2());
    // 通过Lambda表达式
    std::sort(myvector.begin(), myvector.end(), [](int &a, int &b){return a<b;});
    for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) {
        std::cout << *it << ' ';
    }
    std::cout<<std::endl;
    return 0;
}

1.2 stable_sort()

stable_sort()函数基于归并排序实现,能够保证相等元素的相对位置。

函数的使用方法与sort()函数一致。

1.3 partial_sort()partial_sort_copy()

partial_sort()函数为局部排序,适用于支持随机访问的容器。

partial_sort_copy()函数的功能与partial_sort()函数类似,前者不会在原有容器上改动,二是将元素拷贝到指定的数组或者容器中。

partial_sort_copy()函数还支持对list容器或者forward_list容器中存储的元素进行排序,而partial_sort()函数不行。排序后输出的容器需要支持随机访问。

#include <iostream>     // std::cout
#include <algorithm>    // std::is_sorted
#include <list>         // std::list

int main() {
    std::list<int> mylist{1,2,3,4,5};
    // 从大到小排序--结果存储到数组中
    int ret[5] = {0};
    std::partial_sort_copy(mylist.begin(), mylist.end(), ret, ret+5,[](int &a, int &b){return a>b;});
    for(int i=0; i<5; ++i){
        std::cout<<ret[i]<<" ";
    }
    std::cout<<std::endl;
    // 自带排序
    mylist.sort();
    for(auto l: mylist){
        std::cout<<l<<" ";
    }
    std::cout<<std::endl;
    return 0;
}

1.4 nth_element()

nth_element()函数在所有元素中得到排序第n的元素。

容器需要支持随机访问。

#include <iostream>
#include <algorithm>    // std::nth_element
#include <vector>       // std::vector
int main() {
    std::vector<int> myvector{2,3,9,1,7,8,10,5,4,6};
    // 升序
    std::nth_element(myvector.begin(), myvector.begin()+2, myvector.end());
    std::cout << "First sort:"<<std::endl;
    for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) {
        std::cout << *it << ' ';
    }
    std::cout<<std::endl;
    // 降序
    std::nth_element(myvector.begin(), myvector.begin() + 3, myvector.end(),[](int &a, int &b){return a>b;});
    std::cout << "Second sort:"<<std::endl;
    for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) {
        std::cout << *it << ' ';
    }
    std::cout<<std::endl;
    return 0;
}

1.5 is_sorted()is_sorted_until()

is_sorted()函数判断元素的排序是否符合指定的排序规则,is_sorted_until()函数不仅能检测出某个序列是否有序,还会返回一个正向迭代器,该迭代器指向的是当前序列中第一个破坏有序状态的元素。

这个函数不要求容器元素的随机访问。

#include <iostream>     // std::cout
#include <algorithm>    // std::is_sorted
#include <list>         // std::list

int main() {
    std::list<int> mylist{1,2,3,4,5};
    if (!is_sorted(mylist.begin(), mylist.end(),[](int &a, int &b){return a<b;})) {
        std::cout << "容器不是从小到大排序。" << std::endl;
    }else{
        std::cout << "容器是从小到大排序。" << std::endl;
    }
   
    if (!is_sorted(mylist.begin(), mylist.end(),[](int &a, int &b){return a>b;})) {
        std::cout << "容器不是从大到小排序。" << std::endl;
    }else{
        std::cout << "容器是从大到小排序。" << std::endl;
    }
    return 0;
}

2. 查找

2.1 find()

find()函数本质上是一个模板函数,用于在指定范围内查找和目标元素值相等的第一个元素。

2.2 find_if()find_if_not()

按条件查找。find_if()函数用于在指定区域内执行查找操作。find()函数需要明确指定要查找的元素的值,find_if()则允许自定义查找规则。

#include <iostream>     // std::cout
#include <algorithm>    // std::find_if, std::find_if_not
#include <vector>       // std::vector

int main() {
    std::vector<int> vec{ 4,2,3,1,5 };
    std::vector<int>::iterator iter = std::find_if(vec.begin(), vec.end(), [](int &a){return a%2==1;});
    if(iter!=vec.end()){
        std::cout << "*iter = " << *iter << std::endl;
    }
    iter = std::find_if_not(vec.begin(), vec.end(), [](int &a){return a%2==1;});
    if(iter!=vec.end()){
        std::cout << "*iter = " << *iter << std::endl;
    }
    return 0;
}

2.3 find_end()

find_end()函数用于在一个序列中查找另一个序列最后一次出现的位置。

#include <iostream>     // std::cout
#include <algorithm>    // std::find_end
#include <vector>       // std::vector
using namespace std;
//以普通函数的形式定义一个匹配规则
bool mycomp1(int i, int j) {
    return (i%j == 0);
}

//以函数对象的形式定义一个匹配规则
class mycomp2 {
public:
    bool operator()(const int& i, const int& j) {
        return (i%j == 0);
    }
};

int main() {
    vector<int> myvector{ 1,2,3,4,8,12,18,1,2,3 };
    int myarr[] = { 1,2,3 };
    //第一种语法格式
    vector<int>::iterator it = find_end(myvector.begin(), myvector.end(), myarr, myarr + 3);
    if (it != myvector.end()) {
        cout << "最后一个{1,2,3}的起始位置为:" << it - myvector.begin() << ",*it = " << *it << endl;
    }

    int myarr2[] = { 2,4,6 };
    //第二种语法格式
    it = find_end(myvector.begin(), myvector.end(), myarr2, myarr2 + 3, mycomp2());
    if (it != myvector.end()) {
        cout << "最后一个{2,3,4}的起始位置为:" << it - myvector.begin() << ",*it = " << *it;
    }
    return 0;
}

2.4 find_first_of()

find_first_of()函数用于在一个序列中找到另一个序列的第一个元素。

#include <iostream>     // std::cout
#include <algorithm>    // std::find_first_of
#include <vector>       // std::vector

//自定义二元谓词函数,作为 find_first_of() 函数的匹配规则
bool mycomp(int c1, int c2) {
    return (c2 % c1 == 0);
}
//以函数对象的形式定义一个 find_first_of() 函数的匹配规则
class mycomp2 {
public:
    bool operator()(const int& c1, const int& c2) {
        return (c2 % c1 == 0);
    }
};
int main() {
    std::string str = "AAAaaaBBBbbbCCCccc";
    char ch[] = "aa";
    //调用第一种语法格式,找到str中和 "stl" 任一字符相同的第一个字符
    std::string::iterator it = std::find_first_of(str.begin(), str.end(), ch, ch + 4);

    if (it != str.end()) {
        std::cout << "*it = " << *it << std::endl;
    }

    std::vector<int> vec{ 5,7,3,9 };
    int inter[] = { 4,6,8 };
    //调用第二种语法格式,找到 myvector 容器中和 3、5、7 任一元素有 c2%c1=0 关系的第一个元素
    std::vector<int>::iterator iter = std::find_first_of(vec.begin(), vec.end(), inter, inter + 3, mycomp2());
    if (iter != vec.end()) {
        std::cout << "*iter = " << *iter << std::endl;
    }
    return 0;
}

2.5 adjacent_find()

adjacent_find()函数用于在指定范围内查找两个连续相等的元素。

#include <iostream>     // std::cout
#include <algorithm>    // std::adjacent_find
#include <vector>       // std::vector
using namespace std;
//以创建普通函数的形式定义一个查找规则
bool mycomp1(int i, int j) {
    return (i == j);
}
//以函数对象的形式定义一个查找规则
class mycomp2{
public:
    bool operator()(const int& _Left, const int& _Right){
        return (_Left == _Right);
    }
};
int main() {
    std::vector<int> myvector{ 5,20,5,30,30,20,10,10,20 };
    //调用第一种语法格式
    std::vector<int>::iterator it = adjacent_find(myvector.begin(), myvector.end());

    if (it != myvector.end()) {
        cout << "one : " << *it << '\n';
    }
    //调用第二种格式,也可以使用 mycomp1
    it = adjacent_find(++it, myvector.end(), mycomp2());

    if (it != myvector.end()) {
        cout << "two : " << *it;
    }
    return 0;
}

2.6 search()search_n()

search()函数找到第一个匹配的元素,与find_end()相反。

相比于search()函数,search_n()函数能在调用中指定查找序列的长度count以及val

2.7 lower_bound()upper_bound()equal_range()binary_search()

find()find_if()search()等函数使用顺序查找的方式定位元素,对于有序数据,使用二分查找能够加快查找的效率。

lower_bound()函数用于在指定区域内查找不小于目标值的第一个元素。

upper_bound()函数用于在指定范围内查找大于目标值的第一个元素。

equal_range()函数用于在指定范围内查找等于目标值的所有元素。

binary_search()函数会返回一个 bool 类型值,如果函数在 [first, last) 区域内成功找到和 val 相等的元素,则返回 true;反之则返回 false。

#include <iostream>     // std::cout
#include <algorithm>    // std::lower_bound
#include <vector>       // std::vector

int main() {
    int a[5] = { 1,2,3,4,5 };
    //从 a 数组中找到第一个不小于 3 的元素
    int *p = std::lower_bound(a, a + 5, 3);
    std::cout << "*p = " << *p << std::endl;
    std::vector<int> myvector{ 4,5,3,1,2 };
    //根据 mycomp2 规则,从 myvector 容器中找到第一个违背 mycomp2 规则的元素
    std::vector<int>::iterator iter = std::lower_bound(myvector.begin(), myvector.end(),3,[](const int& i, const int& j){return i>j;});
    // myvector 容器中存储的元素看似是乱序的,但对于元素 3 来说,大于 3 的所有元素都位于其左侧,小于 3 的所有元素都位于其右侧,且查找规则查找的是第一个不大于 3 的元素,因此 lower_bound() 函数是可以成功运行的。
    std::cout << "*iter = " << *iter;
    return 0;
}
#include <iostream>     // std::cout
#include <algorithm>    // std::equal_range
#include <vector>       // std::vector

int main() {
    int a[9] = { 1,2,3,4,4,4,5,6,7};
    //从 a 数组中找到所有的元素 4
    std::pair<int*, int*> range = std::equal_range(a, a + 9, 4);
    std::cout << "a[9]: ";
    for (int *p = range.first; p < range.second; ++p) {
        std::cout << *p << " ";
    }

    std::vector<int>myvector{ 7,8,5,4,3,3,3,3,2,1 };
    std::pair<std::vector<int>::iterator, std::vector<int>::iterator> range2 = std::equal_range(myvector.begin(), myvector.end(), 3, [](const int& i, const int& j){return i>j;});
    std::cout << "\nmyvector: ";
    for (auto it = range2.first; it != range2.second; ++it) {
        std::cout << *it << " ";
    }
    return 0;
}
#include <iostream>     // std::cout
#include <algorithm>    // std::binary_search
#include <vector>       // std::vector

int main() {
    int a[7] = { 1,2,3,4,5,6,7 };
    // 从 a 数组中查找元素 4
    bool haselem = std::binary_search(a, a + 9, 4);
    std::cout << "haselem: " << haselem << std::endl;

    std::vector<int>myvector{ 4,5,3,1,2 };
    // 从 myvector 容器查找元素 3
    // 由于 binary_search() 底层实现采用的是二分查找的方式,因此该函数仅适用于“已排好序”的序列。所谓“已排好序”,并不是要求 [first, last) 区域内的数据严格按照某个排序规则进行升序或降序排序,只要满足“所有令 element<val(或者 comp(val, element)成立的元素都位于不成立元素的前面(其中 element 为指定范围内的元素)”即可。
    bool haselem2 = binary_search(myvector.begin(), myvector.end(), 3, [](const int& i, const int& j){return i>j;});
    std::cout << "haselem2: " << haselem2 << std::endl;
    return 0;
}

3. 分组

3.1 partition()stable_partition()

两个函数根据指定的条件对容器中的元素进行分组,并返回指向第二组起始元素的迭代器。两个函数的差别是stable_partition()函数不会改变元素的相对位置。

3.2 partition_copy()

使用partition()stable_partition()这两个函数对容器进行划分会修改原始的数据,使用partition_copy()函数不会对原始数据进行修改。

#include <iostream>     // std::cout
#include <algorithm>    // std::partition_copy
#include <vector>       // std::vector

int main() {
    std::vector<int> myvector{ 1,2,3,4,5,6,7,8,9 };
    int b[10] = { 0 }, c[10] = { 0 };
    std::pair<int*, int*> result= partition_copy(myvector.begin(), myvector.end(), b, c, [](const int &a){return a%2==0;});
    std::cout<< "Result: " << result.first - b<<", "<< result.second - c<<std::endl;
    // for (int *p = b; p < result.first; p++) {
    //     std::cout << *p << " ";
    // }
    // std::cout<<std::endl;
    // for (int *p = c; p < result.second; p++) {
    //     std::cout << *p << " ";
    // }
    // std::cout<<std::endl;
    for(int i=0;i<10;i++){
        std::cout<<b[i]<<", "<<c[i]<<std::endl;
    }
    return 0;
}

3.3 partition_point()

partition()stale_partition()或者partition_copy()函数能够按照指定的规则对数据进行分组,然而在已经分好组的数据中找到分界使用partition_point()函数,返回第一个不符合分组规则的元素迭代器。

#include <iostream>     // std::cout
#include <algorithm>    // std::partition_point
#include <vector>       // std::vector

int main() {
    std::vector<int> myvector{ 2,4,6,8,1,3,5,7,9 };
    //根据 mycomp 规则,为 myvector 容器中的数据找出分界
    std::vector<int>::iterator iter = partition_point(myvector.begin(), myvector.end(),[](const int &a){return a%2==0;});
    //输出第一组的数据
    for (auto it = myvector.begin(); it != iter; ++it) {
        std::cout << *it << " ";
    }
    std::cout << std::endl;
    //输出第二组的数据
    for (auto it = iter; it != myvector.end(); ++it) {
        std::cout << *it << " ";
    }
    return 0;
}

4. 谓词

4.1 all_of()any_of()none_of()

all_of()算法会返回 true,前提是序列中的所有元素都可以使谓词返回 true。
any_of()算法会返回 true,前提是序列中的任意一个元素都可以使谓词返回 true。
none_of()算法会返回 true,前提是序列中没有元素可以使谓词返回 true。

5. 匹配

5.1 equal()

可以用和比较字符串类似的方式来比较序列。如果两个序列的长度相同,并且对应元素都相等,equal() 算法会返回 true。有 4 个版本的 equal() 算法,其中两个用 == 运算符来比较元素,另外两个用我们提供的作为参数的函数对象来比较元素,所有指定序列的迭代器都必须至少是输入迭代器。

5.2 mimatch()

mismatch()算法也是用于判断两个序列是否匹配,而且如果不匹配,还能返回不匹配的位置。

6. 比较

6.1 lexicographical_compare()

lexicographical_compare()算法可以比较由开始和结束迭代器定义的两个序列。它的前两个参数定义了第一个序列,第 3 和第 4 个参数分别是第二个序列的开始和结束迭代器。默认用 < 运算符来比较元素,但在需要时,也可以提供一个实现小于比较的函数对象作为可选的第 5 个参数。如果第一个序列的字典序小于第二个,这个算法会返回 true,否则返回 false。所以,返回 false 表明第一个序列大于或等于第二个序列。

#include<iostream>
#include<algorithm>
#include<vector>
#include<string>
int main(){
    std::vector<std::string> phrase1 {"the", "tigers", "of", "wrath"};
    std::vector<std::string> phrase2 {"the", "horses", "of", "instruction"};
    auto less = std::lexicographical_compare (phrase1.begin(), phrase1.end(), phrase2.begin(), phrase2.end(), [](const std::string& s1, const std::string& s2) { return s1 < s2; });
    std::cout << (less ? "are":"are not") << " less than ";
    return 0;
}

7. 排列组合

7.1 next_permutation()prev_permutation()is_permutaion()

next_permutation()会生成一个序列的重排列,它是所有可能的字典序中的下一个排列,默认使用 < 运算符来做这些事情。
next_permutation()按照字典升序的方式生成的排列。prev_permutation()以降序的方式生成排列时。
is_permutation()算法可以用来检查一个序列是不是另一个序列的排列,如果是,会返回 true。

#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include <algorithm>

int main()
{
    std::vector<std::string> words;
    std::string word = "ABC";
    std::string word_copy (word);
    do
    {
        words.push_back(word);
        std::next_permutation(std::begin(word), std::end(word));
    } while(word != word_copy);

    size_t count{}, max{8};
    for(const auto& wrd : words)
        std::cout << wrd << ((++count % max == 0) ? '\n' : ' ');
    std::cout << std::endl;
    words.clear();
    std::string word1 = "ABC";
    std::string word2 = "ACB";
    std::cout<<std::is_permutation(word1.begin(), word1.end(), word2.begin(), word2.end())<<std::endl;
    return 0;
}

8. 复制

8.1 copy_n()copy_if()copy_backward()

copy_n()算法可以从源容器复制指定个数的元素到目的容器中。第一个参数是指向第一个源元素的输入迭代器,第二个参数是需要复制的元素的个数,第三个参数是指向目的容器的第一个位置的迭代器。

copy_if()算法可以从源序列复制使谓词返回 true 的元素,所以可以把它看作一个过滤器。前两个参数定义源序列的输入迭代器,第三个参数是指向目的序列的第一个位置的输出迭代器,第 4 个参数是一个谓词。

copy_backward()会复制前两个迭代器参数指定的序列。第三个参数是目的序列的结束迭代器,通过将源序列中的最后一个元素复制到目的序列的结束迭代器之前,源序列会被复制到目的序列中。copy_backward()的 3 个参数都必须是可以自增或自减的双向迭代器,这个算法只能应用到序列容器的序列上。与copy()函数不同的是copy_backward()从后向前复制。

8.2 reverse()reverse_copy()

reverse()算法可以在原地逆序它的两个双向迭代器参数所指定序列的元素。
reverse_copy()算法可以将源序列复制到目的序列中,目的序列中的元素是逆序的。定义源序列的前两个迭代器参数必须是双向迭代器。

9. 合并

9.1 merge()inplace_merge()

merge()函数将两个有序序列合并为一个有序序列,两个有序序列的排序规则相同。如果原始的两个序列无序,则合并后序列也无序。

inplace_merge()函数用于将单个数组或者容器中的两个有序序列合并为一个有序序列。

#include <iostream>     // std::cout
#include <algorithm>    // std::merge
#include <vector>       // std::vector
using namespace std;
int main() {
    //first 和 second 数组中各存有 1 个有序序列
    int first[] = { 9,5,10,15,20,25 };
    int second[] = { 21,7,17,27,37,47,57 };
    //用于存储新的有序序列
    vector<int> myvector(11);
    //将 [first,first+5) 和 [second,second+6) 合并为 1 个有序序列,并存储到 myvector 容器中。
    merge(first, first + 5, second, second + 6, myvector.begin());
    //输出 myvector 容器中存储的元素
    for (vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it) {
        cout << *it << ' ';
    }   
    return 0;
}
#include <iostream>     // std::cout
#include <algorithm>    // std::merge
using namespace std;
int main() {
    //该数组中存储有 2 个有序序列
    int first[] = { 5,10,15,20,25,7,17,27,37,47,57 };
    //将 [first,first+5) 和 [first+5,first+11) 合并为 1 个有序序列。
    inplace_merge(first, first + 5,first +11);

    for (int i = 0; i < 11; i++) {
        cout << first[i] << " ";
    }
    return 0;
}

10. 其他

10.1 unique()

unique()算法可以在序列中原地移除重复的元素,这就要求被处理的序列必须是正向迭代器所指定的。在移除重复元素后,它会返回一个正向迭代器作为新序列的结束迭代器。可以提供一个函数对象作为可选的第三个参数,这个参数会定义一个用来代替==比较元素的方法。

#include <iostream>
#include <string>
#include <algorithm>

int main()
{
    std::string text {"There's nooo air in spaaaaaace!"};
    auto iter = std::unique(std::begin(text), std::end(text),[](char ch1, char ch2) { return ch1 == ch2; });
    text.erase(iter, text.end());
    std::cout << text << std::endl;
    return 0;
}

10.2 rotate()rotate_copy()

rotate_copy()算法会在新序列中生成一个序列的旋转副本,并保持原序列不变

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

int main()
{
    std::vector<std::string> words { "one", "two", "three", "four", "five","six", "seven", "eight"};
    std::vector<std::string> words_copy(10);
    std::rotate_copy(words.begin(), words.begin()+3 , words.end(), words_copy.begin());
    for(auto s: words_copy){
        std::cout<< s<< " ";
    }
    std::cout<<std::endl;
    auto iter = std::rotate(words.begin(), words.begin()+3, words.end());
    for(auto s: words){
        std::cout<< s<< " ";
    }
    return 0;
}

10.3 move()

move()算法会将它的前两个输入迭代器参数指定的序列移到第三个参数定义的目的序列的开始位置,第三个参数必须是输出迭代器。这个算法返回的迭代器指向最后一个被移动到目的序列的元素的下一个位置。

10.4 swap_ranges()

swap_ranges()算法来交换两个序列。

10.5 remove()remove_copy()remove_if()remove_copy_if()

remove()可以从它的前两个正向迭代器参数指定的序列中移除和第三个参数相等的对象。基本上每个元素都是通过用它后面的元素覆盖它来实现移除的。它会返回一个指向新的最后一个元素之后的位置的迭代器。

remove_copy()可以将前两个正向迭代器参数指定的序列中的元素复制到第三个参数指定的目的序列中,并忽略和第 4 个参数相等的元素。它返回一个指向最后一个被复制到目的序列的元素的后一个位置的迭代器。序列不能是重叠的。

remove_if()可以从前两个正向迭代器指定的序列中移除能够使作为第三个参数的谓词返回 true 的元素。

remove_copy_if()可以将前两个正向迭代器参数指定的序列中,能够使作为第 4 个参数的谓词返回 true 的元素,复制到第三个参数指定的目的序列中。它返回一个指向最后一个被复制到目的序列的元素的后一个位置的迭代器。序列不能是重叠的。

10.6 fill()fill_n()

fill()fill_n() 算法提供了一种为元素序列填入给定值的简单方式,fill() 会填充整个序列; fill_n() 则以给定的迭代器为起始位置,为指定个数的元素设置值。

10.7 generate()generate_n()

10.8 transform()

transform() 可以将函数应用到序列的元素上,并将这个函数返回的值保存到另一个序列中,它返回的迭代器指向输出序列所保存的最后一个元素的下一个位置。

10.9 replace()

replace() 算法会用新的值来替换和给定值相匹配的元素。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《C++ STL标准程序库开发指南.pdf》是一本关于C++ STL(Standard Template Library,标准模板库)的开发指南。STLC++中常用的一种程序库,提供了丰富的通用模板类和算法,可以帮助开发者更快地开发高效、健壮的程序。 这本指南首先介绍了STL的基本概念和设计原则,包括容器(containers)、迭代器(iterators)、算法(algorithms)和函数对象(function objects)等。然后详细介绍了STL中各种容器和算法的使用方法,包括向量(vector)、列表(list)、集合(set)和映射(map)等。 在介绍STL的使用方法之前,该指南还提供了一些C++基础知识的回顾,例如模板(templates)、迭代器和算法等。这对于初学者来说是非常有用的,可以帮助他们更好地理解和应用STL。 此外,该指南还详细讲解了STL中的一些高级特性和技巧,例如函数对象、迭代器适配器和算法复杂度分析等。对于有一定经验的开发者来说,这些内容可以帮助他们进一步提高代码的效率和可维护性。 总的来说,《C++ STL标准程序库开发指南.pdf》是一本全面而实用的编程指南,对于想要学习和使用C++ STL的开发者来说是一本不可或缺的参考书籍。无论是初学者还是有经验的开发者,都可以从中获得丰富且实用的知识和技巧,帮助他们写出更好的C++程序。 ### 回答2: 《C++ STL标准程序库开发指南》是一本介绍C++ STL(Standard Template Library,标准模板库)的开发指南。STLC++语言的一个重要组成部分,它提供了一套模板类和函数,用于实现常用的数据结构和算法。本书详细介绍了STL的各个组件以及如何使用STL进行开发。 该书分为几个主要部分。首先是对STL的概述,包括STL的基本概念、组件以及使用STL的好处。然后是对STL容器的介绍,包括数组、向量、链表、集合、映射等常用数据结构的实现与使用方法。接着是对STL算法的讲解,包括排序、查找、合并、删除等常用算法的实现与应用。最后是对STL迭代器和函数对象的介绍,以及如何自定义迭代器和函数对象来满足特定需求。 本书以干净、简洁的风格展示了STL的各个方面,同时提供了大量的示例代码和练习题,以帮助读者加深对STL的理解和掌握。作者还结合了实际开发场景,给出了一些使用STL解决实际问题的例子,使读者能够将STL应用于实际开发中。 总的来说,《C++ STL标准程序库开发指南》适合C++程序员、学生和希望深入了解STL的人士阅读。它不仅是一本学习STL的入门教材,还可以作为参考手册供读者查阅。阅读本书可以帮助读者快速掌握STL的使用方法,并在实际开发中提高效率,提供更优质的代码。 ### 回答3: 《C++ STL标准程序库开发指南.pdf》是一本关于C++标准库的开发指南。C++标准库是一组类模板和函数模板的集合,提供了各种功能强大的容器类、算法、迭代器等,使C++程序开发更加方便和高效。 本书的目的是帮助读者全面了解和掌握C++标准库的使用方法,从而加快开发速度和提高代码质量。书中从基本概念开始介绍,逐渐深入讲解各个容器类(如数组、向量、链表、集合、映射等)的特点和用法,以及常用算法(如排序、查找、遍历等)的实现细节和使用场景。此外,还对迭代器、函数对象和内存管理等主题进行了详细阐述。 阅读本书,读者可以学习到如何合理选择和使用标准库提供的容器和算法,以及如何根据实际需求来开发自定义的容器和算法。同时,本书也提供了丰富的例子和代码片段,供读者练习和参考。另外,书中还涵盖了一些高级主题,例如多线程编程、异常处理和模板元编程等,使读者在掌握基本知识的基础上能够进一步深入学习和实践。 《C++ STL标准程序库开发指南.pdf》是一本非常实用和权威的参考书,适合C++程序开发人员和对C++标准库感兴趣的读者阅读。它的内容全面、深入,对于提高C++编程水平和开发效率有着重要的作用。无论是初学者还是有一定经验的程序员,都可以从这本书中获得丰富的知识和经验,为自己的工作和学习带来巨大的收益。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值