一、C++标准模板库STL:基本的6个组成部件为容器、算法、迭代器、仿函数、适配器和空间分配器。
1、C++智能指针: C++11中引入了智能指针的概念,方便管理堆内存。从三个层次理解智能指针
a、从较浅的层面看,智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。
b、智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决。
c、智能指针还有一个作用是把值语义转换成引用语义。C++和Java有一处最大的区别在于语义不同,在Java里面下列代码:
Animal a = new Animal();
Animal b = a;
这里其实只生成了一个对象,a和b仅仅是把持对象的引用而已。但在C++中不是这样,
Animal a;
Animal b = a;
这里却是就是生成了两个对象。
2、智能指针的使用:智能指针在C++11版本之后提供,包含在头文件
3、auto_ptr:是C++98提供的解决方案,C+11已将将其摒弃,并提供了另外两种解决方案。然而,虽然auto_ptr被摒弃,但它已使用了好多年:同时,如果您的编译器不支持其他两种解决力案,auto_ptr将是唯一的选择。
4、shared_ptr的使用:shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
初始化:智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化。也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针,一个是类,一个是指针。例如std::shared_ptr<int> p4 = new int(1);的写法是错误的
拷贝和赋值:拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动释放内存。后来指向的对象引用计数加1,指向后来的对象。
get函数获取原始指针
注意不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存
注意避免循环引用:shared_ptr的一个最大的陷阱是循环引用,循环,循环引用会导致堆内存无法正确释放,导致内存泄漏。循环引用在weak_ptr中介绍。
int main()
{
int a = 10;
std::shared_ptr<int> ptra = std::make_shared<int>(a);
std::cout << ptra.use_count() << std::endl;//输出1
std::shared_ptr<int> ptra2(ptra); //copy,计数加1
std::cout << ptra.use_count() << ptra2.use_count()<< std::endl;
//输出22
int b = 20;
int *pb = &a;
//std::shared_ptr<int> ptrb = pb; //error
std::shared_ptr<int> ptrb = std::make_shared<int>(b);
ptra2 = ptrb; //assign
pb = ptrb.get(); //获取原始指针
std::cout << ptra.use_count() << std::endl;
std::cout << ptrb.use_count() << std::endl;
}
5、unique_ptr的使用:同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。
unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。
unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。
int main(){
{
std::unique_ptr<int> uptr(new int(10)); //绑定动态对象
//std::unique_ptr<int> uptr2 = uptr; //不能賦值
//std::unique_ptr<int> uptr2(uptr); //不能拷貝
std::unique_ptr<int> uptr2 = std::move(uptr); //轉換所有權
uptr2.release(); //释放所有权
}
//超過uptr的作用域,內存釋放
}
6、weak_ptr的使用:是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为。没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。
使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。
weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象,从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。
int main() {
{
std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
std::cout << sh_ptr.use_count() << std::endl;
std::weak_ptr<int> wp(sh_ptr);
std::cout << wp.use_count() << std::endl;
if(!wp.expired()){
std::shared_ptr<int> sh_ptr2 = wp.lock();
//get another shared_ptr
*sh_ptr = 100;
std::cout << wp.use_count() << std::endl;
}
}
//delete memory
}
7、循环引用
8、常用的类或容器:string、vector、set、list、map
a、string
1)、C语言使用字符串
char *ptr = “hello”;//创建指针指向字符串常量,这段字符串我们是不能修改的,编译不会报错,但会有警告,应该如下
char const *ptr = “hello”;
char s2[20] = "Hello”;//可以修改的字符串或如下
char s3[] = "Hello";
char* s4 = (char*)malloc(20);//动态分配内存
2)、C++ 标准库中的string表示可变长的字符串,它在头文件string里面。
string s1;//初始化字符串,空字符串
string s2 = s1; //拷贝初始化,深拷贝字符串,s2是s1的副本
string s3 = "I am Yasuo"; //直接初始化,s3存了字符串
string s4(10, 'a'); //s4存的字符串是aaaaaaaaaa
string s5(s4); //拷贝初始化,深拷贝字符串
string s6("I am Ali"); //直接初始化
string s7 = string(6, 'c'); //拷贝初始化,cccccc
b、vector:好比是C语言中的数组,包含头文件vector
1)、如果vector的元素类型是int,默认初始化为0
如果vector元素类型为string,则默认初始化为空字符串。
2)、初始化
vector<int> v1;//v1是空vector,潜在元素类型为int,执行默认初始化(为0)
vector<string> v2;//v2,潜在元素类型为string
vector<int> v5 = { 1,2,3,4,5 }; //列表初始化,注意使用的是花括号
vector<string> v6 = { "hi","my","name","is","lee" };
vector<int> v7(5, -1); //初始化为-1,-1,-1,-1,-1。第一个参数是数目,第二个参数是要初始化的值
vector<string> v8(3, "hi");
vector<int> v9(10); //默认初始化为0
vector<int> v10(4); //默认初始化为空字符串
3)、push_back:数组尾部加入元素。push_back的作用有两个:告诉编译器为新元素开辟空间、将新元素存入新空间里。pop_back删除末尾元素
vector<int> v1(2);//定义了vector,有两个元素,初始化都为0
v1[0] = 10;
v1[1] = 20;
v1.push_back(30);//那么会在尾部添加一个元素且值为30, pos为2(即v1[2])
vector<int> vec;
vec[0] = 1; //错误!编译器不会报错,就像是数组越界。
4)、 vector<int> v1 = { 1,2,3,4,5 }; //列表初始化,注意使用的是花括号,c++11标准,
//编译时加-std=c++11
v1.push_back(6);//加入一个元素初始化值为6,并把它放在最后
v1.pop_back(); //删除最后一个元素
v1.insert(v1.begin()+1,9); //在第二个位置插入新元素
c、set:跟vector差不多,跟vector的唯一区别就是,set里面的元素是有序的且唯一的。只要你往set里添加元素,它就会自动排序,而且,如果你添加的元素set里面本来就存在,那么这次添加操作就不执行。要想用set先加个头文件set。
d、list:是一个双向链表,而单链表对应的容器则是foward_list。双向链表的优点是插入和删除元素都比较快捷,缺点是不能随机访问元素。
e、map:运用了哈希表地址映射的思想,也就是key-value的思想,来实现的。
9、迭代器:是一种检查容器内元素并遍历元素的数据类型。C++更趋向于使用迭代器而不是下标操作。迭代器iterator就是一种智能指针。它对原始指针进行了封装,而且提供一些等价于原始指针的操作,做到既方便又安全。
a、每种容器都定义了自己的迭代器类型,入vector
vector<int>::iterator iter;//定义一个名为iter的变量
b、每种容器都定义了一对名为begin和end的函数,用于返回迭代器。
vector<int> ivec;
//将迭代器iter1初始化为指向ivec容器的第一个元素
vector<int>::iterator iter1=ivec.bengin();
//将迭代器iter2初始化为指向ivec容器的最后一个元素的下一个位置
vector<int>::iterator iter2=ivec.end();
注意end并不指向容器的任何元素,而是指向容器的最后元素的下一位置,称为超出末端迭代器。
如果vector为空,则begin返回的迭代器和end返回的迭代器相同。一旦向上面这样定义和初始化,
就相当于把该迭代器和容器进行了某种关联,就像把一个指针初始化为指向某一空间地址一样。
c、迭代器的常用运算操作:
*iter //对iter进行解引用,返回迭代器iter指向的元素的引用
iter->men //对iter进行解引用,获取指定元素中名为men的成员。等效于(*iter).men
++iter //给iter加1,使其指向容器的下一个元素
iter++
--iter //给iter减1,使其指向容器的前一个元素
iter--
iter1==iter2 //比较两个迭代器是否相等,当它们指向同一个容器的同一个元素或者都指向同一个容器的超出末端的下一个位置时,它们相等
iter1!=iter2
d、在C++定义的容器类型中,只有vector和queue容器提供迭代器算数运算和除!=和==之外的关系运算
e、迭代器const_iterator:
1)、每种容器还定义了一种名为const_iterator的类型。
2)、该类型的迭代器只能读取容器中的元素,不能用于改变其值。普通的迭代器可以对容器中的元素进行解引用并修改,而const_iterator类型的迭代器只能用于读不能进行重写。
//合法,读取容器中元素值
for(vector<int>::const_iterator iter=ivec.begin();iter!=ivec.end();++iter)
cout << *iter << endl;
//不合法,不能进行写操作
for(vector<int>::const_iterator iter=ivec.begin();iter!=ivec.end();++iter)
*iter=0;
3)、const_iterator和const iterator是不一样的,后者对迭代器进行声明时,必须对迭代器进行初始化,并且一旦初始化后就不能修改其值。这有点像常量指针和指针常量的关系。
vector<int> ivec(10);
const vector<int>::iterator iter = ivec.begin();
*iter=0; //合法,可以改变其指向的元素的值
++iter; //不合法,无法改变其指向的位置
f、使迭代器失效的操作:由于一些对容器的操作如删除元素或移动元素等会修改容器的内在状态,这会使得原本指向被移动元素的迭代器失效,也可能同时使其他迭代器失效。使用无效的迭代器是没有定义的,可能会导致和使用悬垂指针相同的问题。所以在使用迭代器编写程序时,需要特别留意哪些操作会使迭代器失效。使用无效迭代器会导致严重的运行时错误。