十六:IO操作
IO对象无拷贝和赋值,只能引用传递,并且不能是const
函数good() 所有错误位都没置位的情况下返回true;
fail() 一般用作流使用的条件(failbit,badbit)
eof()和bad()只表示特定的错误。
例如:将failbit和badbit复位,但eofbit不变
cin.clear(cin.rdstate() &~cin.failbit & ~cin.badbit);
endl 换行并刷新缓冲区。
要每次输出都刷新缓冲区,用unitbuf cout<<unitbuf;
cout<<nounitbuf; 回到正常的缓冲方式。
x.tie(&o); 将流x关联到流o
ostringstream允许保存字符串,最后输出,他在#include<sstream>中
相对于stirng,他能够使用>>操作东西。
#include<string>
#include<sstream>
usingnamespace std;
int main()
{
ostringstream formatted;
formatted<< " "<<
"Hello" <<endl; //这格式,这样保存,相当于string的+号
cout<< formatted.str();
return 0;
}
十七:容器
----------------支持快速随机访问-----------------
array固定大小。
vector
string
deque
----------不支持随机访问--------------
list
forward_list
----------容器适配器-------------
stack,queue – 基于deque
priority_queue – 基于 vector
array 可以进行拷贝和赋值,内置数组不行。
数组不能初始化给array,花括号也不能赋值给array对象。
array对象的赋值,两边的类型必须完全一样。
swap 交换array时间与元素个数成正比。
swap 交换其他容器,其实只交换两个容器的数据结构,常数时间内就可以完成。
每个容器都支持== 和 != ,但是<=等等不一定。
insert 插入到迭代器指定位置之前(1:迭代器可能指向尾部不存在的元素位置,2:开始位置插入的功能很有用),forward_list有自己特色的insert(insert_after)
emplace_front/emplace/emplace_back (调用对应的构造函数创建对象)
功能对应
push_front/insert/push_back
front/back(除forward_list) 返回首尾元素的引用。
capacity和reserve只适用vector和string
reserve不改变容器元素的数量,仅影响预先分配多大的空间。
resize只改变元素数量,不改变容量。
十八:泛型算法
1:头文件:numeric algorithm
2:迭代器另算法不依赖于容器,但算法依赖于元素类型的操作。
如:find用元素的==完成比较。
3:算法使用的是迭代器,不能执行容器操作,所以
算法永远不会改变底层容器的大小(不会删除和添加元素)
因为插入迭代器,默认在赋值时调用了容器算法:push_back(),所以他可以改变容器大小。
4:插入迭代器
(1)作为目的位置,可以保证算法有足够的元素空间。
如:fill_n(back_inserter(vec),10,0);
(2)赋值,会调用push_back将元素添加到容器:
如:vector<int> vec;
auto it = back_inserter(vec);
*it = 42;//实际调用了push_back;
5:谓词:一个可调用的表达式,返回能用做条件的值
bool isShorter(const string&s1,const string &s2)
{
returns1.size() < s2.size();
}
sort(vec.begin(),vec.end(),isShorter);
//stable_sort 可以保持等长元素间的字典序。
6:lambda表达式
1)
可调用对象:(1)函数 (2)函数指针(3)重载了函数调用运算符的类 (4)lambda表达式
可以理解为:未命名的内联函数(与函数不同的是,他可以定义在函数内部)
[]()->return type {}
[捕获列表](参数列表)->返回类型{函数体} ///捕获列表和函数体不能省略。
2)
lambda根据函数体默认推断返回类型,如果函数体包含任何单一return语句之外的内容,且未指定返回类型,则默认返回void,要明确返回类型,必须指定返回类型。
lambda不能有默认参数。
find_if 只接受一个一元谓词。
一:)捕获列表弥补了参数数量的问题。
二:)参数数量的问题可以用bind函数解决
捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和它所在函数之外的声明的名字。
lambda数据成员(捕获列表)在lambda对象创建时被初始化。而不是调用时。
3)
捕获可以是值捕获或者引用捕获。
可以让编译器根据lambda体中的代码推断我们要使用那些变量,为了指示编译器推断捕获列表,应在捕获列表中写一个&或=;
可以混合使用,如[=,&os],默认都是值捕获,os采用引用捕获。混合使用时,捕获列表的第一个元素必须是&或=
4)
可变lambda
要改变捕获变量的值,必须在参数列表首加个mutable,因此,可变lambda能省略参数列表。
如:
auto f = [v1]()mutable{++v1;};
5)指定lambda返回类型(必须使用尾置返回类型)
transform(vi.begin(),vi.end(),vi.begin(),[](inti)->int{ if(i<0) return –i;else return i;});//函数体不是一个单独return ,并且不是void返回,所以必须指定返回类型。
7:for_each接受可调用对象
for_each(vec.begin(),vec.end(),[](conststring *s){coust<<s<<;};
8:bind函数适配器
接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
1)
find_if(vec.begin(),vec.end(),[sz](conststring &a){return a.size()>=sz;});
find_if(vec.begin(),vec.end(),bind(check_size,_1,sz));//check_size是一个函数,它比较_1字符串长度和sz的大小
如:auto checkfun = bind(check_size,_1,6);
string s = “hello”;
bool b1 = checkfun(s);
_n定义在placeholders的命名空间中,而placeholders定义在std中,也定义在functional头文件中。
using namespace std::placeholders
2)
bind 可以重新安排参数顺序:
如:
auto g = bind(f,a,b,_2,c,_1);
g(X,Y);相当于调用f(a,b,Y,c,X);
这样其实可以灵活运用参数顺序的不同,重排
sort(vec.begin(),vec.end(),isShorter);//正序
sort(vec.begin(),vec.end(),bind(isShorter,_2,_1));//逆序
反向迭代器也可以实现逆序
sort(vec.rbegin(),vec.rend());
3)bind绑定引用参数
ostream &print(ostream &os,conststring &s,char c){ return os<<s<<c;}
for_each(vec.begin(),vec.end(),[&os,c](conststring &s){.....});
引用捕获的代替:
for_each(vec.begin(),vec.end(),bing(print,ref(os),_1,‘ ’));
cref 生成一个保存const引用的类。
(1)查找:find 需要 ==
find(vec.cbegin(),vec.cend(),val);
find(ia+1,ia+4,val); 子范围查找
(2)求和:accumulate需要 +
accumulate(vec.cbegin(),vec.cend(),0);//0是初始值
(可以累加string,拼接)
(3)判断相等:equal 需要== 假定第二个序列和第一个一样长。char*竟也可以用==比较
equal(vec.cbegin(),vec.cend(),list1.cbegin());
(4)值覆盖:fill_n
fill_n(vec.begin(),vec.size(),0);
(5)拷贝 :目的序列至少与输入序列一样多
int a1[]={1,2,3};
int a2[sizeof(a1)/sizeof(*a1)];
auto ret =copy(begin(a1),end(a1),a2);//ret指向a2尾元素之后的位置
(6)替换:
replace(ilst.begin(),ilst.end(),0,42);//把0替换为42
如果希望原序列不变:
replace_copy(ilst.cbegin(),ilst.cend(),back_inserter(vec),0,42);
(7)排序:sort 需要 <
sort(vec.begin(),vec.end());
对排完序的可以去重一下:unique只能处理排好序的
auto it = unique(vec.begin(), vec.end());//it 指向不重复区域’后一个位置’的迭代器
进而可以删除
vec.erase(it,vec.end());
(8)转化
transform(vi.begin(),vi.end(),vi.begin(),[](inti){return i<0?-i:i;});
//目的位置迭代器和输入开始的迭代器相同。
(9)删除
remove_if(v1.begin(),v1.end(),[](inti){return i%2;});//删除奇数元素
//将偶数元素从v1拷贝到v2,v1不变
remove_copy_if(v1.begin(),v1.end(),back_inserter(v2),[](inti){return i%2});
(10)通用版本的sort需要随机访问迭代器,所以不能用于list和forward_list。
(11)取两个set的交集:set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(),s3)
十九:迭代器
1:插入迭代器 赋值时,调用容器操作来操作
back_inserter 使用push_back
front_inserter 使用 push_front会将插入元素的顺序颠倒过来。
inserter 使用insert :*t =val,等价于
it = c.insert(it,val);
++it;//递增it,使它指向原来的元素(所以,可以用inserter连续顺序插入)
*it,++it,it++不会做任何操作,都返回it
2:iostream迭代器
1)
istream读取的类型必须定义了>>运算符
istream_iterator<int> int_it(cin);
istream_iterator<int> int_eof;//默认初始化迭代器是:尾后迭代器
vector<int>vec(in_it,int_eof);//从迭代器范围构造vec
++in,in++ 使用元素类型定义的>>,从输入流中读取下一个值。
2)
ostream 需要类型具有<<输出运算符。
创建ostream_iterator时,可以提供(可选)第二个参数,每个输出元素后都会打印此字符串。
*out,++out,out++ 不做任何操作,都返回out
osteam_iterator<int>out_iter(cout,” “);
for(auto e:vec)
*out_iter++= e;//赋值语句实际上将元素写到cout
等同于out_iter = e;
可以调用copy打印vec中元素,比循环简单:
copy(vec.begin(),vec.end(),out_iter);
3:反向迭代器
++it指向前一个元素
rbegin/crbegin rend/crend 指向容器尾元素和首元素前一个位置的指针。
sort逆序排序
我们只能从既支持++又支持—的迭代器来定义反向迭代器,流不支持递减,所以不能从forward_list和流迭代器创建反向迭代器。
通过调用reverse_iterator的base成员把反向迭代器转换为普通迭代器
|rit.base |end()
--------------------------------
| |rbegin
rit
rit和rit.base指向不同元素,rbegin和end也是不同元素
迭代器类别:(根据支持的操作不同)
1)输入迭代器
2)输出迭代器
3)前向迭代器
4)双向迭代器
5)随机访问迭代器
链表特有的操作会改变容器,如merge,splice(元素合并)
二十:关联容器 map set
有序保存:
map/multimap
set/multiset
无序
unordered_map/unordered_multimap
unordered_set/unordered_multiset
1:
关联容器,除了与顺序容器相同的操作外
1)有独有的操作,如count(val) 返回容器中val的个数
2)类型别名
3)无序容器提供一些调整哈希性能的操作。
key_type
mapped_type(只适用于map)
value_type,对于set与key_type相同,set中的关键字也是const的
对于map,为pair<const key_type,mapped_type>,我们不能改变元素的关键字,所以关键字类型是const的。
2:map 插入
对于不包含重复关键字的容器,insert和emplace 返回一个pair.
pair的first是一个迭代器,指向具有给定关键字的元素。
pair的second是一个bool值,指出插入成功还是已经在容器中。
不能对multi版本的map执行下标运算。
set也不能执行下标运算。
因为下标运算符可能插入一个新的元素,所以,只能对非const的map使用下标运算符。
3:
lower_bound(val)返回第一个与查找元素匹配的元素,如果没有,则指向第一个关键字大于val的元素。
upper_bound(val)返回指向最后一个匹配val的之后的元素。
这两个可以作为一个迭代器范围。
equal_range(val)返回一个迭代器pair。
若关键字存在:第一个迭代器指向第一个与关键字匹配的元素,第二个迭代器指向最后一个匹配元素之后的位置。
若关键字不存在:两个迭代器指向关键字可以插入的位置。
for(auto pos =authors.equal_range(val);pos.first != pos.second;++pos.first)
cout<<pos.first->second<<endl;
4:无序容器 unordered_......
不使用比较运算符组织元素,而是使用一个哈希函数hash<key_type>生成哈希值和== 组织元素。
在存储上组织为一组桶。
性能依赖于哈希函数的质量和桶的数量和大小。
标准库为内置类型(包括指针)提供了hash模板。我们可以直接定义关键字是内置类型(包括指针)、string还是智能指针类型的无序容器。
为了使用自定义的类型定义无序容器,我们需要提供代替==运算符和哈希值计算函数。
size_t hasher(const Sales_data&sd)
{
returnhash<string>()(sd.isbn());
}
bool eqOp(const Sales_data&lhs,const Sales_data &rhs)
{
returnlhs.isbn() == rhs.isbn();
}
using SD_multiset = unordered_multiset<Sales_data,decltype(hasher)*,decltype(eqOp)*>;
SD_multiset bookstore(32,hasher,eqOp);
如果我们定义了==运算符,则可以只重载hash函数
unordered_set<Foo,decltype(FooHash)*>fllSet(10,FooHash);
无论有序还是无序容器,具有相同关键字的元素都是相邻存储的。
二十一:动态内存
智能指针(模板)
1:shared_ptr 允许多个指针指向同一对象
例:shared_ptr<int> p =make_shared<int>(32);//make_shared会构造对象
(1)
对于内置类型加不加()有区别,如果是自定义类型,则没什么区别,都是调用的默认构造函数。
int *p1 = new int;//默认初始化,*p1未定义。
int *p2 = new int();//值初始化为0,*p2值为0;
(2)括号包围的初始化器,可以用auto推断要分配的类型
auto p1 = new auto(obj);//括号中仅有单一初始化器是才可以;
(3)可以对数组元素进行值初始化,但是auto不能推断数组。
int *p1 = new int[10];//10个未初始化int
int *p1 = new int[10]();//10个值初始化为0的int
对于数组,不能在括号中给出初始化器,即,不能用auto做推断。
(4)delete执行两个动作:1:销毁对象 2:释放内存
const 对象不能被改变,但是可以被销毁(delete)
const int *p1 = new const int(10);
delete p1;
(5)接受指针参数的智能指针是explicit的,所以,不能将内置指针隐式转化为智能指针,必须使用直接初始化。
shared_ptr p1 = new int(10);//错误,函数返回也类似
shared_ptr p2(new int(10));//正确
(6)不要混用普通指针和智能指针,因为原则上,如果我们把内存的管理责任交个智能指针,那么就不应该用内置指针来访问了。因为可能已经释放了,用内置指针访问会出错。
(7)不能使用get初始化另一个智能指针,或为智能指针赋值。
使用get返回指针的代码,不能delete此指针。因为用get返回的指针初始化的智能指针与原来的是独立的,互相不知道对方的引用计数。
(8)使用异常处理程序能够在异常发生后另程序流程继续。确保资源被正确释放的方法是使用智能指针。
(9)可以在初始化shared_ptr时,指定删除器。
shared_ptr<T> p(q,fundelete);(当智能指针管理的资源不是new分配的内存时,很管用。比如连接资源);
2:unique_ptr 独占使用所指对象
(1) unique_ptr是独占,所以,不支持拷贝和赋值
unique_ptr<string> p1(new string(“nihao”));
unique_ptr<string> p2 = p1;//错误
unique_ptr<string> p2(p1);//错误
(2) release或reset将指针所有权从一个unique_ptr转移给另一个unique_ptr
(3) 不能拷贝unique_ptr有一个例外,我们可以拷贝将要销毁的unique_ptr,如函数要返回时。(编译器执行特殊的拷贝,移动赋值)
(4) 与重载关联容器(无序)类似,可以在尖括号类型后提供删除器。
unique_ptr<connection,decltype(endfun)*>p(&c,endfun);
3:weak_ptr 若引用,不增加引用计数,指向shared_ptr对象
不能使用weak_ptr直接访问对象,必须调用lock。lock返回一个指向共享对象的shared_ptr.
4:动态数组(并不是数组类型)
(1)new分配并初始化一个对象数组。
未得到数组类型指针,而是数组元素类型的指针。
(2)可以对数组中元素进行值初始化,方法是在大小后跟一对空括号。
delete []p,释放动态数组,元素按照逆序销毁。
(3)标准库提供了一个管理new分配数组的unique_ptr版本。必须在对象类型后加上空方括号。
unique_ptr<int[]>up(new int[10]);
up.release();//自动用delete[]销毁
因为up指向一个数组,而不是单个对象,所以不能用点或者箭头运算符,但是可以使用下标运算符。up[i] = i;
(4)shared_ptr不支持管理动态数组,若希望管理,必须提供自己的删除器。
shared_ptr<int> sp(newint[10],[](int p){delete []p;});
sp.reset();//他会调用delete[]
shared_ptr 没有定义下标运算符,并且不支持指针的算术运算,我们这样做:
*(sp.get()+i) = i;
5:若想让内存分配与对象构造分离,需要使用allocator替代new
new对于没有默认构造函数的类有限制。
智能指针,也只是一个指针,我们要注意分配空间(new,make_shared等)