标准库
一、 顺序容器通用操作
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);