C++ Primer - 第二部分 C++标准库
C++ Primer - 第二部分 C++标准库
第8章 IO库
IO类
iostream
定义了用于读写流的基本类型
fstream
定义了读写命名文件的类型
sstream
定义了读写内存 string
对象的类型
用法都是完全相同的,得益于继承机制
流的状态:
auto old_state = cin.rdstate(); // 获取流的当前状态
cin.clear(); // 将所有条件状态复位,将流的状态置为有效
cin.setstate(old_state); // 根据给定的标志位对流进行复位
输出缓冲:每个输出流都管理一个缓冲区,保存程序读写的数据。
控制输出缓冲:
cout << "hi!" << endl; // 多输出一个换行符,然后刷新缓冲区
cout << "hi!" << flush; // 输出后直接刷新缓冲区
cout << "hi!" << ends; // 多输出一个空字符,然后刷新缓冲区
cout << unitbuf; // 所有输出操作后都立即刷新缓冲区
cout << nounitbuf; // 回到正常的缓冲方式
关联输入和输出流:如果某一个输入流和输出流关联,则从输入流读取的操作会对这个输出流进行刷新。
标准库将 cout
和 cin
关联在一起
cin.tie(&cerr); // 将cin和cerr关联在一起
文件输入输出
ifstream in("infile");
ofstream output("outfile");
string s;
while(getline(in,s)){
output << s << endl;
}
return 0;
显式打开或者关闭文件流:
ofstream output; // 空文件流对象
output.open("outfile"); // 调用open进行关联
output.close(); // 关闭文件流
文件模式:
ofstream output("outfile");
这种方式其实隐含了以输出模式打开文件并进行截断,显式控制如下:
ofstream output("outfile",ofstream::out | ofstream::trunc);
为了保留之前的文件内容,需要显式指定 app
模式
ofstream output("outfile",ofstream::out | ofstream::app);
string流
ifstream in("infile");
ofstream output("outfile",ofstream::out | ofstream::app);
string s;
while(getline(in,s)){
string a,b,c;
istringstream s1(s);
ostringstream s2;
s1 >> a;
s2 << a;
s1 >> b;
s2 << b;
s1 >> c;
s2 << c;
cout << s2.str() << endl;
}
return 0;
第9章 顺序容器
一个容器就是一些特定类型对象的集合,顺序容器为程序员提供了控制元素存储和访问顺序的能力。
顺序容器种类
vector
是可变大小数组,支持快速随机访问。但是在尾部之外的位置插入或者删除元素可能很慢。
deque
是双端队列,支持快速随机访问,在头尾部插入或者删除元素的速度很快。
list
是双向链表,只支持双向顺序访问,在 list
中任意位置进行插入/删除的速度都很快
forward_list
是单向链表,只支持单向顺序访问,在链表中任意位置进行插入/删除的速度都很快
array
是固定大小的数组,支持快速随机访问,不能添加或者删除元素
string
是与 vector
相似的容器,专门用于保存字符,随机访问快,在尾部插入/删除的速度很快
顺序容器几乎可以保存任意类型的元素
各种迭代器:
auto it1 = a.begin(); // list<string>::iterator
auto it2 = a.rbegin(); // list<string>::reverse_iterator
auto it3 = a.cbegin(); // list<string>::const_iterator
auto it4 = a.crbegin(); // list<string>::const_reverse_iterator
元素的拷贝初始化:
list<string> a = {"Milton","SHakespeare","Austen"};
list<string> a2(a);
array具有固定的大小,并且可以进行拷贝
array<int,10> ia1 = {0,1,2,3,4,5,6,7,8,9};
array<int,10> ia2 = ia1;
使用 assign
对不同但相容的类型进行赋值
list<string> names;
vector<const char*> oldstyle;
names.assign(oldstyle.cbegin(),oldstyle.cend());
添加元素三种方法:push_front()
,insert()
,push_back()
新标准对应了三种直接构造元素的方法:emplace_front()
、emplace()
、emplace_back()
更安全的访问元素的方法:svec.at(0)
改变容器大小并使用某个元素填充更大的部分:ilist.resize(15,-1)
管理容量的成员函数:
c.capacity(); // 不重新分配内存空间的话最多能保存多少元素
c.reserve(n); // 分配至少能容纳n个元素的内存空间
c.shrink_to_fit() // 请求将capacity()减小为size()一样的大小
数值转换:
int i = 42;
string s = to_string(i);
double d = stod(s);
cout << s << " " << d << endl;
42 42
第10章 泛型算法
对于容器的其他操作,并没有通过定义成员函数的方式实现,而是定义一套泛型算法,实现了一些算法的公共接口。
在容器中对值进行查找使用 find
,返回查找元素的指针的位置
auto result = find(vec.cbegin(),vec.cend(),val)
返回元素在容器中出现的次数:
vector<int> a;
int temp;
for(int i=0;i<10;i++){
cin >> temp;
a.push_back(temp);
}
cin >> temp;
auto result = count(a.cbegin(),a.cend(),temp);
cout << result << endl;
泛型算法本身不会执行容器的操作,只会运行于迭代器之上,执行迭代器的操作。
因此泛型算法永远不会改变底层容器的大小。
各种泛型算法
元素求和:
vector<int> a{1,2,3,4,5,6,7,8};
int sum = accumulate(a.cbegin(),a.cend(),0);
cout << sum << endl;
36
可以推广到字符串中用来连接字符串:
vector<int> a{1,2,3,4,5,6,7,8};
vector<string> b{"df","fsfds","rte"};
string sum = accumulate(b.cbegin(),b.cend(),string(""));
cout << sum << endl;
dffsfdsrte
确定两个序列中保存的值是否相同(假定第二个序列至少与第一个序列一样长)
vector<int> a{1,2,3,4,5,6,7,8};
vector<string> b{"df","fsfds","rte"};
vector<string> c{"df","fsfds","rte","fdsf"};
auto sum = equal(b.cbegin(),b.cend(),c.cbegin());
cout << sum << endl;
1
使用 fill
和 fill_n
填充元素:
vector<int> a{1,2,3,4,5,6,7,8};
fill(a.begin(),a.end(),0);
for(auto i : a){
cout << i << " ";
}
cout << endl;
fill_n(a.begin(),a.size(),1);
for(auto i : a){
cout << i << " ";
}
cout << endl;
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
算法是不会检查写操作的,泛型算法也不能更改容器的大小。因此需要自行检查容器是否越界等问题。
安全的方式:插入迭代器
vector<int> a{1,2,3,4,5,6,7,8};
vector<string> b{"df","fsfds","rte"};
vector<string> c{"df","fsfds","rte","fdsf"};
fill_n(back_inserter(a),10,1);
for(auto i : a){
cout << i << " ";
}
cout << endl;
1 2 3 4 5 6 7 8 1 1 1 1 1 1 1 1 1 1
将 a1
的内容拷贝到 a2
:copy(begin(a1),end(a1),a2)
对元素进行排序去重:
vector<string> d{"the","quick","red","fox","jumps","over","the","slow","red","turtle"};
sort(d.begin(),d.end()); // 排序
auto end_unique = unique(d.begin(),d.end()); // 将重复的移到末尾,并同时返回最后一个不重复的元素的后一位置
d.erase(end_unique,d.end()); // 使用容器操作删除重复的元素(因为泛型算法无法改变容器大小)
for(auto i : d){
cout << i << " ";
}
cout << endl;
fox jumps over quick red slow the turtle
lambda
表达式:
一个 lambda
表达式表示一个可调用的代码单元,可以理解为一个未命名的内联函数。
auto f = []{return 42;};
cout << f() << endl;
42
int a = 0, b = 1;
auto f6 = [&a, &b]{ return a + b; };
cout << f6() << endl;
1
lambda表达式还有其他的一些用法。
其他迭代器
插入迭代器:
back_inserter
:创建一个使用 push_back
的迭代器
front_inserter
:创建一个使用 push_front
的迭代器
inserter
:创建一个使用 insert
的迭代器
list<int> lst = {1,2,3,4};
list<int> lst2 = {5,6};
list<int> lst3 = {9,10,11};
list<int> lst4 = {12};
copy(lst.cbegin(),lst.cend(),front_inserter(lst2));
for(auto i : lst2){
cout << i << " ";
}
cout << endl;
copy(lst.cbegin(),lst.cend(),inserter(lst3,lst3.begin()));
for(auto i : lst3){
cout << i << " ";
}
cout << endl;
copy(lst.cbegin(),lst.cend(),back_inserter(lst4));
for(auto i : lst4){
cout << i << " ";
}
cout << endl;
4 3 2 1 5 6
1 2 3 4 9 10 11
12 1 2 3 4
流迭代器:
istream_iterator<int> in(cin),eof;
cout << accumulate(in,eof,0) << endl;
> 2 1 4 5 6 7 8 9
42
ostream_iterator<int> out(cout," ");
for(int i=0;i<10;i++){
out = i;
}
cout << endl;
0 1 2 3 4 5 6 7 8 9
反向迭代器:
vector<int> vi{1,2,3,4,5,6,7,8,9,10};
for(auto i = vi.crbegin();i != vi.crend();i++){
cout << *i << " ";
}
cout << endl;
10 9 8 7 6 5 4 3 2 1
要注意反向迭代器真的是反的。。。比如下面的例子:
string line = "first,middle,end";
auto rcomma = find(line.crbegin(),line.crend(),',');
cout << string(line.crbegin(),rcomma) << endl;
cout << string(rcomma.base(),line.cend()) << endl;
dne
end
第11章 关联容器
关联容器中的元素是按关键字来保存和访问的,而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。
map
是关键字-值对的结合
map<string,size_t> word_count;
string word;
while(cin >> word){
++word_count[word];
}
for(const auto &w : word_count){
cout << w.first << " " << w.second << endl;
}
> a b c d e d b c
a 1
b 2
c 2
d 2
e 1
关联容器的元素都是根据关键字存储的,因此不支持位置相关的操作。
multimap
和 multiset
允许相同关键字:
vector<int> vi{1,2,3,4,5,5,4,3,2,1};
set<int> iset(vi.cbegin(),vi.cend());
multiset<int> miset(vi.cbegin(),vi.cend());
cout << iset.size() << " " << miset.size() << endl;
关联容器的迭代器:
vector<int> vi{1,2,3,4,5,5,4,3,2,1};
set<int> iset(vi.cbegin(),vi.cend());
for(auto set_it = iset.cbegin();set_it != iset.cend();set_it++){
cout << *set_it << " ";
}
cout << endl;
1 2 3 4 5
插入元素:
iset.insert(8);
查找元素的下标操作:
c[k]; // 如果没有会添加,并对值进行初始化
c.at(k); // 如果没有会抛出异常
访问元素:find
和 count
在 multimap
中查找元素:
multimap<string,int> mi{make_pair("as",1),make_pair("as",2),make_pair("ab",2),make_pair("ac",2),make_pair("ac",5)};
for(auto pos = mi.equal_range("as");pos.first != pos.second;++pos.first){
cout << pos.first->second << " ";
}
cout << endl;
根据转换规则对文件内容进行转换:
转换规则:
brb be right back
k okay?
y why
r are
u you
pic picture
thk thanks!
l8r later
文件内容:
where r u
y dont u send me a pic
k thk l8r
转换代码:
// 实际的转换工作,生成转换文本
const string & transform(const string &s, const map<string,string> &m){
auto map_it = m.find(s);
if (map_it != m.cend()){
return map_it->second;
}
else{
return s;
}
}
// 读入给定文件,建立转换映射
map<string,string> buildMap(ifstream &map_file){
map<string,string> trans_map;
string key,value;
// 读取第一个单词存入key,剩余内容存入value
while(map_file >> key && getline(map_file,value)){
if(value.size() > 1){
trans_map[key] = value.substr(1);
}
else{
throw runtime_error("No rule for " + key);
}
}
return trans_map;
}
int main(void){
ifstream map_file("rules");
ifstream input("text");
auto trans_map = buildMap(map_file); // 保存转换规则
string text; // 保存输入中的每一行
while(getline(input,text)){
istringstream stream(text); // 读取每个单词
string word;
bool firstword = true; // 控制是否打印空格
while(stream >> word){
if(firstword){
firstword = false;
}
else{
cout << " ";
}
cout << transform(word,trans_map);
}
cout << endl;
}
return 0;
}
输出:
where are you
why dont you send me a picture
okay? thanks! later
无序容器:不适用比较运算符来组织元素,而是使用哈希函数组织元素。
一般情况下的性能要比有序容器更好,但是不能按照顺序输出。
第12章 动态内存
动态内存与智能指针
前面都是静态对象,由程序自动分配内存并销毁。而动态对象需要被显式进行释放。
动态内存需要显式进行分配和释放,因此很容易忘记释放导致一些问题。因此定义了两种智能指针来管理这些动态对象,自动进行释放。
shared_ptr<string> p1; // 指向string的shared_ptr
shared_ptr<list<int>> p2; // 指向int的list的shared_ptr
默认初始化的智能指针中保存着一个空指针。
最安全的分配和使用动态内存的方式是调用 make_shared
的标准库函数。
shared_ptr<int> p3 = make_shared<int>(42);
shared_ptr<string> p4 = make_shared<string>(10,'9');
shared_ptr<int> p5 = make_shared<int>();
shared_ptr
会自动记录有多少个其他 shared_ptr
指向相同的对象,如果没有了,会自动销毁所管理的对象并自动释放相关联的内存。
离开作用域也会被销毁。如果返回这个指针,也不会被销毁(就是挺智能的)
直接管理内存:使用 new
和 delete
int *pi = new int;
string *ps = new string(10,'9');
const string *pcs = new const string;
delete pi;
delete ps;
delete pcs;
delete
不会抛出任何异常,尽管可能已经释放过了,甚至有可能都不是指针也会释放,会造成一些问题。
delete
还可能会造成空悬指针,因此这个 delete
只提供了有限的保护。
不要混用智能指针和普通指针,不要使用 get
初始化另一个智能指针或者赋值。
unique_ptr
“拥有”它所指向的对象,某个时刻只能由一个 unique_ptr
指向一个给定对象。销毁指针就一起销毁了。
weak_ptr
指向一个 shared_ptr
管理的对象,不会改变 shared_ptr
的计数,计数为 0
后会自动释放。
动态数组
使用 new
分配一个 int
数组:
int *p = new int[42];
实际上并没有得到一个数组类型的对象,而是得到一个数据元素类型的指针。
动态分配并初始化数组:
int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};
动态分配的数组的大小可以为0,会返回一个类似于尾后迭代器的指针。
释放动态数组:delete [] p
智能指针管理动态数组:
unique_ptr<int[]> up(new int[10]);
for(size_t i = 0;i != 10;++i){
up[i] = i;
}
up.release();
allocator
将内存分配和对象构造分离开来,提供一种类型感知的内存分配方法。
int n = 5;
allocator<string> alloc;
auto const p = alloc.allocate(n);
这个 allocator
为5个 string
分配了内存
在内存中构造对象:
auto q = p;
alloc.construct(q++,"hi");
cout << *p << endl;
hi
案例:文本查询程序
在一个给定文件中查询单词,最终返回单词在文件中出现的次数及其所在行的列表。