头文件:
流读取写入: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的状态由多个位来表示
检测:
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。
类型别名允许在不关注具体类型的情况下使用它。list
vector ar = {"A", "B"};
vector wo(ar); //错误:拷贝必须类型相同
forward_list wo(ar.begin(), ar.end()); //正确:传递范围进行拷贝。wo和ar类型可以不同,元素类型也可不同,只需拷贝时进行类型转化
// array中大小是类型的一部分
array a; //2个默认初始化的string
array a = {"A", "B"};
a = {"C"}; //错误:不能将一个花括号列表赋予array
array a = {"A"}; //a[0]为A,其余空字符串
list names;
vector old;
names = old; //错误:必须类型相同
names.assign(old.begin(), old.end()); //正确:类似于范围初始化
swap:
1、除array外,swap不会对任何元素使用修改,插入,删除,因此保证在常数时间内完成。而array则会真正的交换元素。
2、不会交换元素意味着元素的迭代器、引用、指针仍然指向之前的元素,不过所属容器改变了(除了string以外,string会使其失效)。而array在swap后迭代器、引用、指针绑定元素保持不变,但是元素值已经改变。
3、统一使用非成员swap,在泛型编程中很重要。
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的
当使用一个对象来初始化或者将一个对象插入容器,其实质上是放入了对象值得一个拷贝,而不是元素本身。
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"
front、back、下标、at返回的都是引用。如果容器为const,返回的也是const
引用。
c.front() = 24;
下标的index不会检查,而at超出范围会出现out of range错误。
vec[0];
vec.at(0);
slist.clear();
slist.erase(slist.begin(), slist.end());
forward_list删除当前元素时需要知道前驱以能够将前驱链接起来。为此提供的操作为:
// 删除奇数
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;
}
}
向容器添加、删除元素时都有可能导致容器的指针、引用、迭代器失效。
添加、删除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); //删除直接移动到下一元素
}
}
resize只改变容器中元素的数量,而不会改变容器的容量;
reserve只有需要容量超过当前容量的时候,才会改变容器的容量;
size指的是容器中已保存元素的数量;
capacity指的是不分配新内存空间的情况下,容器能够容纳的元素数量;
只要操作没有超过容器的容量,容器就不会出现分配内存空间。
只有在执行insert操作时size和capacity相等、或者resize、reserve时给定大小超过当前capacity时,vector才能重新分配内存。
初始化:
裁剪:
其他:
1、新字符可以来自于另一个string、字符指针、花括号包围的字符列表、一个字符+一个计数值。当字符来自于一个string或字符指针,可以传入额外参数控制拷贝所有还是部分。
搜索:
1、搜索返回string::size_type类型(unsigned),搜索失败返回static的string::npos,npos类型为const size_type(unsigned),并且初始化值为-1。因此使用int或者其他带符号类型保存返回值是不好的。
比较:
数值转换:
// 第一个非空白符需为+-或数字,浮点数可以包含.Ee,整型根据基数可以包含字母
d = stod(s2.substr(s2.find_first_of("+-.0123456789")));
容器适配器根据已有容器使其表现出另一种类型。
默认stack和queue基于deque实现;priority_queue基于vector实现
stack> stk;
stack> stk(vec);
关联容器是以关键字来保存和访问的。
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);
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
1、insert返回值依赖于容器类型和参数。对于不包含重叠关键字的容器,insert和emplace返回一个pair,first为一个迭代器,指向给定关键字元素,second输出是否插入成功,如果容器已存在该关键字则为false。对于允许重叠关键字的容器,直接返回指向新元素的迭代器。
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、无序容器在内存组织上为一组桶,使用哈希函数将一个元素映射到对应的桶。如果允许重复关键字,那么所有相同元素将会保存在同一个桶。因此无序容器性能依赖于哈希函数的质量和桶的数量、大小。
如果使用自定义类作为关键字,那么需要实现hash和等于算法:
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);
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使得指向原来的元素
流迭代器(stream):用于遍历所关联的IO流
1、流迭代器并不是立即读取流数据,而是在使用迭代器时才真正读取。这在创建流迭代器没有使用就销毁了;或者两个不同对象同步读取同一个流时,何时读取就重要了
2、流迭代器不支持递减,因为在流上不能反向移动
// 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;
// ostream_iterator使用<<输出流,因此要输出的类型必须定义了输出运算符,不允许空的或者尾后的ostream_iterator
ostream_iterator out_it(cout, " ");
for(auto e : vec){
*out_it++ = e;
}
cout<
sort(v.begin(), v.end()); //正常序
sort(v.rbegin(), v.rend()); //逆序
// 注意crbegin和cend、rcomma和rcomma.base()生成的是相邻但是不相同的位置
cout << string(s.crbegin(), rcomma) <
移动迭代器(move):非拷贝而是移动元素