36, 经典问题解析三

Posted liuyueyue

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了36, 经典问题解析三相关的知识,希望对你有一定的参考价值。

1. 关于赋值的疑问

什么时候需要重载赋值操作符?

浅拷贝不够用这时候需要自定义深拷贝

编译器是否提供默认的赋值操作符?

(1)回答

  ①编译器每个类默认提供重载了赋值操作符-------可以给同一类型的类对象相互赋值

  ②默认的赋值操作符仅完成浅拷贝

  ③当需要进行深拷贝必须重载赋值操作符(赋值操作符一样)

  ④赋值操作符与拷贝构造函数相同的存在意义

编程说明: 编译器会默认提供重载赋值操作符------仅仅完成浅拷贝

 1 #include<iostream>
 2 #include<string>
 3 
 4 using namespace std;
 5 
 6 //编译器会默认提供重载赋值操作符------仅仅完成浅拷贝
 7 
 8 class Test
 9 {
10     int* m_pointer;  //指针成员
11 public:
12     Test()
13     {
14         m_pointer = NULL;
15     }
16     Test(int i)
17     {
18         m_pointer = new int(i);   //指向堆空间的4个字节
19     }
20 
21     void print()
22     {
23         cout << "m_pointer=" << hex << m_pointer << endl;
24     }
25 
26     ~Test()
27     {
28         delete m_pointer;
29     }
30 };
31 
32 int main()
33 {
34     Test t1=1;  //t1对象的内部的成员指针指向堆空间的一片内存,内存里面的值设置为1
35     Test t2;    //t2对象指针指向空
36 
37     t2 = t1;    //t1对象赋值t2     产生内存错误
38                 //t1 t2指向相同堆空间,main()结束之前摧毁t1 t2两个对象,释放调用两次析构函数delete m_pointer;--------不合法的内存操作
39 
40 
41 
42     t1.print();
43     t2.print();
44 
45     return 0;
46 }
 1 #include<iostream>             
 2 #include<string>
 3 
 4 using namespace std;
 5 
 6 //什么时候进行深拷贝----要实现深拷贝就要重载赋值操作符
 7 
 8 //类里有成员m_pointer指针指代了外部资源,浅拷贝不够用这时候需要自定义深拷贝和重载赋值操作符,有必要的时候自定义拷贝构造函数
 9 
10 class Test
11 {
12     int* m_pointer;  //指针成员  
13 
14 public:
15     Test()
16     {
17         m_pointer = NULL;
18     }
19     Test(int i)
20     {
21         m_pointer = new int(i); //指向堆空间的4个字节
22     }
23 
24 
25 
26     //自定义深拷贝构造函数
27     Test(const Test& obj)    
28     {
29         m_pointer = new int(*obj.m_pointer); //1,堆空间申请一片内存,大小由参数对象决定,内存代表int类型的值,
30                                              //2,取出参数对象obj的m_pointer指针所指向的堆空间的整型值,赋值到新申请的空间内
31     }
32     
33 
34 
35     //重载赋值操作符  注意4点:  1,返回值类型一定是引用Test&为了连续赋值   2,参数是const引用类型
36     //                              3,赋值操作符不是自赋值a=a,要避免赋值,通过This判断,this指向当前对象的地址和参数地址不同才进行赋值操作(深拷贝)
37     //                             4,返回当前对象地址*this
38 
39     Test& operator=(const Test& obj)      
40     {       
41         //进行赋值操作
42         if (this != &obj)                   
43         { 
44             delete m_pointer;  //深拷贝之前将当前的指针删除
45             m_pointer = new int(*obj.m_pointer);
46         }
47 
48         return *this;                  
49     }
50 
51     void print()
52     {
53         cout << "m_pointer=" << hex << m_pointer << endl;
54     }
55 
56     ~Test()
57     {
58         delete m_pointer;
59     }
60 };
61 
62 int main()
63 {
64     Test t1=1;  //t1对象的内部的成员指针指向堆空间的一片内存,内存里面的值设置为1
65     Test t2;    //t2对象指针指向空
66 
67     t2 = t1;    //利用赋值操作符重载函数
68                 
69                 // t1对象的m_pointer指针和t2对象的m_pointer指针所指向的堆空间不一样
70 
71     t2 = t2;    //没意义,只是支持 所以要避免自赋值    if (this != &obj)    
72 
73     t1.print();
74     t2.print();
75 
76     return 0;
77 }

问题分析:

      技术图片

t1对象赋值t2     产生内存错误--------分析:
t1 t2指向相同堆空间,main()结束之前摧毁t1 t2两个对象,释放调用两次析构函数delete m_pointer;--------不合法的内存操作
结论:一旦进行深拷贝,那么拷贝构造函数和赋值操作符重载就要自定义

一般性原则重载赋值操作符,必然需要实现深拷贝!!!也要进行拷贝构造的自定义

 整形数组类:

    拷贝构造在private内,外部无法调用拷贝构造函数,也就是使用了二阶构造不允许出现拷贝构造函数,赋值操作允许,可以进行赋值操作符重载

 1 #ifndef _INTARRAY_H_
 2 #define _INTARRAY_H_
 3 
 4 class IntArray
 5 {
 6 private:
 7     int m_length;
 8     int* m_pointer;
 9     
10     IntArray(int len);
11     IntArray(const IntArray& obj);
12     bool construct();
13 public:
14     static IntArray* NewInstance(int length); 
15     int length();
16     bool get(int index, int& value);
17     bool set(int index ,int value);
18     int& operator [] (int index);
19     IntArray& operator = (const IntArray& obj); 赋值操作符重载
20     IntArray& self();
21     ~IntArray();
22 };
23 
24 #endif
25 #endif
  1 #include "IntArray.h"
  2 
  3 IntArray::IntArray(int len)
  4 {
  5     m_length = len;
  6 }
  7 
  8 bool IntArray::construct()
  9 {
 10     bool ret = true;
 11     
 12     m_pointer = new int[m_length];
 13     
 14     if( m_pointer )
 15     {
 16         for(int i=0; i<m_length; i++)
 17         {
 18             m_pointer[i] = 0;
 19         }
 20     }
 21     else
 22     {
 23         ret = false;
 24     }
 25     
 26     return ret;
 27 }
 28 
 29 IntArray* IntArray::NewInstance(int length) 
 30 {
 31     IntArray* ret = new IntArray(length);
 32     
 33     if( !(ret && ret->construct()) ) 
 34     {
 35         delete ret;
 36         ret = 0;
 37     }
 38         
 39     return ret;
 40 }
 41 
 42 int IntArray::length()
 43 {
 44     return m_length;
 45 }
 46 
 47 bool IntArray::get(int index, int& value)
 48 {
 49     bool ret = (0 <= index) && (index < length());
 50     
 51     if( ret )
 52     {
 53         value = m_pointer[index];
 54     }
 55     
 56     return ret;
 57 }
 58 
 59 bool IntArray::set(int index, int value)
 60 {
 61     bool ret = (0 <= index) && (index < length());
 62     
 63     if( ret )
 64     {
 65         m_pointer[index] = value;
 66     }
 67     
 68     return ret;
 69 }
 70 
 71 int& IntArray::operator [] (int index)
 72 {
 73     return m_pointer[index];
 74 }
 75 
 76 IntArray& IntArray::operator = (const IntArray& obj)
 77 {
 78     if( this != &obj )   //拷贝开始
 79     {
 80         int* pointer = new int[obj.m_length];  //申请堆空间内存,大小由参数对象决定
 81         
 82         if( pointer )  //申请成功
 83         {
 84             for(int i=0; i<obj.m_length; i++)  //复制,遍历参数对象所指的堆空间
 85             {
 86                 pointer[i] = obj.m_pointer[i];  //参数对象所指堆空间的内容元素,全部复制到pointer所指的堆空间----以上都是浅拷贝
 87             }
 88             //下面是深拷贝
 89             m_length = obj.m_length;  //第一步:更新长度
 90             delete[] m_pointer;    //第二步:释放原有的空间
 91             m_pointer = pointer;    //第三步:深拷贝的关键-----将当前操作的pointer指针所指向的空间,赋值给m_pointer成员
 92         }
 93     }
 94     
 95     return *this;
 96 }
 97 
 98 IntArray& IntArray::self()
 99 {
100     return *this;
101 }
102 
103 IntArray::~IntArray()
104 {
105     delete[]m_pointer;
106 }

//main.cpp

技术图片
#include <iostream>
#include "IntArray.h" 

using namespace std; 

int main()
{
    IntArray* a = IntArray::NewInstance(5);//数组对象
    IntArray* b = IntArray::NewInstance(10); //数组对象

    if(a && b)//二阶构造都成功,赋值操作
    {
        IntArray& array = a->self();

        IntArray& brray = b->self();      

        cout << "array.length() = " << array.length() << endl; //5      

        cout << "brray.length() = " << brray.length() << endl; //10 

        array = brray;  //赋值

        cout << "array.length() = " << array.length() << endl; //10      

        cout << "brray.length() = " << brray.length() << endl; //10       
    }    

    delete a;
    delete b;
  
    return 0;
}
技术图片

(2)编译器默认提供的函数

面试题:给一个空类,是不是一个真正意义上的空类???

  编译器会自动提供四个函数实现

  • 不带参的构造函数

  • 拷贝构造函数

  • 默认的赋值操作符

  • 析构函数

    技术图片

 (3)一般原则重载赋值操作符,必然需要实现深拷贝!!!

 

2. 关于string的

 技术图片

s.c_str()-----返回字符指针,代表字符串

append----插入字符串

#include<iostream>
#include<string>

//不能混合使用c语言和c++

using namespace std;

int main()
{
    string s = "12345";

    //程序试图在C的方式访问字符串(不建议这样用!)
    const char* p = s.c_str(); //指针指向------c_str表示C方式的字符串内存空间

    cout << p << endl;        //12345        p成为了野指针
  //cout << s << endl;        //12345

    s.append("abcde");      //字符串插入函数
                            //p成为野指针,因为追加字符串,可能
                            //导致堆内存的重新分配,从而m_cstr 
                            //指的堆内存地址改变了,但p并不知道!

    cout << p << endl;       //    12345--仍然指向旧的地址(野指针) 
    //    cout << s << endl;     //12345abcde

    return 0;
}

问题分析:

    技术图片

append()执行结束后,内部的字符指针m_cstr()指向的是一片新的内存空间,值为12345abcded,之前的内存空间已经释放,P指向之前的内存空间,所以p称为野指针。

(2)string类内部维护了一个m_length的变量,用于指示字符串中字符的个数。当使用C的方式使用string对象时,这个m_length可能不会自动更新

(1)string类内部维护一个指向数据的char*指针(m_cstr),这里用于存放字符串数据的堆空间地址。因字符串操作(如复制、合并、追加等),所以这个指针可能在程序运行的过程中发生改变。----------所以一般不要操作此指针

            不要混合使用c语言和c++!!!!!

字符串问题2:

技术图片

技术图片
#include <iostream>
using namespace std;
 
int main()
{
    const char* p = "12345";  //c还是c++代码???

    string s = "";   //使用c++标准库类  s.length==0

    //保留一定量内存以容纳一定数量的字符
    s.reserve(10);    //字符串对象内部的数据指针指向的堆空间大小设置为10个字节    s.length ==0;

    //不要使用C语言的方式操作C++中的字符串
    for(int i=0; i < 5; i++)
    {
        s[i] = p[i];      //字符串赋值----s[i]当作c语言字符数组使用-----error
                // 注意,虽然此时s对象中的字符串内存,确实被赋新的值了。但用这种方式赋值,相等于只是通过指针赋值,s.length不,会自动更新,即仍为0 }
  if(!s.empty())   {
    cout<<s<<endl; //空
  }

  for(int i=0;i<5;i++)
  {
    cout<<s[i]<<endl; //12345 说明s[i]=p[i]赋值成功------但是字符串为空
  }

cout << s.length() << endl; //0 cout << s.empty() << endl; //1 cout << s << endl; //这里将不会有任何输出,因为s.length=0; return 0; }
技术图片

分析bug出现的地方

 

      技术图片

m_length=0,字符串本身不代表12345,还是空串

这样修改:

 1 #include<iostream>
 2 #include<string>
 3 
 4 //不能混合使用c语言和c++
 5 using namespace std;
 6 
 7 int main()
 8 {
 9     const string p = "12345";
10 
11     string s = "";
12 
13     s = p;   //使用字符串对象------抛弃c语言编程,采用面对对象思想
14 
15     cout << s << endl; //12345
16 return 0; 17 }

 

3. 小结

(1)在需要进行深拷贝的时候必须重载赋值操作符(以及拷贝构造)

(2)赋值操作符拷贝构造函数同等重要的意义

(3)string通过一个数据空间保存字符数据

(4)string通过一个成员变量保存当前字符串的长度

(5)C++开发尽量避开C语言中惯用的编程思想

 

以上是关于36, 经典问题解析三的主要内容,如果未能解决你的问题,请参考以下文章

验证码逆向专栏极验三代四代点选类验证码逆向分析

C++项目三代码参考(改进版)

日常Geetest滑动验证码(三代canvas版)处理小结(以B站登录验证为例)

如何通过单击适配器类中代码的项目中的删除按钮来删除列表视图中的项目后重新加载片段?

Air Max 270 REACT 三代 官网同步上架 实拍出货 高品质透气网面 MD发泡大底 缓震跑步鞋 码数:36-45

经典算法动画解析系列:选择排序