导航

c++STL

Posted on 2017-11-30 10:51  困或  阅读(383)  评论(0编辑  收藏  举报

1.STL

  [1]STL容器是直接管理存储数据。

  [2]STL适配器是在这些容器上面增加一些操作函数,封装成一个模板。

2.STL 容器

  [1]容器类型:

    1)array:数组。

    2)bitset:二进制位操作。

    3)deque:双向队列。

    4)forward_list:单向链表。

    5)list:双向链表。

    6)map(multimap):map:键值非重复容器,multimap:键值可重复容器。 

    7)set(multiset):自动排序非重复集合。

    8)vector:动态数组。

    9)unordered_set(unordered_multiset):按照hash来存储的set(multiset)。

    10)unordered_map(unordered_multimap):按照hash来存储的map(multiset)。

  [2]容器的分类:

    1)容器分为顺序容器和关联容器。

    2)顺序容器是不进行自动排序的,关联容器是按照元素的值进行自动排序的。其中map和set是关联容器,其他的是顺序容器,最主要的就是关联容器有find方法,可以快速查找元素。

  [3]顺序容器的使用说明:

    1)除了array是固定大小外,其他容器都提供自动内存管理。

    2)string和vector保存在连续内存中,因此随机访问很快,但是在中间插入删除会很慢。

    3)deque随机访问很快,并且在两端插入删除很快,但是在中间插入删除会很慢。

    4)list和forward_list在任何地方插入删除很快,随机访问很慢。

  [4]关联容器的使用说明:

    1)map的使用,map是一个键值对,其中值是一个pair对象:

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#include <set>
#include <map>
#include <arpa/inet.h>

using namespace std;

class ca
{
public:
    int ip;
};

class comp{
public:
    bool operator()(const ca &a, const ca &b) 
    {
        return a.ip < b.ip;
    }
};

bool operator<(const ca &a, const ca &b)
{
    return a.ip < b.ip;
}

int main()
{
    //map的第三个参数,另外第三个参数使用普通函数时编译不过不知怎么回事
    ca a1, a2, a3;
    map<ca, string> addr2;            //使用全局的<运算符重载函数
    map<ca, string, comp> addr1;    //使用仿函数

    addr1.insert(pair<ca, string>(a1, "1 addr a1"));
    addr2.insert(pair<ca, string>(a1, "2 addr a1"));


    //pair的使用
pair<ca, string> p1(a1, "1 addr a1"); //创建一个pair对象 addr1.insert(p1);
addr1.insert(pair<ca, string>(a2, "1 addr a2")); //隐式的创建一个pair对象,这个是常用的方式,因为通常没有一个现成的对象 addr1.insert(make_pair(a2, "1 addr a3")); //调用make_pair创建一个pair对象 //关联容器定义的类型 /* map的元素类型是一个pair对象,这个很重要。 元素: map的元素就是map容器里面的一个值。就像是set<int>的每个元素是一个int型对象。 关键字: map的关键字是pair对象的第一个元素。 */ map<ca, string>::key_type v1; //v1的类型是类ca,代表关键字的类型 map<ca, string>::mapped_type v2; //v2的类型是string,代表关键字关联的类型 map<ca, string>::value_type v3; //v3的类型是pair<ca, string>,代表值的类型 set<string>::key_type v4; //v4和v5的类型是string,set的键值类型是一样的 set<string>::value_type v5; return 0; }

    2)关键字类型必须定义元素的比较方法,这个是肯定的,否则无法进行排序。实际编程中,如果一个类定义了<运算符,就可以作为关键字了,这个意思就是说关键字没有必要实现>和=运算符,实现了和不实现效果是一样的。还有就是定义<运算符要符合严格弱序的规则,这意思就是自定义的<运算符函数,实现要合理,例如传给这函数一个(a,b),返回true,传给一个(b,a),还是返回true,那就不符合严格弱序了。虽然不符合但是不会导致编译错误,只是用的时候会发生各种错误,例如set插入了相同的元素。

      严格弱序规则就是:(1)如果传入(a,b)返回true,则传入(b,a)必须返回false。

                 (2)如果传入(a,b)返回true,传入(c,a)返回true,则传入(c,b)必须返回true。就是说a<b,c<a,则c要小于b。

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#include <set>
#include <arpa/inet.h>

using namespace std;

class ca
{
public:
    unsigned ip;
    int port;
    ca(int ip1, int port1) {ip = ip1; port = port1;}

    //怎么定义<运算符都是可以的,看需求。
    //只是定义不同时,元素的排序顺序是不一样的。

    //如果用这种方式,则set里面的排列顺序为:
    //    1.1.1.1:80
    //    1.1.1.1:81
    //    1.1.1.2:80
    //    1.1.1.2:81
    //        
    //这意思就是用ip去比较地址,如果ip不同时再用port去比较地址,当然使用这种需求一般用的比较多,这种方让元素之间有一层优先级。
    bool operator<(const ca &other) const{
        if(ip < other.ip) 
            return true;
        else if(other.ip < ip) 
            return false;

        if(port < other.port) 
            return true;
        else if(other.port < port) 
            return false;

        return false;
    }
    
    //完全等同于上面的函数
    bool operator<(const ca &other) const{
        if(ip < other.ip ||(ip == other.ip && port <other.port)) 
            return true;

        return false;
    }
    
    //如果用这种方式,则set里面的排列顺序为:
    //    1.1.1.1:80
    //    1.1.1.2:80
    //    1.1.1.1:81
    //    1.1.1.2:81
    //这意思就是说希望用ip或port去比较地址,只要有一个小于就是小于,很明显这种方式用的少。
    bool operator<(const ca &other) const
    {
        if(ip < other.ip || port < other.port) 
            return true;

        return false;
    }

    //错误的方式,错误的函数不会导致编译错误,但是处理的时候会发生错误。例如下面这种就可以把相同的数据插入到set里面。
    //很明显已经违背了严格弱序的原则,因为a < b为true的时候同时b < a也为true。
    bool operator<(const ca &other) const{
        return true;
    }
};

int main()
{
    set<ca> v;
    set<ca>::iterator it;

    v.insert(ca(inet_addr("1.1.1.1"), 80));
    v.insert(ca(inet_addr("1.1.1.1"), 81));
    v.insert(ca(inet_addr("1.1.1.2"), 80));
    v.insert(ca(inet_addr("1.1.1.2"), 81));

    for(it=v.begin(); it!=v.end(); it++)
        printf("%s:%d\n", inet_ntoa(*((struct in_addr *)&(it->ip))), it->port);

    return 0;
}

    3)set的迭代器是const的,就是说不能通过一个set迭代器去修改一个值,这个也是当然的,因为set是有序的,修改了就无序了,同理map的key值也是不能通过迭代器修改的,但是map的value是可以通过迭代器修改的。

    4)map和set的insert操作可以检查返回值,判断是否插入成功,例如元素已经存在则insert会失败,返回的pair类型的第二个值为false。

    5)lower_bound和upper_bound:这两个操作都返回一个迭代器,如果关键字在容器中,lower_bound返回的迭代器指向第一个元素,upper_bound返回的迭代器指向最后一个位置的之后;如果关键字不在容器中,则lower_bound和upper_bound返回相同的位置,这个位置就是不影响排序的关键字的插入位置。如果存在则只要(begin=lower_bound;begin!=upper_bound;begin++;)就可以遍历这个关键字对应的所有元素。如果不存在,则返回的迭代器指向的位置是要查的关键字可以插入的位置。

    6)multimap的使用(想必multiset的用法也是差不多的)例:

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#include <set>
#include <map>
#include <arpa/inet.h>

using namespace std;

/*
    查找mulimap关键字的方法
*/
int main()
{
    // 1.使用lower_bound和upper_bound
    multimap<int, string> m;
    multimap<int, string>::iterator begin, end;

    m.insert(pair<int, string>(1, "boy1"));
    m.insert(pair<int, string>(2, "girl1"));
    m.insert(pair<int, string>(2, "girl2"));
    m.insert(pair<int, string>(2, "girl3"));
    m.insert(pair<int, string>(5, "dog1"));
    m.insert(pair<int, string>(5, "dog2"));

    /*
        输出:
            key 2 value:girl1
            key 2 value:girl2
            key 2 value:girl3
            end value:dog1
        可见begin指向(2, "girl1"),end指向(5, "dog1")。
    */
    begin = m.lower_bound(2);
    end   = m.upper_bound(2);
    while(begin != end){
        printf("key 2 value:%s\n", begin->second.c_str());
        begin++;
    }
    printf("end value:%s\n", end->second.c_str());

    /*
        输出:
            begin value:dog1
            end   value:dog1
        可见begin 和end都指向(5, "dog1"),因为此位置就是要查的关键字3应该插入的位置。
    */
    begin = m.lower_bound(3);
    end   = m.upper_bound(3);
    printf("begin value:%s\n", begin->second.c_str());
    printf("end   value:%s\n", end->second.c_str());

    /*
        输出:
            begin value:0x7fff4e76b578
            end   value :0x7fff4e76b578
            m.end value:0x7fff4e76b578
        可见begin 和end都指向m.end(),因为此位置就是要查的关键字10应该插入的位置。
    */
    begin = m.lower_bound(10);
    end   = m.upper_bound(10);
    printf("begin value:%p\n", begin);
    printf("end   value:%p\n", end);
    printf("m.end value:%p\n", m.end());


    // 2.使用count方法得到个数
    int count;
    multimap<int, string>::iterator it;
    
    count = m.count(5);    //得到关键字5的元素个数
    it = m.find(5);     //指向关键字5的第一个元素

    /*
        输出:
            key 5 value:dog1
            key 5 value:dog2
    */
    while(count > 0){
        printf("key 5 value:%s\n", it->second.c_str());
        it++;
        count--;
    }

    // 3.使用equal_range
    pair<multimap<int, string>::iterator, multimap<int, string>::iterator> range;
    
    range = m.equal_range(2); //直接得到关键字2的迭代器的起始位置

    /*
        输出:
            key 2 value:girl1
            key 2 value:girl2
            key 2 value:girl3
    */
    while(range.first != range.second){
        printf("key 2 value:%s\n", range.first->second.c_str());
        range.first++;
    }

    return 0;
}

    7)unordered_map和unordered_set:这两个容器是无序的,就是说不是按顺序存储的。因为map和set都是自动排序的,例如遍历map或set,可以得到按顺序排序的值。但是unordered_map和unordered_set是按hash存储的,遍历会得到不按顺序输出的值,所以如果要求存储的元素有序就用map,如果无序并且需要快速查找,就用unordered_map。map和unordered_map效率比较的例子:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#include <set>
#include <map>
#include <arpa/inet.h>
#include <tr1/unordered_map>
using namespace std::tr1;

using namespace std;

double timeuse(struct timeval begin, struct timeval end)
{
    double res;
    
    if(end.tv_usec < begin.tv_usec){
        end.tv_usec += 1000000;
        end.tv_sec--;
    }

    res = end.tv_usec - begin.tv_usec;
    res += (end.tv_sec - begin.tv_sec)*1000000;
    return res;
}

int main()
{
    int i;
    
    map<int, int> m;
    map<int, int>::iterator it;
    unordered_map<int, int> um;
    unordered_map<int, int>::iterator uit;
    struct timeval tv_begin, tv_end;
    double res;

#define BEGIN   gettimeofday(&tv_begin, NULL);
#define END(s)  do{\
                    gettimeofday(&tv_end, NULL); \
                    res = timeuse(tv_begin, tv_end); \
                    printf("%-30s, usetime:%f usec [%f sec]\n", s, res, res/1000000);\
                }while(0)

    BEGIN
    for(i=0; i<10000000; i++)
        m.insert(pair<int, int>(i, i));
    END("map insert");

    BEGIN
    for(i=0; i<10000000; i++)
        um.insert(pair<int, int>(i, i));
    END("unordered_map insert");
    
    BEGIN
    it = m.find(500000);
    END("map find");

    BEGIN
    uit = um.find(500000);
    END("unordered_map find");

    return 0;
}

  输出:

map insert                    , usetime:10102899.000000 usec [10.102899 sec]
unordered_map insert          , usetime:2749505.000000 usec [2.749505 sec]
map find                      , usetime:3.000000 usec [0.000003 sec]
unordered_map find            , usetime:1.000000 usec [0.000001 sec]

  由此可见unordered_map在插入和查找方面还是比map要快很多的,当然内存占用的也多。

3.STL适配器

  [1]stack:栈适配器。这个要注意的就是栈里面是有一个容器来存储元素的,常规的理解栈应该就是一个数组,其实不是,栈默认的容器是一个双向队列(deque),栈也可以用vector、list等作为元素的容器,有个条件就是容易需要提供push_back()、back()、和pop_back()方法,因为set没有提供这些方法,所以不能把set作为栈的容器。所谓栈就是在一个容器上面加了一些操作函数,封装成了栈模板(STL中栈定义:template < class T, class Container = deque<T> > class stack;)。

  [2]queue:单向队列适配器。这个和stack是一样的,特点是先进先出。当然也是需要提供一个容器来存储元素,默认是deque。

  [3]priority_queue :优先级队列适配器。这个适配器是把容器内的元素排序好的,每次可以获取优先级最高的元素。所以可以传入一个优先级比较函数,默认比较函数是<,默认的容器是vector,定义方式priority_queue<Type, Container, Functional>。其实这个适配器只是在queue上面加了一个优先级排序函数。

4.STL具体用法

  [1]const_iterator:如果不需要写访问容器时,应该使用const_iterator迭代器,方法和iterator是一样的。

  [2]reverse_iterator(const_reverse_iterator):可以从后向前遍历容器。

  [3]管理容量:除了array之外,容器的内存都是自动管理的。size()表示当前的元素数量,capacity()表示已经预分配的容量,reserve()表示设置预分配的容量。这个意思就是说capacity()是已经预分配好的容量,不管是程序自动分配还是手动调用reserve()分配。还有就是resize()是设置容器使用的大小,和预分配没有关系,例如当前元素个数是5,则resize(8)会往容器增加3个元素,resize(4)会删除1个元素。

  [4]substr:拷贝原始string的一部分或全部。另外string类还提供了一些其他方法,来完成和char *类型的字符串的处理函数,例如类似strstr、strrstr的函数。

  举例:

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
#include <array>

using namespace std;

int main()
{
    vector<int> v;
    vector<int>::const_iterator c_it;
    vector<int>::const_reverse_iterator cr_it;

    v.push_back(1); v.push_back(2); v.push_back(3);v.push_back(4);v.push_back(5);

    //iterator对应的是begin、end
    for(c_it = v.begin(); c_it!= v.end(); c_it++)
        printf("%d\n", *c_it);

    //reverse_iterator对应的是rbegin、rend,cr_it = v.begin()是编译不过的
    for(cr_it = v.rbegin(); cr_it!= v.rend(); cr_it++)
        printf("%d\n", *cr_it);

    // 容量设置
    printf("size:    %u\n", v.size());
    printf("capacity:%u\n", v.capacity());        //程序自动预分配的容量,例如值是8
    printf("--------------------------\n");

    v.reserve(20);                                //手动设置预分配的容量,如果值小于已经自动预分配的容量则不生效
    printf("size:    %u\n", v.size());
    printf("capacity:%u\n", v.capacity());        //此时值就是20了
    printf("--------------------------\n");

    v.resize(200);                                //强制设置元素个数是200                                 
    printf("size:    %u\n", v.size());            //值是200
    printf("capacity:%u\n", v.capacity());        //超过预分配的容量后,程序就会再次自动预分配

    //string操作
    string src("test string");
    string dst;

    // substr(pos, n)   pos默认是0,n默认是总长度,所以只传一个参数的时候其实是指定了拷贝的起始位置
    dst = src.substr(3);      
    printf("dst:%s\n", dst.c_str());             // dst:"t string" 
    dst = src.substr();      
    printf("dst:%s\n", dst.c_str());             // dst:"test string"

    return 0;
}

 5.C++手册

  http://www.cplusplus.com/reference/