stl使用小结(持续更新中)

下面括起来的这段是转贴的,括号外是自己总结的

/******************************************************************************************************************************************************************************  学习笔记-Effective STL-使用STL编程 新一篇: 杂记-所谓的算法和做菜 条款43:尽量用算法调用代替手写循环

有三个理由:

效率:算法通常比程序员产生的循环更高效。

正确性:写循环时比调用算法更容易产生错误。

可维护性:算法通常使代码比相应的显式循环更干净、更直观。

条款44:尽量用成员函数代替同名的算法

存在既是道理。

条款45:注意count、find、binary_search、lower_bound、upper_bound和equal_range的区别

了解这些函数的依赖和特性。

条款46:考虑使用函数对象代替函数作算法的参数

       把STL函数对象——化装成函数的对象——传递给算法所产生的代码一般比传递真的函数高效。原因是内联。另一个用函数对象代替函数的原因是它们可以帮助你避免细微的语言陷阱。

条款47:避免产生只写代码

       不能读和理解的软件不能被维护,不能维护的软件几乎没有不值得拥有。最后,这样的代码完全不高效。

条款48:总是#include适当的头文件

       几乎所有的容器都在同名的头文件里,比如,vector在<vector>中声明,list在<list>中声明等。例外的是<set>和<map>。<set>声明了set和multiset,<map>声明了map和multimap。

除了四个算法外,所有的算法都在<algorithm>中声明。例外的是accumulate(参见条款37)、inner_product、adjacent_difference和partial_sum。这些算法在<numeric>中声明。

特殊的迭代器,包括istream_iterators和istreambuf_iterators(参见条款29),在<iterator>中声明。

标准仿函数(比如less<T>)和仿函数适配器(比如not1、bind2nd)在<functional>中声明。

条款49:学习破解有关STL的编译器诊断信息

       对于vector和string,迭代器有时是指针,所以如果你用迭代器犯了错误,编译器诊断信息可能会提及涉及指针类型。例如,如果你的源代码涉及vector<double>::iterator,编译器消息有时会提及double*指针。(一个值得注意的例外是当你使用来自STLport的STL实现,而且你运行在调试模式。那样的话,vector和string的迭代器干脆不是指针。对STLport和它调试模式的更多信息,转向条款50。)

提到back_insert_iterator、front_insert_iterator或insert_iterator的消息经常意味着你错误调用了back_inserter、front_inserter或inserter,一一对应,(back_inserter返回back_insert_iterator类型的对象,front_inserter返回front_insert_iterator类型的对象,而inserter返回insert_iterator类型的对象。关于使用这些inserter的信息,参考条款30。)如果你没有调用这些函数,你(直接或间接)调用的一些函数做了。

类似地,如果你得到的一条消息提及binder1st或binder2nd,你或许错误地使用了bind1st或bind2nd。(bind1st返回binder1st类型的对象,而bind2nd返回binder2nd类型的对象。)

输出迭代器(例如ostream_iterator、ostreambuf_iterators(参见条款29),和从back_inserter、front_inserter和inserter返回的迭代器)在赋值操作符内部做输出或插入工作,所以如果你错误使用了这些迭代器类型之一,你很可能得到一条消息,抱怨在你从未听说过的一个赋值操作符里的某个东西。为了明白我的意思,试着编译这段代码:

vector<string*> v;                                   // 试图打印一个

copy(v.begin(), v.end(),                            // string*指针的容器,

ostream_iterator<string>(cout, "/n"));       // 被当作string对象       

你得到一条源于STL算法实现内部的错误信息(即,源代码引发的错误在<algorithm>中),也许是你试图给那算法用的类型出错了。例如,你可能传了错误种类的迭代器。要看看这样的用法错误是怎样报告的,通过把这段代码喂给你的编译器来启发(并愉快!)自己:

list<int>::iterator i1, i2;        // 把双向迭代器

sort(i1, i2);                  // 传给一个需要随机访问迭代器的算法        

你使用常见的STL组件比如vector、string或for_each算法,而编译器说不知道你在说什么,你也许没有#include一个需要的头文件。正如条款48的解释,这问题会降临在长期以来都可以顺利编译而刚移植到新平台的代码。

条款50:让你自己熟悉有关STL的网站

SGI STL网站,http://www.sgi.com/tech/stl/

STLport网站,http://www.stlport.org/

Boost网站,http://www.boost.org/

http://boost.sourceforge.net/ /******************************************************************************************************************************************************************************

条款1:仔细选择你的容器

序列容器

  • 标准STL序列容器vectorstringdequelist
  • 标准STL关联容器setmultisetmapmultimap
  • 非标准序列容器slistropeslist是一个单向链表,rope本质上是一个重型字符串。(“rope”是一个重型“string”。明白了吗?)你可以找到一个关于这些非标准(但常见的)容器的概览在条款50
  • 非标准关联容器hash_sethash_multisethash_maphash_multimap。我在条款25检验了这些可以广泛获得的基于hash表的容器和标准关联容器的不同点。
  • vector<char>可以作为string的替代品。条款13描述了这个替代品可能会有意义的情况。
  • vector作为标准关联容器的替代品。就像条款23所说的,有时候vector可以在时间和空间上都表现得比标准关联容器好。
  • 几种标准非STL容器,包括数组、bitsetvalarraystackqueuepriority_queue。因为它们是非STL容器,所以在本书中关于它们我说得很少

    1.class CustomerList { private: typedef list<Customer> CustomerContainer;       此处使用typedef 可以方便以后更换容器类型 typedef CustomerContainer::iterator CCIterator;  ///道理同上 CustomerContainer customers; public: // limit the amount of list-specific ... // information visible through }; // this interface

    2.当你向容器中添加一个对象(比如通过insert或push_back等),进入容器的是你指定的对象的拷贝。所以必须考虑对象的拷贝成本,还有类继承关系,例如子类插入父类模板类型的容器可能会造成派生部分的丢失(分割问题暗示了把一个派生类对象插入基类对象的容器几乎总是错的) 3STL容器更文明。它们只建立(通过拷贝)你需要的个数的对象,而且它们只在你指定的时候做。是的,我们需要知道STL容器使用了拷贝,但是别忘了一个事实:比起数组它们仍然是一个进步。因为数组一定义,一般需要指定大小,如:char cMAx[1024]; 4.用empty()来代替检查size()是否为0 5.条款5:尽量使用区间成员函数代替它们的单元素兄弟 vector<int> vec1,vec2; vec2.assign(vec1.begin,vec1.end)

     6.使用"swap诡计"来修去多余的空位

  • string s; …   //make s large, then erase most   //of its characters string(s).swap(s);   //这里如果s占用的空间远没有存储的数据大,会对s进行压缩 7.通用的swap.交换两个容器的内容,包括交换它们的迭代器,指针和引用.在一个容器中 迭代器,指针和引用,在变换后的另一个容器中仍有效并指向同一个元素. 8.条款18避免使用vector<bool>,原因是vector容器用位来表示一个bool的ture和false值:所以导致产生不了:

  • vector<bool> * pVec = &v[0];//注意,指针是不能指向位 可以用deque<bool>代替,deque用1字节来表示bool 9.条款21:永远让比较函数对相同元素返回false。这个看下面的例子还有一段文字解析就清楚了 

  • 所要记住的就是比较函数的返回值指明的是在此函数定义的排序方式下,一个元素是否应该位于另一个之前。相同的元素绝不该一个领先于另一个,所以比较函数总应该为相同的元素返回false

  • 你真正需要的比较类型是这个:
    struct StringPtrGreater:                             // this is a valid
    public binary_function<const string*,            // comparison type for
                               const string*,            // associative containers
                               bool> {
    bool operator()(const string *ps1, const string *ps2) const
    {
    return * ps2 < * ps1 ;                          // return whether *ps2
    }                                                // precedes *ps1 (i.e., swap
                                                         // the order of the
    };                                                   // operands
  •  10.如果我们传递v给如此形式的C风格API函数

  • void doSomething(const int* pInts, size_t numInts);

  • 我们可以这么做:

  • doSomething(&v[0],v.size());

  • 考虑用已序vector代替关联容器 分析MAP使用树结构存储,存储了指向前后节点的指针,指针必然占用空间

  • 了解如何通过reverse_iterator的base得到iterator v.erase*((++ri).base());

  • 需要一个一个字符输入时考虑使用istreambuf_iterator 相对于istream_iterator,它们抓取得更快——在我进行的简单测试中能快40%,如果你的结果不同也不用惊奇。

    5.remove并不“真的”删除东西,因为它做不到。

    重复对你有好处:

    remove并不“真的”删除东西,因为它做不到。

    remove不知道它要从哪个容器删除东西,而没有容器,它就没有办法调用成员函数,而如果“真的”要删除东西,那就是必要的 8.v.erase(remove(v.begin(), v.end(), 99), v.end());  // 真的删除所有

    9.ostringstream ost; ost<<n; string str = ost.str();

     10. STL DEQUE容器 :STL中文学习站有一篇专门的介绍deque的文章,并总结出来:deque在处理前端插入,以及内存的释放效率要高于vector容器(详细的东东可以参考那篇文章)

  • 11*使用两种方法来访问vector。 1、   vector::at() 2、   vector::operator[] operator[]主要是为了与C语言进行兼容。它可以像C语言数组一样操作。但at()是我们的首选,因为at()进行了边界检查

  • 12 压缩一个臃肿的vector  这个比较简单,出点毛招.  用vector的assign,赋值的时候会根据vector的实际大小来分配新建vector的空间 eg:

  • vector<int> vecBig;   reserve 1千个数据  ,压入1000个数据 .         vector<int> vecSmall;vecBig.assign(vecSmall);

  • 13. STL源码剖析的东东: 12.1.看了内存分配器和迭代器,越发佩服写标准库的前辈了,orz一个。进入正题,标准库就是:容器(数据结构),算法 其实程序不就是数据结构 + 算法吗,呵呵,容器跟算法中间的桥梁其实是迭代器,是核心的部分。 迭代用了模板 甚至模板特化的机制实现。想知道啥叫模板特化:无非就是在普通的模板类上面,加上对模板类型型参的一个特定的限制,例如: template <typename T> class ctem; template <typename  T*> 规定了模板类型参数只能接受原生指针 12.2 看了vector容器,主要是优秀的内存分配和内存池机制,实现得很巧妙。其实整个vector的容器并不复杂,但你知道了内部的实现原理之后,自然就知道vector容器适合做尾部的插入操作,【】操作。但做删除等操作效率低于list  (OK,今天就写到这吧,下周继续) 14.  关于关联式容器:stl的关联式容器确实比较实用:底层的实现也无非是 数据结构 +算法实现:首先要了解树的概念,最后才深入到 二叉树 ->平衡二叉树-> 红黑树, set map multi+set,map 底层的都是用红黑树结构实现的。何谓红黑树:一般指的就是平衡性比较好的二叉收索树。stl之所以采用该结构,是因为红黑树的平衡性后,在查找随机性大的非超大量容器中,可以比较高效的解决关联容器的快速查找。看到前面的一堆定语了吧:聪明人都可以看到其实红黑树有其弱项,在超大数据量而且查找随机性不大的情况下,其实红黑色效率并不高,例如,从十万个数据的容器中查找1万次特定的叶节点。呜呼,那不死人..  关键时候,不得不出hash-table,哈希表出场,这名字好像很酷,但其实无非就是一些小伎俩,绝非不是什么武林无敌神功。先看小例子如:在十万个16位short的整数中,需要统计每个数出现的次数, 有啥招?都说来看看 本人愚笨,只想出一小招,就是用最基本的数组方法。因为16位的short最大值为 2的16次方,范围也就是:0-65535.此时 int  iaValue[65536];   然后统计的时候, iaValue[i] + 1; 这样最后的iaValue就是完整的数值,里面包括统计值,查找也飞快,只要iaValue[i]。 说了这个例子,目的也就是想告诉自己,hashtable也是依照这样的原理实现,但要复杂些。因为如果上面例子的16位变成了32位,那麽就要2 32方,4g的数组空间,那就死人了。于是哈希表用了一个伎俩也就是 映射的方法,用哈希函数把大值映射到一定范围的数组空间里面。然后再做哈希,当然,这样会引起碰撞的,具体的哈希表碰撞检查方法这里就不写了。反正大概知道stl 关联容器还有那一堆hash_table hash_set的大概实现方法就OK.(做饭去,下周开始看源码剖析的算法了) 14.stl算法确实比较多,先一个个慢慢记录下:count_if ,count,find_if ,find  这种属于其中一种类型吧,就是后面没带IF的直接带类型T,带IF的一般自己写判断函数,一般就是自己做个仿函数,呵呵
    15.仿函数:顾名思义就是模仿函数的东东,一般的做法就是类重载()操作符,
    greater<int>()(5, 6); 仿函数最重要的是可配接性:unary_function,binary_function
    常用的做法是  template  <class T> struct plus : public unary_function<T, T>
    {} .仿函数种类: 逻辑(如:and,or),关系(equal,equal_to),算法(plus,minus)
    16. 配接器:(抄一段~ ~)
    适配器是用来修改其他组件接口的STL组件,是带有一个参数的类模板(这个参数是操作的值的数据类型)。STL定义了3种形式的适配器:容器适配器,迭代器适配器,函数适配器。
    容器适配器:包括栈(stack)、队列(queue)、优先(priority_queue)。使用容器适配器,stack就可以被实现为基本容器类型(vector,dequeue,list)的适配。可以把stack看作是某种特殊的vctor,deque或者list容器,只是其操作仍然受到 stack本身属性的限制。queue和priority_queue与之类似。容器适配器的接口更为简单,只是受限比一般容器要多;
    迭代器适配器:修改为某些基本容器定义的迭代器的接口的一种STL组件。反向迭代器和插入迭代器都属于迭代器适配器,迭代器适配器扩展了迭代器的功能;
    函数适配器:通过转换或者修改其他函数对象使其功能得到扩展。这一类适配器有否定器(相当于"非"操作)、函数指针适配器

    stl源码剖析终于看完了,考虑下下阶段看啥书先,应该还是window 平台下 C++ 相关的东东,最近下了 linux高级环境编程,但感觉还是要先把ubuntu系统先熟悉了再开始相关的开发,目前优先进行win下的东西。考虑下是否进行 《window核心编程》或者《exception CPP》


    呵呵,十一假期到了,上面提的两本书《window核心编程》或者《exception CPP》也都粗略的看完了,前者也写了点总结放博客里面了,趁假期有点时间也顺便重新读了下《stl源码剖析》又有了些收获,确实是一本好书,于是决定花点时间把重要的章节复习一下,顺便把花了2小时左右看的迭代器一章写下小结,对以后可能会有用处

    *偏特化:所谓偏特化就是针对任何模板参数更进一步的条件限制所设计出来的一个特化版本 。呵呵,这应该是我看过的对偏特化的比较合理的解释,光解释还不行,上代码看之:

    注意: traits 萃取器,解决了原生指针类型的迭代器.原因如下

    普通的迭代器设计
    template <class T>
    struct MyIter{ typename T value_type;   //这里做了内嵌类型说明,也就是可以用这个机制来声明一个迭代的value。但是该方法有一漏洞,就是当T为原生指针(例如int*)的时候,该方法不灵验了,无法为它定义内嵌型别
    了,怎么办呢,这时候就需要偏特化出场了,请看:
    template <class T>
    struct iterator_traits<T*>{ typedef  T value_type;} 哈哈 ,这样搞定了原生指针了,但还有个问题,如果类型是const int *,那么只能这样了,定义多一个偏特化版本
    template <class T>
    struct iterator_traits<const T*>{ typedef  T value_type;}
    说白了,这一切的目的无非就是想尽方法利用编译器对模板参数的推导特性,把迭代器的类型萃取出来。。。为什么要取出来呢,因为在算法运用中可能会经常使用到迭代器的类型。例如find等

    *右值不允许赋值操作,左值允许赋值操作.书里面随带提到的,曾经给这样一道面试题考倒,顺便记录下

    * 迭代器五种型别:

    1 value type 不用说了,就是迭代器所指对象的类型

    2 difference type  表示两个迭代器之间的距离

    3.reference type 迭代器所指之物的内容 (如:vecotr<int>::iterator ite; *ite = 5; 修改了容器的内容

    4.pointer type  传回一个迭代器,指向迭代器所指之物 ite->first;

    5.iterator_category  迭代器型别

    必须先了解迭代器分类


       input(只读)            output(只写)
          
                  forward (读写)
                 
                  bidirectional(双向移动)
                 
                  random(随机)
                 
    不同的跌代器类型运用于不同的算法自然有不同的结果,关键是效率的问题,例如 advandced ,同样advanced(100)随机迭代器操作起来当然比input 迭代器速度 快喽

    *每一种STL容器都提供有自己的迭代器(因为迭代器必须要暴露自己的细节)
    *.特性提取器:iterator_traits
    *“算法不改变容器大小”back_inserter()并没有突破这个限制,在使用back_inserter时,不是算法直接改变它所操纵的

    容器大小,而是算法操纵back_inserter迭代器,迭代器的操作导致容器大小的改变,所以,即使使用

    back_inserter也没有改变算法始终没有改变容器大小的原因是******为了使得算法能够独立于容器,从而普适性更好,真正成为“算法
    *算法分类:只读算法find *findfirstof等,写算法fill,fill_n
    *排序算法








评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值