STL笔记(3)- Effective STL

大兔子大兔子 提交于 2020-02-18 07:35:11

Effective STL

 

1: 慎重选择容器类型

 

2: 不要试图编写独立于容器类型的代码

 

3: 确保容器中的对象copy正确且搞笑

存在继承关系时的copy会导致分割, 可以存储对象指针.

 

4: 调用empty而不是检查size()是否为0

 

empty对所有标准容器是常数时间操作.

size()返回容器元素个数, 耗费线性时间.capacity()返回能够存储的总数, 对于vector(type) c, capacity0, 不能赋值, 必须用reserve分配空间.

 

5: 区间成员函数优先于与之对应的单元素成员函数.

避免写显示循环, 这样减少对象拷贝, 减少代码.

 1 vector<int> v1, v2; 2  3 //... 4  5 v1.clear(); 6  7 for(vector<int>::const_iterator ci = v2.begin(); ci != v2.end(); ++ci) 8  9 {10 11        v1.push_back(*ci);12 13 }14 15 相当于16 17 v1.assign(v2.begin(), v2.end());18 19  



而算法

copy(v2.begin(), v2.end(), back_inserter(v1));

中尽管没有显示循环, 但是copy算法中可能有. 可以改为:

v1.insert(v1.end(), v2.begin(), v2.end());

 

6: C++编译器最烦人的分析机制

C++尽可能的解释为函数声明;

1 ifstream dataFile("xx.txt");2 list<int>data(istream_iterator<int>(dataFile),3 istream_iterator<int>()); //被认为是函数声明, 而不是够找一个对象.



最好改成:

1 ifstream dataFile("xx.txt");2 3 istream_iterator<int> data1(dataFile);4 5 istream_iterator<int> data2;6 7 list<int>data(data1, d


ata2);

 

7: 如果容器中包含了通脱new操作创建的指针, 在容器对象析够前将指针delete

容器在析构时会析构容器元素, 但是如果是指针容器, delete操作, 需手动delete

或者可以存储只能指针,

 

8: 切勿创建包含auto_ptr的容器.

 

iterator_traits<pointobj>::value_typepointobj所指向的类型, 如果pointobjvector<int>::iterator, iterator_traits<pointobj>::value_typeint

 

9: 慎重选择删除元素的方法

 

erase-remove方法.

set, multiset, map, multimupremove方法.

erase,所有删除元素之后的指向元素的迭代器会无效.

 

10. 了解分配自的约定和限定

 

11. 理解自定义分配子的合理用法

 

12. 切勿对STL容器的线程安全性有不切实际的依赖

 

13. vectorstring优先于动态分配的数组

 

string使用了引用计数计数, 在多线程中会比较麻烦, 需要同步控制, 使用vector<char>替代string, vector不允许使用引用计数.

 

14. 使用reserve来避免不必要的重新分配

 

15: 注意string实现的多样性

 

16: 了解如何把vectorstring数据传给旧的api

 

vector使用&v[0], 或者&*v.begin(), 注意,vector可能为空, 则产生野指针.

string使用s.c_str();

 

17: 使用swap技巧除去多余的容量.

 

vector<int> v;

//...

vector<int>(v).swap(v); // vector<int>(v)创建临时变量, vcopy, vectorcopy构造函数只分配所需的内存. swap止呕, v去除了不需要的空间.

 

18: 避免使用vector<bool>

vector<bool>并不真正存储bool, 为了节省空间, vector中的bool只占一个二进制位.

vector必须支持operator[],vector<bool>不允许. &v[0]也不允许.

deque<bool> OK

 

19: 理解equalityequivalence的区别.

 

相等equality是以operator==, 等价是以A不大于BB也不大于A.

 

20: 为包含指针的关联容器指定比较类型

 

set默认的比较函数是less.

set<string *> ssp实际为set<string*, kess<strubg*>>ssp;

所以需要编写string *的比较函数

 1 struct stringPtrLess: 2  3        public binary_function<const string*, cosnt string*, bool>{ 4  5               bool operator()(const string *ps1, const string *ps2)const 6  7               { 8  9                      return *ps1 > *ps2;10 11               }12 13 }14 15 set<string *, stringPtrLess> ssp;



21: 总是让比较函数在等值情况下返回false

 

22: 切勿直接修改setmultiset中的键

 

map, multimap中键也不可更改.元素类型为pair<const k, v>

 

23: 考虑用排序的vector替代关联容器

 

标准关联容器通常被实现为平衡的二叉树查找.

sort算法排序vector.

 

24: 当效率至关重要时, 请在map::operator[]map::insert之间谨慎抉择

 

如果key不存在, map::operator[]相当于先构造再赋值

1 map<int, string> m;2 3 m[1] = "123";



相当于

 1 pair<map<int, string>::iterator, bool> result = m.insert(map<int, string>::value_type(1, string())); 2  3 result.first->second = "123"; 4  5   6  7 insert: 8  9 map<int, string> m;10 11 m.insert(map<int, string>::value_type( 1, "123"));



如果元素已经存在, 优先选择operator[], 代码简洁.

 

25: 熟悉非标准的哈希容器

 

26: iterator优先于const_iterator, reverse_iterator, const_reverse_iterator

 

 

iteratorconst_iterator不是同一类型, 不能用const_cast转换.

 

27: 使用distanceadvance将容器的const_iterator转换沉iterator

1 deque<int> d;2 3 deque<int>::const_itertaor ci;4 5 deque<int>::iterator i(d.begin());6 7 advance(i, distance< deque<int>::const_itertaor >(i, ci); /*必须将i转换成deque<int>::const_itertaor, 否则distance模板无法编译.*/


 

distance两个迭代器之间的距离, advance将迭代器移动指定距离.

 

28: 正确理解有reverse_iteratorbase()成员函数所产生的iterator的用法

 

 

相差一个位置

但是不可用v.erase(--ri.base()); C/C++规定了从函数返回的指针不能被修改.

所以改为v.erase((++ri).base());

 

 

29: 对于逐个字符的输入请考虑使用istreambuf_iterator

 

 

30:确保目标区间足够大

 

确保vector中元素存在, 如果不存在, 调用resize, 或者使用back_iterator.

reserve只是增加容器的容量, 而容器size并未改变.

 

31: 了解各种与排序有关的选择

 

sort 完全排序

stable_sort 稳定排序, 如果AB相等且排序前AB, 则排序后A仍然在B

partial_sort 之排序部分,sortEnd为排序的最后一位的下一个.

 

 1 template<class RandomAccessIterator> 2  3    void partial_sort( 4  5       RandomAccessIterator first,  6  7       RandomAccessIterator sortEnd, 8  9       RandomAccessIterator last10 11    );12 13 template<class RandomAccessIterator, class BinaryPredicate>14 15    void partial_sort(16 17       RandomAccessIterator first, 18 19       RandomAccessIterator sortEnd,20 21       RandomAccessIterator last22 23       BinaryPredicate comp24 25    );26 27  28 29 nth_element 与partial_sort不同之处在于只是将某些元素移至到前面,但并未排序.30 31 template<class RandomAccessIterator>32 33    void nth_element(34 35       RandomAccessIterator _First,36 37       RandomAccessIterator _Nth,38 39       RandomAccessIterator _Last40 41    );42 43 template<class RandomAccessIterator, class BinaryPredicate>44 45    void nth_element(46 47       RandomAccessIterator _First,48 49       RandomAccessIterator _Nth, //排序的最后一个元素50 51       RandomAccessIterator _Last,52 53       BinaryPredicate _Comp54 55    );



 

partition 将满足条件的元素移至前面, 返回迭代器指向第一个不满足条件的元素

 

stable_partition

 

32: 如果确实需要删除元素, 需要在remove这类算法后调用容器的erase

 

remove, remove_if, unique等算法不是真正的删除, 算法不会改变容器元素.需要调用

v.erase(remove(v.begin(), v.end(), 22));

list::removelist_unique会真正删除元素.

 

 

33: 对包含指针的容器使用remove这一类算法时要特别小心

 

会将某些指针覆盖无法delete

应该先用for_each将不符合的元素指针delete再置为0, 之后removeerase

 

34: 了解哪些算法要求使用排序的区间作为参数

 

35: 通过mismatchlexicographical_compare实现简单的忽略大小写的字符串比较

 

mismatch

mismatch(s1.begin(), s1.end(), s2.begin(), not2(ptr_fun(charCompare))); 返回pair, firsts1的第一个不匹配的迭代器, seconds2的第一个不匹配的迭代器, 如果都匹配, 则返回pair(s1.end(), s2.begin()+s1.end()-s1.begin());

lexicographical_compare接受判别式判断两个区间大小, 直到判别式不为true, 如果第一个却见结束了, 返回true

 1 bool charLess(char c1, char c2) 2  3 { 4  5        return tolower(static_cast<unsigned char> (c1)<tolower(static_cast<unsigned char> (c2); 6  7 } 8  9 bool stingCompare(const string &s1, const string &s2)10 11 {12 13        return lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), charLess);14 15 }16 17  



36: 理解copy_if算法的正确实现

 

STL中没有copy_if, 利用remove_copy_if

 

37: 使用accumulatefor_each进行区间统计

 

38: 遵循按值传递的原则来设计函数子类

 

39: 确保判别式是纯函数

 

判别式是返回bool类型的函数

 

纯函数是返回值仅和参数相关的函数.只有参数发生变化,返回值才变化.

判别式类是函数子类, operator()为判别式.

 

40: 若一个类是函数子, 则应使它可配接

 1 bool charLess(const char c) 2  3 { 4  5        return c>40; 6  7 } 8  9 int _tmain(int argc, _TCHAR* argv[])10 11 {12 13        find_if(s.begin(), s.end(), not1(charLess)); /*无法编译,charLess缺少not所需的类型, ptr_fun完成了类型定义的工作*/14 15 find_if(s.begin(), s.end(), not1(ptr_fun(charLess)));16 17 }



最简单的方法是让函数子从特定的基类继承, 如果函数子的operator()只有一个实参, 则继承std::unary_function, 如果有两个实参, 继承std::binary_function, 以上基类都为模板, 继承是需指定实参和返回类型.

struct stringCompare:public std::binary_function<string, string, bool>/*注意, 去掉const和&, 但是如果类型为指针, 则不能去掉*/{public:       bool operator()(const string& s1, const string& s2) const       {              return s1<s2;       }};int _tmain(int argc, _TCHAR* argv[]){       vector<string> v;       //...       find_if(v.begin(), v.end(), bind2nd(stringCompare(), "123"));} 



41: 理解ptr_fun, mem_fun, mem_fun_ref的来由

 

mem_fun 参数为成员函数指针, 并返回mem_fun_t类型对象, mem_fun_toperator()函数中调用了通过参数传递进来的对象上的成员函数

mem_fun_refmem_fun, 区别为一个是对象引用, 一个是函数指针.

 1 class Widget 2  3 { 4  5 public: 6  7        void test() //paramter: this 8  9        {10 11        }12 13 };14 15 vector<Widget> v;16 17 //...18 19 for_each(v.begin(), v.end(), mem_fun_ref(&Widget::test));20 21  22 23 vector<Widget *> pv;24 25 //...26 27 for_each(pv.begin(), pv.end(), mem_fun(&Widget::test));28 29  



42: 确保less<T>operator<具有相同的意义

 

43: 算法调用优先于手写的循环

 

44: 容奇的成员函数优先于同名的算法

 

45: 正确区分count, find, binary_seach, lower_bound, upper_bound, equal_range

 

count, find使用相等性搜索, binary_seach, lower_bound, upper_bound, equal_range使用了等价性.

 

46: 考虑使用函数对象而不是函数作为STL算法的参数

 

函数作为参数时, 会产生函数调用, 且函数指针抑制了inline机制.

 

47: 避免产生write_only代码

 

48: 总是包含正确的头文件

 

49: 学会分析与STL相关的编译器诊断信息

 

50: 熟悉与STL相关的web站点

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!