C++primer知识点(二)

来源:互联网 发布:复旦博士后待遇 知乎 编辑:程序博客网 时间:2024/05/20 08:44

十六: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等)

 

 

 

 

 

1 0