layout: post
title: C++prime读书笔记(二)C++标准库
description: C++prime读书笔记(二)C++标准库
tag: 读书笔记
下边是io错误的例子,期望的ival是int类型,假如我们键入“Boo”,读操作就会失败,cin进入错误状态。所以代码通常应该在使用一个流之前检查它是否处于良好的状态,最好的方法就是把它当作一个条件使用,例如下边的while语句,当键入int数字时,条件为真,维持键入状态,否则会退出while。
int ival;
cin << ival;
while(cin << ival)
// ok:读操作成功
unitbuf
操纵符,告诉流对象接下来每次写操作之后都进行一次flush操作。使用nounitbuf
(unitbuf
前边加no
,取消的意思)操纵符,重置流,恢复默认的刷新机制。#include
ofstream ofs;
ofs.open("文件路径",打开方式);
ofs << "写入的数据";
ofs.close();
|
操作符ios::binary | ios::out
#include
ofstream ifs;
ifs.open("文件路径",打开方式);
ofs.close();
#include
#include
using namespace std;
void main()
{
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
// 读数据
/*
第一种
char buf[1024] = { 0 };
while (ifs >> buf)
{
cout << buf << endl;
}
ifs.close();
*/
/*第二种
char buf[1024] = { 0 };
// ifs.getline(buf, sizeof(buf),读取到buf,最多读sizeof(buf)个字节
while (ifs.getline(buf, sizeof(buf)))
{
cout << buf << endl;
}
*/
/*
* 第三种
* string buf;
* while(getline(ifs, buf))
* {
cout << buf << endl;
* }
*
*/
/*第四种
* 一个字符一个字符读取,判断是否到达文件尾部EOF
*/
char c;
while (( c = ifs.get()) != EOF)
{
cout << c;
}
ifs.close();
return;
}
下边这段代码用于循环处理下边的信息:
morgan 0003031 324244
drew 12323
lee 9900 33132 331333
文件的每条记录都以人名开始,跟随着一个或者多个电话号码。
最外层的while循环逐行读取数据,直至cin遇到文件尾。
用输入字符串流 istringstream与读取到的文本行line绑定,记录为record。
struct PersonInfo
{
string name;
vector<string> phones;
};
int main() {
string line, word; // 分别保持来自输入的一行和单词
vector<PersonInfo> people;
while (getline(cin, line))
{
PersonInfo info;
istringstream record(line); // 将记录绑定到刚读入的行
record >> info.name; // 读取名字
while (record >> word)
{
info.phones.push_back(word); // 保持它们
}
people.push_back(info);
}
return 0;
}
* vector
* deque
* list(双向链表)
* forward_list(单向链表)
* array(固定大小的数组)
* string
注意1:当用一个容器对另一个容器进行拷贝赋值时,两个容器的类型和容器中元素的类型都必须一致。
注意2: 标准库array与内置数组不同,允许赋值和花括号初始化,但不允许花括号列表赋值,因为花括号列表元素的大小可能与固定数组array不一致。
// ans.rbegin()用法
vector<int>ans({ 1, 2, 3 });
for (vector<int>::reverse_iterator it = ans.rbegin(); it != ans.rend();it++)
{
cout << (*it) << endl;
}
// 拷贝赋值
vector<int>copy_ans(ans);
// 双端队列可以用vector的子序列拷贝赋值,这是由于双端队列的底层有用vector来实现
deque<int> dq(ans.begin(), ans.end());
// 标准库array与内置数组不同,允许赋值和花括号初始化,但不允许花括号列表赋值,因为花括号列表元素的大小可能与固定数组array不一致.
array<int, 10> a1 = {0,1,2,3,4,5};
array<int, 10> a2 = {0}; // 10个0
a1 = a2; //用a2给a1赋值
a2 = {0}; // 错误,不允许用花括号列表给array赋值
assign允许将一个不同但是相容的类型赋值,或者从容器的一个子序列赋值。例如使用assign可以实现将一个vector中的一段char*值赋予一个list中的string:
list<string> names;
vector<const char*> oldstyle;
names = oldstyle; // 错误,容器类型不相符
names.assign(oldstyle.cbegin(), oldstyle.cend());
list<string> slist1(1); // 一个空的字符串元素
slist.assign(10, "Hiya");
swap交换两个相同类型的容器的内容,swap不对任何元素进行拷贝,删除或插入,因此可以在很快的常数时间内完成。假定iter在swap前指向svec1[3]的string,那么在swap交换后,iter指向了svec2[3]的元素。与其他容器不同,对一个string调用swap会导致迭代器,引用和指针失效。
svec.insert(svec.end(), 10, "Anna"); //从某个位置起(包含),插入10个“Anna”
ans.insert(ans.end(), ans.begin(), ans.end());
C++11引入了三个新成员——emplace_front、emplace、emplace_back();
这些操作对于push_front,push,push_back();二者的区别可以用下边一个例子说明:
假定用容器c保存PersonInfo元素。
test.emplace_back(“lzy”, 22); // 正确
相当于:test.push_back(PersonInfo(“lzy”, 22)); //正确创建一个临时的PersonInfo对象传递给push_back.
emplace_back会在容器管理的内存中直接创建对象,而调用push_back则会创建一个局部临时对象,并将它压入容器,所以emplace是原地构造新的元素对象。
struct PersonInfo
{
PersonInfo(string _name, int _age) : name(_name), age(_age) {}
string name;
int age;
};
int main() {
vector<PersonInfo> test;
test.emplace_back("lzy", 22); // 正确
test.push_back("lzy", 22); // 错误,没有接收2个参数push_back
test.push_back(PersonInfo("lzy", 22)); //正确创建一个临时的PersonInfo对象传递给push_back.
return 0;
}
删除单个元素:
注意下边语句中迭代器仅在不需要删除元素时后移,这是因为删除操作会动态改变容器数据,迭代器指向的元素会发生变化。
list<int> lst = {0,1,2,3,4,5};
auto it = lst.begin();
while(it != lst.end()){
if(*it % 2){
it = lst.erase(it); // 删除此元素
}else{
++it;
}
}
删除多个元素:
接首两个迭代器参数的erase允许我们删除一个范围内的元素。同样因为删除会改变数据结构长度,迭代器指向的元素会变化。它可以由返回值,返回指向最后一个被删元素之后位置的迭代器。
elem1指向删除的第一个元素位置,elem2指向删除的最后一个元素之后的位置。(左闭右开)
删除完毕后,返回的迭代器就是elem2,故最后elem1 = elem2
elem1 = slist.erase(elem1, elem2);
vector与string都是动态增长存储空间,空间增长后就会重新分配存储空间
),指向插入位置之前的元素的迭代器,指针和引用仍然有效,但指向插入位置之后元素的迭代器、指针和引用失效。vector和string通常会分配比新空间需求更大的内存空间,以预留空间备用。这种分配策略避免了每次添加新元素时都需要重新分配内存空间。
vector中string提供了一些成员函数,允许我们与它实现中的内存分配互动。
const char *cp = "Hello world!!!"; // 以空字符结束的数组
char noNull[] = {'H', 'i'}; //不是空字符结束的数组
string s1(cp); // 拷贝cp中的字符,直到遇到空字符
string s2(noNull, 2); // 从noNull拷贝两个字符
string s3(noNull); // 可以会出现问题,因为noNull不以空字符结尾,所以拷贝时不知何时结束
string s4(cp + 6, 5) // 从cp[6]开始拷贝5个字符,得到"world"
string s5(s1,6, 5 ) // 从s1[6]开始拷贝5个字符
string s6(s1, 6) // 从s1[6]开始拷贝到结束
string s7(s1, 6, 20); // 正确,只会拷贝到结尾
string s8(s1, 16); // 错误,抛出一个越界的错误
s.substr(pos, n); // 返回从字符串s的下标pos开始,长为n的子字符串,n的默认大小为s.size() - pos
append操作在string末尾插入字符串
replace操作是调用erase和insert的一种简写形式
string s("C++ prime"), s2 = s;
s.insert(s.size(), " 4th Ed."); // s == "C++ prime 4th Ed."
s2.append(" 4th Ed."); // 与上一行含义一致
s.erase(11, 3);
s.insert(11, "5th")
s2.replace(11, 3, "5th"); // 从s2的11开始删除3个字符替换为5th,等价上边两句
args必须是以下形式之一:
c, pos 从s中位置pos开始查找字符c,pos默认为0
s2, pos从s中位置pos开始查找字符串s2.pos默认为0
cp,pos从s中位置pos开始查找指针cp指向的以空字符结尾的c风格字符串,pos默认为0
cp, pos, n,从s中位置pos开始查找指针cp指向数组前n个字符,pos和n无默认值
除了顺序容器,标准库还定义了三个顺序容器适配器:stack,queue和priority_queue。
stack stk(deq); // 从deq拷贝元素构造stk
// 在vector上实现的空栈
stack<string, vector<string>> str_stk;
// str_stk2在vector上实现,初始化时保存svec的拷贝
stack<string, vector<string>>str_stk2(svec);
大多数算法都定义在头文件algorithm
中,标准库还在头文件numeric
中定义了一组数值泛型算法。一般情况下算法并不直接操作容器,而是遍历由两个迭代器指定的一个元素范围。迭代器令算法不依赖于容器,算法永远不会执行容器的操作。
标准库提供超过100个算法,但这些算法有一致的结构,理解结构可以帮助我们更容易地学习和使用这些算法
一些算法只读取输入范围的元素,但不改变元素。例如find
,count
和定义在numeric
中的accumulate
,它接受3个参数,前两个指定了求和的元素的范围,第三个参数是和的初始值。例如下边这条语句sum求取了vec中所有元素的和。
int sum = accumulate(vec.cbegin(), vec.cend(), 0);
accumulate将第三个参数作为求和的起点,这里隐含地假设了元素类型是可以求和的操作的,故上例中元素类型可以是int,long,double,long long等。
由于string定义了+运算符,因此可以调用accumulate来将vector中的string元素连接起来:
string sum = accumulate(v.cbegin(), v.cend(), string(""));
注意这里最后一个参数显式地创建了一个string,而非直接将字面值""
作为参数传递,原因在于如果我们传递的是字符串字面值,用于保存和的对象的类型将是const char *.这样便会产生矛盾,所以应该构建一个临时的string变量作为参数传递,而不能使用字面值常量。
equal
是另一个只读算法,用于确定两个序列是否保存相同的值。如果两序列所有对应元素相等,返回true,否则返回false,此算法也可以接受三个迭代器,前两个表示第一个序列的元素范围,第三个表示第二个序列的首元素。这些只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长,并且比较的长度基于第一个序列的长度。例如下边的例子,str1与str2的equal结果是1.
string str1 = "abc";
string str2 = "abcd";
cout << equal(str1.begin(), str1.end(), str2.begin()); // 打印结果为1,相等
一些算法将新值赋予序列中的元素,当使用这类算法时,必须确保序列的大小,至少不小于我们要求算法写入的元素的数目。例如fill
,它接受一对迭代器表示一个范围,还接受一个值作为第三个参数,将这个给定值赋予输入序列范围中每个元素。
fill(vec.begin(), vec.end(), 0); // 将每个元素重置为0
fill(vec.begin(), vec.begin() + vec.size() / 2, 10;)
一些算法接受一个迭代器指出一个单独的目的位置,从该位置开始赋值。例如fill_n
fill_n(vec.begin(), vec.size(), 0);
一种保证算法有足够元素空间来容纳输出数据的方法是使用插入迭代器back_inserter
,它是定义在头文件iterator
中的一个函数。它接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器,当我们向此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中。
vector<int>vec;
auto it = back_inserter(vec); // 通过它赋值,会将元素添加到vec中
*it = 42; // vec中现有一个元素,值为42
我们常常使用back_inserter来创建一个迭代器,作为算法的目的位置来使用:
vector<int>vec;
fill_n(back_inserter(vec), 10, 0); // 添加10个元素到vec
在每步迭代中,fill_n向给定序列的一个元素赋值,由于我们传递的参数是back_inserter返回的迭代器,因此每次赋值都会在vec上调用push_back.
copy
算法接受三个迭代器,前两个表示一个输入范围,第三个表示目的序列的起始位置,此算法将输入范围中的元素拷贝到目的序列中,传递给copy的目的序列至少要包含与输入序列一样多的元素。可以使用copy实现内置数组的拷贝:
int a1[] = {0,1,2,3,4,5};
int a2[sizeof(a1) / sizeof(*a1)];
auto ret = copy(begin(a1), end(a1), a2); // 把a1的内容拷贝给a2,ret指向a2尾元素下一个位置
replace算法读入一个序列,并将其中所有等于给定值的元素都改为另一个值。此算法接受4个参数,前两个是迭代器,后两个一个是要搜索的值,另一个是新值。
如果希望保留原序列不变,可以调用replace_copy算法,此算法额外接受第三个迭代器参数,指向调整后序列的保存位置:
replace(ilst.begin(), ilst.end(), 0, 42); // 将ilst中所有的0替换为42
replace_copy(ilst.cbegin(), ilst.cend(), back_inserter(ivec), 0, 42);
// 此调用后,ilst并未改变,ivec包含ilst的一份拷贝,不过原来在ilst中0替换为了42
某些算法会重排容器中的元素的顺序,比如sort
。
为了消除重复单词,首先将vector排序,使得重复的单词相邻出现,一旦vector排序完毕,使用unique
算法重排vector,使得不重复的元素出现在vector的开始部分,返回一个迭代器,指向元素不重复出现序列尾部的下一个位置。由于算法不能执行容器的操作,所有我们使用erase成员函数来完成真正的删除操作。
void elimDumps(vector<string> &words){
// 按字典序排序words
sort(words.begin(), words.end());
auto end_unique = unique(words.begin(), words.end());
// 使用向量操作erase删除重复单词
words.erase(end_unique, words.end());
}
sort算法默认使用元素类型的<
运算符,但可能我们希望的排序顺序与<
所定义的顺序不同,或是我们的序列可能保存的是未定义<
运算符的元素类型,在这两种情况下,都需要重载sort的默认行为。重载的sort接收第三个参数,它是一个谓词
。
谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。谓词分为两类:一元谓词,二元谓词(意味着它有两个参数)。接受谓词参数的算法对输入序列中的元素调用谓词。因此元素类型必须能转换为谓词的参数类型。
接受一个二元谓词的sort使用这个谓词来代替**<**来比较元素。
例如:下边这段函数可以按长度由短到长排序words
bool isShorter(const string &s1, const string &s2){
return s1.size() < s2.size();
}
sort(words.begin(), words.end(), isShorter); // 按长度由短到长排序words
在words按大小重排的同时,还希望具有相同长度的元素按字典序重排,为了保存相同长度的单词按字典序排列,可以使用stable_sort算法。稳定排序算法维持相等元素的原有顺序。
elimDups(words); // 将words按照字典序重排并消除重复单词
stable_sort(word.begin(), word.end(), isShorter);
for(const auto &s : words){
cout << s << " ";
}
cout << endl;
根据算法接受一元谓词还是二元谓词,我们传递给算法的谓词必须验证接受一个或两个参数。但有时,我们希望进行的操作需要更多参数。以下边这个需求为例:
求大于等于一个给定长度的单词有多少。
使用标准库find_if
算法来查找第一个具有特定大小是元素,find_if算法接受三个参数,前两个是一对迭代器,表示一个范围,第三个参数是一个谓词,返回第一个使得谓词非0的元素,如果不存在这样的元素,返回尾迭代器。find_fi接受一个参数。没有办法再传递给他第二个参数表示长度。为此,需要引用lambda表达式。
void biggies(vector<string>& words, vector<string>::size_type sz){
elimDups(words); // 字典序排序,去重
stable_sort(words.begin(), words.end(), isShorter);
// 获取一个迭代器,指向第一个满足size() >= sz的元素
// 计算满足size >= sz的元素的数目
// 打印长度大于等于给定值的单词,每个单词后边接一个空格
}
一个lambda表达式表示一个可以调用的代码单元,可以把他理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型,一个参数列表和一个函数体。但与函数不同的是,lambda可能定义在函数内部。
一个lambda表达式具有如下形式:
[capture list] (parameter list) ->return type{function body}
其中capture list(捕获列表)是一个lambda所在函数中定义的局部变量列表,参数列表与函数体与普通函数一样。不同的是,lambda必须使用尾置返回来指定返回类型
。我们可以忽略参数列表与返回类型,但是必须永远包含捕获列表和函数体
。
auto f = [] {return 42;};
上边这句代码定义了一个可调用对象f,不接受参数,返回42.
cout << f() << endl; // 打印42
在lambda中忽略括号和参数列表等价指定一个空参数列表。
下边采用lambda表达式来编写一个功能与isShorter函数相同的函数:
空捕获列表表明此lambda不使用它所在函数中任何局部变量,lambda的参数与isShorter是类似的
[] (const string& a, const string& b){return a.size() < b.size()}
// 使用lambda调用算法
stable_sort(words.begin(), words.end(), [](const string & a, const string &b){return a.size() < b.size();});
一个lambda可以使用一个函数中的局部变量,但必须明确地在捕获列表中指明。
[sz](const string &a, const string &b){return a.size() >= sz;}
完整的biggies
void biggies(vector<string>& words, vector<string>::size_type sz){
elimDups(words); // 字典序排序,去重
stable_sort(words.begin(), words.end(), [](const string &a, const string &b) {return a.size() < b.size();}); // 使用lambda按照长度排序
// 获取一个迭代器,指向第一个满足size() >= sz的元素
auto wc = find_if(words.begin(), words.end(), [sz](const string &a){return a.size() >= sz;})
// 计算满足size >= sz的元素的数目
auto count = words.end() - wc;
cout << count << " " << make_plural(count, "word", "s");
<< "of length" << sz << "or longer" << endl;
// 打印长度大于等于给定值的单词,每个单词后边接一个空格
for_each(wc, words.end(), [](const string &s){cout << s << " ";});
cout << endl;
}