博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Effective STL 笔记
阅读量:6544 次
发布时间:2019-06-24

本文共 10583 字,大约阅读时间需要 35 分钟。

● 标准STL序列容器:vector、string、deque和list。

● 标准STL关联容器:set、multiset、map和multimap

------------------------------------ 

vector<int> a;  a[5]访问越界时不会抛出异常; a.at(5)访问越界时会抛出异常

 --------

 

- c.erase(remove(c.begin(), c.end(), 1963), c.end()); // 当c是vector、string或deque时,erase-remove惯用法是去除特定值的元素 的最佳方法
- c.remove(1963); // 当c是list时, remove成员函数是去除特定值的元素的最佳方法

 

- c.erase(1963); // 当c是标准关联容器时erase成员函数是去除特定值的元素的最佳方法

 ---------

 

bool
badValue(int x); // 返回x是否是“bad”
- c.erase(remove_if(c.begin(), c.end(),
badValue), c.end());// 当c是vector、string 或deque时这是去掉 badValue返回真 的对象的最佳方法
c.remove_if(
badValue); // 当c是list时这是去掉badValue返回真 的对象的最佳方法

- 对于关联容器:

 

for ( AssocContainer<int>::iterator i = c.begin(); i != c.end();
/*nothing*/ ){ // for循环的第三部分是空的; i现在在下面自增
   if (
badValue(*i)) c.erase(i++); // 对于坏的值,把当前的 i传给erase,然后作为副作用增加i;
   else ++i; //对于好的值,只增加i
}

 

---------

 

 

在循环内做某些事情(除了删除对象之外),比如:不仅删除badValue返回真的每个元素,而且每当一个元素被删掉时,我们也想把一条消息写到日志文件中。
- 如果容器是标准序列容器(vector、string和deque),写一个循环来遍历容器元素,每当调用erase时记得都用它的返回值更新你的迭代器。

for ( SeqContainer<int>::iterator i = c.begin(); i != c.end();  ){

    if (badValue(*i)){

        logFile << "Erasing " << *i << '\n';
        i = c.erase(i); // 通过把erase的返回值赋给i来保持i有效
    }
    else ++i;
}
 

- 对于关联容器:

for ( AssocContainer<int>::iterator i = c.begin();i !=c.end();/*nothing*/){

    if (badValue(*i)){
        logFile << "Erasing " << *i <<'\n'; // 写日志文件
        c.erase(i++); // 删除元素
   }else ++i;
}

-------------------------------------------------

 

考虑使用vector<char>来代替string,vector实现不允许使用引用计数,所以string里隐藏的多线程性能问题(不同平台string的实现不同,有些可能使用引用计数)不会出现了

-------------------------------------------------

 

他们会告诉你说可以用v.begin()代替&v[0]。那经常是正确的,并不总是如此。begin的返回类型是iterator,而不是一个指针,当你需要一个指向vector内部数据的指针时绝不该使用begin。

--使用“交换技巧”来修整过剩容量-----------------------------------------------

 

 

class Contestant {...};
vector<Contestant> contestants;
vector<Contestant>(contestants).swap(contestants);

 

vector<Contestant> v;
... // 使用v
vector<Contestant>().swap(v); // 清除v而且最小化它的容量
-------------------------------------
要点是无论何时你建立一个指针的标准关联容器,你必须记住容器会以指针的值排序。
-------------------------------------
条款21: 永远让比较函数对相等的值返回false
struct StringPtrGreater: // 对关联容器来说
public binary_function<const string*, const string*, bool>// 这是有效的比较类型
{
    bool operator()(const string *ps1, const string *ps2) const
    {
        return *ps2 < *ps1; // 返回*ps2是否大于*ps1(也就是交换操作数的顺序)
    }
};
书中条款21
    multiset<int, less_equal<int> > s; // s仍然以“<=”排序
    s.insert(10); // 插入10A
    s.insert(10); // 插入10B
的实例:
#include <iostream>
#include <set>
using namespace std;
int main ()
{
    multiset<int, less_equal<int> >::iterator it;
    pair<
        multiset<int, less_equal<int> >::iterator,
        multiset<int, less_equal<int> >::iterator
    > ret;
    multiset<int, less_equal<int> > mymultiset;
    mymultiset.insert(10);
    mymultiset.insert(7);
    mymultiset.insert(10);//这里会崩溃,原因是less_equal(10,10)出发assert
    ret = mymultiset.equal_range(10);
    cout << "equal_range contains:";
    for (it=ret.first; it!=ret.second; ++it)
        cout << " " << *it;
    cout << endl;
    return 0;
}

------------------------------------------------------

 

● 如果不关心移植性,你想要改变set或multiset中元素的值,而且你的STL实现让你侥幸成功,继续做。
只是要确定不要改变元素的键部分,即,会影响容器有序性的元素部分。
● 如果你在乎移植性,就认为set和multiset中的元素不能被修改,至少不能在没有映射的情况下。-

------------------------------------------------------

 

typedef set<Employee, IDNumberLess> EmpIDSet;
EmpIDSet se;
EmpIDSet::iterator i = se.find(selectedID);
if (i != se.end()) {
    i->setTitle("Corporate Deity"); // 有些STL实现会拒绝这样,因为*i是const
}

 

 

修改set的值,如何既正确又可移植:

正确的写法:

 

if (i != se.end()) {
    const_cast<Employee&>(*i).setTitle("Corporate Deity"); // 映射掉*i的常量性
}
错误的3种写法(它们不能修改*i!在这两个情况里,映射的结果是一个*i副本的临时匿名对象,而setTitle是在匿名的物体上调用,不在*i上!)

 

 

if (i != se.end()){
    static_cast<Employee>(*i).setTitle("Corporate Deity"); // 把*i映射到 一个Employee
}

 

if (i != se.end()) {
    ((Employee)(*i)).setTitle("Corporate Deity"); // 同上,但使用C映射语法
}

 

if (i != se.end()){
    Employee tempCopy(*i); // 把*i拷贝到tempCopy修改tempCopy
    tempCopy.setTitle("Corporate Deity");
}

 

-----------------------------------------------------------
因此出于对效率的考虑,当给map添加一个元素时,我们断定insert比operator[]好;而从效率和美学考虑,当
更新已经在map里的元素值时operator[]更好。
熟悉tr1::hash table用法
-----------------------------------------------------------
条款26:尽量用iterator代替const_iterator,reverse_iterator和const_reverse_iterator
-----------------------------------------------------------
条款27:用distance和advance把const_iterator转化成iterator
///i <--- ci
ConstIter ci;
... // 让ci指向d
Iter i(d.begin()); // 初始化i为d.begin()
advance(i, distance<ConstIter>(i, ci)); // 把i移到指向ci位置(但请留意下面关于为什么在它编译前要调整的原因)
---------------------------------------
条款29:需要一个一个字符输入时考虑使用istreambuf_iterator
ifstream inputFile("interestingData.txt");
string fileData((istreambuf_iterator<char>(inputFile)),  istreambuf_iterator<char>());

 

---------------------------------------
Item31:
partial_sort(widgets.begin(),widgets.begin() + 20, widgets.end(), qualityCompare);
//把最好的20个元素(按顺序)放在widgets的前端
nth_element(widgets.begin(), widgets.begin() + 19, widgets.end(), qualityCompare);
// 把最好的20个元素放在widgets前端,但不用担心它们的顺序
partial_sort,nth_element,sort是不稳定的;stable_sort是稳定的
goalPosition = begin + widgets.size() / 2;
nth_element(begin, goalPosition, end, qualityCompare);// 找到widgets中中等
vector<Widget>::size_type goalOffset = 0.25 * widgets.size();
nth_element(begin, begin + goalOffset, end, qualityCompare);// 找到质量值为75%的Widget
// begin + goalOffset现在指向质量等级为75%的Widget
vector<Widget>::iterator goodEnd = partition(widgets.begin(), widgets.end(), hasAcceptableQuality);
// 把所有满足hasAcceptableQuality的widgets移动到widgets前端,并且返回一个指向第一个不满足的widget的迭代器
//分割时保持同样质量等级的Widget的相对位置很重要,用stable_partition来代替partition
唯一我们可能会但不能使用sort、stable_sort、partial_sort或nth_element的容器是list,list通过提供sort成
员函数做了一些补偿。list::sort提供了稳定排序。
总结:
● 如果你需要在vector、string、deque或数组上进行完全排序,你可以使用sort或stable_sort。
● 如果你有一个vector、string、deque或数组,你只需要排序前n个元素,应该用partial_sort。
● 如果你有一个vector、string、deque或数组,你需要鉴别出第n个元素或你需要鉴别出最前的n个元素,
而不用知道它们的顺序,nth_element是你应该注意和调用的。
● 如果你需要把标准序列容器的元素或数组分隔为满足和不满足某个标准,你大概就要找partition或
stable_partition。
● 如果你的数据是在list中,你可以直接使用partition和stable_partition,你可以使用list的sort来代替sort和
stable_sort。如果你需要partial_sort或nth_element提供的效果,你就必须间接完成这个任务,但正如我
在上面勾画的,会有很多选择。
需要资源(时间和空间)从小到大的顺序:
partition < stable_partition < nth_element < partial_sort<sort<stable_sort
--------------------------------------------
binary_search、lower_bound、upper_bound和equal_range(参见条款45)需要有序区间
set_union、set_intersection、set_difference和set_symmetric_difference需要有序区间
merge和inplace_merge提供了有效的单遍合并排序算法,需要有序区间
includes用来检测是否一个区间的所有对象也在另一个区间中,需要有序区间

-------------------------------------------

 

bool ciCharLess(char c1, char c2) // 返回在忽略大小写的情况下c1是否在c2前面;
{
tolower(static_cast<unsigned char>(c1)) < // 条款46解释了为什么 一个函数对象可能比函数好
tolower(static_cast<unsigned char>(c2));
}
bool ciStringCompare(const string& s1, const string& s2)
{
return lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), ciCharLess);

}

--------------------------------------

 

template<typename InputIterator, // 一个copy_if的正确实现
              typename OutputIterator,
              typename Predicate>
OutputIterator copy_if(InputIterator begin,
                                 InputIterator end,
                                 OutputIterator destBegin,
                                 Predicate p)

{

     while (begin != end) {

          if (p(*begin))*destBegin++ = *begin;
          ++begin;
     }
     return destBegin;
}

 

 --------------------------------------------------

 

 
条款40:使仿函数类可适配
list<Widget*> widgetPtrs;
bool isInteresting(const Widget *pw);
list<Widget*>::iterator i = find_if(widgetPtrs.begin(), widgetPtrs.end(),isInteresting);
list<Widget*>::iterator i = find_if(widgetPtrs.begin(), widgetPtrs.end(), not1(ptr_func(isInteresting)));
not1,not2,bind1st,bind2nd
--------------------------------------------
条款41:了解使用ptr_fun、mem_fun和mem_fun_ref的原因
class Widget {
public:
void test();
};
void test(Widget& w);
vector<Widget> vw;
for_each(vw.begin(), vw.end(), test); //
for_each(vw.begin(), vw.end(), ptr_fun(test)); //
for_each(vw.begin(), vw.end(), mem_fun_ref(&Widget::test)); //
list<Widget*> lpw;
for_each(lpw.begin(), lpw.end(), mem_fun(&Widget::test));

 

--------------------------------------------
条款45:注意count、find、binary_search、lower_bound、upper_bound和equal_range的区别
count和find算法都用相等来搜索,而binary_search、lower_bound、upper_bound和equal_range则用等价。
VWIterPair p = equal_range(vw.begin(), vw.end(), w);
if (p.first != p.second) {
    // 如果equal_range不返回空的区间, 说明找到了,p.first指向第一个,
    //而p.second指向最后一个的下一个
} else {
    没找到,p.first和 p.second都指向搜索值的插入位置
}
equal_range返回的东西是两个迭代器,对它们作distance就等于区间中对象的数目
VWIterPair p = equal_range(vw.begin(), vw.end(), w);
cout << "There are " << distance(p.first, p.second) << " elements in vw equivalent to w.";
要测试在set或map中是否存在某个值,使用count的惯用方法来对成员进行检测:
set<Widget> s; // 建立set,放入数据
Widget w; // w仍然是保存要搜索的值
if (s.count(w)) { // 存在和w等价的值
} else { // 不存在这样的值
}
要测试某个值在multiset或multimap中是否存在,find往往比count好

----------------+------------------------------+--------------------------------------------

你想知道的
........
|使用的算法
.....................
|使用的成员函数

................
|-----------+------------------+-------------+------------------------------

................
|在无序区间
...
|在有序区间
.........
|在set或map上..|在multiset或multimap上

----------------+-----------+------------------+-------------+------------------------------

期望值是否存在?...|find
.......
|binary_search
.....
|count
........
|find

----------------+-----------+------------------+--------------------------------------------

期望值是否存在?...|find
.......
|equal_range
.......
|find
.........
|find或lower_bound(参见下面)

如果有,第一个等...|
...........
|
..................
|
.............|

于这个值的对象在...|
...........
|
..................
|
.............|

哪里?...........|
...........
|
..................
|
.............|

----------------+-----------+------------------+-------------+------------------------------

第一个不在期望值...|find_if
....
|lower_bound
.......
|lower_bound..|lower_bound

之前的对象在哪里...|
...........
|
..................
|
.............|

----------------+-----------+------------------+-------------+------------------------------

第一个在期望值之...|find_if
....
|upper_bound
.......
|upper_bound..|upper_bound

后的对象在哪里?...|
...........
|
..................
|
.............|

----------------+-----------+------------------+-------------+------------------------------

有多少对象等于期...|count
......
|equal_range
.......
|count
........
|count

望值?...........|
............
|然后distance
.......
|
.............|

----------------+-----------+------------------+-------------+------------------------------

等于期望值的所有...|find(迭代)..|equal_range
......
|equal_range..|equal_range

对象在哪里?.......|
...........
|
..................
|
.............|

----------------+-----------+------------------+-------------+------------------------------
 
--------------------------------------------
条款46:考虑使用函数对象代替函数作算法的参数
仿函数类StringSize的创造不仅避开了编译器一致性问题,(由于内联的原因)会带来性能提升。

 

 

转载于:https://www.cnblogs.com/yaoyansi/articles/2363240.html

你可能感兴趣的文章
[LeetCode]Longest Increasing Path in a Matrix
查看>>
C++基础之适配器
查看>>
集合set-深入学习
查看>>
C#语言学习——面向对象的几大原则
查看>>
zk 常用资料整理(转)
查看>>
JavaScript 字符串操作
查看>>
Android中asset文件夹和raw文件夹区别
查看>>
Fuel 30 分钟快速安装openstack 分类: 软件插件学习 ...
查看>>
第二章家庭作业 2.78
查看>>
Android 下拉刷新上拉载入 多种应用场景 超级大放送(上)
查看>>
Risc-V指令集
查看>>
Python进阶04 函数的参数对应
查看>>
C语言结构体的“继承”
查看>>
WebView之禁止调用第三方浏览器
查看>>
POJ 3468 A Simple Problem with Integers(线段树 区间更新)
查看>>
安装apr-1.6.3报错[cannot remove `libtoolT’: No such file or directory]解决方法
查看>>
C# 操作Excel,控制格式[转]
查看>>
iOS开发中一些常用的属性
查看>>
Git 使用教程
查看>>
spring--基于ioc的配置文件方式
查看>>