美文网首页
第十六章 string类和标准模板库(一)

第十六章 string类和标准模板库(一)

作者: 鬼枭嗜 | 来源:发表于2019-04-26 22:24 被阅读0次

    16.1 string类

    string实际上是basic_string<char>的一个typedef,同时省略了与内存管理相关的参数。size_type是一个依赖于实现的整型,是在string中定义的。string类将string::npos定义为字符串的最大长度,通常为unsigned int的最大值。另外,使用缩写NBTS(null-terminated string)来表示以空字符结束的字符串——传统的C字符串。

    string(string && str)类似于复制构造函数,它不保证将str视为const。在有些情况下,编译器可使用它而不是复制构造函数,以优化性能。

    同时列表初始化适用于string类

    string类输入

    对于类,需要知道有哪些输入方式可用。对于C风格字符串,有3种方式:

                char info[100];

        cin >> info;//读取一个词

        cin.getline(info,100);//读取一行,遇到\n结束,并丢弃\n

        cin.get(info, 100);//读取一行,遇到\n结束,将\n丢弃在输入队列中

    对于string对象,有两种方式:

        string stuff;

        cin >> stuff;//读取一个词

        getline(cin,stuff);//读取一行,遇到\n结束并丢弃\n

                    两个版本的getline()都有一个可选参数,用于指定使用哪个字符来确定输入的边界。功能上getline将自动调整大小。

    string版本的getline函数从输入中读取字符,并将其存储到目标string中,直到遇到下面三种情况之一:

    1.到达文件尾,在这种情况下,输入流的eofbit将被设置,这意味着fail()和eof()都将返回true。

    2.遇到分界字符(默认为\n),在这种情况下,它把分界字符从输入流中删除,但不存储它。

    3.读取的字符数达到最大的允许值(string::npos和可供分配的内存字节数中较小的一个),在这种情况下,将设置输入流的failbit,这意味着方法fail将返回true。

    比较字符串,对于每个关系运算符,都以三种方式被重载,以便能够将string对象与另一个string对象、C-风格字符串进行比较,并能够将C-风格字符串与string对象进行比较。

    可以以多种不同的方式在字符串中搜索给定的字符串或字符。

    size_type find(const string & str, size_type pos = 0) const 从pos位置开始查找str。如果找到子字符串str,返回首次出现时其首字母的索引,否则返回string::npos

    size_type find(const char * s, size_type pos = 0) const 从pos位置开始查找s。如果找到子字符串s,返回首次出现时其首字母的索引,否则返回string::npos

    size_type find(const char *s, size_type pos = 0, size_type n) 从pos开始,查找s的前n个字符组成的字符串。如果找到子字符串s,返回首次出现时其首字母的索引,否则返回string::npos

    size_type find(char ch, size_type pos = 0) const 从字符串的pos位置开始,查找字符ch。如果找到ch,返回首次出现时的索引,否则返回string::npos

    相关方法 rfind()   find_first_of()   find_last_of()  find_first_not_of() find_last_not_of()

    上述方法重载函数特征标都与find()方法相同。

    rfind()方法查找子字符串或字符最后一次出现的位置。find_first_of() 方法在字符串中查找参数中任何一个字符首次出现的位置。find_last_of()同上,只不过他查找的是最后一次出现的位置。find_first_not_of()方法在字符串中查找第一个不包含在参数中的字符,find_last_not_of()同理查找最后一个不包含在参数中的字符。

    方法capacity()返回当前分配给字符串的内存块大小,reserve()方法让您能够请求内存块的最小长度。

    方法c_str() //返回一个指向C风格字符串的指针。

     open()方法要求使用一个C风格字符串作为参数,幸运的是,c_str方法返回一个指向C风格字符串的指针,该C风格字符串的内容和用于调用c_str方法的string对象相同。因此,可以这样做:

    string filename;

    cin >> finlename;

    fout.open(filename.c_str())

    智能指针模板类

    智能指针是行为类似于指针的类对象。

    auto_ptr是C++98提供的解决方案,C++11已将其摒弃,并提供了另外两种解决方案。然而,虽然auto_ptr被摒弃,但它已经使用了多年;同时,如果编译器不支持其他两种解决方案,auto_ptr将是唯一的解决方案。

    auto_ptr、unique_ptr和shared_ptr这三个智能指针模板都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当智能指针过期,其析构函数将使用delete来释放内存。因此,如果将new返回的地址赋给这些对象,将无需记住稍后释放这些内存:在智能指针过期时,这些内存将自动被释放。

    要创建智能指针对象,必须包含头文件memory,该文件模板定义。然后使用通常的模板语法来实例化所需类型的指针。例如,模板auto_ptr包含如下构造函数:

        template<class X> class auto_ptr{

        public:

          explicit auto_ptr(X *p = 0) throw(); 

        ...};

      throw()意味着构造函数不会引发异常;与auto_ptr一样,throw()也被摒弃。因此,请求X类型的auto_ptr将获得一个指向X类型的auto_ptr:

        auto_ptr<double> pd(new double);

        auto_ptr<string> ps(new string);

    new double是new返回的指针,指向新分配的内存块。它是构造函数auto_ptr的参数,即对应于原型中参数p的实参。同样,new string也是构造函数的实参。其他两种智能指针使用同样的语法:

    unique_ptr pdu(new double);

        shared_ptr<string> pss(new string);

      智能指针模板位于名称空间std中。下面的程序演示了如何使用全部三种智能指针。每个智能指针都放在同一个代码块内,这样离开代码的时候,指针将过期。Report类使用方法报告对象的创建和销毁。

    #include <iostream>

    #include <memory>

    class Report{

    private:

        std::string str;

    public:

        Report(const std::string & s):str(s){

            std::cout << "创建了对象" << str <<"! \n";

        }

        ~Report(){

            std::cout << "销毁了对象" << str << "! \n";

        }

        void comment()const{

            std::cout << str << "\n";

        }

    };

    int main(int argc, char * argv[])

    {

        using namespace std;

        {

            auto_ptr<Report> ps(new Report("using auto_ptr"));

            ps->comment();

        }

        {

            shared_ptr<Report> ps(new Report("using shared_ptr"));

            ps->comment();

        }

        {

            unique_ptr<Report> ps(new Report("using unique_ptr"));

            ps->comment();

        }

        return 0;

    }

    输出结果:

    创建了对象using auto_ptr!

    using auto_ptr

    销毁了对象using auto_ptr!

    创建了对象using shared_ptr!

    using shared_ptr

    销毁了对象using shared_ptr!

    创建了对象using unique_ptr!

    using unique_ptr

    销毁了对象using unique_ptr!

    所有智能指针都有一个explicit构造函数,该构造函数将指针作为参数。因此不需要自动将指针转换为智能指针对象。

    由于智能指针模板定义方式,智能指针对象的很多方面都类似于常规指针。

    shared_ptr<double> pd;

    double *p_reg = new double;

    pd = p_reg;        //not allowed隐式转换

    pd = shared_ptr<double>(p_reg);//allowed显式转换

    shared_ptr<double> pshared = p_reg;//not allowed隐式转换

    share_ptr<double> pshared(p_reg);//allowed直接调用构造函数

    三种智能指针都应该避免的陷进:

      string vacation(“I wandered lonely as a cloud.”);

      shared_ptr<string> pvac(&vacation);

    pvac过期时,程序会把delete运算符用于非堆内存,这是错误的。

    为何使用三种智能指针呢?实际上有4种,另外一种是weak_ptr,但我们不讨论它。为何摒弃auto_ptr呢?

      先来看下面的复制语句:

        std::auto_ptr<std::string> ps(new std::string("Good morning!"));

        std::auto_ptr<std::string> vocation;

        vocation = ps;

      上述赋值语句将完成什么工作呢?如果ps和vocation是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序将视图删除同一个对象两次——一次是ps过期时,另一次是vocation过期时。要避免这种问题,方法有多种。

        *定义赋值运算符,使之执行深度复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本。

        *建立所有权(ownership)概念,对于特定的值,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的析构函数会删除该对象。然后,让赋值操作转让所有权。这就是用于auto_ptr和unique_ptr的策略,但unique_ptr的策略更严格。

        *创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数(reference counting)。例如,赋值时,计数将加1,而指针过期时,计数将减1 。仅当最后一个指针过期时,才调用delete。这是shared_ptr采用的策略。

      当然同样的策略也适用于复制构造函数。

    每种方法都有用途。下面的程序清单是一个不适合auto_ptr的示例:

    #include <iostream>

    #include <memory>

    #include <string>

    int main(int argc, char * argv[])

    {

        using namespace std;

        auto_ptr<string> films[5] = {

            auto_ptr<string> (new string("卡卡西")),

            auto_ptr<string> (new string("佐助")),

            auto_ptr<string> (new string("宇智波鼬")),

            auto_ptr<string> (new string("凯")),

            auto_ptr<string> (new string("宁次"))

        };

        auto_ptr<string> pwin;

        pwin = films[2];

        for (int i = 0; i < 5; i++) {

            cout << *films[i] << endl;

        }

        cout << *pwin << endl;

        cin.get();

        return 0;

    }

    输出结果:

    卡卡西

    佐助

    (lldb)  //程序崩溃了

    程序崩溃的原因在于,下面的语句将所有权从film[2]转让给pwin:

        pwin = films[2]; //films[2]丢失所有权

      这导致films[2]不再引用该字符串。在auto_ptr放弃对象的所有权后,便可能使用它来反问该对象。当程序打印films[2]指向的字符串时,却发现这是一个空指针。

      如果在上面的程序清单中用shared_ptr代替auto_ptr,则程序运行正常:

    输出结果:

    卡卡西

    佐助

    宇智波鼬

    宁次

    宇智波鼬

    这次pwin和films[2]指向同一个对象,而引用计数从1增加到2 。在程序末尾,后声明的pwin首先调用其析构函数,该析构函数将引用计数降低到1 。然后,shared_ptr数组的成员被释放,对films[2]调用析构函数时,将引用计数降低到0,并释放以前分配的空间。

      unique_ptr与auto_ptr一样,也采用所有权模型。但使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译阶段出现错误。

    unique_ptr为何优于auto_ptr 

       先看下面的语句:

        auto_ptr<string> p1(new string("Good morning!"));  //#1

        auto_ptr<string> p2;                // #2

        p2 = p1;                      //  #3

      在语句#3中,p2接管了string对象的所有权后,p1的所有权将会被剥夺。这是件好事,可防止p1和p2的析构函数试图删除同一个对象;但如果程序随后试图使用p1,这将是坏事,因为p1不再指向有效的数据。

      下面来看使用unique_ptr的情况:

        unique_ptr<string> p3(new string("auto"));    // #4

        uniqie_ptr<string> p4;              //#5

        p4 = p3;                     // #6

      编译器认为语句#6非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全)。

      但有时候,将一个智能指针赋给另一个并不会留下危险的悬挂指针。假设有如下的函数定义:

        unique_ptr<string> demo(const char * s){

          unique_ptr<string> temp(new string(s));

          return temp;

        }

      并假设编写如下的代码:

        unique_ptr<string> ps;

        ps = demo("Good morning!");

    demo()返回一个临时unique_ptr,然后ps接管了原本归返回的unique_ptr所有的对象,而返回的unique_ptr被销毁。这没有问题,因为ps拥有了string对象的所有权。但这里的另一个好处是,demo()返回的临时unique_ptr很快被销毁,没有机会使用它来访问无效的数据。换句话说,没有理由禁止这种赋值。神奇的是,编译器允许这种赋值。

      总之,程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是一个临时右值,编译器允许这样做;如果unique_ptr将存在一段时间,编译器将禁止这样做:

        unsing namespace std;

        unique_ptr<string> pu1(new string("Hello world!"));

        unique_ptr<string> pu2;

        pu2 = pu1;                      //#1 不允许

        unique_ptr<string> pu3;

        pu3 = unique_ptr<string>(new string("Hello!"));  //#2 允许

    语句#1将留下悬挂的unique_ptr(pu1),这可能导致危险。语句#2不会留下悬挂的unique_ptr,因为它调用unique_ptr的构造函数,该构造函数创建的临时对象在其所有权转让给pu3后就会被销毁。这种随情况而异的行为表明,unique_ptr优于允许两种赋值的auto_ptr。这也是禁止(只是一种建议,编译器并不会禁止)在容器对象中使用auto_ptr,但允许使用unique_ptr的原因。如果容器算法试图对包含unique_ptr的容器执行类似于语句#1的操作,将导致编译错误;如果算法试图执行类似于#2的操作,则不会有任何问题。而对于auto_ptr,类似于语句#1的操作可能导致不确定的行为和神秘的崩溃。

      当然,可能确实想执行类似语句#1的操作。仅当以非智能的方式使用遗弃的智能指针(如解引用时),这种赋值才不安全。要安全地重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让您能够将一个unique_ptr赋给另一个。下面是一个使用前面demo()函数的例子,该函数返回一个unique_ptr<string>对象:

        unsing namespace std;

        unique_ptr<string> ps1, ps2;

        ps1 = demo("Hello world!");

        ps2 = move(ps1);

        ps1 = demo("Good morning!");

        cout << *ps2 << ps1 << endl;

      相比于auto_ptr,unique_ptr还有另一个优点。它有一个可用于数组的变体。别忘了,必须将delete和new配对使用,将delete []和new []配对。模板auto_ptr使用delete而不是delete [],因此只能与new一起使用,而不能与new []一起使用。但unique_ptr有使用new []和delete []的版本:

        std::unique_ptr<double []> pda(new double[5]);  //将会使用delete []

      警告:使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new []分配内存时,不能使用它们。不使用new分配内存时,不能使用auto_ptr和shared_ptr;不使用new或new []分配内存时,不能使用unique_ptr。

    选择智能指针

      应使用哪种智能指针呢?如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这种情况包括:有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;两个对象包含都指向第三个对象的指针;STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译发出警告)和auto_ptr(行为不确定)。如果编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。

      如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权将转让给接受返回值的unique_ptr,而该智能指针负责调用delete。可将unique_ptr存储到STL容器中,只要不调用将一个unique_ptr复制或赋值给另一个方法或算法(如sort())。例如,可在程序中使用类似于下面的代码段,这里假设包含了正确的include和using语句:

        unique_ptr<int> make_int(int n){

          return unique_ptr<int> (new int(n));

        }

        void show(unique_ptr<int> & p){

          cout << *p << '\n';

        }

        int main(){

        ...

          vector<unique_ptr<int>> vp(size);

          for(int i = 0; i < vp.size(); i++){

            vp[i] = make_int(rand() % 1000);

          }

          vp.push_back(make_int(rand() % 1000));

          for_each(vp.begin(), vp.end(), show);

                ...

        }

      其中的push_back()调用没有问题,因为它返回一个临时unique_ptr,该unique_ptr被赋值给vp中的一个unique_ptr。另外,如果按值而不是按引用给show()传递对象,for_each()将非法,因为这将导致使用一个来自vp的非临时unique_ptr初始化pi,而这时不允许的。前面说过,编译器将发现错误使用unique_ptr的企图。

      在unique_ptr为右值时,可将其赋给shared_ptr,这与将一个unique_ptr赋给另一个需要满足的条件相同。与前面一样,在下面的代码中,make_int()返回的类型为unique_ptr<int>:

        unique_ptr<int> pup(make_int(rand() % 1000));  //允许

        shared_ptr<int> spp(pup); //不允许

        shared_ptr<int> spr(make_int(rand() % 1000)) ; //允许

      模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。shared_ptr将接管原来归unique_ptr所有的对象。

      在满足unique_ptr要求的条件时,也可使用auto_ptr,但unique_ptr是更好的选择如果编译器没有提供unique_ptr,可考虑使用BOOST库提供的scoped_ptr,它与unique_ptr类似。

    标准模板库

     STL提供了一组表示容器、迭代器、函数对象和算法的模板。容器是一个与数组类似的单元,可以存储若干个值。STL容器是同质的,即存储的值的类型相同;算法是完成特定任务(如对数组进行排序或在链表中查找特定的值)的处方;迭代器能够用来遍历容器的对象,与能够遍历数组的指针类似,是广义指针;函数对象是类似于函数的对象,可以是类对象或函数指针(包括函数名,因为函数名被用作指针)。STL使得能够构造各种容器(包括数组、队列和链表)和执行各种操作(包括搜索、排序和随机排列)。

      STL不是面相对象的编程,而是一种不同的编程模式——范型编程(generic programming)。

     3.1 模板类vector

      在计算机中,矢量(vector)对应数组,而不是数学矢量(在数学中,可以使用N个分量来表示N维数学矢量,因此从这方面讲,数学矢量类似一个N维数组。然而,数学矢量还有一些计算机矢量不具备的其他特征,如内乘积和外乘积)。计算机矢量存储了一组可随机访问的值,即可以使用索引来直接访问矢量的第10个元素,而不必首先访问前面第9个元素。在头文件vector中定义了vector模板。

      要创建vector模板对象,可使用通用的<type>表示法来指出要使用的类型。另外,vector模板使用了动态内存分配,因此可以用初始化参数来指出需要多少矢量:

        #include <vector>

        using namespace std;

        vector<int> ratings(5);   //创建一个包含5个int值的vector对象

        int n;

        cin >> n;

        vector<double> scores(n);  //创建一个包含n个double值的vector对象

      由于运算符[]被重载,因此创建vector对象后,可以使用通用的数组表示法来访问各个元素:

        ratings[0] = 9;

        for(int i = 0; i < n; n++){

          cout << scores[i] << endl;

        }

    分配器

      与string类相似,各种STL容器模板都接受一个可选的模板参数,该参数指定使用哪个分配器对象来管理内存。例如,vector模板的开头与下面类似:

        template<class T, class Allcoator = allocator<T>>

          class vector{...};

      如果省略该模板参数的值,则容器模板将默认使用allocator<T>类。这个类使用new和delete。

      下面的程序是一个要求不高的应用程序,它使用了这个类。该程序创建两个vector对象——一个是int规范,另一个是string规范,它们都包含5个元素。

    #include <iostream>

    #include <string>

    #include <vector>

    #include <iomanip>

    const int NUM = 5;

    int main(int argc, char * argv[])

    {

        using namespace std;

        vector<int> scores(NUM);

        vector<string> students(NUM);

        cout << "依次输入五个学生的成绩和名字\n";

        for (int i = 0; i < NUM; i++) {

            cout << "请输入第"<< i+1 <<"个学生名字:";

            cin >> students[i];

            while (cin.get() != '\n');

            cout << "请输入学生的成绩:";

            cin >> scores[i];

            while (cin.get() != '\n');

        }

        cout << "\n这是学生的成绩:\n";

        for (int i = 0; i < NUM; i++) {

            cout << setw(20) << right << students[i] << ": " << setw(4) << right << scores[i] << endl;

        }

        return 0;

    }

    输出结果:

    依次输入五个学生的成绩和名字

    请输入第1个学生名字:xiaohong

    请输入学生的成绩:100

    请输入第2个学生名字:xiaoming

    请输入学生的成绩:98

    请输入第3个学生名字:xiaoyu

    请输入学生的成绩:88

    请输入第4个学生名字:xiaonan

    请输入学生的成绩:85

    请输入第5个学生名字:xiaohua

    请输入学生的成绩:68

    这是学生的成绩:

                xiaohong:  100

                xiaoming:  98

                  xiaoyu:  88

                xiaonan:  85

                xiaohua:  68

    可对矢量执行的操作

      所有的STL容器都提供了一些基本方法,其中包括size()——返回容器中的元素数目、swap()——交换两个容器的内容、begin()——返回一个指向容器第一个元素的迭代器、end()——返回一个表示超过容器尾的迭代器。

    迭代器是一个广义指针。事实上,它可以是指针,也可以是一个可对其执行类似指针的操作——如解除引用(如operator*())和递增(如operator++())——的对象。通过将指针广义化为迭代器,让STL能够为各种不同的容器类(包括那些简单指针无法处理的类)提供统一的接口。每个容器都定义了一个合适的迭代器,该迭代器的类型是一个名为iterator的typedef,其作用域为整个类。例如,要为vector的double类型规范声明一个迭代器,可以这样做:

    vector::iterator pd;

      假设scores是一个vector<double>对象:

        vector<double> scores;

      则可以使用迭代器pd执行这样的操作:

        pd = scores.begin();

        *pd = 22.3;

        ++pd;

      迭代器的行为就像指针。顺便说一句,还有一个C++11自动类型推断很有用的地方。例如,可以不这样做

        vector<double>::iterator pd = scores.begin();

        而这样做:

        auto pd = scores.begin();

      什么是超过结尾(past-the-end)呢?它是一种迭代器,指向容器最后一个元素后面的那个元素。这与C-风格字符串最后一个字符后面的空字符类似,只是空字符是一个值,而“超过结尾”是一个指向元素的指针(迭代器)。end()成员函数标识超过结尾的位置。如果将迭代器设置为容器的第一个元素,并不断递增,则最终它将到达容器的结尾,从而遍历容器的内容。例如,假设students是一个vector<string>对象,则可以使用下面的方法遍历students:

        for(auto pd = students.begin(); pd != students.end(); pd++)

          cout << *pd << endl;

      所有的容器都包含上面讨论的方法。vector模板类也包含一些只有某些STL容器才有的方法。push_back()是一个方便的方法,它将元素添加到矢量末尾。这样做时,它将负责内存管理,增加矢量的长度,使之能够容纳新的成员。这意味着可以这样编写代码:

        vector<double> scores;

        double temp;

        while(cin >> temp && temp >= 0)

          scores.push_back(temp);

        cout << "You entered " << scores.size() << " scores.\n";

      每次循环都给scores对象增加一个元素。在编写或运行程序时,无需了解元素的数目。只要能够取得足够的内存,程序就可以根据需要增加scores的长度。

      erase()方法删除矢量中给定区间的元素。它接受两个迭代器参数,这些参数定义了要删除的区间。了解STL如何使用两个迭代器来定义区间至关重要。第一个迭代器指向区间的起始处,第二个迭代器位于区间终止处的后一个位置。例如,下述代码删除第一个和第二个元素,即删除begin()和begin()+1指向的元素(由于vector提供了随机访问功能,因此vector类迭代器定义了诸如begin()+2等操作):

        scores.erase(scores.begin(), scores.begin() + 2);

      如果it1和it2是迭代器,则STL文档使用[it1, it2)来表示从it1到it2(不包括it2)的区间。因此,区间[begin(), end()]将包括集合的所有内容,而区间[p1, p1)为空。[)表示法并不是C++的组成部分,因此不能在代码中使用,而只能出现在文档中。

      insert()方法的功能与erase()相反。它接受三个迭代器参数,第一个参数指定了新元素的插入位置,第二个和第三个迭代器参数定义了被插入区间,该区间通常是另一个容器对象的一部分。例如,下面的代码将矢量new_v中除第一个元素外的所有元素插入到old_v矢量的第一个元素前面

        vecor<int> old_v;

        vector<int> new_v;

        ...

        old_v.insert(old_v.begin(), new_v.begin() + 1, new_v.end());

      顺便说一句,拥有超尾元素是非常方便的,因为这使得在矢量尾部附加元素非常简单。下面的代码将新元素插入到old_v.end()前面,即矢量最后一个元素的后面:

        old_v.insert(old_v.end(), new_v.begin() + 1, new_v.end());

    下面的程序演示了size()、begin()、end()、push_back()、erase()和insert()的用法:

    #include <iostream>

    #include <string>

    #include <vector>

    struct Review{

        std::string title;

        int rating;

    };

    bool FillReview(Review & rr);

    void ShowReview(const Review & rr);

    int main(int argc, char * argv[])

    {

        using namespace std;

        vector<Review> books;

        Review temp;

        while (FillReview(temp)) {

            books.push_back(temp);

        }

        size_t num = books.size();

        if (num > 0) {

            cout << "谢谢。下面是你输入的:\n" << "Rating\tBook\n";

            for (int i = 0; i < num; i++) {

                ShowReview(books[i]);

            }

            cout << "重复:\n" << "Rating\tBook\n";

            for (auto pd = books.begin(); pd != books.end(); pd++) {

                ShowReview(*pd);

            }

            vector<Review> oldlist(books);

            if (num > 3) {

                books.erase(books.begin() + 1, books.begin() + 3);

                cout << "删除数目后:\n";

                for (auto ps = books.begin(); ps != books.end(); ps++) {

                    ShowReview(*ps);

                }

            }

            books.insert(books.begin(), oldlist.begin() + 1, oldlist.begin() + 2);

            cout << "插入新数目后:\n";

            for (auto ps = books.begin(); ps != books.end(); ps++) {

                ShowReview(*ps);

            }

        }

        else{

            cout << "什么都没有输入\n";

        }

        return 0;

    }

    bool FillReview(Review & rr){

        std::cout << "请输入书籍的名字(输入quit退出):";

        std::getline(std::cin, rr.title);

        if (rr.title == "quit") {

            return false;

        }

        std::cout << "输入书的价格: ";

        std::cin >> rr.rating;

        while (std::cin.get() != '\n') {

            continue;

        }

        return true;

    }

    void ShowReview(const Review & rr){

        std::cout << rr.rating << "\t" << rr.title << std::endl;

    }

    输出结果:

    请输入书籍的名字(输入quit退出):Juhua

    输入书的价格: 122

    请输入书籍的名字(输入quit退出):Meigui

    输入书的价格: 150

    请输入书籍的名字(输入quit退出):Lanhua

    输入书的价格: 199

    请输入书籍的名字(输入quit退出):Huaishu

    输入书的价格: 200

    请输入书籍的名字(输入quit退出):quit

    谢谢。下面是你输入的:

    Rating    Book

    122    Juhua

    150    Meigui

    199    Lanhua

    200    Huaishu

    重复:

    Rating    Book

    122    Juhua

    150    Meigui

    199    Lanhua

    200    Huaishu

    删除数目后:

    122    Juhua

    200    Huaishu

    插入新数目后:

    150    Meigui

    122    Juhua

    200    Huaishu

    对矢量可执行的其他操作

      STL从更广义的角度定义了非成员函数来执行诸如搜索、排序、随机排序等操作。这种设计理念省却了大量的重复工作。如果每个类都有自己的成员函数,则需要定义80(8*10)个成员函数。但采用STL方式时,只需要定义10个非成员函数即可。在定义新的容器类时,只要遵循正确的指导思想,则它也可以使用已有的10个非成员函数来执行查找、排序等操作。

      另一方面,即使有执行相同任务的非成员函数,STL有时也会定义一个成员函数。这是因为对有些操作来说,类特定算法的效率比通用算法高,因此,vector的成员函数swap()的效率比非成员函数swap()高,但非成员函数可用来交换两个类型不同的容器的内容。

      下面来看3个具有代表性的STL函数:for_each()、random_shuffle()和sort()。

      for_each()函数可用于很多容器类,它接受3个参数。前两个是定义容器中区间的迭代器,最后一个是指向函数的指针(更普遍地说,最后一个参数是一个函数对象)。for_each()函数将被指向的函数应用于容器区间中的各个元素。被指向的函数不能修改容器元素的值。可以用for_each()函数代替for循环。例如,可以将代码:

        vector<Review>::iterator pr;

        for(pr = books.begin(); pr != books.end(); ps++) 

          ShowReview(*pr);

        替换为:

        for_each(books.begin(), books.end(), ShowReview);

        这样可以避免显式地使用迭代器变量。

      random_shuffle()函数接受两个指定区间的迭代器参数,并随机排列该区间中的元素。例如,下面的语句随机排列books矢量中所有元素:

        random_shuffle(books.begin(), books.end());

        与可用于任何容器类的for_each()不同,该函数要求容器类允许随机访问,vector类可以做到这一点。

      sort()函数也要求容器支持随机访问。该函数有两个版本,第一个版本接受两个定义区间的迭代器参数,并使用为存储在容器中的类型元素定义的<运算符,对区间中的元素进行操作。例如,下面de1语句按升序对nums的内容进行排序,排序时使用内置的<运算符对值进行比较:

        vector<int> nums;

        ....

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

      如果容器元素是用户定义的对象,则要使用sort(),必须定义能够处理该类型对象的operator<()函数。

      sort()函数的另一个版本接受3个参数,前两个参数也是用来指定区间的迭代器,最后一个参数是指向要使用的函数的指针(函数对象),而不是用比较的operator<()。返回值可转换为bool,false表示两个参数的位置不正确。

    // vect3.cpp -- using STL functions

    #include <iostream>

    #include <string>

    #include <vector>

    #include <algorithm>

    struct Review {

        std::string title;

        int rating;

    };

    bool operator<(const Review & r1, const Review & r2);

    bool worseThan(const Review & r1, const Review & r2);

    bool FillReview(Review & rr);

    void ShowReview(const Review & rr);

    int main()

    {

        using namespace std;

        vector<Review> books;

        Review temp;

        while (FillReview(temp))

            books.push_back(temp);

        if (books.size() > 0)

        {

            cout << "Thank you. You entered the following "

                << books.size() << " ratings:\n"

                  << "Rating\tBook\n";

            for_each(books.begin(), books.end(), ShowReview);

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

            cout << "Sorted by title:\nRating\tBook\n";

            for_each(books.begin(), books.end(), ShowReview);

            sort(books.begin(), books.end(), worseThan);

            cout << "Sorted by rating:\nRating\tBook\n";

            for_each(books.begin(), books.end(), ShowReview);

            random_shuffle(books.begin(), books.end());

            cout << "After shuffling:\nRating\tBook\n";

            for_each(books.begin(), books.end(), ShowReview);

        }

        else

            cout << "No entries. ";

        cout << "Bye.\n";

        // cin.get();

        return 0;

    }

    bool operator<(const Review & r1, const Review & r2)

    {

        if (r1.title < r2.title)

            return true;

        else if (r1.title == r2.title && r1.rating < r2.rating)

            return true;

        else

            return false;

    }

    bool worseThan(const Review & r1, const Review & r2)

    {

        if (r1.rating < r2.rating)

            return true;

        else

            return false;

    }

    bool FillReview(Review & rr)

    {

        std::cout << "Enter book title (quit to quit): ";

        std::getline(std::cin,rr.title);

        if (rr.title == "quit")

            return false;

        std::cout << "Enter book rating: ";

        std::cin >> rr.rating;

        if (!std::cin)

            return false;

        // get rid of rest of input line

        while (std::cin.get() != '\n')

            continue;

        return true;

    }

    void ShowReview(const Review & rr)

    {

        std::cout << rr.rating << "\t" << rr.title << std::endl;

    }

     基于范围的for循环(C++11)

    基于范围的for循环是为用于STL而设计的。先来看一个例子:

    int main(int argc, const char * argv[]) {

        using namespace std;

        double prices[5] = {12.3, 33.5, 66.8, 22.5, 55.8};

        for (double var:prices) {

            cout << var << ", ";

        }

        return 0;

    }

    输出结果:

    12.3, 33.5, 66.8, 22.5, 55.8,

     在这种for循环中,括号内的代码声明一个类型与容器存储的内容相同的变量,然后指出了容器的名称。接下来,循环体使用指定的变量依次访问容器的每个元素。还可以使用auto进行自动类型推断,例如:

    int main(int argc, const char * argv[]) {

        using namespace std;

        double prices[5] = {12.3, 33.5, 66.8, 22.5, 55.8};

        for (auto var:prices) {

            cout << var << ", ";

        }

        return 0;

    }

    假设books是一个vector<string>类型,Show(const std::string & str)用来显示string对象的信息,因此可以将下面的代码:

    for_each(books.begin(), books.end(), Show);

    替换为:

    for(auto x : books) Show(x);

    不同于for_each(),基于范围的for循环可以修改容器的内容,诀窍是指定一个引用参数。例如:

    #include <iostream>

    int main(int argc, const char * argv[]) {

        using namespace std;

        int nums[5] = {12, 45, 66, 78, 90};

        for (auto & var : nums) {

            cout << var << ", ";

        }

        cout << '\n';

        for (auto & var : nums) {

            var += 10;

        }

        for (auto var : nums) {

            cout << var << ", ";

        }

        return 0;

    }

    输出结果:

    12, 45, 66, 78, 90,

    22, 55, 76, 88, 100,

    重载函数operator++()是++作为前置版本,operator++(int)是后置版本

    不同的算法对迭代器的要求不同。查找算法需要定义++运算符,以便迭代器能够遍历整个容器;它要求能够读取数据,但不要求能够写数据(它只查看数据,而并不修改数据)。而排序算法要求能够随机访问,以便能够交换两个不相邻的元素。排序算法要求能够读写数据。

    STL定义了5种迭代器,并根据所需的迭代器类型对算法进行了描述。这5中迭代器分别是输入迭代器、输出迭代器、正向迭代器、双向迭代器和随机访问迭代器。输入迭代器是单向迭代器,可以递增,但不能倒退。输出迭代器是单通行、只写算法。正向迭代器既可以使得能够读取和修改数据,也可以使得只能读取数据;双向迭代器具有正向迭代器的所有特性,同时支持两种(前缀和后缀)递减运算符;随机访问迭代器具有双向迭代器的所有特性,同时添加了支持随机访问的操作(如指针增加运算)和用于对元素进行排序的关系运算符。

    相关文章

      网友评论

          本文标题:第十六章 string类和标准模板库(一)

          本文链接:https://www.haomeiwen.com/subject/owgngqtx.html