C++11基础语法知识总结(三)

标准库

一、 顺序容器通用操作

a、begin(),rbegin(),cbegin()
cbegin()对应的是const_iterator,这是C++11为了支持auto引进的,这样就可以使用

auto iter = a.cbegin() 

iter会根据cbegin()确定调用const_iterator

b、insert()

insert(iter,1)  **//将元素插入到迭代器所指的位置之前,不是之后**
insert(sv.end(),10,1)**//插入范围元素,在end()前插入10个1**

C++11新标准在insert插入后,会返回指向新插入的第一个元素的迭代器

c、emplace_back()、emplace()、emplace_front() C++11新标准
这三个函数是直接在容器中构造函数,而不像push_back()是将对象拷贝到容器中,所以这三个函数接受的参数是容器元素构造函数的参数,通常比push_back()更快。

`c.emplace_back(978,25,15.99)`  

上述方法会调用Sales_data的构造函数,将参数传入构造函数中,然后再容器所在内存尾部直接构造对象。如果传入类的另一个对象,则调用拷贝构造函数。

c.push_back(Sales_data(978,25,15.99))  

先构造临时对象,再将对象拷贝到容器中。在MSVC的STL中push_back的内部实现也是间接调用了emplace_back(),然后传入要添加的对象,emplace_back()会在调用一次拷贝构造函数。

erase()

erase(iter)  **//删除迭代器指定位置元素,迭代器指向删除的元素之后的位置**
erase(iter1,iter2) **//删除一个范围的元素,迭代器指向删除的元素之后的位置**

shrink_to_fit() C++11新标准
将capacity()减少为与size()相同

二、lambda表达式

2.1、lambda引入原因

在C++泛型算法中,有些算法可以传递函数参数,这个函数参数称为谓词,非为一元谓词、二元谓词
但有些算法只接受一元谓词,如find_if(),在这种情况下,如果想要向find_if()的_Pred函数传递多个参数,就会出现问题:

bool lengthCompare(int& a)
{
	return a == 3;
}
auto ll = find_if(num.begin(), num.end(), lengthCompare); //lengthCompare()必须只接受一个参数

2.2、lambda表达式形式:

[capture list](parameter list) -> return type {function body}

参数列表与普通参数列表相同,如果不需要参数传递,则可以连括号一起省略。

借用lambda表达式就可以解决传参问题:
可以将额外的参数放在捕获列表中传入函数中,而参数列表依然保持一个参数

int m = 3;
auto s = find_if(num.begin(), num.end(), [m](int& a) {return a == m; });

!!!注意:
lambda表达式的返回类型必须使用尾置返回类型。
如果忽略返回类型,当函数体只是一个return语句,则返回类型可以从返回的表达式的类型推断而来,否则返回类型默认为void

2.3、lambda表达式捕获列表

	**lambda只有在捕获列表中捕获一个它所在的函数中的局部变量,才能在函数体中使用该变量**

当定义一个lambda时,编译器生成一个与lambda对应的(未命名的)类类型。当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的对象:从而向函数传递的就是此编译器生成的类类型的未命名对象。

**//传递的是lambda对应的类类型的对象**
auto s = find_if(num.begin(), num.end(), [=](int& a) {return a == m; });

其次,当使用auto定义一个用lambda初始化的变量时,定义了一个从lambda生成的类型的对象

auto f = [] {return 42; };**//f是lambda对应的类类型的对象**
2.3.1、值捕获与引用捕获

Lambda表达式采用值捕获时,是对局部变量进行拷贝,该拷贝是创建时拷贝,不是调用时拷贝。
Lambda表达式也可采用引用捕获,引用捕获必须保证被引用的对象在lambda执行时是存在的。

2.3.2、隐式捕获

在捕获列表中写一个&或=可以指示编译器推断捕获列表。
&表示编译器采用引用捕获方式,=则表示值捕获方式。这里的&、=可以表示隐式捕获多个变量,而不止是一个变量

三、动态内存

使用动态内存的原因:
1、程序不知道自己需要使用多少对象
典型应用:stl容器
2、程序不知道所需对象的准确类型
3、程序需要在多个对象之间共享数据

3.1、shared_ptr

shared_ptr类如何实现在多个shared_ptr对象指向同一个资源时实现引用计数的增减?

3.1.1、MSVC源码分析:

shared_ptr中有两个成员变量:

element_type * _Ptr{nullptr};
_Ref_count_base * _Rep{nullptr};
	element_type* _Ptr:指向所管理的资源的指针
	_Ref_count_base * _Rep:是一个抽象类的指针,它的派生类_Ref_count中管理者引用计数,派生类_Ref_count_resource中管理这引用计数和删除器。

所以shared_ptr实现多对象共享引用计数的方式实际上是通过间接地访问_Ref_count对象所在的内存中的计数值,而每个shared_ptr中只是都把持着指向这个对象的指针,当引用计数改变,每个shared_ptr通过指针_Rep去修改_Rep_count对象中的计数值。

3.1.2、智能指针初始化:
Solution* p = new Solution();
shared_ptr<Solution> ptr(p);//直接初始化
shared_ptr<Solution> ptr1(new Solution());//直接初始化
shared_ptr<Solution> ptr2 = make_shared<Solution>();//make_shared函数参数为Solution类的某个构造函数的参数
shared_ptr<Solution> ptr3(ptr);

不能采用如下形式初始化:

Shared_ptr<Solution> ptr=new Solution();//这是错误的
	因为接受指针参数的智能指针构造函数是explicit的,因此不能将一个内置指针类型隐式转换为一个智能指针。

!!!注意:不要混合使用普通指针和智能指针
当将一个使用new生成的普通指针用来初始化了一个智能指针后,如果智能指针离开作用域会将内存释放,这是再去使用普通指针就会出错。其次,亦可能将一个普通指针绑定到多个独立的智能指针对象上,如果其中一个智能指针析构了,则其他的智能指针都会悬空。

3.2、直接管理内存

3.2.1、使用new动态分配内存和初始化对象

1、默认初始化:

int* pi = new int;**//默认初始化,pi的值未定义**
string *ps = new string;**//默认初始化为空string**

在这种情况下,动态分配的对象被默认初始化。其中,内置类型或组合类型的对象的值是未定义的。类类型的对象会用默认构造函数进行初始化(没有则报错)

2、值初始化:

int* pi = new int();//值初始化为0
string *ps = new string();//值初始化为空string

值初始化就是在默认初始化的基础上加一个空括号,这对于类类型而言,都是调用默认构造函数。但对于内置类型就不一样,值初始化的内置类型对象有着良好定义的值,而默认初始化的对象的值是未定义的。

3、直接初始化:

int* pi = new int(1);//直接初始化为1
string *ps = new string("hello");//直接初始化为hello

在直接初始化时,如果括号中内容是单一的,则可采用auto关键字,这时返回的指针所指向的类型和括号中的变量类型一致,如果括号中的内容不单一,则不能采用auto关键字:

auto p1 = new auto(1);//p1为指向int类型的指针
auto p2 = new auto(string());//p2为指向string类型的指针

4、动态分配const对象

const int* pi = new const int(1);//初始化为1
const string *ps = new const string;//默认初始化为空
const string *ps = new const string("hello");//直接初始化为hello

一个动态分配的const对象必须初始化,对于定义了默认构造函数的类类型来说,其const对象可以隐式初始化,而其他类型的对象就必须显示初始化。

3.2.2、使用delete销毁对象和释放动态内存

传递给delete的指针必须指向动态分配的内存或者一个空指针。释放一块并非new分配的内存,或者将相同的指针值释放多次都会出错
在delete之后,指针值失效,但指针依然保持这动态内存的地址,但此时指针变成了空悬指针,如果后续还要这个指针变量,则应重置指针为nullptr。
在对于没有析构函数的C++类而言,智能指针在释放时不会释放资源,可以给智能指针传一个可调用函数对象,让它执行释放资源的工作。

3.2.3、智能指针陷阱

1、不要使用相同的内置指针初始化多个智能指针
2、不delete get()函数返回的指针
3、不使用get()初始化或reset另一个智能指针
4、如果使用get()返回的指针,记住在最后一个智能指针销毁后,指针就失效了
5、在使用智能指针管理不是new分配的内存时,一定要传递一个删除器(可调用对象)

3.3、unique_ptr

某个时刻,只能有一个unique_ptr指向一个给定的对象。初始化unique_ptr必须采用直接初始化方式
unique_ptr<int> p1;
unique_ptr<int> p2(new int(42));

unique_ptr不支持普通的拷贝或者赋值操作

unique_ptr的成员函数

release():返回当前unique_ptr保存的指针并将unique_ptr置为空
reset():接受一个指针参数,令unique_ptr指向新的指针。如果unique_ptr不为空,则它原来指向的对象被释放。

传递unique_ptr参数和返回unique_ptr

可以拷贝或赋值一个将要被销毁的unique_ptr,比如从函数中返回一个unique_ptr,只是因为会执行移动构造函数。

3.4、weak_ptr

1、weak_ptr不控制所指对象生存期,它指向一个由shared_ptr所管理的对象。当将一个weak_ptr绑定到一个shared_ptr,不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,即使weak_ptr指向对象,对象仍会被释放。
2、weak_ptr可视为shared_ptr所指对象的观察者,它保留将观察对象提升为shared_ptr,并进一步操作的权限,但在不需要操作时,则保持自己普通的观察员的身份。当要将weak_ptr提升为shared_ptr时,要使用成员函数lock()。
lock():检查weak_ptr所指对象是否仍存在。如果存在,lock()返回一个指向共享对象的shared_ptr,否则返回一个空的shared_ptr。
3、weak_ptr的用途
weak_ptr一个重要作用:解决shared_ptr循环引用导致内存泄漏的问题
在这里插入图片描述
当shared_ptr类型的spA、spB对象析构时,ref_count减一;由于Obj A 和Obj B中的shared_ptr智能指针还相互引用,导致ref_count不为零,sp A和sp B不会析构所指的对象,最终导致内存泄漏。
而当Obj A和Obj B中的智能指针是weak_ptr时,循环引用时不会增加shared_ptr智能指针的引用计数,当sp A和sp B析构时,导致ref_count为零,同时所指的对象也就被析构了。
在这里插入图片描述

3.5、动态数组

3.5.1、分配动态数组

使用new分配一个对象数组时,需要在类型名后跟一个方括号,在其中指明要分配的对象的数目,分配成功后返回指向第一个对象的指针。

	int *pa = new int[42];
	int *p = new int[get_size()];//方括号中不要求是常量
	typedef int arrT[42];
	int *p1 = new arrT;//调用new[]
3.5.2、初始化动态分配的对象数组

new动态数组时,不加空括号视为默认初始化,对于内置类型,则不会初始化。而加了空括号是值初始化。没有向括号中填入构造函数参数进行直接初始化的方式。

	int *pa = new int[42];  //42个未初始化的int
	int *p = new int[42]();//42个值初始化为0的int
	string *psa = new string[10];//10个默认初始化为空的string
	string *psa2 = new string[10]();//10个值初始化为空的string

C++11提供了采用花括号列表进行初始化的方式:

int *pa3 = new int[10]{ 0,1,2,3,4 };//将列表中的元素填入动态数组中,剩余的位置进行值初始化
string *ps = new string[10]{ "a","an","the" };

列表初始化器只会初始化动态数组开始部分的元素,如果初始化器数目小于元素个数,剩余元素将进行值初始化;如果初始化器个数大于元素个数,则new失败,不会分配内存。

3.5.3、释放动态数组

采用delete[]的形式释放动态数组:

	string *ps = new string[10]{ "a","an","the" };
	delete[] ps;

不加[]就会变成释放单一对象,这种行为是未定义的。上述语句会销毁ps指向的数组中的元素,并释放内存,销毁顺序是采用逆序销毁,从数组的尾部向头部销毁。

3.5.4、智能指针与动态数组

unique_ptr智能指针可以直接管理new分配的动态数组,其形式如下:

unique_ptr<int[]> up(new int[10]);

通过在对象类型后加一个[]就可以指明智能指针所指类型为动态数组。当智能指针调用release()函数时,会自动调用delete[]。由于unique_ptr指向的是动态数组,所以不能使用点和箭头成员运算符,但可以使用下标运算符访问数组中的元素
与unique_ptr不同,shared_ptr不能直接管理动态数组。要使用shared_ptr管理动态数组,必须提供自定义的删除器,并且器对象类型不需要加[],shared_ptr不会自动调用delete[]。

shared_ptr<int> sp(new int[10], [](int *p) {delete[] p; });

shared_ptr不支持下标运算符,要访问动态数组元素,只能用get()获取其内置指针,再采用下标运算符

3.6、allocator类

1、在有些场景中,我们可能需要将内存的分配和对象的构造分开,比如对象池。这时new就不适用了,通过allocator类则可以实现。

allocator<string> alloc;//可以分配string内存的allocator对象
auto const p = alloc.allocate(5);//分配5个未初始化的string类型的内存
alloc.construct(p, "string");//在原始内存中p指针所指位置构造对象,传入的参数和string类某个构造函数符合即可
alloc.destroy(p);  //析构指针p所指的string对象
alloc.deallocate(p, 5);//释放alloc申请的整个原始内存

2、拷贝和填充未初始化的内存

allocator<string> alloc;//可以分配string内存的allocator对象
auto  p = alloc.allocate(10);//分配5个未初始化的string类型的内存
vector<string> strs{ "1","1","3" };
string s = "4";
auto q = uninitialized_copy(strs.begin(), strs.end(), ++p);
auto q1 = uninitialized_copy_n(strs.begin(), 1, q);
uninitialized_fill(p, ++p, s);
auto q2 = uninitialized_fill_n(p, 2, s);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值