《C++ Primer》第||部分读书笔记(第8~12章)
第八章
ioseam
iofsteam
文件的操作:https://www.cnblogs.com/likebeta/archive/2012/06/16/2551662.html
写程序流程要求:
① 创建输入输出流,判断文件是否打开成功;
② 如果文件打开成功,则处理。
iostringsteam
第九章
1. 顺序容器
容器类型 | 概述 |
---|---|
vector | 可变大小数组,支持快速访问。在尾部之外的位置插入删除数据元素可能很慢 |
deque | 双端队列。支持快速随机访问,在头部位置插入/删除速度很快 |
list | 双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快 |
forward_list | 单向链表,只支持单项顺序访问。在链表任何位置进行插入/删除操作速度都很快 |
array | 固定大小数组。支持快速随机访问,不能添加或删除元素 |
string | 与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快 |
2. 迭代器
如果不确定应该使用哪种容器,你们可以在程序中只是用vector和list的公共操作:使用迭代器,不使用下标操作,避免随机访问。这样,在必要的时候使用vector或list都很方便。
一个迭代器范围(iterator range)由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置。
· 如果begin与end不等,则范围至少包含一个元素,且begin指向范围中的第一个元素。
· 我们可以对begin递增若干次,使得begin==end。so:
while (begin != end )
{
*begin = val;
++begin;
}
当我们向容器中添加或删除元素时,迭代器可能失效。使用失效的迭代器、指针或引用的严重的运行时错误。
因此必须保证每次改变容器的操作之后都正确地重定位迭代器。
例子:
// 傻瓜循环,删除偶数元素,复制每个奇数元素
vector<int> vi = {0,1,2,3,4,5,6,7,8,9 };
auto iter = vi.begin();
while (iter != vi.end()){ // 不要保存end返回的迭代器,因为插入或删除元素后,end中的迭代器失效了
if (*inter % 2){
iter = vi.insert( iter, *iter );
iter += 2;
}
else
iter = vi.erase( iter );
// 不应移动迭代器,iter指向我们删除的元素之后的元素
}
3. vector 对象是如何增长的
vector对象要求元素连续存储。
标准库实现者采用了可以减少容器空间重新分配的次数的策略。不得不获取新的空间时,vector和string的实现通常会分配新的空间需求更大的内存空间。容器预留这些空间作为备用,可用来保存更多的新元素。这样,就不需要每次添加新元素都重新分配空间了。
c.capacity() 不重新分配内存空间的话,c可以保存多少元素;
c.reserve(n) 分配至少能容纳n个空间的内存元素。
vector的实现采用的策略是每次需要分配新内存时,将当前容量翻倍。但也可以选择自己的内存分配策略。但是必须遵守的一条原则是:只有当迫不得已时,才可以分配新的内存空间。
4. string的其他操作
substr
string s("hello world");
string s2 = s.substr(0, 5); // s2 = hello
string s3 = s.substr(6); // s3 = world
string s4 = s.substr(6, 11); // s2 = world
string s4 = s.substr(12); // 抛出一个out_of_range异常
string的搜索操作:
str.find("hello"),返回子字符串的第一个字母的位置。
还用很多其他搜索方法。
5. 容器适配器
除了顺序容器之外,标准库还定义了三个顺序容器适配器:stack、queue和priority_queque。
适配器(adaptor)是标准库中的一个通用概念。容器、迭代器和函数都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看上去像是另外一类事物一样。
对于给定的适配器,可以使用哪些容器是有限制的。
—————————二刷分割线———————————
容器类型成员:
为了使用这些类型,必须显式使用其类型名,例子:
list<string>::literator iter // 迭代器iter是string的list的迭代器
容器操作
—————————二刷分割线———————————
第十章
1. 泛型算法概念
容器只定义了很少的操作,标准库定义了一组泛形算法(generic algorithm)。定义在头文件algorithm中。
一般情况下,这些算法不直接操作容器,而是遍历有两个迭代器指定的一个元素范围。
例子:find
int val = 42; // 我们将要查找的值
auto result = find(vec.cbegin(), vec.cend(), val);
cout << "The Value " << val
<< (result == vec.cend() )? "is not present " : "is present" << endl;
迭代器令算法不依赖与容器:
泛型算法本身不会执行容器的操作,他们只会运行在迭代器上,执行迭代器的操作。泛型算法可能改变容器中保存的值,也可能在容器内移动元素,但永远不会直接添加或删除元素。
2. 只读算法
3. 写容器的算法
算法不检查写操作。例子:fill_n
vector<int> vec; // 空vector
fill_n(vec.bgin(), vec.size(), 0);
函数fill_n默认写入指定元素是安全的。一个初学者非常容易犯的错误就是在一个空容器上调用fill_n(或类似的写元素的算法),此行为的后果是位未定义的。
4. lambda函数
匿名函数,只是用一次所以连名字都没要必要费心取的函数,不取名反而更增加整个程序的可读性。
参考资料:
https://www.zhihu.com/question/20125256
对于只在一两个地方使用的简单操作,lambda表达式是最有用的。如果我们需要在很多地方用相同的操作,通常应该定义一个函数,而不是多次编写相同的lambda表达式。
关于lambda函数的捕获值的资料:http://blog.csdn.net/zh379835552/article/details/19542181
例子可见二刷for_each部分。
5. bind 函数适配器
当只能传入一个参数的时候,通过bind可以传入多个参数。例子:
// 按单词长度由短至长排序
sort(words.begin(), words.end(), isShorter);
// 按单词长度由长至短排序
sort(words.begin(), words.end(), bind(isShorter, _2, _1);
—————————二刷分割线———————————
只读算法
① accumulate,例子:
int sum = accumulate ( vec.cbegin(), vec.cend(), 0); // 对vec中元素求和,和的初始值为0
② equal,可用于比较两个不同类型的容器中的元素,元素类型不同也可以,只要能用==来比较两个元素即可。
写容器的算法
① fill,填充元素。
② back_inserter
③ copy,拷贝算法。例子:
int a1[] = {0,1,2,3,4,5,6,7,8,9};
int a2[sizeof(a1) / sizeof(*a1)]; // a2的大小和a1一样
// ret指向拷贝到a2的尾元素之后的位置
auto ret = copy(begin(a1), end(a1), a2); // 把a1的内容拷贝给a2
④ replace算法,例子:
replaice(ilist.begin(), ilist.end(), 0, 42) // 将所有值为0的替换成值为42
重排容器元素的算法
① sort 排序
② unique 不重复元素排序(返回指向不重复排序尾部的指针)
③ stable_sort 对某一系列排序,并保证相等的元素的顺序还是原来的顺序,例子+详情:
http://blog.csdn.net/earbao/article/details/54911878
可用于双重或多重标准的排序。
删除操作
erase,例子:
auto end_unique = unique(words.begin(), words.end());
words.erase(end_unique, words.end());
查找算法
① find
② find_if,在两个迭代器(函数前两个入参)之间的范围内,查找符合条件(第三个入参)的值,返回一个迭代器。例子:
auto wc = find_if (word.begin(), word.end(),
[sz](const string &a)
{ return a.size() >= sz; });
其他
for_each算法,对两个迭代器(函数的前两个入参)之间的每个元素进行某操作(第三个入参)例子:
// 例1:打印出每个单词
for_each(words.begin(), words.end(),
[](const string &s)(const string &s){cout << s << " ";})
// 例2:lamda函数两个入参,打印字符到os
for_each(words.begin(), words.end(),
[&os, c](const string &s){ os << s << c; });
// os是引用捕获,所以可以改变os的值,而c是值捕获。
—————————二刷分割线———————————
第十一章
类型 | 说明 | 是否有序保存 | 其他 |
---|---|---|---|
map | 关联数组:保存键值对(key-value) | 按关键字有序保存元素 | 例子:字典 |
set | 关键字即值,即只保存关键字的容器 | 有序保存 | |
multimap | 关键字可重复出现的map | 有序保存 | |
multimap | 关键字可重复出现的map | 有序保存 | |
unordered_map | 用哈希函数组织的map | 无序集合 | |
unordered_set | 用哈希函数组织的set | 无序集合 | |
unordered_multimap | 哈希组织的map,关键词可重复出现 | 无序集合 | |
unordered_multiset | 哈希函数的set,关键词可重复出现 | 无序集合 |
关联容器
关联容器支持高校的关键字查找和访问。两个主要的挂念容器是map和set。
标准库提供8个关联容器如表所示。
map类型通畅被称为“关联数组”。关联数组与正常数组类似,不同的是其下标不必是整数。
与之相对,set就是关键字的简单集合。当只想知道一个值是否存在时,set是最有用的。例子:
// 例子:用set保存想忽略的单词,只不在集合中出现的单词统计出现次数
map<string, site_t> word_count;
set<string> exclude = {"the","a","an","The","A","an"};
string word;
while(cin>>word)
if(exclude.fine(word) == exclude.end)
{
++word_count[word];
}
pair类型
pair是map的元素。
管理桶
无序容器在存储上为一组桶,每个桶存储零个或多个元素。
第十二章
静态内存用来保存局部static对象、类static成员,以及任意定义在任何函数之外的变量。占内存用来保存定义在函数内的非static对象。
分配在静态或栈内存或占内存中的对象由编辑器自动创建和销毁。
除了静态内存和占内存,每个程序还拥有一个内存池。这部分内存被称为自由空间或堆。程序用堆来存储动态分配对象。
动态内存
new:在动态内存中为对象分配空间并返回一个指向该对象的指针。
delete:接受一个动态对象的指针,销毁该对象,并释放预支关联的内存。
动态内存的问题:
- 忘记释放内存——内存泄漏。
- 尚有指针引用内存时释放了它——产生非法引用内存的指针。
智能指针:和普通指针的区别是负责自动释放所指向的对象。
shared_ptr 允许多个指针指向一个对象;unique_ptr“独占”所指的对象。此外,还有weak_ptr一种弱引用。
allocator类
定义在头文件memory中,帮助我们将内存分配和对象构造分离开来。它提供一种类型感知的内存分配方法,它分配的内存是原始的、为构造的。
其他备忘
单冒号
可以用于取出vector中的每一项。例子:
for ( const auto &entry : people) // 对people中的每一项
{
// ......
for ( const auto &nums : entry.phones) // 对entry中的每个数
{
// ......
}
}
a2的大小和a1一样
int a1[] = {0,1,2,3,4,5,6,7,8,9};
int a2[sizeof(a1) / sizeof(*a1)];