C++_十六章_智能指针_关于string对象、string指针和空指针的总结_关于智能指针相互赋值会导致出现空字符的问题_标准模板库
目录
1、智能指针
01)在使用new为指针分配内存空间的时候,有可能会出现忘记添加delete或者是没有忘记但不执行delelte的情况 ,此时就会导致内存泄露,例如如下情况:
1 void remodel(std::string & str) 2 { 3 std::string * ps = new std::string(str); 4 double * pd1 = new double[8]; //new返回一个可以存储8个double行数据的地址,pd1是一个指针 5 ... 6 if(weird_thing()) 7 throw exception(); //如果执行此句,那么就有可能不执行下面的delete 8 str = *ps; 9 delete ps; 10 return; 11 }
02)使用智能指针
(1)这三个智能指针模板(auto_ptr、unique_ptr和shared_ptr)都定义了类似指针的对象,可以将new获得的地址赋给这些对象。当智能指针过期时,其析构函数将使用delete
来释放内存。下图说明了常规指针和auto_ptr之间的差别.(unique_ptr和shared_ptr的行为和auto_ptr类似)
(2)智能指针使用语法(需要包含头文件memory):
1 auto_ptr<double> pd(new double); //类似于double* pd = new doouble; 只不过auto_ptr不用使用delete释放内存 2 auto_ptr<string> ps(new string); //类似于string* ps = new string; ps后面的new string是new返回的指针,指向新分配的内存块,作为构造函数auto_ptr<double>的参数
其他两种智能指针也是同样的语法:
1 unique_ptr<double> pd(new double); 2 shared_ptr<string> ps(new string);
使用智能指针修改remodel()函数后的结果:
1 #include <memory> 2 void remodel(std::string & str) 3 { 4 std::auto_ptr<std::string> ps(new std::string(str)); //将string对象str的地址赋给pd 5 ... 6 if(weird_thing()) 7 throw exception(); //如果执行此句,那么就有可能不执行下面的delete 8 str = *ps; 9 return; 10 }
03)使用智能指针完整的一个例子:
使用智能指针指向一个类对象,其中类对象的参数为初始化该对象时候传入类构造函数的参数(
指针都是要指向一个对象的吧,那么对象在创建的时候肯定是包含参数的了啊,所以指针指向的内存区域也是有参数的)。
1 #include <iostream> 2 #include <string> 3 #include <memory> 4 5 class Report 6 { 7 private: 8 std::string str; 9 public: 10 Report(const std::string s) : str(s) 11 { 12 std::cout << "Object created!\n"; 13 } 14 ~Report() 15 { 16 std::cout << "Object deleted!\n"; 17 } 18 void show() const 19 { 20 std::cout << str << "\n"; //打印创建Report对象时设置的私有数据 21 } 22 }; 23 24 int main() 25 { 26 /*使用代码块,这样在执行完代码块之后,在代码块中创建的所有变量都将被销毁*/ 27 { 28 std::auto_ptr<Report> ps(new Report("using auto_ptr")); //等价于Report* ps = new Report("using auto_ptr"); 29 ps->show(); //Report指针Report类中的方法 30 }//执行到这里后ps将会自动调用auto_ptr类的析构函数,将ps销毁并释放内存 31 /*同理使用代码块*/ 32 { 33 std::shared_ptr<Report> pd(new Report("using shared_ptr")); //ps指向数据为"using shared_ptr"的Report对象 34 pd->show(); 35 }//执行到这里后pd将会自动调用auto_ptr类的析构函数,将ps销毁并释放内存 36 /*同理使用代码块*/ 37 { 38 std::unique_ptr<Report> pa(new Report("using unique_ptr")); //ps指向数据为"using unique_ptr"的Report对象 39 pa->show(); 40 } 41 system("pause"); 42 return 0; 43 }
执行结果:
注意:
1 std::shared_ptr<double> pd; //创建智能指针,但不分配内存 2 double* p_reg = new double; //创建普通指针p_reg,并分配内存 3 //pd = p_reg; //将普通指针赋给智能指针不合法!! 4 pd = std::shared_ptr<double>(p_reg); //允许,使用了显式转换 5 //std::shared_ptr<double> pshared = p_reg; //不允许,因为进行了隐式转换,而shared_ptr含一个参数的构造函数使用了关键字explicit 6 std::shared_ptr<double> pshared(p_reg); //允许
04)需要注意的问题
对于三种智能指针都应该避免的是:
1 std::string vacation("I wandered longly as a cloud."); //由于不是使用new创建的字符变量vacation,所以vacation是非堆变量 2 std::shared_ptr<string> pvac(&vacation); //当pvac过期时,就会调用delete删除pvac指向的非堆内存,这是不允许的!!!
2、关于string对象、string指针和string空指针的总结
std::string str00 = "可以了---陈奕迅"; std::cout << str00 << std::endl; //这样是可以正常打印的,打印字符串可以了---陈奕迅 std::string* str0 = new std::string("可以了---陈奕迅"); std::cout << str0 << std::endl; //这样是可以正常打印的,打印str0的地址(因为str0本身就是一个指针) std::string* str1 = new std::string("可以了---陈奕迅"); std::cout << *str1 << std::endl; //这样是可以正常打印的,打印字符串可以了---陈奕迅 std::string* str2; //空指针 std::cout << *str2 << std::endl; //打印空指针就会报错了,相当于下面的films[2]放弃原来对指针的控制权
3、关于智能指针相互赋值会导致出现空字符的问题
01)auto_ptr创建的智能指针拥有所有权的概念,赋值后将丢失该所有权。编译器会认为下面的第三句是错误的(书上写的是不会标红,但实际上标红了,估计是没有将下面三句放入主函数中的事情)
1 auto_ptr<string> ps(new string("YiYA"); //创建智能指针ps,并使ps指向字符串YiYA 2 auto_ptr<string> pd; //创建智能指针,有内存但是没有指向字符串 3 pd = ps; //对于auto_ptr来说,该句会导致ps成为空指针;即auto_ptr创建的智能指针拥有所有权的概念,赋值后将丢失该所有权
02)unique_ptr创建的智能指针也拥有所有权的概念,只不过unique_prt策略更严格(书上写的是:假如将上面的auto_ptr替换为unique_ptr,那么第三句是错误的,将会被标红)
03)对于shared_ptr
shared_ptr创建智能更高的指针,跟踪引用特定对象的智能指针数.这成为引用计数。例如智能指针赋值时,计数将加1,
而指向同一字符串的指针过期时,计数将减1.仅当最后一个指向同一字符串的指针过期时,才调用delete
04)对于使用auto_ptr,第三句会标红,既有好处也有坏处:
好处:pd将接管ps的对"YiYA"的所有权,ps对该string对象的所有权将被剥夺,这是件好事,可防止ps和pd的析构函数同时删除同一个对象;
坏处:如果随后程序将使用ps,这将是一件坏事,因为ps不再指向有效的数据。
05)对于02unique_ptr智能指针不能相互赋值的反例:unique_ptr智能指针作为函数返回值,赋值给一个unique_ptr智能指针是允许的,如下:
1 #include <iostream> 2 #include <string> 3 #include <memory> 4 5 using namespace std; 6 7 //假设有子函数 8 unique_ptr<string> demo(const char* s) //返回值为指向string的智能指针 9 { 10 unique_ptr<string> temp(new string(s)); 11 return temp; //在demo()被执行完后,返回的temp很快被销毁,没有机会用它来访问无效数据 12 } 13 14 //调用程序为 15 int main() 16 { 17 unique_ptr<string> ps; 18 ps = demo("Uniquely special"); //这样使用两个unique_ptr指针之间相互赋值时允许的; 19 //在demo()被执行完后,返回的temp很快被销毁,没有机会用它来访问无效数据 20 cout << *ps << endl; //打印Uniquely special 21 22 cin.get(); 23 return 0; 24 }
总之,程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是一个临时右值,编译器允许这样做;如果源unique_ptr将存在很长一段时间,编译器将禁止这样做。编译器将允许这样做:
1 unique_ptr<string> pu1(new string("Hi ho!")); 2 unique_ptr<string> pu2; 3 pu2 = pu1; //不允许,因为pu1将存在很长一段时间 4 unique_ptr<string> pu3; 5 pu3 = unique_ptr<string>(new string("Yo!")); //允许!string("Yo!")将返回一个临时string对象,之后将很快被销毁
但是如果确实想使用上面代码中的第三句,可以使用std::move(),即将上面的第三句改为:
ps2 = std::move(ps1);
下面程序中第十八行出现了智能指针相互赋值从而导致films[2]成为空指针,打印空指针进而会报错
1 #include <iostream> 2 #include <string> 3 #include <memory> 4 5 int main() 6 { 7 using namespace std; 8 /*创建一个数组films,数组内包含五个指针*/ 9 auto_ptr<string> films[5] = 10 { 11 auto_ptr<string> (new string("Fowl Balls")), //可以没有变量的名字;注意最后一个是逗号而不是分号 12 auto_ptr<string> (new string("Duck Walks")), 13 auto_ptr<string> (new string("Chicken Runs")), 14 auto_ptr<string> (new string("Turkey Errors")), 15 auto_ptr<string> (new string("Goose Eggs")) 16 }; 17 auto_ptr<string> pwin; //创建一个指向string对象的智能指针对象 18 //pwin = films[2]; //这一句将导致films[2]不再引用该字符串,从而fimls[2]变成了一个空指针,在打印*films[2]的是时候程序会出现意外 19 cout << "The nominess for best avin baseball film are:\n"; 20 for (int i = 0; i < 5; i++) 21 cout << *films[i] << endl; 22 //cout << "The winner is " << *pwin << "!\n"; 23 cin.get();//输入任意字符后终止程序 24 return 0; 25 }
但是将以上的所有auto_ptr替换为shared_ptr,上述程序将不会报错,正常执行。
06)智能指针的选择
(1)如果程序要使用多个指向同一对象的之恩,应选择shared_ptr。这样的情况包括:有一个指针数组,并使用一些辅助指针来标记特定的元素;两个对象包含都指向第三个对象的指针;STL容器包含指针。
(2)如果程序不需要多个指向同一个对象的指针,则课使用unique_ptr。可将unique_ptr存储到StL容器中,只要不调用将一个unique_ptr复制或赋给另一个的方法或算法
1 #include <iostream> 2 #include <string> 3 #include <memory> 4 #include <vector> 5 #include <algorithm> //for for_each() 6 7 using namespace std; 8 9 /*创建返回值为unique_ptr智能指针的子函数make_int()*/ 10 unique_ptr<int> make_int(int n) 11 { 12 return unique_ptr<int>(new int(n)); 13 } 14 15 /*显示unique_ptr指针指向的内容的子函数*/ 16 void show(unique_ptr<int> & pi) 17 { 18 cout << *pi << endl; 19 } 20 21 int main() 22 { 23 //vector<typeName> vt(n_elem);//创建一个vector对象vt,可以存储n_elem个类型为typeName的元素 24 vector<unique_ptr<int>> vp(8); //创建可以存储8个指向int型数据智能指针的vector对象vp 25 for (int i = 0; i < vp.size(); i++) 26 vp[i] = make_int(rand() % 1000); //使用子函数make_int()对vector对象vp填充数据 27 vp.push_back(make_int(rand() % 1000)); //将make_int()子函数返回的unique_ptr智能指针填充到vector对象vp的尾部 28 for_each(vp.begin(), vp.end(), show); 29 }
对于for_each()函数:
1 for_each(_InputIterator __first, _InputIterator __last, _Function __f) 2 /** 3 * @brief Apply a function to every element of a sequence. 4 * @ingroup non_mutating_algorithms 5 * @param __first An input iterator. 循环开始迭代器 6 * @param __last An input iterator. 循环结尾迭代器 7 * @param __f A unary function object. 处理函数 8 * @return @p __f (std::move(@p __f) in C++0x). 9 * 10 * Applies the function object @p __f to each element in the range 11 * @p [first,last). @p __f must not modify the order of the sequence. 12 * If @p __f has a return value it is ignored. 13 */
for_each()参考博客:https://blog.csdn.net/raozhufa/article/details/99741195
执行结果:
07)可以将unique_ptr指针赋值给shared_ptr,但是反过来是不可以的,如:
1 unique_ptr<int> pup(make_int(rand() % 1000)); //make_int()返回的unique_int指针作为参数 2 shared_ptr<int> spp(pup); //不允许,虽然也是将unique_ptr指针赋值给shared_ptr 3 shared_ptr<int> spr(make_int(rand() % 1000)); //允许
4、标准模板库
01)STL提供了一组表示容器、迭代器、函数对象和算法的模板。容器时一个与数组类似的单元,可以存储若干个类型相同的值;
迭代器能够用来遍历容器,与能够遍历数组的指针类似,是广义指针;函数对象时类似于函数的对象,可以是类对象或函数指针,
其中函数指针包括函数名,因为函数名被用作指针。
02)以下代码为创建vector容器对象:
1 #include <vector> 2 using std::vector; 3 vector<int> ratings(5); //创建一个包含5个int型数据的vector对象ratings,注意这里使用的是小括号!!而vector<int> ratings[5]是创建五个vector数组(或创建5个vector对象ratings[1]表示第一个vector对象) 4 for(int i=0;i<5;i++) 5 { 6 std::cout << ratings[i] << std::endl; //运算符[]被重载,可以使用通常的数组表示方法来访问各个元素 7 }
01)迭代器:它是一个广义指针。事实上,它可以是指针,也可以是一个可对其执行类似指针的操作(如接触引用和递增)的对象。
要为vector的double类型声明一个迭代器,可以这样做:
1 vector<double>::iterator pd; //pd是一个迭代器,pd指向类型为double的vector容器
假设scores是一个vector<double>对象:
1 vector<double> scores;
则可以使用迭代器pd执行如下操作:
1 pd = scores.begin(); //使pd指向scores中的第一个元素 2 *pd = 23.3; //对pd解除引用,并对scores中的第一个元素赋值; 3 ++pd; //使pd指向scores中的下一个元素
02)auto替代vector<double>::iterator
可以这样做:
1 vector<double>::iterator pd = scores.begin();
也可以这样做:
1 auto pd = scores.begin();
03)end()函数
该函数返回一个超越结尾的迭代器。超越结尾的迭代器,是指向容器最后一个元素后面那个元素的迭代器。与c风格字符串最后一个字符后面的
类似,只是空字符是一个值,而“超越结尾的迭代器”是一个指向元素的指针(迭代器)。因此遍历scores容器中的元素可以使用如下方法:
1 vector<double>::iterator pd; 2 vector<double> scores;
1 for(pd = scores.begin(); pd != scores.end(); pd++) 2 cout << *pd << endl;
04)push_back()函数
该函数将元素添加到矢量(容器)末尾。无需了解元素的数目,只要能够取得足够的内存,程序就可以根据需要增加容器的长度,如:
1 vector<double> scores; 2 double temp; 3 while(cin>>temp && temp >= 0) 4 scores.push_back(temp); //将temp添加到scores容器尾 5 cout << "You entered " << scores.size() << " scores.\n";
05)erase()
该函数删除矢量(容器)指定区间的元素。它接受两个迭代器参数,这些参数定义了要删除的区间。例如:
1 scores.erase(scores.begin(), scores.begin()+2); //删除scores中第一个和第二个元素
06)inser()
该方法接受三个迭代器参数,第一个参数指定了新元素的插入位置,第二个和第三个定义了被插入的区间,该区间通常是另一个容器对象的一部分
1 vector<double> old_v; //创建一个指向double的vector对象old_v 2 vector<double> new_v; //创建一个指向double的vector对象new_v 3 old_v.inset(old_v.begin(), new_v.begin()+1, new_v.end()); //将new_v中除第一个元素外的所有元素插入到old_v中,插入第一个位置为old_v第一个位置
07)for_each()
该函数接受三个参数。前两个是定义容器中(要显示或者是其他操作的)区间的迭代器,最后是一个指向函数的指针,
该函数指出了要对这个区间的数据做的操作,如下示例:
可以将代码:
1 truct Review{ 2 std::string title; 3 int rating; 4 }; //定义一个结构体 5 vector<Review> books; //创建一个指向Review的vector对象books 6 vector<Review>::iterator pr; //pr是一个迭代器,pr指向类型为double的vector容器 7 for(pr = books.begin(); pr != books.end(); pr++) 8 ShowReview(*pr); //显式books容器中的内容
替换为:
1 struct Review{ 2 std::string title; 3 int rating; 4 }; //定义一个结构体 5 vector<Review> books; //创建一个指向Review的vector对象books 6 vector<Review>::iterator pr; //pr是一个迭代器,pr指向类型为double的vector容器 7 for_each(books.begin(),books.end(),ShowReview);
08)基于范围的for循环
(01)第五章的示例:
1 double prices[5] = {4.99,2.32,5.65,4.56,9.68}; 2 cout << x <<endl;
(02)第十六章的示例:
1 for_each(books.begin(), books.end(), ShowReivew); //其中books为vector对象,ShowReview为一个子函数 2 //可替换为: 3 for(auto x : books) //根据books的类型(vector<Review>),编译器会判断出x的类型为Review 4 ShowReview(x);
但是基于范围的for循环可以修改容器中的内容,诀窍是使用一个引用参数,如下所示:
1 void InflatReview(Review & r) //其中Review为一个结构,包括int型数据rating 2 { 3 r.rating++; 4 } 5 for(auto & x : books) 6 InflatReview(x);
09)关于vector对象
1 vector<int> ratings(5); //创建一个包含5个int型数据的vector对象ratings,注意这里使用的是小括号!! 2 vector<int> ratings[5] //是创建五个vector数组(或创建5个vector对象ratings[1]表示第一个vector对象) 3 vector<int> a[10]; //a是一个向量数组 4 vector<int> b(10); //b是一个向量 5 vector<vector<int>> c; //c是一个响亮 6 vector<vector<int>,10> d; //d是一个向量数组