Andy Niu Help
1.0.0.0
|
模块 | |
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.