C++11学习(C++primer) 第二部分

第八章

IO类

头文件类型
iostreamistream, wistream
ostream, wostream
iostream,wiostream
fstreamifstream,w… 从文件读取数据
ofstream,w…
fstream
sstreamistringstream,w… 从string读取
ostringstream,w…
stringstream,w…

w开头的是为了支持宽字符语言。

IO对象无拷贝无赋值

进行IO操作的函数通常以引用的方式传递和返回流,并且传递和返回值不能是const的。

条件状态

第九章

顺序容器概述

deque双端队列
list双向链表
forward_list单向链表
array固定大小数组
  • 如果容器元素为一个类且该类是一个没有默认构造函数的类型,那么我们需要提供元素的初始化器。
顺序容器中的差别
  • array不能执行插入\删除操作
  • forward_list没有size成员函数

当不需要写访问时,应该使用cbegin和cend。

容器定义和初始化

标准库array具有固定大小

array不支持普通容器的构造函数,它的构造函数需要确定容器的大小,要么隐式或显式。

array<int,10>;

同时,使用array的类型,也需要指定大小

array<int,10>::size_type i;

array的初始化和普通数组一样,如果初始化元素个数不足则后续补默认初始化,如果超出容器大小则出错。

如果元素类型是一个类,那么其必须要有一个默认构造函数,以便值初始化能够进行。

值得注意的是:不能对内置数组类型进行拷贝或对象赋值操作,但是array可以,但元素类型和容器大小必须一致。

array<int,10>a1{1,2,3};
array<int,10>a2=a1;

同时,array不支持花括号的赋值操作(注意,不是初始化,而是赋值操作)

a2 = {1,2,3};//错误,array不支持花括号赋值操作。

赋值和swap

assign函数,是一个替换元素的操作。其不适用于关联容器和array。

seq.assign(b,e)将容器seq的元素替换成迭代器b和e之间的元素
seq.assign(il)将容器seq的元素替换成 初始化列表 il 中的元素
seq.assign(n,t)将seq中的元素替换为n个t的元素
assign允许我们从一个不同但相容的类型赋值
list<string> names;
vector<char *>old;
names=old;//错误,类型不匹配
names.assign(old.begin(),old.end());//正确
swap

与其它容器不同,对一个string调用swap会导致迭代器,引用,指针失效。

顺序容器操作

类似emplace_back的emplace 前缀的操作是 构造一个新元素插入的操作,与push_back不同,后者是拷贝再插入的操作,所以前者可能还需要提供构造函数的参数。

  • vector和string不支持在头部push_front,pop_front的操作
  • forward_list有自己的操作,不要和vector等的混用(后面会提)
insert操作
c.insert(p,t)在p指向元素之前创建一个值为t的元素
c.insert(p,n,t)不需要多解释吧
c.insert(p,b,e)在p指向元素之前插入迭代器b和e之间的元素
c.insert(p,il)在p指向元素之前插入花括号列表中il中的元素

注意:insert的插入都是插入到迭代器指定的元素的前一个位置。返回值为指向第一个新加入元素的迭代器。

如果传递给insert一对迭代器,那么迭代器不能指向与目标位置相同的容器

使用emplace

emplace_front,emplace,emplace_back分别对应push_front,insert,push_back操作。

只是emplace是将参数传递给构造函数,然后在容器管理的空间中直接构造元素。而push等是将元素类型的对象传递給它们。

c.emplace_back("2474989489",25,15.99);//调用了构造函数创建了一个对象

c.push_back("2474989489",25,15.99);//错误,没有接受三个参数的push_back版本
访问元素

包括array在内每个顺序容器都要一个front成员函数,除了forward_list没有back其它都有。

访问成员函数返回的是引用

front,back,下标和at返回的都是引用,

如果我们希望用auto来保存并改变元素的值,必须将变量定义为引用类型。

安全的下标操作

如果我们希望确保下标是合法的,可以使用at函数,如果下标越界,其会抛出异常。

删除元素

forward_list有特殊版本的erase,forward_list不支持pop_back

此处只说明erase

c.erase§删除p指定的元素,返回一个被删元素之后的元素的迭代器
c.erase(b,e)删除迭代器之间的元素,返回指向最后一个被删元素的后一个元素的迭代器
特殊的forward_list操作

forward_list的插入 insert的操作和删除的操作,都是对当前迭代器的后一个元素进行的。

lst.before_begin()返回指向链表首元素之前不存在的元素的迭代器
lst.isnert_after(p,t)在迭代器p之后进行的操作,返回的都是最后插入元素的下一个位置
lst.isnert_after(p,n,t)
lst.isnert_after(p,b,e)
lst.isnert_after(p,il)
emplace_after§
lst.erase_after§
lst.erase_after(b,e)删除b(不包括b)之后,到e之前的元素,返回最后一个删除元素的后一个位置
不要保存end返回的迭代器

当我们添加/删除元素vector或string的元素后,原来end返回的迭代器会失效,需要时重新获取是一个好的操作。

第十章

lambda表达式

一个lambda表达式表示一个可以调用的代码单元,我们可以将其理解成一个未命名的内联函数。

但与函数不同,一个lambda可以定义在函数内部。

lambda的一般形式:

[ 捕获列表](参数列表)->返回类型 {函数体};

其中:

  • 捕获列表是一个lambda所在函数中定义的局部变量的列表

与普通函数不同:lambda必须使用尾置返回来指定返回类型

auto f = [](int i) -> int {return 42;};

lambda的调用方式和普通函数一样,都是用调用运算符。

f();

在lambda中忽略括号和参数列表等价于指定一个空参数列表。

如果lambda函数体包含了其它任何一条 return语句以外的内容,且 未指定 返回类型,则返回void。这种情况下,理所当然的,如果return了值,那么就会出错。

lambda 不能有默认参数。

空捕获列表表示此lambda不使用它所在函数中的任何局部变量。

使用捕获列表

在[] 中写入局部变量名字,lambda将会捕获它。一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,其才能在函数体中使用该变量。

注意:捕获列表只用于局部非 static变量,lambda可以直接使用局部static变量和它所在函数之外声明的名字。

lambda的值捕获和引用捕获

lambda对变量的捕获分为值捕获和引用捕获。

值捕获

相当于只是拷贝了变量的值,但是,与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。

int i = 42;
auto f = [i]{return i;};
i=0;
cout<<f()<<endl;//将会输出42
可变lambda

默认情况下,对于一个值拷贝的变量,lambda不会改变其值,如果我们希望改变被捕获变量的值,就必须在参数列表前面加上 mutable

int i =42;
auto f = [i] matable(){return i++;};
cout<<f()<<endl;//输出43,注意这里是指改变lambda拷贝的变量,这并不是值捕获,故

cout<<i<<endl;//输出42;
引用捕获

在捕获的变量前面加上&表示引用捕获,引用捕获就相当于函数中的引用,在lambda中保存的是引用。

int i = 42;
auto f = [&i]{return i;};
i=0;
cout<<f()<<endl;//将会输出0;

引用捕获和返回引用有着相同的问题和限制,我们必须保证被引用的对象在lambda执行时是存在的。

隐式捕获

除了显式列出我们希望使用的来自所在函数的变量外,还可以让编译器自己推断。为了指示编译器推断,我们需要在捕获列表中写一个 =或&。=告诉编译器采用值捕获,&告诉编译器使用引用捕获。

wc=find_if(w.begin(),w.end(), [=](const string &s){return s.size()>sz;});

我们还可以混用隐式捕获和显式捕获,但需注意,捕获列表中的第一个元素必须是一个 = 或 &。

for_each(w.begin(),w.end(),[=,&os](const string &s){os<<s<<c;});

显式捕获的类型必须和隐式捕获不同。

参数绑定

现在假设在一个函数中,有一个局部变量sz。然后,我们需要用到一个算法 find_if,接受的是一个一元谓词,find_if需要的谓词中的判断,需要用到一个参数s,和sz。我们自然可以用lambda表达式的捕获,来获取sz,故只需要传入一个参数s。但是,如果把lambda表达式替换成函数,那么就避免不了需要传入两个参数,这显然是个二元谓词,与find_if需要的一元谓词不一致。

还可以这样理解:算法find_if给lambda传了s,sz是lambda自己捕获的。如果换成函数,find_if给函数传了s(find_if本身只有资源s),函数自己没有能力去获取sz。

我们可以使用bind来解决这个问题。

bind函数

调用bind函数的一般形式为:

auto newCallable = bind(callable,arg_list);

其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表。对应给定的callable的参数。即我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。

arg_list中的参数可能包含形如 _n的名字,其中n是一个整数,这些参数是占位符,它们占据了传递给newCallable的参数的位置,并按照在arg_list中的位置传递给callable。

_1为newCallable的第一个参数,_2为第二个。

解决前面提到的问题

bool check_size(const string &s,string::size_type size);//定义暂且忽略
...
int sz=6;
...
auto wc = alg(w.begin(),w.end(),bind(check_size,_1,sz));

这样就可以解决问题了。

使用placeholders名字

名字_n都定义在一个名为 placeholders的命名空间中,而这个命名空间本身定义在std命名空间中他,为了分别使用

_n,我们可以使用using

using std::placeholders::_1;

或者各部分方便的用法

using namespace std;
using namespace placeholders;

或
using namespace std::placeholders;
bind的参数

我们可以用bind修正参数的值

auto g = bind(f,a,b,_2,_1);

g(s1,s2);//实际上调用了下面这个
f(f,a,b,s2,s1);
用bind重排顺序
auto g = bind(f,_3,_2,_1);
绑定引用参数

默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用的参数中。但是,与lambda类似,有时候我们希望以引用的方式传递,或者要绑定的参数的类型无法拷贝(比如ostream)

这个时候,我们不能直接使用bind来代替,ostream的捕获。

如果我们希望传递给bind一个对象而又不拷贝它,就必须使用标准库ref函数。

ostream& print(ostream &os,const string &s,char c)
{
    return os<<s<<c;
}

for_each(word.begin(),word.end(),bind(print,ref(os),_1,' '));

标准库中还有一个cref函数,生成一个保存 const 引用的类。

第十一章

第十二章 动态内存

头文件

动态内存与智能指针

shared_ptr和unique_ptr都支持的操作

shared_ptrsp空智能指针,指向类型T的对象
unique_ptrsp
p.get()返回p中保存的指针
swap(p,q)交换指针
p.swap(q)交换指针
shared_ptr类
shared_ptr特有的操作
make_shared(args)返回一个shared_ptr
shared_ptrp(q)q是shared_ptr,p将拷贝其,并且递增q中的计数
p=qp和q都是shared_ptr
p.unique若p.use_count()为1,返回真,否则返回假
p.use_count返回智能指针p所指向对象的智能指针数量;可能很慢,主要用于调试
make_shared函数

最安全的分配和使用动态内存的方法。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。

make_shared<T>()

shared_ptr<int> sp = make_shared<int>(42);

类似顺序容器的emplace成员,make_shared函数用其参数来构造给定类型的对象。如果我们不传递任何参数,对象就会进行值初始化。

shared_ptr<string> sp = make_shared<string>(10,'c');

我们通常用auto定义一个对象来保存make_shared的结果:

auto sp = make_shared<string>(10,'c');

对于一块内存,shared_ptr保证只要有任何shared_ptr对象引用它,它就不会被释放掉。

如果你将shared_ptr存放于一个容器中,而后不需要全部容器,而只需要其中一部分,要记得erase删除不需要的那些元素。

直接管理内存

默认情况下,动态分配的对象是默认初始化的,这意味着内置类型或组合类型的对象的值是未定义的。

int *pi = new int;
string *ps = new string;

也可以对动态内存进行值初始化,只需要在类型名之后跟一对空括号即可

int *pi = new int();
string *ps = new string();

如果我们提供了一个括号包围的初始化器,就可以使用auto从此初始化器来推断类型。但是,以外编译器要用初始化器类型来推断分配的类型,只有当括号中仅有单一初始化器时才可以使用auto

auto it = new auto(obj);//正确

auto it = new auto{a,b,c};//错误,括号中只能有单个初始化器
动态分配的cosnet对象
const int * p = new const int();
内存耗尽
释放动态内存

我们传递给delete的指针必须指向动态分配的内存,或者是一个空指针。

释放一块非new分配的内存,或者将相同指针值多次释放,行为都是未定义的。

shared_ptr和动态内存结合使用

接受指针参数的智能指针构造函数是explicit的,(阻止隐式转换),因此我们不能将一个内置指针隐式转换为智能指针,必须适用直接初始化

直接初始化:不用 = 的初始化都是直接初始化。

shared_ptr<int>p1 = new int(42);//错误,这不是直接初始化
shared_ptr<int>p1(new int (42));//正确,直接初始化

出于同样的原因,一个返回shared_ptr的函数不能在其返回语句中隐式转换一个普通指针。

我们必须将shared_ptr显式绑定到一个想要返回的指针上:

shared_ptr<int> clone(int p)
{
    return shared_ptr<int>(new int(p));
}
定义和改变shared_ptr的方法
shared_ptrp (q)p管理内置指针q或智能指针q所指对象
shared_ptrp(q,d)p接管了内置指针q指向对象的所有权。并且使用d来代替p的delete
shared_ptrp(p2,d)p是shared_ptr p2的拷贝,并且用d来代替delete
p.reset()实质就是改变p的指向。如果p是唯一一个指向对象的shared_ptr,那么
p.reset(q)自然就释放对象了。d和前面一样,使用d来替代delete
p.reset(q,d)
不要混合使用普通指针和智能指针

看下面这种情况:

void func(shared_ptr<int>p);

int *x(new int());
func(shared_ptr<int>(x));
*x=10;//错误,在上一步中,函数退出后智能指针的计数将变成0,内存释放。
也不要用get初始化另一个智能指针或为智能指针赋值

智能指针定义了一个get的函数,用来返一个内置指针。

看这种情况

shared_ptr<int>p(new int(42));

int *q=p.get();

{
    shared_ptr<int>(q);//注意,虽然此处的智能指针和p指向的是同一块内存,但是各种智能指针的计数是1
}//所以当这里退出后,随着该代码块中的智能指针的销毁,内存将会释放。

int foo=*p;//错误

get用来将指针的访问权限传递给代码,只有确定代码不会delete指针的情况下,才能使用get。

智能指针陷阱
  • 不使用相同的内置指针初始化(或reset)多个智能指针;
  • 不delete get()返回的指针
  • 不使用get()返回的指针初始化或reset另一个智能指针
  • 如果使用了get返回的指针,当最后一个对应的智能指针销毁后,你的指针就无效了。
  • 如果你使用的智能指针管理的资源不是new分配的资源,记住传递给它一个删除器

区分下面例子的区别

//p和q的计数器都是2
shared_ptr<int>p(new int(42));
shared_ptr<int>p(q);

//p和q的计数器都是1
int *x(new int(42));
shared_ptr<int>p(x);
shared_ptr<int>q(x);
unique_ptr

某一时刻只能有一个unique_ptr指向一个对象。当其销毁时,其指向的对象也销毁。

与shared_ptr类似,unique_ptr只能使用直接初始化形式

unique_ptr<T> p1;//可以指向一个int的unique_ptr
unique_ptr<int>p2(new int(42));

由于只允许一个unique_ptr指向它的对象,所以unique_ptr不支持普通的拷贝或赋值操作

unique_ptr<string> p1(new string("hello"));
unique_ptr<string> p2(p1);//错误,不支持拷贝操作
unique_ptr<string> p3;
p3=p1;//错误,不支持赋值操作。

虽然我们不能拷贝或赋值unique_ptr,但是可以通过调用release或reset将指针所有权从一个(非const)unique_ptr转移给另一个unique_ptr。

unique_ptr<T,D>u空unique_ptr,u将会使用D的可调用对象来释放它的指针
unique_ptr<T,D>u
unique_ptr<T,D>u(d)空unique_ptr,用类型为D的对象 d 来代替delete
u=nullptr释放u指向的对象,将u置为空
u.release()u放弃对指针的控制权,返回指针,并将u置空。
u.reset()
u.reset(q)先释放u指向的对象,如果提供了内置指针q,令u指向这个对象;否则把u置空
u.reset(nullptr)

关于release的理解:其返回一个指针,把u置空,注意,把u置空和释放u所指对象,是两回事。这里把u置空,u所指对象还未被释放。

传递unique_ptr参数和返回unique_ptr

不能拷贝unique_ptr的规则有个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr

unique_ptr<int>clone(int p)
{
    return unique_ptr<int>(new int(p));
}
//还可以返回一个局部对象的拷贝
unique_ptr<int>clone(int p)
{
    unique_ptr<int>clone(new int(p));
    return clone;
}
向unique_ptr传递删除器

例子写法

unique_ptr<connextion.deletype(end_connextion)*>p(&c,end_connection);
weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr上不会影响改变shared_ptr的引用计数。一旦最后一个shared_ptr被销毁,其指向的对象就会销毁。

weak_ptrw空weak_ptr,可以指向类型为T的对象
weak_ptr(sp)与shared_ptr指向相同的对象
w=pp可以是一个shared_ptr或weak_ptr
w.reset()将w置为空
w.use_count()与w共享对象的shared_ptr的个数
w.expired()若w.use_count为空,则返回true,否则返回false
w.lock()若expired为true,即没有shared_ptr,则返回一个空shared_ptr,否者返回一个指向w的对象的shared_ptr

使用的例子

auto p=make_shared<int>(42);
weak_ptr<int>w(p);

if(shared_ptr<int>np = w.lock())
{
}

动态数组

new和数组

为了让new分配一个对象数组,在类型名后面加上方括号,并且指明分配的对象的数目。

int *p = new int[10];

typedef int arrT[10];
int *p = new arr;
分配一个数组会得到一个元素类型的指针

如上述代码一样,用指针来接收。

虽然我们称为动态数组,但分配的内存不是一个数组类型,不能使用begin,end和for来进行处理。

动态数组的初始化

和普通数组的初始化差不多。

int *p = new int[10]();
string *sp= new string[10]();

虽然我们用空括号对数组中元素进行值初始化,但是不能在括号中提供初始化器,这也意味着不能使用auto分配数组。

动态分配一个空数组是合法的
释放动态数组

在指针前加上一个空括号对:

delete []p;

typedef int arrT[10];
int *p= new arr;
delete []p;//方括号是必须的。
智能指针和动态数组(重点

标准库提供了一个可以管理new分配的数组的unique_ptr版本。为了用unique_ptr管理动态数组,我们必须在对象类型后面跟一个方括号。

unique_ptr<int[]>u(new int[10])

当unique_ptr销毁它管理的指针时,会自动使用delete []。注意:shared_ptr是没有这个特征的。

当一个unique_ptr指向一个数组时,我们不能使用点和箭头成员运算符。毕竟其指向的是数组不是对象。另一方面,可以使用下标运算符来访问数组中的元素。

与unique_ptr不同,shared_ptr不支持直接管理动态数组,如果希望用shared_ptr管理,必须提供自己的删除器:

shared_ptr<int>sp(new int[10],[](int *p){delete []p;});
sp.reset();//使用我们提供的lambda释放的数组,它使用delete[];

如果未定义删除器,这段代码将是未定义的。同时,shared_ptr未定义下标运算符。

allocator类
allocatora定义了一个名为a的allocator对象,它可以为类型为T的对象分配内存
a.allocate(n)分配一段原始的,未构造的内存,保存n个类型为T的对象,返回指向首地址的指针
a.deallocate(p,n)释放从T*指针p中地址开始的内存,这段内存保存了n个类型为T的对象,p必须
是一个先前由allocate返回的指针,且n必须是p创建时要求的大小。
在调用deallocate之前,必须对每个在这块内存中创建的对象调用destroy
a.construct(p,args)p必须是一个类型为T*的指针,指向一块原始内存。args被传递给类型T的构造函数
用来在o指向的内存中构造一个对象
a.destroy§p为T*类型的指针,对p指向的对象执行析构函数。
allocator分配为构造的内存

allocator分配的内存是未构造的。还未构造对象的情况下就使用原始内存是错误的。

我们传递给deallocate的指针不能为空,必须指向由allocate分配的内存。而且,传递给deallocate的大小参数必须与调用allocate分配内存时提供的大小参数具有一样的值。

拷贝和填充未初始化内存的算法
uninitialized_copy(b,e,b2)从迭代器b和e之间的元素拷贝到迭代器b2未指定的原始内存中
uninitialized_copy_n(b,n,b2)从b开始的n个元素拷贝到b2开始的内存中
uninitialized_fill(b,e,t)在迭代器b和e之间的原始内存中创建对象,对象均为t的拷贝
uninitialized_fill_n(b,n,t)在迭代器b开始的原始内存中构建n个对象
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值