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

    目录

    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!
    ";
    13     }
    14     ~Report()
    15     {
    16         std::cout << "Object deleted!
    ";
    17     }
    18     void show() const
    19     {
    20         std::cout << str << "
    ";  //打印创建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:
    ";
    20     for (int i = 0; i < 5; i++)
    21         cout << *films[i] << endl;
    22     //cout << "The winner is " << *pwin << "!
    ";
    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.
    ";

    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是一个向量数组
  • 相关阅读:
    【C语言学习】-08 指针
    【C语言学习】-07 结构体
    【C语言学习】-06 函数
    百度地图添加大头针和视图
    ios8中修改的 推送和地图
    ios 有关设备信息
    详解Objective-C runtime
    IOS7 ~ Xcode5 制作 framework
    IOS 取消表格单元格 TableViewCell 去掉高亮状态 点击Cell取消选择状态
    验证码设计(转)
  • 原文地址:https://www.cnblogs.com/YiYA-blog/p/11498721.html
Copyright © 2011-2022 走看看