C++_十六章_智能指针_关于string对象、string指针和空指针的总结_关于智能指针相互赋值会导致出现空字符的问题_标准模板库

允我心安 提交于 2020-02-28 10:55:18

目录

1、智能指针

2、关于string对象、string指针和空指针的总结

3、关于智能指针相互赋值会导致出现空字符的问题

4、标准模板库

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 }
delete有可能不会被执行的情况

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 }
修改remote()后的结果

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 }
View Code

总之,程序试图将一个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对象,之后将很快被销毁
View Code

但是如果确实想使用上面代码中的第三句,可以使用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 }
View Code

对于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是一个向量数组

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!