重温STL, STD



一, 定义

STL = Standard Template Library,标准模板库

http://baike.baidu.com/link?url=ZgViQn8lnJ3Z8MhF-R8MmEeiUeXUi9cnrCXCxUhC0PioEpdOQQ4FbNBOUOLpN6YhNLxEX2OSJPIfVOOf8ekaZ_yiA77k6mn8pFpgc2MvPwC


标准模板库, 惠普实验室开发的一系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室工作时所开发出来的。这可能是一个历史上最令人兴奋的工具的最无聊的术语。从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合。这里的“容器”和算法的集合指的是世界上很多聪明人很多年的杰作。 STL的目的是标准化组件,这样就不用重新开发,可以使用现成的组件。 STL现在是C++的一部分,因此不用额外安装什么。
STL被内建在你的 编译系统之内。STL的版本很多,常见的有HP STL、PJ STL、 SGI STL等。
C++标准中,STL被组织为下面的13个头文件:< algorithm>、< deque>、< functional>、< iterator>、< array>、< vector>、< list>、、< map>、、< memory>、< numeric>、< queue>、< set>、、< stack>和< utility>。

组成部分编辑

STL可分为容器(containers)、迭代器(iterators)、空间配置器(allocator)、配接器(adapters)、算法(algorithms)、仿函数(functors)六个部分。

STL容器就为我们提供了这样的方便,它允许我们重复利用已有的实现构造自己的特定类型下的数据结构,通过设置一些模板类,STL容器对最常用的数据结构提供了支持,这些模板的参数允许我们指定容器中元素的数据类型,可以将我们许多重复而乏味的工作简化。

容器部分主要由头文件< vector>,,,,,组成。对于常用的一些容器和容器适配器(可以看作由其它容器实现的容器),可以通过下表总结一下它们和相应头文件的对应关系。
序列式容器
向量(vector) 连续存储的元素
列表(list) 由节点组成的双向链表,每个结点包含着一个元素
双端队列(deque) 连续存储的指向不同元素的指针所组成的数组
容器适配器
(stack) 后进先出的值的排列
队列(queue) 先进先出的值的排列
优先队列(priority_queue) 元素的次序是由作用于所存储的值对上的某种谓词决定的的一种队列
关联式容器
集合(set) 由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序
多重集合(multiset) 允许存在两个次序相等的元素的集合
映射(map) 由{键,值}对组成的集合,以某种作用于键对上的谓词排列
多重映射(multimap) 允许键对有相等的次序的映射

迭代器 从作用上来说是最基本的部分,可是理解起来比前两者都要费力一些(至少笔者是这样)。 软件设计 有一个基本原则,所有的问题都可以通过引进一个间接层来简化,这种简化在STL中就是用迭代器来完成的。概括来说,迭代器在STL中用来将算法和容器联系起来,起着一种黏和剂的作用。几乎STL提供的所有算法都是通过迭代器存取元素序列进行工作的,每一个容器都定义了其本身所专有的迭代器,用以存取容器中的元素。
迭代器部分主要由头文件,组成。是一个很小的头文件,它包括了贯穿使用在STL中的几个模板的声明,中提供了迭代器使用的许多方法,而对于的描述则十分的困难,它以不同寻常的方式为容器中的 元素分配存储空间,同时也为某些算法执行期间产生的临时对象提供机制,中的主要部分是 模板类allocator,它负责产生所有容器中的默认分配器。

STL提供了大约100个实现 算法 的模版函数,比如算法for_each将为指定序列中的每一个元素调用指定的函数,stable_sort以你所指定的规则对序列进行稳定性排序等等。这样一来,只要我们熟悉了STL之后,许多代码可以被大大的化简,只需要通过调用一两个算法模板,就可以完成所需要的功能并大大地提升效率。
算法 部分主要由 头文件 组成。是所有STL头文件中最大的一个(尽管它很好理解),它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。中则定义了一些 模板类 ,用以声明 函数对象



二, 为何某些公司不允许使用C++ STL?

听朋友说他们公司不允许使用 C++ STL。有点不懂,为何不允许使用呢?难道出于安全、效率?不应该啊,当时急着没细问,大家能解释一下吗?不知道 Boost 如何?

说几个STL的缺点吧,虽然都是在比较极端的情况下出现,但是对于一些大项目还是会遇到的

1. 代码膨胀问题
每一个实例化过的模板类,都会膨胀出一份独立的代码,比如
std::vector, std::vector,编译后会产生两份代码,在VC2008下,每份代码大约是3-4kb,这是因为vector比较简单代码少,如果是map则会产生30-50kb的代码,因为map里有个复杂的红黑树。对于数据处理类的代码里一般会定义很多种不同的结构体,不同的结构体放到不同的容器里,就会实例化出很多个类的代码,我见过一个项目里,这样的vector就有数百个。

2. 内存使用效率问题 (以vc++2008为例)
stl在内存使用效率上是比较低效的,比如std::string,它的sizeof大概是28,因为它有一个内置的16字节数组,用来做小字符串优化的,就是说低于16字节的字符串都会至少占用28字节内存,如果刚好17字节字符串,则会占用28字节+额外分配的字符串内存,额外分配的内存是一个堆块,又有很多浪费,相比用一个char *存储字符串大约多占用了一倍内存。
还有map<>,每一个map的node都是一块独立分配的内存,如果是 map呢,那就很悲剧了,为了存一个int要消耗几十个字节,很浪费的。
如果元素数量有百万级,那么内存占用就很可观了,这种情况下建议自己实现allocator,做内存池。

3. deep copy问题
让两个容器的实例做赋值操作,看起来就一条语句,实际上容器里的每个元素都执行了一次赋值操作。如果容器里有百万级的数据,那么一个等号就产生了几百万次的构造和析构。
传递参数的时候一定要用 const 引用,赋值可以用 swap代替。

4. 隐式类型转换
比如 有个函数
void doSomething(const std::string &str);
调用的时候 
doSomething("hello");
能编译执行,但是会产生一个临时的匿名的std::string实例,把"hello"复制一遍,然后在调用完成后析构掉。如果这个发生在循环体内部有可能影响性能。

以上这些问题,在小程序里或者数据规模不大的时候,比如容器内元素只有几千这个规模,都不是什么大问题,那时开发效率才是重点,但是一旦有大数据stl容器会成为性能瓶颈的。

我并不是主张不用STL,而是要充分了解STL的优缺点,根据应用场景做选择。


1,性能 

随着C++11标准在各大编译器的实现,有了move和rvalue,STL的性能不会是瓶颈,而且另一方面,你的程序既然要最高的性能,为什么要选择C++呢?你还是看重C++的抽象能力(相对于C)对吧?既然你都看重的不仅仅是性能了,那还拿什么性能说事儿?
当然,你也可以自己去实现一套STL的东西(比如EA就自己搞过一套EA STL),但谁来负责进化呢?你要做很多benchmark,写很多test case,还要在项目成员间宣传“用了我的STL,手不疼了,脚不抽经了。。。”,不然你离职后,这坨东西自会被后人慢慢当一坨东西一样扔掉。
有这时间,少百分之几的性能又怎样,陪陪老婆孩子不挺好的吗?
2,对STL不熟悉
如楼上已经有几位知友提到,Server端的程序员大多是从C转过来的,对C++的一些高级特性(包括STL的实现)并没有很好的掌控能力,自然会选择用C的方式来用C++。
3,调试
如果不用STL就好调试了,那为什么还有那么多关于调试的书籍?而且还卖的不错。



三, 理解C++之std 与 stl

1,首先明确 std是一个 命名空间的名字。
2,其次,明确STL是 Standard Template Library的缩写,即标准模板库。
3,2者关系:       In fact, all identifiers of the C++ standard library are defined in a namespace called std.     而STL被容纳与C++ Standard Library里。

   即2者均属于 C++ Standard Library里。STL是其中的一部分内容,std是作为一个外部的名字。

4,举例 C++一般的库函数  也要#include     std::cout,感觉更多的是库函数。
        C++之STL里的函数 用个#include ,感觉更多的是容器。




四, c++0x   c++11 几个特性:

原生支持正则表达式?

各种匿名函数?

还有 auto 的一种使用是根据上下文推断数据类型?



五, c++11 STL,    C++11 中STL库中新增内容

C++ 11一个比较显著的变化是以前boost库中的一些函数被正式标准化合入到STL中了,本文就简单的介绍一下。

引用包装器(Reference Wrapper)

当模板函数参数为泛型类型的时候,无法推导出是传值还是传引用,默认情况下会使用传值的方式。这是我们可以用std::ref显式指定以传引用的方式实例化模板函数。

    #include 
    #include 

    template <class T>
    void foo(T arg)
    {
        arg++;
    }

    int main()
    {
        int count = 3;

        foo(count);        //
此时传的是值,模板实例化为
foo(int)count值不变
        std::cout << count << std::endl;

        foo(
std::ref(count));    //
此时传的是引用,模板实例化为foo(int&)count值加1
        std::cout << count << std::endl;
    }

智能指针(Smart Pointers)

智能指针主要引入了shared_ptr、weak_ptr、unique_ptr三种,其中shared_ptr和weak_ptr就是boost库中的应对象,我以前也写过相关文章介绍他们,这里就不介绍了。

而新引入的unique_ptr和以前介绍过的boost库中的scoped_ptr比较类似,而STL中本身就有一个类似的auto_ptr,它们之间的主要区别是:

  • auto_ptr可以支持'='操作,也可作为函数的返回值
  • unique_ptr不支持'='操作,可以作为函数的返回值
  • scoped_ptr即不支持'='操作,也不能作为函数的返回值

相比较而言,unique_ptr权限比auto_ptr小,也没有scope_ptr不能作为返回值的限制,是用起来最合适的,完全可以代替auto_ptr

仿函数

四个boost库也合入了stl库中:

  • function
  • bind
  • result_of
  • mem_fn

其中function和bind我以前在介绍boost库中已经介绍过,在支持auto关键字后,通过bind创建function更加简单了,我们只需要用一句话就能创建成员函数的仿函数。

    #include 
    #include 
    using namespace std;
    
using namespace std::placeholders; 

    struct X
    {
        bool foo (int a) { cout<< a << endl; return false;}
    };

    int main()
    { 
        X x;

        
auto func = bind(&X::foo, &x, _1);
        func(5);
    }

PS:使用bind的时候需要加入std::placeholders的using,否则编译报语法错误。

不过感觉bind基本上被lambda表达式给秒了,对于上面例子里,用lambda表达式的写法如下:

    auto func = [&x](int a) { x.foo(a); };
    function<void (int)> func = [&x](int a) { x.foo(a); };

由于lambda表达式是语法糖,因此可读性方面更好(感觉简洁度都快接近C#的匿名函数了),没有_1,_2之类的占位符,对于函数的调用方式也是显式直接调用,更加直观。

而result_of在auto引入后感觉也基本上没有用了,直接使用auto要简单得多。

容器

容器方面主要加了如下几个:

  1. tuple
  2. array
  3. unordered_set和unordered_map

其中tuple和array基本上就是boost相关库给标准化了,而unordered_set和unordered_map则是hash表方式的set和map,以提供更高的查询性能。使用方式和原来二叉树版的也大同小异,这里就不多介绍了。

正则表达式

Boost的regex库也终于标准化了,要使用字符串处理的可以不用到处找第三方的正则表达式库了。不过目前VC还不支持像C#那样取消转义字符(gcc可以),在代码里面的正则表达式依然非常难读,希望MS能尽快把raw string literal给支持上。

线程

Boost的线程库也标准化了,另外那个类似于.Net TPL库的packaged_task也标准化了,由于它有不少可以介绍的地方,我会专门写篇文章来介绍它,这里就不多说了。

时间函数

其实C语言标准库是提供了时间函数的,不过极度难用,现在Boost的时间函数chrono已经给标准化了,虽然还是比不上.Net的TimeSpan好用,但起码比标准C的那套好太多了。

    #include 
    #include 
    #include 
    using namespace std;

    int fibonacci(int n)
    {
        if (n < 3) return 1;
        return fibonacci(n-1) + fibonacci(n-2);
    }

    int main()
    {
        auto start = chrono::system_clock::now();
        int result = fibonacci(40);
        auto end = chrono::system_clock::now();

        int elapsed_seconds = chrono::duration_castmilliseconds>
                                 (end-start).count();

        auto end_time = chrono::system_clock::to_time_t(end);

        std::cout << "result: " << result << endl
                 << "finished computation at " << std::ctime(&end_time)
                 << "elapsed time: " << elapsed_seconds << "ms\n";
    }

另外一个日期函数Boost.Date好像还没有标准化,要用到日期相关功能还是只能用boost库。



六, boost

Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一。 Boost库由C++标准委员会库工作组成员发起,其中有些内容有望成为下一代C++标准库内容。在C++社区中影响甚大,是不折不扣的“准”标准库。Boost由于其对跨平台的强调,对标准C++的强调,与编写平台无关。大部分boost库功能的使用只需包括相应头文件即可,少数(如正则表达式库,文件系统库等)需要链接库。但Boost中也有很多是实验性质的东西,在实际的开发中实用需要谨慎。

Boost库是为C++语言标准库提供扩展的一些C++ 程序库的总称。
Boost库由Boost社区组织开发、维护。其目的是为C++程序员提供免费、同行审查的、可移植的程序库。 Boost库可以与C++标准库完美共同工作,并且为其提供扩展功能。Boost库使用Boost License来授权使用。
Boost社区建立的初衷之一就是为C++的标准化工作提供可供参考的实现,Boost社区的发起人Dawes本人就是C++标准委员会的成员之一。在Boost库的开发中,Boost社区也在这个方向上取得了丰硕的成果。在送审的C++标准库TR1中,有十个Boost库成为标准库的候选方案。在更新的TR2中,有更多的Boost库被加入到其中。从某种意义上来讲,Boost库成为具有实践意义的准标准库。
可下载Boost C++ Libraries安装boost库。大部分boost库功能的使用只需包括相应头文件即可,少数(如正则表达式库,文件系统库等)需要链接库。里面有许多具有工业强度的库,如graph库。



你可能感兴趣的:(c-c++_gdb_etc)