Andy Niu �����ĵ�

Andy Niu

Andy Niu Help  1.0.0.0
STL

模块

 Effective__STL
 
 String有关
 

变量

 理解迭代器
 
 map的元素重载小于符号
 
 迭代器失效
 
 迭代器失效的测试
 
 理解函数对象
 
 容器删除迭代器
 
 容器删除元素
 
 对序列容器的元素排序
 
 集合保存对象和保存指针
 

详细描述

变量说明

map的元素重载小于符号
1、map是用红黑树实现的,要比较大小,要求元素重载了操作符<
    方法<的两个参数都是const引用,因此如果是是用成员方法重载<,方法必须是const,
    也就是在方法之后加上const
容器删除元素
1、vector删除元素
    int main(int argc, char* argv[])
    {
        vector<int> intArray;
        intArray.push_back(1);
        intArray.push_back(2);
        intArray.push_back(3);
        intArray.push_back(2);
        intArray.push_back(4);
        intArray.push_back(5);
        intArray.push_back(2);
    
        /*
            vector删除元素
            remove不能真正删除元素,因为:
                要删除元素,必须知道是哪一种容器,调用容器的成员方法删除元素。
                但是remove只接收一对迭代器,不知道是哪一种容器。
            remove做的事情是:
                一种压缩,被删除的值挖掉,后面保留的值补充上来。操作之后,删除的值可能不在区间。
                remove返回新的逻辑终点。
        */
        intArray.erase(remove(intArray.begin(),intArray.end(),2),intArray.end());
    
        return 0;
    }
2、list删除元素,也可以使用上面的方法,但是有更好的方法,如下:
    intArray.remove(2);
3、map直接erase关键字,如下:
    int main(int argc, char* argv[])
    {
        map<int,string> aMap;
        aMap[1] = "Andy";
        aMap[2] = "Bill";
        aMap[3] = "Caroline";
        
        aMap.erase(2);
        
        return 0;
    }
容器删除迭代器
1、先看vector,示例代码如下:
    int main(int argc, char* argv[])
    {
        vector<int> intVec;
        intVec.push_back(1);
        intVec.push_back(2);
        intVec.push_back(3);
        intVec.push_back(4);
    
        for(vector<int>::iterator iter = intVec.begin(); iter != intVec.end();)
        {
            if(*iter > 2)
            {
                intVec.erase(iter);
            }
            else
            {
                ++iter;
            }
        }
    
        getchar();
        return 0;
    }
2、特别注意:intVec.erase(iter); 操作导致iter失效,正确的做法是 iter = intVec.erase(iter);
3、对于map存在同样的问题,因此解决办法也是一样,如下:
    int main(int argc, char* argv[])
    {
        map<int,string> intStrMap;
        intStrMap[1] = "Andy";
        intStrMap[2] = "Bill";
        intStrMap[3] = "Caroline";
        intStrMap[4] = "David";
    
        for(map<int,string>::iterator iter = intStrMap.begin(); iter != intStrMap.end();)
        {
            if(iter->first > 2)
            {
                iter = intStrMap.erase(iter);
            }
            else
            {
                ++iter;
            }
        }
    
        getchar();
        return 0;
    }
4、需要注意的是,在linux平台,map的erase方法并不返回下一个迭代器,
    因此,跨平台的解决办法是 intStrMap.erase(iter++); 但是vector却不能这样用。
5、总结如下:
    对于vector,必须使用 iter = vector.erase(iter); 
    对于map,   必须使用 map.erase(iter++);
    对于list, 上面的两种方式都可以
对序列容器的元素排序
示例代码如下:
#include <string>
#include <vector>
using namespace std;

struct Person
{
public:
    int _Age;

    Person(int age)
    {
        _Age = age;
    }
};

int main(int argc,char* argv[])
{
    Person p1(5);   
    Person p2(2);
    Person p3(4);
    Person p4(1);
    Person p5(3);

    vector<Person> pVec;
    pVec.push_back(p1);
    pVec.push_back(p2);
    pVec.push_back(p3);
    pVec.push_back(p4);
    pVec.push_back(p5);

    return 0;
}
第一种办法,添加重载<操作符的成员方法,调用sort(first,last),如下:
struct Person
{
public:
    int _Age;

    Person(int age)
    {
        _Age = age;
    }

    bool operator< (const Person& rhs)
    {
        return this->_Age < rhs._Age;
    }
};

int main(int argc,char* argv[])
{
    Person p1(5);   
    Person p2(2);
    Person p3(4);
    Person p4(1);
    Person p5(3);

    vector<Person> pVec;
    pVec.push_back(p1);
    pVec.push_back(p2);
    pVec.push_back(p3);
    pVec.push_back(p4);
    pVec.push_back(p5);

    sort(pVec.begin(),pVec.end());

    return 0;
}
第二种办法,添加重载<操作符的普通方法,调用sort(first,last),如下:
struct Person
{
public:
    int _Age;

    Person(int age)
    {
        _Age = age;
    }
};

bool operator< (const Person& lhs,const Person& rhs)
{
    return lhs._Age < rhs._Age;
}

int main(int argc,char* argv[])
{
    Person p1(5);   
    Person p2(2);
    Person p3(4);
    Person p4(1);
    Person p5(3);

    vector<Person> pVec;
    pVec.push_back(p1);
    pVec.push_back(p2);
    pVec.push_back(p3);
    pVec.push_back(p4);
    pVec.push_back(p5);

    sort(pVec.begin(),pVec.end());

    return 0;
}
第三种办法,添加普通方法,比较大小,调用sort(first,last,pred),如下:
struct Person
{
public:
    int _Age;

    Person(int age)
    {
        _Age = age;
    }
};

bool PersonLess(const Person& lhs,const Person& rhs)
{
    return lhs._Age < rhs._Age;
}

int main(int argc,char* argv[])
{
    Person p1(5);   
    Person p2(2);
    Person p3(4);
    Person p4(1);
    Person p5(3);

    vector<Person> pVec;
    pVec.push_back(p1);
    pVec.push_back(p2);
    pVec.push_back(p3);
    pVec.push_back(p4);
    pVec.push_back(p5);

    sort(pVec.begin(),pVec.end(),PersonLess);

    return 0;
}
第四种办法,使用函数对象,比较大小,调用sort(first,last,pred),如下:
struct Person
{
public:
    int _Age;

    Person(int age)
    {
        _Age = age;
    }
};

class PersonCompare
{
public:
    bool operator()(const Person& lhs,const Person& rhs)
    {
        return lhs._Age < rhs._Age;
    }
};

int main(int argc,char* argv[])
{
    Person p1(5);   
    Person p2(2);
    Person p3(4);
    Person p4(1);
    Person p5(3);

    vector<Person> pVec;
    pVec.push_back(p1);
    pVec.push_back(p2);
    pVec.push_back(p3);
    pVec.push_back(p4);
    pVec.push_back(p5);

    sort(pVec.begin(),pVec.end(),PersonCompare());

    return 0;
}
特别注意:上述的解决方法都是在windows下使用的,在linux也有对应的解决方法。
但是在linux下,用于比较大小的函数,两个参数要求都是const引用。而在windows下可以不是const引用。
因此,在linux下,第一种方法对应的代码实现分别为:
struct Person
{
public:
    int _Age;

    Person(int age)
    {
        _Age = age;
    }

    bool operator< (const Person& rhs) const
    {
        return this->_Age < rhs._Age;
    }
};
可以认为linux的要求更加严格,也非常合理,为了兼容,都应该使用const引用
如果是要求按年龄逆序,只需要修改一下比较的地方就好了。如下:
return this->_Age > rhs._Age;
理解函数对象
1、考虑下面的需求,函数接受一个int参数,返回这个参数加上100,如下:
    int add(int a)
    {
        return a+100;
    }
    
    int main()
    {
        int b = add(1);
        return 0;
    }
2、这里add是一个函数,但是sum也可以是一个对象,如下:
    class Add
    {
    public:
        int operator() (int a)
        {
            return a+100;
        }
    };
    
    int main()
    {
        Add add;
        int b = add(1);
        return 0;
    }
3、在上面的示例中,add是一个对象。可以把add当成一个函数来使用,是因为Add类重载了小括号。
    Add类重载了小括号,小括号就是一个方法名称,换个方式调用,看看它的真面目,如下:
    int main()
    {
        Add add;
        int b = add.operator()(1);
        return 0;
    }
    operator()就是一个方法名,只不过可以省略(连同点号),如下:int b = add(1);
4、当然也可以把一个匿名对象当成函数使用,如下:
    int main()
    {
        int b = Add()(1);
        return 0;
    }
5、再扩展一下,可以使用模板类,如下:
    template<typename T>
    class Add
    {
    public:
        T operator() (T a)
        {
            return a+100;
        }
    };
    
    int main()
    {
        int    b = Add<int>()(1);
        double d = Add<double>()(1.0);
        return 0;
    }
6、那么问题来了,使用函数挺好的,也可以创建模板函数,为什么还要使用函数对象呢?使用函数对象有必要吗?
    考虑下面的需求,有另一个客户,要求对函数的参数加上200,而原来的客户还是要求对函数的参数加上100
    因此,不能修改函数的实现,必须增加一个函数。
    问题的根源在于:函数只能使用形参,而这里的只有一个形参。
    而对象却很容易解决这个问题,因为对象可以包含字段。如下:
    class Add
    {
    public:
        Add(int delta):_Delta(delta)
        {
    
        }
    
        int operator() (int a)
        {
            return a+_Delta;
        }
    
    public:
        int _Delta;
    };
    
    int main()
    {
        int clientA = Add(100)(1);
        int clientB = Add(200)(1);
    
        return 0;
    }
7、还有一点很重要,函数对象还可以适配。
理解迭代器
1、迭代器是对指针的封装,具备指针语义
2、迭代器内部关联一个指针ptr,指向Item,*与->操作符实现如下:
    Item& operator*() const {return *ptr;}
    Item* operator->() const {return ptr;}
    特别注意:空指针是判断 ptr == NULL ,而迭代器不行,迭代器是判断 iter == end()
迭代器失效
1、示例代码如下:
    int main()
    {
        vector<int> aVec;
        aVec.push_back(1);
        aVec.push_back(2);
        aVec.push_back(3);
    
        vector<int>::iterator iter = aVec.begin();
        aVec.erase(iter);
    
        if(iter == aVec.end())
        {
    
        }
    
        return 0;
    }
    if(iter == aVec.end())运行崩溃,错误信息Expression: vector iterators incompatible
2、示例代码如下:
    int main()
    {
        vector<int> aVec;
        aVec.push_back(1);
        aVec.push_back(2);
        aVec.push_back(3);
    
        vector<int>::iterator iter = aVec.begin();
        aVec.erase(iter);
    
        int aa = *iter;
    
        return 0;
    }
    int aa = *iter;运行崩溃,错误信息Expression: vector iterator not dereferencable
3、对于map和set也是同样的问题,错误原因是:迭代器失效。
4、思考,迭代器失效到底是什么意思?
    无论是基于连续内存的容器,还是基于节点的容器,保存的元素都是原对象的一个副本,这个元素占用一定的内存空间。
    迭代器是对指针的封装,这个指针就是指向容器元素的内存地址。
    当容器删除一个元素,或者元素发生移动,容器会对这个元素进行析构,也就是调用对象的析构方法,但是元素指针不会修改。
5、但是对于分配的内存空间呢?
    详见 迭代器失效的测试
6、注意:如果容器保存的元素是指针,必须手动对指针进行delete,为什么?
    因为容器是对元素析构,就是对指针析构,而对指针析构是不做任何事情,必须对指针delete才能释放内存。
迭代器失效的测试
1、当对迭代器删除或者移动的时候,会导致迭代器失效,迭代器失效到底意味着什么?
    迭代器是对指针的封装,取指针值的方法是:
    vector<int>::iterator iter = aContainer.begin(); int* pp = &*iter;
    当对迭代器删除或者移动的时候,会对迭代器关联的对象进行析构,但是对元素指针并不进行任何操作。
2、测试场景可以分为如下:
    int
            连续内存
                    WinDebug
                    WinRelease
                    Linux
            基于节点
                    WinDebug
                    WinRelease
                    Linux
    Person
            连续内存
                    WinDebug
                    WinRelease
                    Linux
            基于节点
                    WinDebug
                    WinRelease
                    Linux
3、先看 int    连续内存,测试代码如下:
    #include <stdio.h>
    #include <string>
    #include <vector>
    #include <list>
    using namespace std;
    
    int main()
    {
        vector<int> aContainer;
        aContainer.push_back(1);
        aContainer.push_back(2);
        aContainer.push_back(3);
    
        vector<int>::iterator iter = aContainer.begin();
        int* pp = &*iter;
        int aa = *iter;
        printf("Int addr[%p] value[%d]\n",
            pp,
            aa);
    
        aContainer.erase(iter);
    
        pp = &*iter;
        aa = *iter;
        printf("Int addr[%p] value[%d]\n",
            pp,
            aa);
    
        getchar();
        return 0;
    }
    a、WinDebug下 pp = &*iter; 会崩溃,调试可以发现,原因是:
        迭代器会关联容器的指针,在WinDebug下,会检查迭代器关联的容器。
        在删除迭代器之后,迭代器关联容器指针为空,导致断言失败。
    b、WinRelease下,打印如下:
        Int addr[007C2478] value[1]
        Int addr[007C2478] value[2]
        原因是:release正常,因为没有断言进行检查。
        删除迭代器,迭代器关联的元素指针不变,对int值1的内存析构,这块内存取值应该为垃圾,
        但是基于连续内存,删除一个元素,后面的元素会逐一补上来,因此这块垃圾内存被设置为2
    c、Linux下,和WinRelease结果一样。
4、看 int 基于节点,测试代码如下:
    #include <stdio.h>
    #include <string>
    #include <vector>
    #include <list>
    using namespace std;
    
    int main()
    {
        list<int> aContainer;
        aContainer.push_back(1);
        aContainer.push_back(2);
        aContainer.push_back(3);
    
        list<int>::iterator iter = aContainer.begin();
        int* pp = &*iter;
        int aa = *iter;
        printf("Int addr[%p] value[%d]\n",
            pp,
            aa);
    
        aContainer.erase(iter);
    
        pp = &*iter;
        aa = *iter;
        printf("Int addr[%p] value[%d]\n",
            pp,
            aa);
    
        getchar();
        return 0;
    }
    a、WinDebug下,和基于连续内存一样,会崩溃。
    b、WinRelease下,打印如下:
        Int addr[00552468] value[1]
        Int addr[00552468] value[-17891602]
        原因是:release正常,因为没有断言进行检查。
        删除迭代器,迭代器关联的元素指针不变,对int值1的内存析构,导致这块内存取值为垃圾,
        基于节点,后面的元素并不移动。
    c、Linux下,打印如下:
        [root@localhost niu]# ./main
        Int addr[0x9c16010] value[1]
        Int addr[0x9c16010] value[1]
        为什么?
        可以这样认为,windows析构,会对这块内存取值重置为无效的值,而linux析构,对这块内存的取值不处理,
        但是标识这块内存可以被使用,在运行一段时间后,这块内存被重新使用,会导致取值为垃圾,导致未定义的行为。
        也就是说,没有被析构,这块内存不能被使用。当析构时,windows会重置为无效值,被标识为可用。
        而linux对原来的取值不处理,仅仅标识这块内存可用。不知道什么时候被使用,总之,继续使用很危险。
5、再看 Person 连续内存,测试代码如下:
    #include <stdio.h>
    #include <string>
    #include <vector>
    #include <list>
    using namespace std;
    struct Person
    {
        int     _Age;
        string  _Name;
    
        Person()
        {
    
        }
    
        Person(int age,string name)
        {
            _Age = age;
            _Name = name;   
        }
    
        Person(const Person& p)
        {
            _Age = p._Age;
            _Name = p._Name;
        }
    
    };
    
    
    int main()
    {
        list<Person> aContainer;
        aContainer.push_back(Person(1,"Andy"));
        aContainer.push_back(Person(2,"Bill"));
        aContainer.push_back(Person(3,"Caroline"));
    
        list<Person>::iterator iter = aContainer.begin();
        Person* pp = &*iter;
        Person aa = *iter;
        printf("Person addr[%p] value[%d:%s]\n",
            pp,
            aa._Age,
            aa._Name.c_str());
    
        aContainer.erase(iter);
    
        pp = &*iter;
        aa = *iter;
        printf("Person addr[%p] value[%d:%s]\n",
            pp,
            aa._Age,
            aa._Name.c_str());
    
        getchar();
        return 0;
    }
    测试结果,道理和int 连续内存一样。
6、再看 Person 连续节点。
    测试结果,道理和int 连续节点一样。     
7、无论集合中的元素是基本类型还是用户定义的类型,集合都会对动态内存进行自动管理。
    但是如果集合元素是指针本身,需要手动管理。
集合保存对象和保存指针
1、考虑下面的需求,在vector集合中保存一组Dog对象,可以使用vector<Dog>,也可以使用vector<Dog*>
    二者的区别是:
    vector<Dog>在vector分配的内存上,直接存放Dog对象。
    vector<Dog*>在vector分配的内存上,存放Dog指针,Dog对象存放在new出来的动态内存上。
2、优先考虑使用vector<Dog>?
    a、vector<Dog>更节省内存,vector分配的内存也在动态内存上,相对于vector<Dog*>,每个元素节省一个指针的开销。
    b、vector<Dog>管理起来更简单,vector存放的是对象的副本,删除或者移动一个元素的时候,会调用这个对象析构方法。
        (注:这个时候对应的内存是一堆垃圾,还指向这块内存的迭代器就失效了)。
        但是对于指针需要手动管理,这增加内存管理的负担。没有释放,重复释放,或者野指针的问题。
3、但是有些场景下,需要使用vector<Dog*>
4、第一种情况,对象的copy构造成本很大,或者不具备copy构造的语义。
5、第二种情况,包含一组类型不同但是相关联的对象,也就是继承关系,要求具备多态语义。
    这个时候必须使用vector<Dog*>,否则保存副本,对象切割,不具备多态行为。
6、使用vector<Dog*>增加内存管理的负担,解决办法是:使用代理,栈上对象管理动态分配的内存。
    也就是智能指针。
Copyright (c) 2015~2016, Andy Niu @All rights reserved. By Andy Niu Edit.