码迷,mamicode.com
首页 > 其他好文 > 详细

36, 经典问题解析三

时间:2020-07-26 23:18:19      阅读:82      评论:0      收藏:0      [点我收藏+]

标签:toolbar   面试题   保留   对象   ati   i++   app   char*   ima   

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, 经典问题解析三

标签:toolbar   面试题   保留   对象   ati   i++   app   char*   ima   

原文地址:https://www.cnblogs.com/liuyueyue/p/13381752.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!