Loading

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];
}

 

 

posted @ 2012-12-05 15:07  阿凡卢  阅读(5314)  评论(4编辑  收藏  举报