String 类和 STL (Standard Template Library)


一. string 类

  •        很多应用程序都需要处理字符串。C语言在string.h(在++中为cstring)中提供了一系列的字符串函数,很多早期的C++实现为处理字符串提供了自己的类。

  •         string类是由头文件string支持的(以头文件string.h和cstring支持对C风格字符串进行操纵的C库字符串函数,但不支持string类)。要使用类,关键在于知道它的公有接口,而string类包含大量的方法,其中包括了若干构造函数,用于将字符串赋给变量、合并字符串、比较字符串和访问各个元素的重载运算符以及用于在字符串中查找字符和子字符串的工具等。以string类包含的内容很多。

1. 构造字符串

  •        先来看string的构造函数。毕竟,对于类而言,最重要的内容之一是,有哪些方法可用于创建其对象。程序清单1使用了string的7个构造函数(用ctor标识,这是传统C++中构造函数的缩写)。表1简要地描述了这些构造函数,它首先按顺序简要描述了程序清单1使用的7个构造函数,然后列出了C++11新增的两个构造函数。使用构造函数时都进行了简化,即隐藏了这样一个事实:string实际上是模板具体化basic_string<char>的一个typedef,同时省略了与内存管理相关的参数。 size_type是一个依赖于实现的整型,是在头文件string中定义的。string类将string::npos定义为字符串的最大长度,通常为unsigned int的最大值。以表格中使用缩写NBTS(null终止string)来表示以空字符结束的字符串一一传统的C字符串。

    表1 string类的构造函数

    构 造 函 数

    描述

    string(const char * s)

    将string对象初始化为s指向的NBTS

    string(size_type n, char c)

    创建一个包含n个元素的string对象,其中每个元素都被初始化为字符c

    string(const string & str)

    将一个string对象初始化为string对象str(复制构造函数)

    string()

    创建一个默认的string对象,长度为0(默认构造函数)

    string(const char * s, size_type n)

    将string对象初始化为s指向的NBTS的前n个字符,即使超过了NBTS结尾

    template<class Iter>
    string(Iter begin, Iter end)

    将string对象初始化为区间[begin, end)内的字符,其中begin和end的行为就像指针,用于指定位置,范围包括begin在内,但不包括end

    string(const string & str, size_type pos, size_type n = npos)

    将一个string对象初始化为对象str中从位置pos开始到结尾的字符,或从位置pos开始的n个字符

    string(string && str) noexcept

    这是C++11新增的,它将一个string对象初始化为string对象str,并可能修改str(移动构造函数)

    string(initializer_list<char> il)

    这是C++11新增的,它将一个string对象初始化为初始化列表il中的字符

    程序清单1 :
    #include <iostream>
    #include <string>
    // using string constructors
    int main()
    {
        using namespace std;
        string one("Lottery Winner!"); // ctor #1
        cout << one << endl;           // overloaded <<
        string two(20, '$');           // ctor #2
        cout << two << endl;
        string three(one);             // ctor #3
        cout << three << endl;
        one += " Oops!";               // overloaded +=
        cout << one << endl;
        two = "Sorry! That was ";      //will Clean up the original string in object two
        three[0] = 'P';
        string four;                   // ctor #4
        four = two + three;            // overloaded +, =
        cout << four << endl;
        char alls[] = "All's well that ends well";
        string five(alls,20);          // ctor #5
        cout << five << "!\n";
        string six(alls+6, alls + 10); // ctor #6
        cout << six << ", ";
        string seven(&five[6], &five[10]); // ctor #6 again
        cout << seven << "...\n";
        cout << "Now four is :" << four << endl;
        string eight(four, 7, 16);     // ctor #7
        cout << eight << " in motion!" << endl;
        system("pause");
        return 0;
    }
    

    程序清单1 的输出:

    Lottery Winner!
    $$$$$$$$$$$$$$$$$$$$
    Lottery Winner!
    Lottery Winner! Oops!
    Sorry! That was Pottery Winner!
    All's well that ends!
    well, well...
    Now four is :Sorry! That was Pottery Winner!
    That was Pottery in motion!
    Press any key to continue . . .
    

2. string类输入

  • 有哪些输入方式可用呢? 对于C风格的字符串, 有3种方式:

    char info[100];
    cin >> info;             // read a word
    cin.getline(info, 100);  // read a line, discard \n
    cin.get(info, 100);      // read a line, leave \n in queue
    
  • 对于string 对象 有两种方式:

    string stuff;
    cin >> stuff;        // read a word
    getline(cin, stuff); // read a line, discard \n
    
  • 两个版本的getline() 都有一个可选参数, 用于指定使用什么字符作为读取的边界;

    cin.getline(info,100,':'); // read up to :, discard :
    getline(cin,stuff, ':');   // read up to :, discard :
    

    对于string版本的getline() 能够自动调整目标string 对象的大小, 使之刚好能存储输入的字符:

    char fname[10];
    string lname;
    cin >> fname;            // could be a problem if input size > 9 characters
    cin >> lname;            // can read a very, very long word
    cin.getline(fname, 10);  // may truncate input
    getline(cin, fname);     // no truncation
    

    自动调整大小的功能让 string版本的getline() 不需要指定要读取多少个字符的参数

  • string 版本的 getline() 从输入流中读取字符, 并将其放入目标string 中, 直到发生下面几种情况:

    • 到达文件尾的输入流的eofbit将被设置,这意味着方法fail()和eof()都将返回true;
    • 遇到分界字符(默认为\n) 在这种情况下, 将把分界字符从输入流中删除,但不存储它;
    • 读取的字符数达到最大允许值(string::npos和可供分配的内存字节数中较小的一个) 在这种情况下,将设置输入流的failbit,这意味着方法fail()将返回true。

    eofbit fail()等与 流状态 相关, 将在 ~ C++输入输出和文件 -> 三. 使用cin进行输入 -> 2. 流状态 ~ 中讲解

3. 使用字符串

  • 字符串比较
    string 类对全部6个关系运算符都进行了重载, 如果机器排列序列为ASCII码, 那么数字字符 < 大写字符 < 小写字符;
    对于每个关系运算符, 都以三种方式被重载, 以便将string对象和 另一个string对象,C风格字符串 进行比较 :

    #include <iostream>
    #include <exception>
    int main()
    {
        using namespace std;
        string snake1("cobra");
        string snake2("coaal");
        char snake3[20] = "cobra";
        if (snake1 < snake2) // operator<(const string &, const string &)
        {
            cout << "snake1 < snake 2" << endl;
        }
        if (snake1 == snake3) // operator==(const string &, const char *)
        {
            cout << "snake1 == snake3" << endl;
        }
    
        if (snake3 != snake2) // operator!=(const char *, const string &)
        {
            cout << "snake3 != snake2" << endl;
        }
        system("pause");
        return 0;
    }
    

    size() 和 length() 都返回字符串的字符数
    length()成员来自较早版本的string类, 而size()则是为STL兼容性而添加的

  • 字符串查找
    string::npos是字符串可存储的最大字符数, 通常是无符号int或long的最大取值;

    表2 重载的find()方法

    方 法 原 型

    描 述

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

    从字符串的pos位置开始,查找子字符串str。如果找到,则返回该子字符串首次出现时其首字符的索引;否则,返回string :: npos

    size_type find(const char * s,
    size_type pos = 0)const

    从字符串的pos位置开始,查找子字符串s。如果找到,则返回该子字符串首次出现时其首字符的索引;否则,返回string :: npos

    size_type find(const char * s,
    size_type pos, size_type n)

    从字符串的pos位置开始,查找s的前n个字符组成的子字符串。如果找到,则返回该子字符串首次出现时其首字符的索引;否则,返回string :: npos

    size_type find(char ch,
    size_type pos = 0)const

    从字符串的pos位置开始,查找字符ch。如果找到,则返回该字符首次出现的位置;否则,返回string :: npos

    string 库还提供了相关的方法: rfind()、find_first_of()、find_last_of()、find_first_not_of()和find_last_not_of(),它们的重载函数特征标都与find()方法相同。rfind()方法查找子字符串或字符最后一次出现的位置;find_first_of()方法在字符串中查找参数中任何一个字符首次出现的位置。例如,下面的语句返回 r 在“cobra”中的位置(即索引3),因为这是“hark”中各个字母在“cobra”首次出现的位置:
    int where = snake1.find_first_of("hark");
    

    find_last_of()方法的功能与此相同,只是它查找的是最后一次出现的位置。因此,下面的语句返回a在“cobra”中的位置:

    int where = snake1.find_last_of("hark");
    

    find_first_not_of()方法在字符串中查找第一个不包含在参数中的字符,因此下面的语句返回c在“cobra”中的位置,因为“hark”中没有c:

    int where = snake1.find_first_not_of("hark");
    

4. 其他string类方法

  • 很多, 就萝莉一些, 再挑几个讲. 其他的用到的时候就知道了

    a) =,assign() //赋以新值 
    b) swap() //交换两个字符串的内容 
    c) +=,append(),push_back() //在尾部添加字符 
    d) insert() //插入字符 
    e) erase() //删除字符 
    f) clear() //删除全部字符 
    g) replace() //替换字符 
    h) + //串联字符串 
    i) ==,!=,<,<=,>,>=,compare() //比较字符串 
    j) size(),length() //返回字符数量 
    k) max_size() //返回字符的可能最大个数 
    l) empty() //判断字符串是否为空 
    m) capacity() //返回重新分配之前的字符容量 
    n) reserve() resize()//保留一定量内存以容纳一定数量的字符 
               //resize() 会重置size和length 参数如果小于当前的size,则截短,大于则扩充
        //resize() 扩充时默认是用'\0'填充, 第二个可选参数可指定用什么字符填充
    	//resize()减少size时 不会影响capacity
    o) [], at() //存取单一字符  at()索引无效时,会抛出out_of_range异常
    p) >>,getline() //从stream读取某值 
    q) << //将谋值写入stream 
    r) copy() //将某值赋值为一个C_string 
    s) c_str() //将内容以C_string返回  不可修改
    t) data() //将内容以字符序列指针形式返回 可修改
    u) substr() //返回某个子字符串 
    v)查找函数 
    w)begin() end() //提供类似STL的迭代器支持 
    x) rbegin() rend() //逆向迭代器 
    y) get_allocator() //返回配置器 
      //string 中 capacity 最小 15
    z) shrink_to_fit() //A non-binding request to reduce capacity() to size() 
    
  • compare 返回值意义(吾用区间表示法表示):[小于,0,大于] , 0:相等

    string s("abcd"); 
    s.compare("abcd"); //返回0 
    s.compare("dcba"); //返回一个小于0的值 
    s.compare("ab"); //返回大于0的值 
    s.compare(s); //相等 
    //参数1:下标 2:字符个数 3:比较的对象 4:下标 5:字符个数
    s.compare(0,2,s,2,2); //用"ab"和"cd"进行比较 小于零 
    //参数1:下标 2:字符个数 3:比较的对象 4:字符个数
    s.compare(1,2,"bcx",2); //用"bc"和"bc"比较。
    
  • assign 重新分配

    s.assign(str); //字面意思
    //参数1:目标 2:下标 3:字符数
    s.assign(str,1,3);//如果str是"iamangel" 就是把"ama"赋给字符串 
    s.assign(str,2,string::npos);//把字符串str从索引值2开始到结尾赋给s 
    s.assign("gaint"); //字面意思
    s.assign("nico",5);//把’n’ ‘I’ ‘c’ ‘o’ ‘’赋给字符串 
    s.assign(5,'x');//把五个x赋给字符串 
    
  • append 附加

    s.append(str); 
    s.append(str,1,3);//不解释了 同前面的函数参数assign的解释 
    s.append(str,2,string::npos)//
    s.append("my name is jiayp"); 
    s.append("nico",5); 
    s.append(5,'x'); 
    s.push_back('a');//这个函数只能增加单个字符
    
  • insert 插入

    s.insert(0,"my name");
    s.insert(1, "m");
    s.insert(1,str); 
    
  • replace erase 替换和擦除

    s.replace(1,2,"nternationalizatio");//从索引1开始的2个替换成后面的C_string或string对象
    s.erase(13);//从索引13开始往后全删除 
    s.erase(7,5);//从索引7开始往后删5个 
    
  • substr 返回子字符串(新的string)

    s.substr();//返回s的全部内容 
    s.substr(11);//从索引11往后的子串 
    s.substr(5,6);//从索引5开始6个字符 
    
  • copy 复制并替换目标中原有的字符

    char str1[20] = "Hello";
    char str2[20] {0};
    string sl = "World";
    //参数1:目标对象 2:要copy的字符数 3:从sl的下标?开始
    sl.copy(str1, 5, 2);//
    cout << str1 << endl;
    
  • 方法capacity()返回当前分配给字符串的内存块的大小,而reserve()方法让您能够请求增大内存块

    #include <iostream>
    #include <string>
    int main()
    {
        using namespace std;
        string empty;
        string small = "bit";
        string larger = "Elephants are a girl's best friend";
        cout << "Sizes:\n";
        cout << "\tempty: " << empty.size() << endl;
        cout << "\tsmall: " << small.size() << endl;
        cout << "\tlarger: " << larger.size() << endl;
        cout << "Capacities:\n";
        cout << "\tempty: " << empty.capacity() << endl;
        cout << "\tsmall: " << small.capacity() << endl;
        cout << "\tlarger: " << larger.capacity() << endl;
        empty.reserve(50);
        cout << "Capacity after empty.reserve(50): "
             << empty.capacity() << endl;
        return 0;
    }
    
  • 如果您有string对象,但需要C风格字符串,该如何办呢?

    string filename;
    filename.c_str(); //返回c风格字符串
    
  • 如果你需要string对象提供一个可供修改内容字符串的地址 以便传入需要修改该地址上C风格字符串的函数
    可以使用data()成员函数返回可修改的C字符串地址, 但你必须十分小心:很多成员函数行为都将变异

    • string 无法知道你对C风格字符做了什么修改, 因为你不是通过string类提供的方法修改的
      这意味着size() length()成员函数都返回原先的值
    • 经过测试, 无参构造且不调用reserve()扩容的情况下, 默认分配给内部C字符串指针29个字节的内存还有一个留给’\0’.
    • 不应该保留data() 成员函数返回的指针, 一旦使用reserve()扩容, 这个地址随时可能改变 (realloc都保不住)
    • 如果要继续使用该string对象, 你应该使用类似s.append(s.data());能使它的成员恢复正常的方法
    • 在把string对象内部C字符串指针传给要修改C字符串风格的函数前, 应调用reserve()扩大到足够的容量
    • 你可以用这些代码来测试: (我发现现代编译器很离谱,居然自己调整这些, 我现在不确定分配给内部C字符串的内存一开始是多少了)
      #include <iostream>
      #include <fstream>
      #include <string>
      #include <cstring>
      #include <memory>
      #include <iomanip>
      #include <vector>
      using namespace std;
      int main()
      {
          string buf;
          cout << setw(16) << left;
          cout << setw(16) << "before buf size():" << buf.size() << endl;
          cout << setw(16) << "before buf capacity():" << buf.capacity() << endl;
      
          cin.get(buf.data(), 100);
          //    buf.append(buf.data());
          //    buf.assign(buf.data());
          //    buf = buf.data();
          cout << setw(16) << "buf size():" << buf.length() << endl;
          cout << setw(16) << "buf capacity():" << buf.capacity() << endl;
          cout << setw(16) << "empty():" << boolalpha << buf.empty() << endl;
          cout << endl;
          cout << setw(16) << "buf:" << setw(20) << buf << "address :" << &buf << endl;
          cout << setw(16) << "buf.c_str():" << setw(20) << buf.c_str() << "address :" << (int*)buf.c_str() << endl;
          cout << setw(16) << "buf.data():" << setw(20) << buf.data() << "address :" << (int*)buf.data() << endl;
          cout << setw(16) << "strlen(buf.c_str()):" << strlen(buf.c_str()) << endl;
          cout << endl;
          cout << "cin.fail():" << cin.fail() << endl;
          cout << "cin.eof():" << cin.eof() << endl;
          cout << "cin.bad():" << cin.bad() << endl;
          system("pause");
          return 0;
      }
      
      • 如果使用无参默认构造string, 则修改data()上的数据后使用s.append(s.data());是安全的
      • 如果不确定内部分配给data()函数返回的指针多少个字节的内存, 为避免越界, 应使用 reserve()扩容
  • string 对象中 data()和c_str()成员函数返回的是同一个地址, 它的所有成员都是用来限定string对象单独维护的一个字符串的, 而不是data()和c_str()返回的字符串. 如果说你在外部增加或修改了data()中的字符, string对象变现(指变成字符串)时 会按当前内部成员size截取 data()上的字符放入那个隐藏维护的字符串地址上 并在size+1处添加’\0’ .

5. 字符串种类

  • 本节将string类看作是基于char类型的。事实上,正如前面指出的,string库实际上是基于一个模板类的:

    template<class charT, class traits = char _traits<charT>,
            class Allocator = allocator<charT> >
    basic_string {...};
    
  • 模板basic_string有4个具体化(特化),每个具体化都有一个typedef名称:

    typedef basic_string<char> string;
    typedef basic_string<wchar_t> wstring;
    typedef basic_string<char16_t> u16string;   // C++11
    typedef basic_string<char32_t> u32string ; // C++11
    

    这让您能够使用基于类型wchar_t、char16_t、char32_t和char的字符串。甚至可以开发某种类似字符的类,并对它使用basic_string类模板(只要它满足某些要求)。traits类描述关于选定字符类型的特定情况,如如何对值进行比较。对于wchar_t、char16_t、char32_t和char类型,有预定义的char_traits模板具体化,它们都是traits的默认值。Allocator是一个管理内存分配的类。对于各种字符类型,都有预定义的allocator模板具体化,它们都是默认的。它们使用new和delete。

二. 智能指针

  • 要创建智能指针, 必须包含头文件 memory

    template<class X> class auto_ptr {
    public:
        explicit auto_ptr(X* p =0) throw();
    ...};
    
    auto_ptr<double> pd(new double); // pd an auto_ptr to double
                                     // (use in place of double * pd)
    auto_ptr<string> ps(new string); // ps an auto_ptr to string
                                     // (use in place of string * ps)
    
  • auto_ptr 已被弃用, 应使用功能更明确的shared_ptr 和 unique_ptr

  • shared_ptr

    表 3 shared_ptr<T>模板类常用成员方法
    成员方法名 功 能
    operator=() 重载赋值号,使得同一类型的 shared_ptr 智能指针可以相互赋值。
    operator*() 重载 * 号,获取当前 shared_ptr 智能指针对象指向的数据。
    operator->() 重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。
    swap() 交换 2 个相同类型 shared_ptr 智能指针的内容。
    reset() 当函数没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针;当为函数传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1。
    get() 获得 shared_ptr 对象内部包含的普通指针。
    use_count() 返回同当前 shared_ptr 对象(包括它)指向相同的所有 shared_ptr 对象的数量。
    unique() 判断当前 shared_ptr 对象指向的堆内存,是否不再有其它 shared_ptr 对象再指向它。
    operator bool() 判断当前 shared_ptr 对象是否为空智能指针,如果是空指针,返回 false;反之,返回 true。
  • 空的 shared_ptr 指针,其引用计数为 0,而不是 1。

    std::shared_ptr<int> p1;             //不传入任何实参
    std::shared_ptr<int> p2(nullptr);    //传入空指针 nullptr
    
  • shared_ptr 模板还提供有相应的拷贝构造函数和移动构造函数,例如:

    std::shared_ptr<int> p3 = std::make_shared<int>(10);//同p3(new int(10))
    //调用拷贝构造函数
    std::shared_ptr<int> p4(p3);//或者 std::shared_ptr<int> p4 = p3;
    //调用移动构造函数
    std::shared_ptr<int> p5(std::move(p4)); //或者 std::shared_ptr<int> p5 = std::move(p4);
    
  • shared_ptr 只对它自身类型的指针包裹器(我们把这种智能指针称作指针包裹器吧)统计引用计数:

    int* ptr = new int;
    shared_ptr<int> p1(ptr);
    shared_ptr<int> p2(ptr);//错误用法
    shared_ptr<int> p3(ptr);//错误用法
    
  • unique_ptr

    表 4 unique_ptr指针可调用的成员函数
    成员函数名 功 能
    operator*() 获取当前 unique_ptr 指针指向的数据。
    operator->() 重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。
    operator =() 重载了 = 赋值号,从而可以将 nullptr 或者一个右值 unique_ptr 指针直接赋值给当前同类型的 unique_ptr 指针。
    operator []() 重载了 [] 运算符,当 unique_ptr 指针指向一个数组时,可以直接通过 [] 获取指定下标位置处的数据。
    get() 获取当前 unique_ptr 指针内部包含的普通指针。
    get_deleter() 获取当前 unique_ptr 指针释放堆内存空间所用的规则。
    operator bool() unique_ptr 指针可直接作为 if 语句的判断条件,以判断该指针是否为空,如果为空,则为 false;反之为 true。
    release() 释放当前 unique_ptr 指针对所指堆内存的所有权,但该存储空间并不会被销毁。
    reset(p) 其中 p 表示一个普通指针,如果 p 为 nullptr,则当前 unique_ptr 也变成空指针;反之,则该函数会释放当前 unique_ptr 指针指向的堆内存(如果有),然后开始维护 p 所指堆内存。可以这样用:reset(new int)
    swap(x) 交换当前 unique_ptr 指针和同类型的 x 指针。
  • 基于 unique_ptr 类型指针不共享各自拥有的堆内存原则,因此 C++11 标准中的 unique_ptr 模板类没有提供拷贝构造函数,只提供了移动构造函数。例如

    std::unique_ptr<int> p4(new int);
    std::unique_ptr<int> p5(p4);//错误,堆内存不共享
    std::unique_ptr<int> p5(std::move(p4));//正确,调用移动构造函数
    
  • 释放规则

    //指定 default_delete 作为释放规则
    std::shared_ptr<int> p6(new int[10], std::default_delete<int[]>());
    //自定义释放规则
    void deleteInt(int*p) {
        delete []p;
    }
    //初始化智能指针,并自定义释放规则
    std::shared_ptr<int> p7(new int[10], deleteInt);
    
  • weak_ptr

    • weak_ptr是一种 辅助 shared_ptr 的指针
    • 比如用一个 shared_ptr 初始化 weak_ptr 后,
    • 该 shared_ptr 对象 reset() 自己后, 该对象.use_count() 永远返回零而不管实际的引用数如何
    • 因为她希望别人把她当做"指针"来对待而不是"对象" ,但该对象里面仍然有记录这实际的引用数的成员;
    • 仍然可以通过 weak_ptr 来访问到这个成员… … … 一个字: 离谱
      int* ptr = new int;
      shared_ptr<int> p3(ptr);
      shared_ptr p1(p3);
      shared_ptr p2(p1);
      weak_ptr wp(p1);// 注意  p1.reset(); 放在此句后面
      p1.reset();
      cout << boolalpha << endl;
      cout << wp.use_count() << endl;//2
      cout << p1.use_count() << endl;//0
      
    表 5 weak_ptr指针可调用的成员方法
    成员方法 功 能
    operator=() 重载 = 赋值运算符,是的 weak_ptr 指针可以直接被 weak_ptr 或者 shared_ptr 类型指针赋值。
    swap(x) 其中 x 表示一个同类型的 weak_ptr 类型指针,该函数可以互换 2 个同类型 weak_ptr 指针的内容。
    reset() 将当前 weak_ptr 指针置为空指针。
    use_count() 查看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量。
    expired() 判断当前 weak_ptr 指针为否过期(指针为空,或者指向的智能指针记录的堆内存已经被释放)。
    lock() 如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。(只是返回并不会增加引用计数)
    • weak_ptr 类型指针并不会影响所指 shared_ptr 内部统计的引用计数。
    • weak_ptr<T> 模板类没有重载 * 和 -> 运算符,因此 weak_ptr 类型指针只能访问某一 shared_ptr 指针指向的堆内存空间,无法对其进行修改。

三. 函数适配器

  • 函数符的概念:
    正如STL定义了容器和 iterator 的概念一样,它也定义了函数符(functor)的概念。
    生成器(generator)是不用参数就可以调用的函数符。
    一元函数(unary function)是用一个参数可以调用的函数符。
    二元函数(binary function)是用两个参数可以调用的函数符。
    例如,提供给for_each()的函数符应该是一元函数,因为它每次用于一个容器元素。当然,这些概念都有相应的改进版本:
    返回bool值的一元函数是谓词(predicate).
    返回bool值的二元函数是二元谓词(binary predicate).

    下面的代码删除容器中所有大于100的元素

    bool tooBig(int n){ return n > 100; }
    list<int> scores;
    ...
    scores.remove_if(tooBig);
    

    二元函数可用函数对象包裹成一元函数

    template<class T>
    class TooBig
    {
    private:
        T cutoff;
    public:
        TooBig(const T & t) : cutoff(t) {}
        bool operator()(const T & v) { return v > cutoff; }
    };
    
  • 自定义函数适配器

    //1、自定义unary_function
    //只要类继承了my_unary_function类,就可以被下面的AdaptorNot适配。
    template <typename arg, typename res>
    struct my_unary_function
    {
        typedef arg argument_type;
        typedef res result_type;//有这两个类型成员的称为自适应函数对象
    };
    template<class Arg, class Ret>
    class my_fun: public my_unary_function<Arg, Ret>
    {
        my_fun() {};
        Ret operator()(Arg arg)
        {
            Ret mybool;
            //do something
            return mybool;
        }
    
    };
    //2、改写带模板的函数适配器
    template <typename T>
    class AdaptorNot
    {
    protected:
        T opt;
    
    public:
        AdaptorNot(T x) : opt(x) {}
        //重载了括号
        typename T::result_type operator()(typename T::argument_type flag)
        {
    	typename T::result_type ret;
    	//do something
            return !opt(flag);
        }
    };
    

四. STL容器

Sequence containers:

1. array

  • 和其它容器不同,array 容器的大小是固定的

  • array<T,N> 类模板中,T 用于指明容器中的存储的具体数据类型,N 用于指明容器的大小,需要注意的是,这里的 N 必须是编译时能确定的常量,不能用变量表示。

    std::array<double, 10> values {0.5,1.0,1.5,,2.0};
    

    这里只初始化了前 4 个元素,剩余的元素都会被初始化为 0.0。

  • 当两个 array 容器满足大小相同并且保存元素的类型相同时,两个 array 容器可以直接直接做赋值操作,即将一个容器中的元素赋值给另一个容器。

  • 在满足以上 2 个条件的基础上,如果其保存的元素也支持比较运算符,就可以用任何比较运算符直接比较两个 array 容器。

    表 6 array容器成员函数汇总
    成员函数 功能
    begin() 返回指向容器中第一个元素的随机访问迭代器。
    end() 返回指向容器最后一个元素之后一个位置的随机访问迭代器,通常和 begin() 结合使用。
    rbegin() 返回指向最后一个元素的随机访问迭代器。
    rend() 返回指向第一个元素之前一个位置的随机访问迭代器。
    cbegin() 和 begin() 功能相同,只不过在其基础上增加了 const 属性,不能用于修改元素。
    cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
    crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
    crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
    size() 返回容器中当前元素的数量,其值始终等于初始化 array 类的第二个模板参数 N。
    max_size() 返回容器可容纳元素的最大数量,其值始终等于初始化 array 类的第二个模板参数 N。
    empty() 判断容器是否为空,和通过 size()==0 的判断条件功能相同,但其效率可能更快。
    at(n) 返回容器中 n 位置处元素的引用,该函数自动检查 n 是否在有效的范围内,如果不是则抛出 out_of_range 异常。
    front() 返回容器中第一个元素的直接引用,该函数不适用于空的 array 容器。
    back() 返回容器中最后一个元素的直接应用,该函数同样不适用于空的 array 容器。
    data() 返回一个指向容器首个元素的指针。利用该指针,可实现复制容器中所有元素等类似功能。
    fill(val) 将 val 这个值赋值给容器中的每个元素。
    array1.swap(array2) 交换 array1 和 array2 容器中的所有元素,但前提是它们具有相同的长度和类型。

2. vector

  • vector 常被称为向量容器,因为该容器擅长在尾部插入或删除元素,在常量时间内就可以完成,时间复杂度为O(1);而对于在容器头部或者中部插入或删除元素,则花费时间要长一些(移动元素需要耗费时间),时间复杂度为线性阶O(n)。<< 大O表示法 >>
  • 初始化vector
    std::vector<double> values(20);//values 容器开始时就有 20 个元素,它们的默认初始值都为 0。
    std::vector<double> values(20, 1.0);//第二个参数指定了所有元素的初始值,因此这 20 个元素的值都是 1.0。
    int num=20;
    double value =1.0;
    std::vector<double> values(num, value);
    std::vector<char>value1(5, 'c');
    std::vector<char>value2(value1);
    int arr[]={1,2,3};
    std::vector<int>values(arr, arr+2);//values 将保存{1,2}
    std::vector<int>value1{1,2,3,4,5};
    std::vector<int>value2(std::begin(value1),std::begin(value1)+3);//value2保存{1,2,3}
    
    表 7 vector 容器的成员函数
    函数成员 函数功能
    begin() 返回指向容器中第一个元素的迭代器。
    end() 返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin() 结合使用。
    rbegin() 返回指向最后一个元素的迭代器。
    rend() 返回指向第一个元素所在位置前一个位置的迭代器。
    cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
    cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
    crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
    crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
    size() 返回实际元素个数。
    max_size() 返回元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。
    resize() 改变实际元素的个数。
    capacity() 返回当前容量。
    empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
    reserve() 增加容器的容量。
    shrink _to_fit() 将内存减少到等于当前元素实际所使用的大小。
    operator[ ] 重载了 [ ] 运算符,可以向访问数组中元素那样,通过下标即可访问甚至修改 vector 容器中的元素。
    at() 使用经过边界检查的索引访问元素。
    front() 返回第一个元素的引用。
    back() 返回最后一个元素的引用。
    data() 返回指向容器中第一个元素的指针。
    assign() 用新元素替换原有内容。
    push_back() 在序列的尾部添加一个元素。
    pop_back() 移出序列尾部的元素。
    insert() 在指定的位置插入一个或多个元素。
    erase() 移出一个元素或一段元素。
    clear() 移出所有的元素,容器大小变为 0。
    swap() 交换两个容器的所有元素。
    emplace() 在指定的位置直接生成一个元素。
    emplace_back() 在序列尾部生成一个元素。
  • 还需注意的是,如果调用 reserve() 来增加容器容量,之前创建好的任何迭代器(例如开始迭代器和结束迭代器)都可能会失效,这是因为,为了增加容器的容量,vector<T> 容器的元素可能已经被复制或移到了新的内存地址。所以后续再使用这些迭代器时,最好重新生成一下。

3. deque

  • 双端队列, 如其名, 头部和尾部都能添加元素; 那么如何管理给元素分配的内存并能合理的增删改查呢 ?
    • 1.在堆上分配size大小的指针数组, 记录该数组头和尾和的地址b e,并记录元素的beg end cur位置, 在size/2的位置 开始添加元素. 这样,尽管给元素分配的内存都不连续(数组中每个指针指向的内存), 但这些指针变量本身的地址却记录在序列的数组内. 这样就足以增删改查
      • 如果beg==b, 或 end==e ,那么就需要重新分配更大的指针数组, 并把新的指针数组内的各个指针指向原来的指针数组内的各个指针指向的内存. 元素数据没变, 包裹着元素的容器却变了
    • 2.使用链表, 双向链表. 使用链表总是能使大数量的数据能够形成逻辑上自洽的,,,等结构
  • 与vector 相比, 添加了在前端增加元素的front相关的方法, 删除了capacity()、reserve() 和 data() 成员函数。
    • 这就意味着 deque 容器几乎完全自行管理内存
  • 双端队列都不能保证存储其所有要素在连续的储存地点
  • 涉及频繁插入或清除元素操作时可选用 deque 容器

4. list

  • 内部使用双向链表来存储数据
  • 表 8 list 容器可用的成员函数
    成员函数 功能
    begin() 返回指向容器中第一个元素的双向迭代器。
    end() 返回指向容器中最后一个元素所在位置的下一个位置的双向迭代器。
    rbegin() 返回指向最后一个元素的反向双向迭代器。
    rend() 返回指向第一个元素所在位置前一个位置的反向双向迭代器。
    cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
    cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
    crbegin()  和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
    crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
    empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
    size() 返回当前容器实际包含的元素个数。
    max_size() 返回容器所能包含元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。
    front() 返回第一个元素的引用。
    back() 返回最后一个元素的引用。
    assign() 用新元素替换容器中原有内容。
    emplace_front() 在容器头部生成一个元素。该函数和 push_front() 的功能相同,但效率更高。
    push_front() 在容器头部插入一个元素。
    pop_front() 删除容器头部的一个元素。
    emplace_back() 在容器尾部直接生成一个元素。该函数和 push_back() 的功能相同,但效率更高。
    push_back() 在容器尾部插入一个元素。
    pop_back() 删除容器尾部的一个元素。
    emplace() 在容器中的指定位置插入元素。该函数和 insert() 功能相同,但效率更高。
    insert()  在容器中的指定位置插入元素。
    erase() 删除容器中一个或某区域内的元素。
    swap() 交换两个容器中的元素,必须保证这两个容器中存储的元素类型是相同的。
    resize() 调整容器的大小。
    clear() 删除容器存储的所有元素。
    splice() 将一个 list 容器中的元素插入到另一个容器的指定位置。
    remove(val) 删除容器中所有等于 val 的元素。
    remove_if() 删除容器中满足条件的元素。
    unique() 删除容器中相邻的重复元素,只保留一个。
    merge() 合并两个事先已排好序的 list 容器,并且合并之后的 list 容器依然是有序的。
    sort() 通过更改容器中元素的位置,将它们进行排序。
    reverse() 反转容器中元素的顺序。
  • 初始化如同 vector 的初始化方式

5. forward_list

  • 内部使用单向链表来存储数据
  • 步进还可以使用advance();
  • distance(),传入两个迭代器, 计算它们之间的元素个数;

Associative containers:

  • 和序列式容器不同的是,关联式容器在存储元素时还会为每个元素在配备一个键,整体以键值对的方式存储到容器中。相比前者,关联式容器可以通过键值直接找到对应的元素,而无需遍历整个容器。另外,关联式容器在存储元素,默认会根据各元素键值的大小做升序排序。
    相比其它类型容器,关联式容器查找、访问、插入和删除指定元素的效率更高

1. map

  • pair: 单个键值对容器 在#include <utility>中定义

    pair kl("hello", 20);
    
  • map: 多个键值对容器, 并且能指定排序规则

  • 使用 map 容器存储的各个键值对,键的值既不能重复也不能被修改
    map 模板头:

    template <typename _Key, typename _Tp, typename _Compare = std::less<_Key>,
        typename _Alloc = std::allocator<std::pair<const _Key, _Tp> > >
      class map {...}
    

    第三个参数是排序规则 默认是 std::less<_Key> 升序 一般是依据键 来排序
    初始化:

    std::map<std::string, int>myMap{ {"some c++ things", 10}, {"python things", 20} };
    std::map newMap(++myMap.begin(), myMap.end());
    cout << newMap.begin()->first << endl; //some c++ things
    
    表 9 C++ map容器常用成员方法
    成员方法 功能
    begin() 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
    end() 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
    rbegin() 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
    rend() 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
    cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
    cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
    crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
    crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。
    find(key) 在 map 容器中查找键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
    lower_bound(key) 返回一个指向当前 map 容器中第一个 >= key 的键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
    upper_bound(key) 返回一个指向当前 map 容器中第一个 > key 的键值对的迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
    equal_range(key) 该方法返回一个包含 2 个双向迭代器 pair 对象,其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对(map 容器键值对唯一,因此该范围最多包含一个键值对)。
    empty()  若容器为空,则返回 true;否则 false。
    size() 返回当前 map 容器中存有键值对的个数。
    max_size() 返回 map 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。
    operator[] map容器重载了 [] 运算符,只要知道 map 容器中某个键值对的键的值的引用,就可以向获取数组中元素那样,通过键直接获取对应的值。如果容器中没有这个键,就增加这个键,其值:基本数据类型,则值为 0;如果是 string 类型,其值为 "",即空字符串
    at(key) 找到 map 容器中 key 键对应的值,如果找不到,该函数会引发 out_of_range 异常。
    insert() 向 map 容器中插入键值对。
    erase() 删除 map 容器指定位置、指定键(key)值或者指定区域内的键值对。后续章节还会对该方法做重点讲解。
    swap() 交换 2 个 map 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。
    clear() 清空 map 容器中所有的键值对,即使 map 容器的 size() 为 0。
    emplace() 在当前 map 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。
    emplace_hint() 在本质上和 emplace() 在 map 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。
    count(key) 在当前 map 容器中,查找键为 key 的键值对的个数并返回。注意,由于 map 容器中各键值对的键的值是唯一的,因此该函数的返回值最大为 1。
  • equal_range(key) 用法示例:

    #include <iostream>
    #include <utility>  //pair
    #include <map>      // map
    #include <string>       // string
    using namespace std;
    int main() {
        //创建并初始化 map 容器
        std::map<string, string>myMap{ {"STL教程","http://c.biancheng.net/stl/"},
                                       {"C语言教程","http://c.biancheng.net/c/"},
                                       {"Java教程","http://c.biancheng.net/java/"} };
        //创建一个 pair 对象,来接收 equal_range() 的返回值
        pair <std::map<string, string>::iterator, std::map<string, string>::iterator> myPair = myMap.equal_range("C语言教程");
        //通过遍历,输出 myPair 指定范围内的键值对
        for (auto iter = myPair.first; iter != myPair.second; ++iter) {
            cout << iter->first << " " << iter->second << endl;
        }
        return 0;
    }
    

    和 lower_bound(key)、upper_bound(key) 一样,该方法也更常用于 multimap 容器,因为 map 容器中各键值对的键的值都是唯一的,因此通过 map 容器调用此方法,其返回的范围内最多也只有 1 个键值对。

    用[]修改pair 中的 second:

    #include <iostream>
    #include <map>      // map
    #include <string>   // string
    using namespace std;
    int main() {
        //创建空 map 容器
        std::map<string, string>myMap;
        myMap["STL教程"]="http://goodstl/somthingelse/";
        myMap["Python教程"] = "http://goodcode/python/";
        myMap["STL教程"] = "http://goodstl/stl/";//假设没有"STL教程"这个键的话,那么这就是添加操作
        for (auto i = myMap.begin(); i != myMap.end(); ++i) {
            cout << i->first << " " << i->second << endl;
        }
        return 0;
    }
    

    insert() 插入键值对; 也能以初始化列表的方式传入

    • 当该方法将键值对成功插入到 map 容器中时,其返回的迭代器指向该新插入的键值对,同时 bool 变量的值为 true;
    • 当插入失败时,则表明 map 容器中存在具有相同键的键值对,此时返回的迭代器指向此具有相同键的键值对,同时 bool 变量的值为 false。
    std::map<string, string>myMap;
    pair p{"somekey", "somevalue"};
    auto ret = myMap.insert(p);//返回 std::pair<iterator, bool> 类型
    cout << ret.second << endl;
    cout << ret.first->first <<" "<<ret.first->second<< endl;
    

    emplace() 或者 emplace_hint() 效率比 insert() 高;可直接用键,值 来传参

    mymap.emplace("someKey", "http://c.someValue.net/");
    

    注意: 可以用初始化列表初始化 但不能使用 pair 类型初始化

    multimap<string, string>mymultimap{pair("zhello", "world")};
    map<string, string>mymap{pair("zhello", "world")};
    map<string, string>mymap(pair("zhello", "world"));//error
    //这就意味着没有重载pair类型版本的构造函数, 而是在初始化列表版本的构造函数里对pair类型做处理
    

2. multimap

  • 和 map 容器的区别在于,multimap 容器中可以同时存储多(≥2)个键相同的键值对。(值也可以相同)
  • emplace() 返回该标识该键值对的迭代器

3. set

  • 成员方法类似map容器, 但键和值都归为同一个值,同一类型 合体了属于是. 下面是定义:
    template<typename _Key, typename _Compare = std::less<_Key>,
       typename _Alloc = std::allocator<_Key> >
    class set
    {
    ...
      typedef _Key     key_type;
      typedef _Key     value_type;
    ...
    }
    
  • set 容器存储的元素必须互不相等。

4. multiset

  • multiset 容器可以存储多个相同的元素。

5. 自定义排序规则

  • 先看看冒泡排序:
    int sortNum[] {1, 8, 5, 4, 3};
    auto sortNumSize = end(sortNum) - begin(sortNum);
    for (int i = sortNumSize - 1; i >= 0; i--)
    {
        for (int j = i; j >= 0 ; --j)
        {
            if (sortNum[i] < sortNum[j]) //为true时交换
            {
                //交换a与b的位置
                auto temp = sortNum[i];
                sortNum[i] = sortNum[j];
                sortNum[j] = temp;
            }
        }
    }
    for (int i = 0; i < sortNumSize; i++)
    {
        cout << sortNum[i] << "," ;
    }
    
    为true时交换, 从代码上看, 如果是sortNum[i] < sortNum[j]则是升序, 而如果是sortNum[i] > sortNum[j]则是降序. 完全可以把这部分代码独立出去 作为排序规则;
    你来指定规则, 而我按你的规则为true时来排序,参见<<三. 函数适配器>>这就是STL容器排序和排序规则分离的大概实现思路.

Unordered associative containers:

  • 无序关联式容器,又称哈希容器。和关联式容器一样,此类容器存储的也是键值对元素;不同之处在于,关联式容器默认情况下会对存储的元素做升序排序,而无序关联式容器不会。
  • set容器的底层实现是红黑树, 而所有无序容器的底层实现都采用的是哈希表存储结构。
  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值