文章目录

  • 容器
    • STL是建立在泛化之上的。
  • vector/string
  • 关联容器
  • 迭代器
    • 输入迭代器
    • 输出迭代器
    • 双向迭
  • 算法
    • 条款30:确保目标区间足够大
    • 条款31:了解你的排序选择
    • 条款32:如果你真的想删除东西的话就在类似rem ove的算法后接上erase
    • 条款33:提防在指针的容器上使用类似rem ove的算法
    • 条款34:注意哪个算法需要有序区间
    • 条款35:通过mismatch或lexicographical比较实现简单的忽略大小写字符串比较
    • 条款36:了解copy_if的正确实现
    • 条款37:用accum ulate或for_each来统计区间
  • 仿函数
  • 使用STL编程

容器

STL是建立在泛化之上的。

  • 数组泛化为容器,参数化了所包含的对象的类型。
  • 函数泛化为算法,参数化了所用的迭代器的类型。
  • 指针泛化为迭代器,参数化了所指向的对象的类型。
    标准STL序列容器:vector、string、deque和list。
    标准STL关联容器:set、multiset、map和multimap。
    非标准序列容器slist和rope。slist是一个单向链表,rope本质上是一个重型字符串。
    非标准关联容器hash_set、hash_multiset、hash_map和hash_multimap
    vector可以作为string的替代品。
    vector作为标准关联容器的替代品。
    几种标准非STL容器,包括数组、bitset、valarray、stack、queue和priority_queue。

代码规范

if (c.size() == 0)

这种写法的时候,可以改成如下

if (c.empty())

对于list的分析
简介:
list是一种序列式容器。list容器完成的功能实际上和数据结构中的双向链表是极其相似的,list中的数据元素是通过链表指针串连成逻辑意义上的线性表,也就是list也具有链表的主要优点,即:在链表的任一位置进行元素的插入、删除操作都是快速的。list的实现大概是这样的:list的每个节点有三个域:前驱元素指针域、数据域和后继元素指针域。前驱元素指针域保存了前驱元素的首地址;数据域则是本节点的数据;后继元素指针域则保存了后继元素的首地址。其实,list和循环链表也有相似的地方,即:头节点的前驱元素指针域保存的是链表中尾元素的首地址,list的尾节点的后继元素指针域则保存了头节点的首地址,这样,list实际上就构成了一个双向循环链。由于list元素节点并不要求在一段连续的内存中,显然在list中是不支持快速随机存取的,因此对于迭代器,只能通过“++”或“–”操作将迭代器移动到后继/前驱节点元素处。而不能对迭代器进行+n或-n的操作,这点,是与vector等不同的地方。

只有list提供了不用拷贝数据就能把元素从一个地方接合到另一个地方
的能力

vector/string

字符串值可能是或可能不是引用计数的。默认情况下,很多实现的确是用了引用计数,但它们通常提供了关闭的方法,一般是通过预处理
器宏。 条款13 给了一个你可能要关闭的特殊环境的例子,但你也可能因为其他原因而要那么做。比如,引用计数只对频繁拷贝的字符串有
帮助,而有些程序不经常拷贝字符串,所以没有那个开销。
● string对象的大小可能从1到至少7倍char*指针的大小。
● 新字符串值的建立可能需要0、1或2次动态分配。
● string对象可能是或可能不共享字符串的大小和容量信息。
● string可能是或可能不支持每对象配置器。
● 不同实现对于最小化字符缓冲区的配置器有不同策略。

vector<int> v;
v.reserve(1000);
for (int i = 1; i <= 1000; ++i) v.push_back(i);
string s;
...
if (s.size() < s.capacity()) {
s.push_back('x');
}
void doSom ething(const int* pInts, size_t num Ints);
我们可以这么做:使用STLAPI
if (!v.em pty()) {
doSom ething(& v[0], v.size());
}
void doSom ething(const char *pString);
doSom ething(s.c_str());

swap

string s;
... // 使s变大,然后删除所有
// 它的字符
string(s).swap(s); // 在s上进行“收缩到合适”vector<Contestant> v;
string s;
... // 使用v和s
vector<Contestant>().swap(v); // 清除v而且最小化它的容量
string().swap(s); // 清除s而且最小化它的容量

vector确实只有两个问题。第一,它不是一个STL容器。第二,它并不容纳bool。
deque和bitset是基本能满足你对vector

关联容器


set,multiset,map,multimap都是有序的。less
相等是以operator==为基础的;等价是以operator<为基础的,"在已排序的区间中对象值的相对顺序。
应优先使用成员函数(像std::find)而不是与之对应的非成员函数(像find)。
标准关联容器总是保持排列顺序的,所以每个容器必须有一个比较函数(默认为less)来决定保持怎样的顺序。
等价的定义正是通过该比较函数而确定。

21.为包含指针的关联容器指定比较类型
在使用关联容器时,如果存储的是指针,又想按照指针指向的对象的某个字段顺序存储,
需要自己实现比较函数对象。作为第二个参数传入构造。

struct DereferenceLess {template <typename PtrType>bool operator()(PtrType pT1, // 参数是值传递的,PtrType pT2) const // 因为我们希望它们{ // 是(或行为像)指针return *pT1 < *pT2;}
};
set<string*, DereferenceLess> ssp; // 行为就像
set<string*, less<string*>, allocator<string*> > ssp;
struct StringPtrGreater: // 对关联容器来说
public binary_function<const string*, // 这是有效的比较类型
const string*,
bool> {bool operator()(const string *ps1, const string *ps2) const{return *ps2 < *ps1; // 返回*ps2是否} // 大于*ps1(也就是
}; // 交换操作数的顺序)

避免原地修改set和multiset的键

  1. 定位你想要改变的容器元素。
  2. 拷贝一份要被修改的元素。对map或multimap而言,确定不要把副本的第一个元素声明为const。毕竟,你想要改变它!
  3. 修改副本,使它有你想要在容器里的值。
  4. 从容器里删除元素,通常通过调用erase
  5. 把新值插入容器。如果新元素在容器的排序顺序中的位置正好相同或相邻于删除的元素,使用insert
    的“提示”形式把插入的效率从对数时间改进到分摊的常数时间。使用你从第一步获得的迭代器作为
    提示。

map<int, Widget> m;
m[1] = 1.50;
如果要更新一个已有的map,则优先选择operator[];但如果要添加一个新的元素,那么最好还是选择insert。
Operator[]返回一个引用(m[k]=v),它指向与k相关联的值对象。然后v被赋给该引用所指的对象。

迭代器

大概意思是每个容器有每个容器的特性,不要滥用。

  • 在一个序列容器上用一个迭代器作为参数调用erase,会返回一个新迭代器,但在关联容器上什么都不返回。
  • 只有序列容器支持push_front或push_back
  • 你把一个对象插入一个序列容器中,它保留在你放置的位置。但如果你把一个对象插入到一个关联容器中,容器会按照的排列顺序把这个对象移到它应该在的位置
  • 只有关联容器支持count和lower_bound

迭代器是一个“可遍历STL容器内全部或部分元素”的对象。
迭代器指出容器中的一个特定位置。
迭代器就如同一个指针。
迭代器提供对一个容器中的对象的访问方法,并且可以定义了容器中对象的范围。

这里大概介绍一下迭代器的类别。

  • 输入迭代器:也有叫法称之为“只读迭代器”,它从容器中读取元素,只能一次读入一个元素向前移动,只支持一遍算法,同一个输入迭代器不能两遍遍历一个序列。

  • 输出迭代器:也有叫法称之为“只写迭代器”,它往容器中写入元素,只能一次写入一个元素向前移动,只支持一遍算法,同一个输出迭代器不能两遍遍历一个序列。

  • 正向迭代器:组合输入迭代器和输出迭代器的功能,还可以多次解析一个迭代器指定的位置,可以对一个值进行多次读/写。

  • 双向迭代器:组合正向迭代器的功能,还可以通过–操作符向后移动位置。
    随机访问迭代器:组合双向迭代器的功能,还可以向前向后跳过任意个位置,可以直接访问容器中任何位置的元素。

c.begin()  // 返回一个迭代器,它指向容器c的第一个元素
c.end() // 返回一个迭代器,它指向容器c的最后一个元素的下一个位置
c.rbegin()// 返回一个逆序迭代器,它指向容器c的最后一个元素
c.rend() //返回一个逆序迭代器,它指向容器c的第一个元素前面的位置int arry[] = {1,2,3,4,5};vector<int> v(arry,arry+sizeof(arry)/sizeof(arry[0]));//正向迭代器for(vector<int>::iterator it = v.begin();it != v.end();it++){cout<<*it<<" ";}//反向迭代器for(vector<int>::reverse_iterator it = v.rbegin();it != v.rend();it++){cout<<*it<<" ";}

输入迭代器

#include<iostream>
#include<iterator>//必须
using namespace std;
int main() {istream_iterator<int> it(cin);//定义一个输入int型数据的输入迭代器,绑定cin为输入源,并立即开始输入istream_iterator<int> eof;//定义一个输入int型数据的“哨兵”while (it != eof) {cout << *(it++)<< endl;// 先将it给*运算符,再使it往前移动(即输一个数据),然后输出原it的内容,所以我们会看到:输出的是上上次的内容。 }cout << "over" << endl;//当你输入的不是int后会看到这里的内容,包括按下Ctrl+D(for windows)时。system("pause");return 0;
}

输出迭代器

#include<iostream>
#include<iterator>//必须
using namespace std;
int main() {ostream_iterator<int> it(cout);for (int i = 0;i < 5;i++) {*it = i;it++;}cout << endl;ostream_iterator<int> it2(cout,"\n");//有间隔内容的for (int i = 0;i < 5;i++) {*it2 = i;it2++;//当给it指向的内容附值时,即向cout输出}ostream_iterator<int> it3(cout);for (int i = 0;i < 5;i++) {*it3 = i;//输出后,it3会自动向前移动}system("pause");return 0;
}

双向迭

代器与随机访问迭代器
双向迭代器支持的操作:
it++, ++it, it–, --it,*it, itA = itB,
itA == itB,itA != itB
其中list,set,multiset,map,multimap支持双向迭代器。
随机访问迭代器支持的操作:
在双向迭代器的操作基础上添加
it+=i, it-=i, it+i(或it=it+i),it[i],
itA<itB, itA<=itB, itA>itB, itA>=itB 的功能。
其中vector,deque支持随机访问迭代器。

去除一个容器中有特定值的所有对象:
如果容器是vector、string或deque,使用erase-rem ove惯用法。
如果容器是list,使用list::rem ove。
如果容器是标准关联容器,使用它的erase成员函数。
● 去除一个容器中满足一个特定判定式的所有对象:
如果容器是vector、string或deque,使用erase-rem ove_if惯用法。
如果容器是list,使用list::rem ove_if。
如果容器是标准关联容器,使用rem ove_copy_if和swap,或写一个循环来遍历容器元素,当你把迭代
器传给erase时记得后置递增它。
● 在循环内做某些事情(除了删除对象之外):
如果容器是标准序列容器,写一个循环来遍历容器元素,每当调用erase时记得都用它的返回值更新你
的迭代器。

算法

条款30:确保目标区间足够大

vector<int> results; // 把transm ogrify应用于
if (results.size() < values.size()){ // 确保results至少
results.resize(values.size()); // 和values一样大,减少性能消耗
}
transform (values.begin(), values.end(), // values中的每个对象,
back_inserter(results), // 在results的结尾
transm ogrify); // 插入返回的values
transform (values.begin(), values.end(), // 在results前端
front_inserter(results), //  以反序
transm ogrify); // 插入transform 的结果

transform它不会给不存在的对象赋值

条款31:了解你的排序选择

bool qualityCom pare(const W idget& lhs, const W idget& rhs)
{
// 返回lhs的质量是不是比rhs的质量好
}
...
partial_sort(widgets.begin(), // 把最好的20个元素
widgets.begin() + 20, // (按顺序)放在widgets的前端
widgets.end(),
qualityCom pare);
... // 使用widgets...
nth_elem ent(widgets.begin(), // 把最好的20个元素
widgets.begin() + 19, // 放在widgets前端,
widgets.end(), // 但不用担心
qualityCom pare); // 它们的顺序

stable_sort是稳定的,会保持相等的时候顺序不变
ist::sort提供了稳定排序
如果你需要在vector、string、deque或数组上进行完全排序,你可以使用sort或stable_sort。
● 如果你有一个vector、string、deque或数组,你只需要排序前n个元素,应该用partial_sort。
● 如果你有一个vector、string、deque或数组,你需要鉴别出第n个元素或你需要鉴别出最前的n个元素,
而不用知道它们的顺序,nth_elem ent是你应该注意和调用的。
● 如果你需要把标准序列容器的元素或数组分隔为满足和不满足某个标准,你大概就要找partition或
stable_partition。
● 如果你的数据是在list中,你可以直接使用partition和stable_partition,你可以使用list的sort来代替sort和
stable_sort。如果你需要partial_sort或nth_elem ent提供的效果,你就必须间接完成这个任务,但正如我
在上面勾画的,会有很多选择。
性能排名

  1. partition 4. partial_sort
  2. stable_partition 5. sort
  3. nth_elem ent 6. stable_sort

条款32:如果你真的想删除东西的话就在类似rem ove的算法后接上erase

rem ove并不“真的”删除东西,因为它做不到。
“类似rem ove”的算法:rem ove_if和unique
value相同值的元素都会被覆盖,而其他的元素都会依次前移。最后remove返回"指向最后一个’有用’
元素的iterator",但是在remove算法过程中,并没有修改原容器的size,以及end()。
v.erase(rem ove(v.begin(), v.end(), 99), v.end()); // 真的 删除所有

条款33:提防在指针的容器上使用类似rem ove的算法

void delAndNullifyUncertified(W idget*& pW idget) // 如果*pW idget是一个
{ // 未通过检验W idget,
if (!pW idget->isCertified()) { // 删除指针
delete pW idget; // 并且设置它为空
pW idget = 0;
}
}
for_each(v.begin(), v.end(), // 把所有指向未通过检验W idget的
delAndNullifyUncertified); // 指针删除并且设置为空
v.erase(remove(v.begin(), v.end(), // 从v中除去空指针
static_cast<W idget*>(0)), // 0必须映射到一个指针,
v.end()); // 让C++可以
// 正确地推出rem ove的
// 第三个参数的类型

条款34:注意哪个算法需要有序区间

binary_search、lower_bound、upper_bound和equal_range二分法查找来搜索值
set_union、set_intersection、set_difference和set_sym m etric_difference提供了线性时间设置它们名字
m erge和inplace_m erge执行了有效的单遍合并排序算法
includes。它用来检测是否一个区间的所有对象也在另一个区间中。

vector<int> v; // 建立一个vector,
... // 把一些数据放进去
sort(v.begin(), v.end(), greater<int>()); // 降序排列
... // 使用这个vector
// (没有改变它)
bool a5Exists = // 搜索5
binary_search(v.begin(), v.end(), 5. greater<int>()); // 把greater作为
// 比较函数

条款35:通过mismatch或lexicographical比较实现简单的忽略大小写字符串比较

mismatch并行比较两个序列, 指出第一个不匹配的位置, 返回一对 iterator, 标志第一个不匹配元素位置。
如果都匹配, 返回每个容器的 last。
exicographical_compare是一个泛化版本,可以比较任何类型的值的区间

条款36:了解copy_if的正确实现

STL 中有11个包含了copy的算法:
copy:复制整个序列到一个新位置
copy_backward:与Copy相同,不过元素是以相反的顺序拷贝
replace_copy_if:将指定范围内所有操作结果为 true 的元素用新值代替输出到另一个容器
reverse_copy:将指定范围内元素重新反序排序输出到另一个容器
unique_copy:将序列中重复元素输出到另一个容器
remove_copy:移除值为value的元素,并将处理后在容器复制到另一容器里.
rotate_copy:将指定范围内元素移到容器末尾
remove_copy_if:将所有不匹配的元素拷贝到一个指定容器,原容器的值不变。
partial_sort_copy:对序列做部分排序,输出到另一个容器。


template<typename InputIterator,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;
}

条款37:用accum ulate或for_each来统计区间

struct Point {...); // 同上
class PointAverage:
public unary_function<Point, void> { // 参见 条款40
public:
PointAverage(): xSum (0), ySum (0), num Points(0) {}
void operator()(const Point& p)
{
++num Points;
xSum += p.x;
ySum += p.y;
}
Point result() const
{
return Point(xSum /num Points, ySum /num Points);
}
private:
size_t num Points;
double xSum ;
double ySum ;
};
list<Point> Ip;
...
Point avg = for_each(lp.begin(), lp.end(), PointAverage()).result;
string::size_type
stringLengthSum(string::size_type sumSoFar, const string& s)
{return sumSoFar + s.size();
}
set<string> ss; // 建立字符串的容器,
... // 加入字符串
string::size_type lengthSum = accumulate(ss.begin(), ss.end(), static_cast<string::size_type>(0), stringLengthSum);

仿函数

条款42:确定less表示operator<

此条比较重要

应该尽量避免修改less的行为,因为这样做很有可能误导其他的程序员。如果使用了less,无论是显式还是隐式,你都需要确保她与operator<有相同的意义。若希望以一种特殊的方式来排序对象,那么最好创建一个特殊的函数子类,它的名字不能是less,这样做其实是很简单的。

使用STL编程

优先使用成员函数
迭代器并没有划分一个有序区间,你就只能用线性时间的算法count、count_if、find和find_if
有序区间binary_search、lower_bound、upper_bound和equal_range,是对数时间

你想知道的 无序区间 在有序区间 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,然后distance count count
等于期望值的所有对象在哪里? find(迭代) equal_range equal_range equal_range

less<
less_equal <=
if (find(lw.begin(), lw.end(), w) != lw.end()) {
… // 找到了
} else { // 没找到
}

vector<double> v;//排序double数组
sort(v.begin(), v.end(), greater<double>());
typedef vector<int>::iterator VecIntIter;
// 把rangeBegin初始化为指向最后一个
// 出现一个大于等于y的值后面的元素。
// 如果没有那样的值,
// 把rangeBegin初始化为v.begin()。如果那个值的
// 最后一次出现是v中的最后一个元素,
// 就把rangeBegin初始化为v.end()。
VecIntIter rangeBegin = find_if(v.rbegin(), v.rend(),bind2nd(greater_equal<int>(), y)).base();
// 从rangeBegin到v.end(),删除所有小于x的值
v.erase(remove_if(rangeBegin, v.end(), bind2nd(less<int>(), x)), v.end());

set声明了set/multiset/,map同理map/multimap
中声明了算法,除了在inner_product/adjacent_differencd和partial_sum
特殊的迭代器,包括istream_iterators和istreambuf_iterators,在中声明。
标准仿函数(比如less)和仿函数适配器(比如not1、bind2nd)在中声明

Effective STL 精华版读书笔记相关推荐

  1. 《Effective STL》中文版 读书笔记

    50条有效使用STL的经验 第一条 慎重选择容器类型(20190713) 第二条 不要试图编写独立于容器类型的代码(20190713) 第三条 确保容器中的对象副本正确而高效(20190713) 第四 ...

  2. 《Effective C#》的读书笔记

    突然看到的一篇关于阅读 Bill Wagner先生的<Effective C#>的读书笔记,觉得写的不错,就在这里进行了链接.如果谁有这本书的中文版,希望可以给我发一下先谢谢了 <E ...

  3. 【Effective Objective-C 2.0读书笔记】第六章:块(Blocks)和大中枢派发(GCD)

    继续这本书的读书笔记,希望在其中也加入自己的一些总结,以加深理解.之前这一章写了很多了,保存到草稿箱中,不知道为何丢失了,真是可惜,看来CSDN的MarkDown编辑器还存在一些bugs,在它打上补丁 ...

  4. Java Web开发实战经典 李兴华版 读书笔记(一)

    有的时候总感觉读书没有效率,或是记不住,或是不能专注.所以,把读的书都做一个笔记.贴上来.方便日后回顾. 本人java后端开发,大概算个中级程序员.所以笔记中的难易程度都是根据我自身水平的判断. 看完 ...

  5. 《Effective C艹》读书笔记(13)

    条款20:宁以pass-by-reference-to-const替换pass-by-value 传递引用比起传递值的参数有一个明显的优势就是,传递引用并不会构造新的对象,从而避免了构造函数和析构函数 ...

  6. Effective TCP/IP Programming读书笔记

    技巧1 理解基于连接和无连接协议之间的差异 TCP/IP分为四层,分别是接口层/物理层/链路层-->网络层-->传输层-->应用层 网络层主要就是IP层,该层提供了一个很好的高效的, ...

  7. [UnityShader入门精要读书笔记]37.水波效果

    在这里,使用一张立方体纹理(cubemap)作为环境纹理,模拟反射.为了模拟折射效果,我们使用GrabPass来获取当前屏幕的渲染纹理,并使用切线空间下的法线方向对像素的屏幕坐标进行偏移,再使用该坐标 ...

  8. Effective C++ Third Edition 读书笔记 3

    Item 3: Use const whenever possible const关键字是对代码的一种约束,是一种编译器可以识别的注释,而且绝对不是废话以及误导的注释. 平时只是用来定义常量(替代宏) ...

  9. 传智播客-刘意-java深入浅出精华版学习笔记Day05

    [视频的前半段讲的是方法.因为方法和函数几乎是一样的,所以直接跳过去了,从数组开始看,唯一有一点需要注意的,就是现在我们暂时都把方法定义为public static型的] 定义格式: 数组的初始化: ...

最新文章

  1. 大数据WEB阶段(十三)JSP(一)JSP基础、JSP指令详解、四大域九大隐式对象总结
  2. Java集合—List如何一边遍历,一边删除?
  3. “幕后英雄”之Backing Fields【Microsoft Entity Framework Core随笔】
  4. [html] js放在html的<body>和<head>有什么区别?
  5. 系统gpu 调试_KubeFlow上的GPU即服务:快速,可扩展且高效的ML
  6. 数据科学 IPython 笔记本 9.9 花式索引
  7. git命令提交本地代码到远程仓库
  8. C# - 企业框架下的存储过程输出参数
  9. Java基础篇:嵌套 switch 语句
  10. ubuntu常用系统命令
  11. 数学基础(0)-- 高等数学、概率论与数理统计
  12. boost升压电路解析
  13. [单片机]KeilC51简单流水灯制作与原理
  14. 百度云直链下载-IDM+网页解析(三)
  15. VMware虚拟机优化,提高虚拟机运行速度的方法?
  16. Linux压缩、解压、打包文件 修改文件所属组
  17. 准备考试?python也能帮你划重点,上考场
  18. angular.js使用路由时,子控制器监听不到父级$boardcast的事件
  19. ngx之日志切割 、ngx信号
  20. SD card boot and flashing tool for TI davinic DM368

热门文章

  1. IOS网络基础学习三:NSURLSession的Download下载任务和代理方法
  2. Linux ARP 代理专题
  3. 吉大计算机转专业素质测试,通知|关于吉林大学2020级本科生考核转专业(类)工作的通知...
  4. 如何将收件箱中的发件人批量导入企业云邮通讯录
  5. c++运算符重载+的三种类型
  6. 史上最牛独立开发者:花20美元狂赚100万美元
  7. 聚类分析在市场细分中的应用
  8. 加一度教你如何做好百度竞价数据分析
  9. 什么是真正的爱情?(经典)
  10. [Python 爬虫]从eBay页面获取商品高清图片