C++ Primer中小细节 章节三:C++标准库

IO类型:

头文件:
流读取写入:iostream
文件读取写入:fstream
string读取写入:sstream

io类型不可拷贝和赋值,因此无法用于形参或返回类型,而通常以引用方式传递。读写一个io对象会改变其状态,因此不能用const。

文件io:
ifstream in(infile);
ofstream out;
out.open(outfile, ofstream::app);   //默认以out方式,可指定app以追加方式打开
out.is_open();   //  或者 if(out) 检测状态
out.close();  // out对象被销毁时会自动调用close
string io:
istringstream record("name phone");
record >> name;   //读取
while(record >> phone){   }

ostringstream outformat;
outformat << " " << name;  //写入
outformat.str()  //返回保存字符串的拷贝

io状态:

io的状态由多个位来表示
检测:

while( cin >> word ) {}
s.eof()    // 结束
s.fail()   // fail(IO操作失败) 或 bad位被置位
s.bad()    // bad位(流崩溃)被置位
s.good()   //完好
cin.clear(cin.rdstate() & ~cin.failbit)       //  无参对指定位复位,有参更新状态(复位failbit,其他位不变)
s.setstate(iostate)    // 对指定位置位
s.rdstate()            // 返回当前状态

刷新缓冲区:

endl  换行+刷新
flush 直接刷新
ends  空字符+刷新

cout<

关联流:

标准库将cin和cout关联一起,任何读取操作都会先刷新关联的输出流。

cin.tie(&cout)  //输入关联输出
cin.tie()  //返回关联的输出流指针,无则返回空指针

顺序容器:

vector  可变大小数组,随机访问,尾部插入删除快,其他很慢
deque  双端队列,随机访问,头尾插入删除很快,中间较慢
list  双向链表,双向顺序访问,任何位置插入删除很快
forward_list  单向链表,单向顺序访问,任何位置插入删除很快
array  固定大小数组,随机访问,不能添加删除元素
string  类似于vector,随机访问,尾部插入删除快,其他很慢

1、优先选择vector。除了array固定长度,其他容器都可以扩展大小。
2、string、vector存在连续空间,插入删除都要移动后续元素,适合随机访问,中部操作不频繁;list、forward_list访问元素是只能遍历整个容器,适合中部操作频繁。
3、forward_list没有size,在保存和计算大小时消耗大。
4、如果既需要中部插入,又需要随机访问,则需测试性能考虑占据主导的操作。

如果程序在输入时频繁操作中部,随后只是随机访问,那么:
1、是否真的需要中间位置操作,可以先尾部插入,然后sort排序;
2、如果必须在中间插入,先使用list,然后拷贝到vector中。

vector> nums;
vector v1(10, init);   //初始化时nodefault没有默认构造函数,必须传入init。

容器通用操作:

C++ Primer中小细节 章节三:C++标准库_第1张图片
类型别名允许在不关注具体类型的情况下使用它。list::iterator iter;
C++ Primer中小细节 章节三:C++标准库_第2张图片

vector ar = {"A", "B"};
vector wo(ar);  //错误:拷贝必须类型相同
forward_list wo(ar.begin(), ar.end());  //正确:传递范围进行拷贝。wo和ar类型可以不同,元素类型也可不同,只需拷贝时进行类型转化

C++ Primer中小细节 章节三:C++标准库_第3张图片

// array中大小是类型的一部分
array a;  //2个默认初始化的string
array a = {"A", "B"};
a = {"C"}; //错误:不能将一个花括号列表赋予array
array a = {"A"}; //a[0]为A,其余空字符串

C++ Primer中小细节 章节三:C++标准库_第4张图片

list names;
vector old;
names = old;  //错误:必须类型相同
names.assign(old.begin(), old.end());  //正确:类似于范围初始化

swap:
1、除array外,swap不会对任何元素使用修改,插入,删除,因此保证在常数时间内完成。而array则会真正的交换元素。
2、不会交换元素意味着元素的迭代器、引用、指针仍然指向之前的元素,不过所属容器改变了(除了string以外,string会使其失效)。而array在swap后迭代器、引用、指针绑定元素保持不变,但是元素值已经改变。
3、统一使用非成员swap,在泛型编程中很重要。

C++ Primer中小细节 章节三:C++标准库_第5张图片
C++ Primer中小细节 章节三:C++标准库_第6张图片
1、比较运算类似于string的比较,因为是逐元素比较,因此需要元素定义相应的比较运算。
在这里插入图片描述

//显式定义
list::iterator it = a.begin();
list::const_iterator it = a.begin();
//以c开头是为了集合auto使用begin和end
auto t = a.begin();  //是否是const与a的类型有关,当a为const,t才为const
auto t = a.cbegin(); //肯定是const的 

C++ Primer中小细节 章节三:C++标准库_第7张图片

顺序容器特有操作:

C++ Primer中小细节 章节三:C++标准库_第8张图片
当使用一个对象来初始化或者将一个对象插入容器,其实质上是放入了对象值得一个拷贝,而不是元素本身。

while(cin >> word){
	iter = names.insert(iter, word);
}

emplace和push区别:
emplace对应于push的相关操作,不过emplace是直接利用参数在容器的内存位置构造元素,而push是将构造好的元素拷贝到容器的内存位置,多了一个拷贝的操作。

c.emplace_back("131", 25);   //正确,emplace会使用这些参数构造Sales_Data对象
c.push_back("131", 25);  //错误:push操作不支持
c.push_back(Sales_Data("131", 25));  //正确:push操作会将这个对象拷贝到容器里

//emplace的参数类型和元素类型的构造函数相同
c.emplace_back();  //默认构造函数
c.emplace(iter, "131");  //Sales_Data("131")构造函数
std::string s = "abc";
std::vector vec;
vec.push_back(s);            // copied
vec.push_back(std::move(s)); // good, moved in
vec.emplace_back("abc");     // good, constructed in place
vec.emplace_back(s);         // copied
vec.emplace_back(10, 'a');   // constructed as "aaaaaaaaaa"

C++ Primer中小细节 章节三:C++标准库_第9张图片

front、back、下标、at返回的都是引用。如果容器为const,返回的也是const
引用。
c.front() = 24;
下标的index不会检查,而at超出范围会出现out of range错误。
vec[0];
vec.at(0);

C++ Primer中小细节 章节三:C++标准库_第10张图片
1、删除函数并不检查参数,在删除之前需要检查元素是否存在。

slist.clear();
slist.erase(slist.begin(), slist.end());

C++ Primer中小细节 章节三:C++标准库_第11张图片

forward_list:

forward_list删除当前元素时需要知道前驱以能够将前驱链接起来。为此提供的操作为:
C++ Primer中小细节 章节三:C++标准库_第12张图片

// 删除奇数
auto prev = flist.before_begin();
auto curr= flist.begin();
while(curr != flist.end()){
	if(*curr % 2){
		curr = flist.erase_after(prev);
	}else{
		prev = curr;
		++curr;
	}
}

迭代器失效:

向容器添加、删除元素时都有可能导致容器的指针、引用、迭代器失效。
C++ Primer中小细节 章节三:C++标准库_第13张图片
C++ Primer中小细节 章节三:C++标准库_第14张图片
添加、删除vector、string或deque元素都需要考虑迭代器、引用、指针的失效问题。因为end返回的迭代器在添加或删除后总是会失效,因此不要在循环前保存end返回的迭代器,且C++中end操作很快。

auto iter = vi.begin();
while(iter != vi.end()){
	if(*iter % 2){
		iter = vi.insert(iter, *iter);  //跳过插入元素和当前元素
		iter += 2;
	}else{
		iter = vi.erase(iter);  //删除直接移动到下一元素
	}
}

vectore对象的增长:

C++ Primer中小细节 章节三:C++标准库_第15张图片
resize只改变容器中元素的数量,而不会改变容器的容量;
reserve只有需要容量超过当前容量的时候,才会改变容器的容量;

C++ Primer中小细节 章节三:C++标准库_第16张图片

size指的是容器中已保存元素的数量;
capacity指的是不分配新内存空间的情况下,容器能够容纳的元素数量;
只要操作没有超过容器的容量,容器就不会出现分配内存空间。
只有在执行insert操作时size和capacity相等、或者resize、reserve时给定大小超过当前capacity时,vector才能重新分配内存。

额外string操作:

初始化:
C++ Primer中小细节 章节三:C++标准库_第17张图片
C++ Primer中小细节 章节三:C++标准库_第18张图片
裁剪:
C++ Primer中小细节 章节三:C++标准库_第19张图片
其他:
1、新字符可以来自于另一个string、字符指针、花括号包围的字符列表、一个字符+一个计数值。当字符来自于一个string或字符指针,可以传入额外参数控制拷贝所有还是部分。

C++ Primer中小细节 章节三:C++标准库_第20张图片
C++ Primer中小细节 章节三:C++标准库_第21张图片
在这里插入图片描述
C++ Primer中小细节 章节三:C++标准库_第22张图片
C++ Primer中小细节 章节三:C++标准库_第23张图片
搜索:
1、搜索返回string::size_type类型(unsigned),搜索失败返回static的string::npos,npos类型为const size_type(unsigned),并且初始化值为-1。因此使用int或者其他带符号类型保存返回值是不好的。
C++ Primer中小细节 章节三:C++标准库_第24张图片
C++ Primer中小细节 章节三:C++标准库_第25张图片
比较:
C++ Primer中小细节 章节三:C++标准库_第26张图片
数值转换:
C++ Primer中小细节 章节三:C++标准库_第27张图片

// 第一个非空白符需为+-或数字,浮点数可以包含.Ee,整型根据基数可以包含字母
d = stod(s2.substr(s2.find_first_of("+-.0123456789")));

容器适配器:stack、queue、priority_queue

容器适配器根据已有容器使其表现出另一种类型。
C++ Primer中小细节 章节三:C++标准库_第28张图片
默认stack和queue基于deque实现;priority_queue基于vector实现

stack> stk;
stack> stk(vec);

C++ Primer中小细节 章节三:C++标准库_第29张图片
C++ Primer中小细节 章节三:C++标准库_第30张图片
C++ Primer中小细节 章节三:C++标准库_第31张图片

关联容器:

关联容器是以关键字来保存和访问的。
C++ Primer中小细节 章节三:C++标准库_第32张图片
C++ Primer中小细节 章节三:C++标准库_第33张图片
1、value_type为pair,可以改变pair的值,但不能改变关键字;
2、auto it = map.begin(), *it为pair的引用,it->first打印key;
3、set的iterator和const_iterator都是修改关键字;
4、有序容器迭代器按照关键字升序进行遍历;
5、通常不能对关联容器使用泛型算法,因为关键字的const使得不能将关联容器传给修改或重排容器元素的算法,因为这类算法需要向元素写入值。关联容器能够使用只读取元素的算法,但大多数算法都需要搜索序列,而关联容器不能通过关键字进行快速查找,因此使用泛型搜索算法几乎是个坏主意。如果需要对关联容器使用算法:将关联容器当作源序列,使用copy泛型算法拷贝到另一个序列;可以调用inserter将一个插入器绑入关联容器,通过使用inserter将关联容器当作一个目的位置来调用另一个算法。
初始化:

set ss = {"A", "B"};
map sm = { {1, "A"}, {2, "B"} };

关键字类型:
1、有序容器需要关键字严格弱序;
2、如果元素没有定义比较函数,可以传入比较函数来比较;

multiset  bookstore(compare);

C++ Primer中小细节 章节三:C++标准库_第34张图片

set2.insert(vec.cbegin(), vec.cend());
set2.insert({1, 2, 3});
map2.insert({key, 1});
map2.insert(make_pair(key, 1));
map2.insert(pair(key, 1));
auto ret = map2.insert(map::value_type(key, 1));
// ret类型:pair::iterator, bool>

1、insert返回值依赖于容器类型和参数。对于不包含重叠关键字的容器,insert和emplace返回一个pair,first为一个迭代器,指向给定关键字元素,second输出是否插入成功,如果容器已存在该关键字则为false。对于允许重叠关键字的容器,直接返回指向新元素的迭代器。
C++ Primer中小细节 章节三:C++标准库_第35张图片
C++ Primer中小细节 章节三:C++标准库_第36张图片
在这里插入图片描述
C++ Primer中小细节 章节三:C++标准库_第37张图片
1、对于map,使用下标索引如果关键字未在map之中,会自动插入,但如果我们不想插入,可以使用find。
2、对于multimap的查找,有多个相同关键字时则会相邻存储。

// 统计数量查找
auto be = find();
auto num = count();
while(num){
	++be;
	--num;
}

// 使用bound
// lower_bound指向第一个key位置,如果不存在,将会指向第一个关键字大于key的位置,有可能是尾后迭代器。
// upper_bound指向最后一个key元素之后的元素位置,如果不存在将会指向关键字的插入位置。
for(auto be = lower_bound(), end = upper_bound(); beg != end; ++beg){
	beg->second;
}

//使用equal_range返回迭代器pair
for (auto pos = equal_range(key); pos.first != pos.end; ++pos.first){
	pos.first->second;
}

无序容器:

1、使用哈希函数和关键字类型==运算符来组织元素。
2、无序容器在内存组织上为一组桶,使用哈希函数将一个元素映射到对应的桶。如果允许重复关键字,那么所有相同元素将会保存在同一个桶。因此无序容器性能依赖于哈希函数的质量和桶的数量、大小。
C++ Primer中小细节 章节三:C++标准库_第38张图片
如果使用自定义类作为关键字,那么需要实现hash和等于算法:
C++ Primer中小细节 章节三:C++标准库_第39张图片
C++ Primer中小细节 章节三:C++标准库_第40张图片
C++ Primer中小细节 章节三:C++标准库_第41张图片

Pair类型:

C++ Primer中小细节 章节三:C++标准库_第42张图片

泛型算法:服务于容器

1、大多数定义在头文件algorithm中,部分在numeric里;
2、通常情况下这些算法并不是直接应用于容器,而是在两个迭代器范围内遍历。由此导致算法不会执行容器的操作(添加、删除),而只是运行在迭代器之上(移动、改变值)。
3、迭代器导致这些算法不依赖容器,但是算法依赖于元素类型的操作,比如等于;

只读算法:
1、只读最好使用const迭代器;
2、用一个单一迭代器访问容器,都表示算法假设第二个序列长度至少比第一个序列一样长,而这需要程序员来保证。如equal函数;

查找函数 find
find_if(w.begin(), w.end(), func);   //根据条件查找

计数函数 count

累计函数 accumulate
string sum = accumulate(v.cbegin(), v.cend(), string(""));  //注意显式的构造空字符串,因为此类型决定了使用哪个+运算符。如果使用字面""就会出错,因为const char*没有+运算符。

相等函数 equal
equal(v1.cbegin(), v1.cend(), v2.cbegin()); //equal假设二者一样长,不要求容器类型相同(基于迭代器)、元素类型相同(基于==)

写算法:

填充函数 fill

填充函数 fill_n
fill_n(vec.begin(), 10, 0);  //需要保证vec至少含有10个元素,否则结果未定义。
fill_n(back_inserter(vec), 10, 0);

back_inserter:插入迭代器,一种向容器中添加元素的迭代器。头文件iterator中
auto it = back_inserter(vec);  //通过向迭代器赋值,迭代器在vec上调用push_back
*it = 42;

拷贝函数 copy
auto ret = copy(begin(arr), end(arr), arr2);  //将arr内容拷贝到arr2,arr为数组类型,ret指向拷贝到arr2尾元素之后的位置

替换函数 replace
replace(ilist.begin(), ilist.end(), 0, 42);  //将0替换为42
replace_copy(ilist.cbegin(), ilist.cend(), back_inserter(vec), 0, 42);  //保留原序列不变,

重排算法:

排序函数 sort

消重函数 unique
sort(w.begin(), w.end()); //先排序,方便查找重复
auto end_unique = unique(w.begin(), w.end());  //相邻重复消除,返回指向不重复范围末尾的迭代器
w.erase(end_unique, w.end());  //泛型算法运行于迭代器上并不能删除元素。即使w没有重复,该操作也是安全的。

定制操作:
sort函数的第三参数即称为谓词。接受一个参数为一元谓词(unary predicate),接受两参数称为二元谓词(binary predicate)。

bool shorter(const string &s1, const string &s2){
	return s1.size() < s2.size();
}
sort(w.begin(), w.end(), shorter);

lambda表达式:

1、可调用对象:函数、函数指针、类重载运算符、lambda表达式。
2、当定义一个lambda时,编译器生成了一个与lambda对应的新的类类型。在向函数传递lambda时,同时定义了新类型和该类型的一个对象,传递的参数就是生成的未命名对象。当使用auto定义一个lambda初始化的变量时定义了从lambda类型生成的对象。
3、默认情况下从lambda生成的类都包含了该lambda所捕获的变量的数据成员,类似于普通类的数据成员。这些数据成员在lambda对象被创建时被初始化。
4、可以函数返回lambda,但是该lambda不能包含引用捕获(函数内局部变量会被销毁)。

[capture list] (parameter list) -> return type {function body}
capture list:捕获列表,lambda所在函数中定义的局部变量的列表
return tpye:返回类型,lambda必须使用尾置返回
可以忽略参数列表和返回类型,但必须包含捕获列表和函数体
auto f = [] {return 42};  //如果只有一个return,则返回类型可以推断出,如果有多个则返回类型为void
cout<

捕获普通变量(int、string)可以采用值捕获,捕获指针或迭代器或引用捕获需保证对象值符合期望。避免捕获指针或引用。

值捕获:前提是对象可拷贝,在lambda创建时拷贝
[v1]

引用捕获:需确保在lambda执行时引用对象一直存在。如ostream对象不可拷贝。
[&os]

隐式捕获:
[&]  //内部变量引用捕获
[=]  //内部变量值捕获
[&, c]  //混合捕获,必须&或=在前,且隐式捕获和显式捕获方式必须不同,&之后变量只能值捕获
[=, &os]  //混合捕获,必须&或=在前,且隐式捕获和显式捕获方式必须不同,=之后变量只能引用捕获

默认情况下,lambda不能修改其捕获的拷贝变量,如果修改,可使用mutable

size_t v1 = 42;
auto f = [v1] () mutable {return ++v1;} 
v1 = 0;
auto j = f();  // j=43,与lambda外v1无关。

引用捕获变量能否修改依赖于该引用指向变量是否const
size_t v1 = 42;
auto f = [&v1] () mutable {return ++v1;} 
v1 = 0;
auto j = f();  // j=1,在调用之前v1值变了。
指定返回类型
[](int i) -> int {if(i<0) return -i else return i;} 

参数绑定:
lambda用于简单函数的替代,当重复调用较多时,函数仍是较佳选择。不过存在的问题时lambda可以通过捕获的方式获得当前所处函数变量。而函数需要采用绑定的方式才能获得。

// bind在functional头文件中
auto newCall = bind(call, args);

auto check6 = bind(check_size, _1, 6);  //占位符_1表示check6只接受一个参数,check6使用_1位置的变量和6来调用check_size(s, 6)

using std::placeholders::_1;  //或者using namespace std::placeholders;包含所有 
find_if(w.begin(), w.end(), bind(check_size, _1, sz)); //解决了find_if的一元谓词和check_size两个参数的矛盾
新可调用对象有两个参数,分别作为第三个(g的第二个参数)和第五个(g的第一个参数)传递给f,f的第一、二、四参数分别被绑定到a、b、c上
auto g = bind(f, a, b, _2, c, _1) 
g(_1, _2) //调用g时参数按照_1,_2位置绑定

重排参数
sort(w.begin(), w.end(), shorter())  //升序
sort(w.begin(), w.end(), bind(shorter, _2, _1)) //降序
bind非占位符参数采用拷贝的方式,不过有些变量无法拷贝时
函数ref返回一个对象,包含给定的引用,该对象是可以拷贝的。functional中还有个cref函数
for_each(w.begin(), w.end(), bind(print, ref(os), _1, ' '));

再探迭代器:

插入迭代器(insert):用于向容器插入元素

back_inserter  使用push_back的迭代器
front_inserter  使用push_front的迭代器
inserter  总是使用第二参数插入容器迭代器所表示元素的位置之前

inserter等价于:
it = c.insert(it, val);
++it;  //递增it使得指向原来的元素

C++ Primer中小细节 章节三:C++标准库_第43张图片

流迭代器(stream):用于遍历所关联的IO流
1、流迭代器并不是立即读取流数据,而是在使用迭代器时才真正读取。这在创建流迭代器没有使用就销毁了;或者两个不同对象同步读取同一个流时,何时读取就重要了
2、流迭代器不支持递减,因为在流上不能反向移动
C++ Primer中小细节 章节三:C++标准库_第44张图片

// istream_iterator使用>>读取流,因此要读取的类型必须定义了输入运算符
istream_iterator int_it(cin);  //从cin读取int
istream_iterator int_eof; //尾后迭代器

ifstream in("afile");
istream_iterator str_it(in);
读取示例:
istream_iterator int_it(cin);  //从cin读取int
istream_iterator int_eof; //尾后迭代器
while(int_it != int_eof){
	vec.push_back(*int_it++);  //++返回旧值,*解引用
}
可简写成:
istream_iterator int_it(cin), eof; 
vector vec(int_it, eof);
使用算法操作流迭代器:
cout << accumulate(int_it, eof, 0) << endl;

C++ Primer中小细节 章节三:C++标准库_第45张图片

// ostream_iterator使用<<输出流,因此要输出的类型必须定义了输出运算符,不允许空的或者尾后的ostream_iterator
ostream_iterator out_it(cout, " ");
for(auto e : vec){
	*out_it++ = e;
}
cout<

反向迭代器(reverse):迭代器向后移动
C++ Primer中小细节 章节三:C++标准库_第46张图片
C++ Primer中小细节 章节三:C++标准库_第47张图片

sort(v.begin(), v.end()); //正常序
sort(v.rbegin(), v.rend());  //逆序

// 注意crbegin和cend、rcomma和rcomma.base()生成的是相邻但是不相同的位置
cout << string(s.crbegin(),  rcomma) <

移动迭代器(move):非拷贝而是移动元素

你可能感兴趣的:(C/C++)