关于赋值的疑问

什么时候需要重载赋值操作符?
编译器是否提供默认的赋值操作符?

  • 编译器为每个类默认重载了赋值操作符
  • 默认的赋值操作符仅完成浅拷贝
  • 当需要进行深拷贝时必须重载赋值操作符
  • 赋值操作符与拷贝构造函数有相同的存在意义

编程实验: 默认赋值操作符重载

test_1.cpp

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int* m_pointer;
public:
    Test()
    {
        m_pointer = NULL;
    }
    Test(int i)
    {
        m_pointer = new int(i);
    }
    void print()
    {
        cout << "m_pointer = " << hex << m_pointer << endl;
    }
    ~Test()
    {
        delete m_pointer;
    }
};

int main()
{
    Test t1 = 1;
    Test t2;
    
    t2 = t1;
    
    t1.print();
    t2.print();
    
    return 0;
}
输出:【运行时发生内存错误】
m_pointer = 0x942f008
m_pointer = 0x942f008
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0942f008 ***
......
已放弃

现象:
t1.m_pointer 与 t2.m_pointer 指向同一片内存空间

修正: test_2.cpp

#include <iostream>
#include <string>

using namespace std;

class Test
{
    int* m_pointer;
public:
    Test()
    {
        m_pointer = NULL;
    }
    Test(int i)
    {
        m_pointer = new int(i);
    }
    Test(const Test& obj)
    {
        m_pointer = new int(*obj.m_pointer);
    }
    Test& operator = (const Test& obj)
    {
        if( this != &obj )
        {
            delete m_pointer;
            m_pointer = new int(*obj.m_pointer);
        }
        
        return *this;
    }
    void print()
    {
        cout << "m_pointer = " << hex << m_pointer << endl;
    }
    ~Test()
    {
        delete m_pointer;
    }
};

int main()
{
    Test t1 = 1;
    Test t2;
    
    t2 = t1;
    
    t1.print();
    t2.print();
    
    return 0;
}
输出:
m_pointer = 0x83ac008
m_pointer = 0x83ac018

现象:
t1.m_pointer 与 t2.m_pointer 指向不同的内存空间

问题分析

clipboard.png

  1. test_2.cpp 类中未定义赋值操作符重载函数。在 t2 = t1; 对象赋值时,编译器默认提供的赋值操作符重载函数被调用,发生浅拷贝;
  2. t1.m_pointer 与 t2.m_pointer 指向 t1.m_pointer 所指向的内存空间,未给 t2.m_pointer 重新分配;
  3. 在对象销毁,析构函数被调用时,同一片内存空间被释放两次,导致运行时发生内存错误。

一般性原则

重载赋值操作符,必然需要实现深拷贝!

重载赋值操作符注意事项

Test& operator = (const Test& obj)
{
    if( this != &obj )
    {
       // ...
    }
    
    return *this;
}
  • 返回类型为引用(连续赋值)
  • 参数为 const 引用类型
  • 进行自赋值判断
  • 返回当前对象

编程实验: 数组类的优化

IntArray.h

#ifndef _INTARRAY_H_
#define _INTARRAY_H_

class IntArray
{
private:
    int m_length;
    int* m_pointer;
    
    IntArray(int len);
    IntArray(const IntArray& obj);
    bool construct();

public:
    static IntArray* NewInstance(int length);
    int length();
    bool get(int index, int& value);
    bool set(int index, int value);
    int& operator [] (int index);
    IntArray& operator = (const IntArray& obj);
    IntArray& self();
    ~IntArray();
};

#endif

IntArray.cpp

#include "IntArray.h"

IntArray::IntArray(int len)
{
    m_length = len;
}

bool IntArray::construct()
{
    bool ret = true;
    
    m_pointer = new int[m_length];
    
    if( m_pointer )
    {
        for(int i=0; i<m_length; i++)
        {
            m_pointer[i] = 0;
        }
    }
    else
    {
        ret = false;
    }
    
    return ret;
}

IntArray* IntArray::NewInstance(int length)
{
    IntArray* ret = new IntArray(length);
    
    if( !(ret && (ret->construct())) )
    {
        delete ret;
        ret = 0;
    }
    
    return ret;
}

int IntArray::length()
{
    return m_length;
}

bool IntArray::get(int index, int& value)
{
    bool ret = (index >= 0) && (index < length());
    
    if( ret )
    {
        value = m_pointer[index];
    }
    
    return ret;
}

bool IntArray::set(int index, int value)
{
    bool ret = (index >= 0) && (index < length());
    
    if( ret )
    {
        m_pointer[index] = value;
    }
    
    return ret;
}

int& IntArray::operator [] (int index)
{
    return m_pointer[index];
}

IntArray& IntArray::operator = (const IntArray& obj)
{
    if( this != &obj )
    {
        int* pointer = new int[obj.m_length];
        
        if( pointer )
        {
            for(int i=0; i<obj.m_length; i++)
            {
                pointer[i] = obj.m_pointer[i];
            }
            
            m_length = obj.m_length;
            delete m_pointer;
            m_pointer = pointer;
        }    
    }
    return *this;
}

IntArray& IntArray::self()
{
    return *this;
}

IntArray::~IntArray()
{
    delete[] m_pointer;
}

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;
        cout << "brray.length() = " << brray.length() << endl;
        
        array = brray;
        
        cout << "array.length() = " << array.length() << endl;
        cout << "brray.length() = " << brray.length() << endl;
    }
    
    delete a;
    delete b;

    return 0;
}
输出:
array.length() = 5
brray.length() = 10
array.length() = 10
brray.length() = 10

经典的面试问题

class T
{
};

==>

class T
{
public:
    T();
    T(const T&);
    T& operator = (const T&);
    ~T();
};

关于 string 的疑问

  • 下面的代码输出什么?为什么?
void code()
{
    string s = "12345";
    const char* p = s.c_str();
    
    cout << p << endl;
    
    s.append("abcde");
    
    cout << p << endl;
}

编程实验: 字符串问题 1

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string s = "12345";
    const char* p = s.c_str();
    
    cout << p << endl;
    
    s.append("abcde");
    
    cout << p << endl;          // p 成为了野指针!
    
    return 0;
}
输出:
12345
12345

问题分析

clipboard.png

【string 对象内部维护了一个指向数据(位于堆空间中)的 char* 指针,这个指针可能在运行的过程中发生改变】

s = "12345"; ==> s 对象在堆空间申请内存并将字符串"12345"拷贝进入,同时内部指针指向这一空间 (0xFF112233)
p = s.c_str(); ==> p 指针指向保存数据的内存空间 (0xFF112233)
s.append("abcde"); ==> s 对象重新在堆空间申请内存,并把两部分字符串拷贝进入,原有的堆空间被释放(0xFF112233),内部指针指向新的内存空间 (0xFF445566)

  • 下面的程序输出什么?为什么?
void code()
{
    const char* p = "12345";
    string s = "";
    
    s.reserve(10);
    
    for(int i=0; i<5; i++)
    {
        s[i] = p[i];
    }
    
    cout << "s.length : " << s.length() << endl;
    cout << "s.empty : " << s.empty() << endl;
    
    cout << "s : " << s << endl;
}

编程实验: 字符串问题 2

#include <iostream>
#include <string>

using namespace std;

int main()
{
    const char* p = "12345";
    string s = "";
    
    s.reserve(10);               // 将 s 的容量调整为 10
    
    for(int i=0; i<5; i++)       // 不要使用 C 语言中的方式操作 C++ 中的字符串
    {
        s[i] = p[i];
    }
    
    cout << "s.length : " << s.length() << endl;
    cout << "s.empty : " << s.empty() << endl;
    
    cout << "s : " << s << endl;
    
    return 0;
}
输出:
s.length : 0
s.empty : 1
s : 

问题分析

clipboard.png

for 循环前:
m_cstr 指向10个字节的堆空间(0xFF010203)
m_length 字符长度为 0 (空串)

for 循环后:
m_cstr 指向 10 个字节的堆空间前 5 个字节发生改变(0xFF010203)
m_length 字符长度为 0 (空串)

警告:
在 C++ 中尽量只采用 C++ 编程思想,而不采用混合方式开发,否则可能产生意想不到的问题

小结

  • 在需要进行深拷贝的时候必须重载赋值操作符
  • 赋值操作符和拷贝构造函数有同等重要的意义
  • string 类通过一个数据空间保存字符数据
  • string 类通过一个成员变量保存当前字符串的长度
  • C++ 开发时尽量避开 C 语言中惯用的编程思想

以上内容参考狄泰软件学院系列课程,请大家保护原创!


TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧