zoukankan      html  css  js  c++  java
  • C++ STL 学习笔记

    #.string 建议

      使用string 的方便性就不用再说了,这里要重点强调的是string的安全性。    

      string并不是万能的,如果你在一个大工程中需要频繁处理字符串,而且有可能是多线程,那么你一定要慎重(当然,在多线程下你使用任何STL容器都要慎重)。

      string的实现和效率并不一定是你想象的那样,如果你对大量的字符串操作,而且特别关心其效率,那么你有两个选择,首先,你可以看看你使用的STL版本中string实现的源码;另一选择是你自己写一个只提供你需要的功能的类。

      string的c_str()函数是用来得到C语言风格的字符串,其返回的指针不能修改其空间。而且在下一次使用时重新调用获得新的指针。

      string的data()函数:如果string的size()不为0,返回的指针指向string对应的字符串的第一个字符,大小为size()个;如果string的size()为0,返回一个非null的指针,该指针不能被解引用。 对于c_str() data()函数,返回的指针指向的内容都是const的,内容都是由string本身拥有,不可修改其内容。其原因是许多string实现的时候采用了引用机制,也就是说,有可能几个string使用同一个字符存储空间。而且你不能使用sizeof(string)来查看其大小。

      尽量去使用操作符,这样可以让程序更加易懂(特别是那些脚本程序员也可以看懂)。

      find()函数都返回一个size_type类型,这个返回值一般都是所找到字符串的位置,如果没有找到,则返回string::npos。有一点需要特别注意,所有和string::npos的比较一定要用string::size_type来使用,不要直接使用int或者unsigned int等类型。

    #.sort 建议

      默认的都是从小到大排序

      1.若需对vector, string, deque, 或 array 容器进行全排序,你可选择sort或stable_sort;

      2.若只需对vector, string, deque, 或 array 容器中取得top n的元素,部分排序partial_sort是首选.   如:前5个:partial_sort(vect.begin(), vect.begin()+5, vect.end());

      3.若对于vector, string, deque 或 array 容器,你需要找到第n个位置的元素或者你需要得到top n且不关系top n中的内部顺序,nth_element是最理想的。   如:找排在第5的位置 nth_element(vect.begin(), vect.begin()+4, vect.end()); //注意是begin()+4

      partial_sort()和nth_element()都把5个最小的放在前面

      4.若你需要从标准序列容器或者array中把满足某个条件或者不满足某个条件的元素分开,你最好使用partition或stable_partition。   如:student exam("pass", 60); stable_partition(vect.begin(), vect.end(), bind2nd(less<student>(), exam));

      5.若使用的list容器,你可以直接使用partition和stable_partition算法,你可以使用list::sort代替sort和stable_sort排序。若你需要得到partial_sort或nth_element的排序效果,你必须间接使用。

      6.sort, stable_sort, partial_sort, 和nth_element算法都需要以随机迭代器(random access iterators)为参数,因此这些算法能只能用于vector, string, deque, 和array等容器,对于标准的关联容器map、set、multimap、multiset等,这些算法就有必要用了,这些容器本身的比较函数使得容器内所有元素一直都是有序排列的。

      7.对于容器list,看似可以用这些排序算法,其实也是不可用的(其iterator的类型并不是随机迭代器),不过在需要的时候可以使用list自带的排序函数sort(有趣的是list::sort函数和一个“稳定”排序函数的效果一样)。 对一个list容器使用partial_sort或nth_element,只能间接使用: 方法(1):把list中的元素拷贝到带有随机迭代器的容器中,然后再使用这些算法; 方法(2):生成一个包含list::iterator的容器,直接对容器内的list::iterator进行排序,然后通过list::iterator得到所指的元素; 方法(3):借助一个包含iterator的有序容器,然后反复把list中的元素连接到你想要链接的位置。

      list成员函数的行为和它们兄弟的行为经常不同。如想从容器删除对象,调用remove,remove_if和unique算法后,必须接着调用erase才能真正删除对象,但list的remove,remove_if和unique真的删除掉了对象。sort算法不能用于list,但list可以调用自己的sort成员函数。

      8.partition和stable_partition与sort、stable_sort、partial_sort和nth_element不同,它们只需要双向迭代器。因此可以在任何标准序列迭代器上使用partition和stable_partition

      9.时间和空间复杂度: stable_sort > sort > partial_sort > nth_element > stable_partition > partition

    #.删除容器中的特定值

      1.如果容器是vector string deque,使用erase和remove的惯用法:          

      c.erase(remove(c.begin(),c.end(),value),c.end())   remove并不能“真的”删除元素,因为它做不到,如果你真的要删除元素,要在remove上接上erase

      2.如果容器是list,使用list::remove (ls.remove(value))

      3.如果容器是关联容器,用erase删除容器中满足一个特定条件的值:c.erase(c.begin())  

      1.如果容器是vector string deque,使用erase和remove_if的惯用法

      2.如果容器是list,使用list::remove_if

      3.如果容器是关联容器,用remove_copy_if和swap,或写一个循环遍历容器元素,把迭代器传给erase时后置递增它。

    循环遍历删除容器中的值:

    //顺序容器循环遍历容器元素删除值:
    for(vector<int>::iterator i=c.begin(); i!=c.end(); )
        if( condition(*i) ) i=c.erase(i);
        else ++i;
    
    //关联容器循环遍历容器元素删除值:关联容器的erase(i)会返回删除元素的个数,这里只关心删除的值
    for(map<int,int>::iterator i=c.begin(); i!=c.end(); )
        if( condition(*i) ) c.erase(i++);
        else ++i;

    #.迭代器失效

    vector:

    1.当插入(push_back)一个元素后,end操作返回的迭代器肯定失效。

    2.当插入(push_back)一个元素后,capacity返回值与没有插入元素之前相比有改变,则需要重新加载整个容器,此时begin和end操作返回的迭代器都会失效。

    3.当进行删除操作(erase,pop_back)后,指向删除点的迭代器失效;指向删除点后面的元素的迭代器也将全部失效。

    deque迭代器的失效情况:

    1.在deque容器首部(push_front)或者尾部(push_back)插入元素不会使得任何迭代器失效。

    2.在其首部(pop_front)或尾部(pop_back)删除元素则只会使指向被删除元素的迭代器失效。

    3.在deque容器的任何其他位置的插入(insert)和删除(erase)操作将使指向该容器元素的所有迭代器失效。

    list/set/map

    1.删除时,指向该删除节点的迭代器失效

    迭代器使用注意事项:

    1.resize 操作可能会使迭代器失效。在 vector 或 deque 容器上做 resize 操作有可能会使其所有的迭代器都失效。

    2.不要存储 end 操作返回的迭代器。添加或删除 deque 或 vector 容器内的元素都会导致存储的end迭代器失效。

    3.使用越界的下标,或调用空容器的 front 或 back 函数,都会导致程序出现严重的错误。

    4.赋值和 assign 操作使左操作数容器的所有迭代器失效。swap 操作则不会使迭代器失效。完成 swap 操作后,尽管被交换的元素已经存放在另一容器中,但迭代器仍然指向相同的元素。

    5.由于 assign 操作首先删除容器中原来存储的所有元素,因此,传递给 assign 函数的迭代器不能指向调用该函数的容器内的元素。

    6.只适用于vector和deque容器的迭代器操作:iter + n、iter - n、iter1 += iter2、>, >=, <, <=

    7.list 容器的迭代器既不支持算术运算(加法或减法),也不支持关系运算(<=, <, >=, >),它只提供前置和后置的自增、自减运算以及相等(不等)运算。

    #.使用“交换技巧”来休整过剩容量

      class Contestant{....}
      vector<Contestant> v;
      ....  //是v变大然后删除部分
      vector<Contestant>(v).swap(v); //在v上“收缩到合适”(v.size()=v.capacity())
      vector<Contestant>( ).swap(v); //清除v而且最小化它的容量

      string s;
      .....     //是s变大然后删除部分
      string(s).swap(s); //在s上“收缩到合适”
      string( ).swap(s); //清除s而且最小化它的容量

    #.为指针的关联容器指定比较类型(不是比较函数)

    set<string*> ssp; //建立一个指针的关联容器,容器会以指针的值排序,所以输出时候string不会按字典顺序

    struct StringLess: public binary_function<const string*, const string*, bool>
    {
        bool operator()(const string* ps1,const string* ps2) const
        {
            return *ps1 < *ps2;
        }
    };
    
    typedef set<string*,StringLess> StringPtrSet;
    StringPtrSet ssp;

    或者:

    struct DereferenceLess
    {
        template<typename PtrType>
        bool operator()(PtrType p1, PtrType p2) const
        {
            return *p1 < *p2;
        }
    };
    
    set<string*,DereferenceLess> ssp;

    如果有一个智能指针或迭代器的关联容器,也得为它指定“比较类型”

    #.equal_range的用法

     typedef vector<string>::iterator vit; 
     typedef pair<vit,vit> vitpair;
     sort(svec.begin(),svec.end());
     vitpair vp = equal_range(svec.begin(),svec.end(),"aaa");
     if(vp.first != vp.second)
        cout<<"\nFind the string\n";
     else cout<<" Not find the string\n";
     if(vp.first != vp.second)
        cout<<"There are "<<distance(vp.first,vp.second)<<" aaa \n";

    #.使iterator(i)指向const iterator(ci)

       typedef vector<int>::iterator Iter;
       typedef vector<int>::const_iterator ConstIter;
       从iterator到const iterator没有隐式类型转换,也不能用Iter i(const_cast<Iter>(ci));因为iterator(i)和const iterator(ci)是两种完全不同的类型
       可以用以下技巧:
       advance(i,distance<ConstIter>(i,ci)); //使i指向ci指向的元素
       建议:尽量用iterator代替const_iterator和reverse_iterator

    #.只能操作有序数据的算法

      搜索算法:  binary_search,lower_bound,upper_bound,equal_range,   set_union,set_intersection,set_difference,set_symmetric_difference   merge, inplace_merge includes

      一般用于有序区间:unique,unique_copy  

    #.保证用于算法的比较函数和用于排序的一致

      sort(v.begin(),v.end(),greater<int>())
      //bool it = binary_search(v.begin(),v.end(),5); //error 默认的是升序,会导致未定义的行为
      bool it = binary_search(v.begin(),v.end(),5,greater<int>());

    #.用accumulate和for_each来统计区间

      #include<numeric>   //包含accumulate
      double sum = accumulate(v.begin(),v.end(),0.0);

    #.ptr_fun(),mem_fun()和mem_fun_ref()的用法

      ptr_fun()可以把指向一个函数的指针转化为一个函数对象。函数必须是一元或是二元函数(不能为无参的函数)
      transform(begin,end,dest,not1(ptr_fun(fun)));

      mem_fun 与 mem_fun_ref 是为了使 STL 算法可以将成员函数(member functions)当作参数而加入的,如下:
      list<Widget *> lpw;
      for_each(lpw.begin(), lpw.end(),mem_fun(&Widget::test)); // pw->test();

      vector<Widget> vw;
      for_each(vw.begin(), vw.end(),mem_fun_ref(&Widget::test)); // w.test();
      mem_fun_ref的作用和用法跟mem_fun一样,唯一的不同就是:当容器中存放的是对象实体的时候用mem_fun_ref,当容器中存放的是对象的指针的时候用mem_fun。

    #.避免对 set 及 multiset 的key进行原地修改

      这里的“键部分(key part)”指的是 set/multiset 存储的对象 T 中对 set/multiset 的排序算法有影响的部分,或者说是参与排序的部分。比如下例中 User::ID:

    class User {
    public:
        unsigned int ID;
        string name;
        unsinged int age;
        const string& gettitle() const;
        void settitle(string& title);  
    };
    
    class UserIDLess : public binary_function<User, User, bool> {
    public:
        operator() (const User &lhs, const User &rhs) const {
           return lhs.ID < rhs.ID;
        }
    };
    typedef set<User, UserIDLess> IDUserSet;

    修改 set/multiset 中的对象时,注意不要改变对象的key(对set/multiset的排序算法有影响的部分),否则容器会被破坏。
    如果必须改变键部分,采取如下策略:
    1. 找到要修改的对象。i = set.find();
    2. 复制对象。        User b(*i);
    3. 修改复制的对象。  b.changeSome();
    4. 删除原对象。      set.erase(i++); //自增这个迭代器,保持它有效
    5. 插入复制的对象。  set.insert(i,b);

    #.copy_if的正确实现(STL里没有copy_if)

    #include <iostream>
    #include <vector>
    #include <functional>
    #include <iterator>
    #include <algorithm>
    using namespace std;
    
    template<typename InputIterator,typename OutputIterator,typename Predicate>
    OutputIterator copy_if(InputIterator begin,InputIterator end,OutputIterator destBegin,Predicate p)
    {
        while(begin!=end)
        {
            if(p(*begin)) *destBegin++=*begin;
            ++begin;
        }
        return destBegin;
    }
    
    int main()
    {
        int a[]={0,3,2,1,5,4,6,8,7,9};
        vector<int> v(a,a+sizeof(a)/sizeof(int));
        cout<<"\nOutput the number greater than 4:\n";
        copy_if(v.begin(),v.end(),ostream_iterator<int>(cout," "),bind2nd(greater<int>(),4));
        cout<<"\nOutput the number greater than 4:\n";
        //STl里没有copy_of,但有 remove_copy_of
        remove_copy_if(v.begin(),v.end(),ostream_iterator<int>(cout," "),bind2nd(less_equal<int>(),4)); 
    
        return 0;
    }

    #.使仿函数类可适配

    ptr_fun可以使四个标准函数适配器(not1、not2、bind1st和bind2nd)存在某些typedef,提供这些必要的typedef的函数对象称为可适配的。

    operator()带一个实参的仿函数类,要继承的结构是std::unary_function。operator()带有两个实参的仿函数类,要继承的结构是std::binary_function。

    unary_function和binary_function是模板,所以你不能直接继承它们。取而代之的是,你必须从它们产生的类继承,而那就需要你指定一些类型实参,对于unary_function,你必须指定的是由你的仿函数类的operator()所带的参数的类型和它的返回类型。对于binary_function,你要指定三个类型:你的operator的第一个和第二个参数的类型,和你的operator地返回类型。

    两个的例子:

    template<typename T>
    class MeetsThreshold: public unary_function<Widget, bool>
    {
    private:
        const T threshold;
    
    public:
        MeetsThreshold(const T& threshold);
        bool operator()(const Widget&) const;
        ...
    };
    
    struct WidgetNameCompare:public binary_function<Widget, Widget, bool>
    {
        bool operator()(const Widget& lhs, const Widget& rhs) const;
    };

    一般来说,传给unary_function或binary_function的非指针类型都去掉了const和引用。

    当operator()的参数是指针时这个规则变了。这里有一个和WidgetNameCompare相似的结构,但这个使用Widget*指针:

    struct PtrWidgetNameCompare:public binary_function<const Widget*, const Widget*, bool> 
    {
        bool operator()(const Widget* lhs, const Widget* rhs) const;
    };

    #.其他

    当用某个模板类型下的成员前面需加上typename,但是不能用class更不能用struct
    typename vector<T>::iterator it = v.begin();

    用empty()来代替检查size()是否为0

    给map添加一个元素时,insert比operator[]效率高

    更新已经在map的元素时,operator[]效率高

    尽量用算法代替手写循环

    尽量用成员函数代替同名算法

    C++中重载[]要两个版本来满足需要:

    char & String::operator[](int n)
    {
        return data[n];
    }
    
    const char & String::operator[](int n) const
    {
        return data[n];
    }
  • 相关阅读:
    linux学习之线程篇(二)
    linux学习之线程篇(一)
    linux学习之进程篇(四)
    linux学习之信号篇(二)
    linux学习之信号篇(一)
    myshell案例
    linux学习之gdb调试工具篇
    linux学习之Makefile篇
    linux学习之进程篇(三)
    Linux常用命令-1
  • 原文地址:https://www.cnblogs.com/luxiaoxun/p/2803136.html
Copyright © 2011-2022 走看看