文章目錄
-
- 1 指派運算符與深拷貝
-
- 1.1 注意事項
- 2 重載指派操作符優化數組類
- 3 小結
前面我們學習了操作符重載:
在 【C++深度解析】20、操作符重載中講了操作符可以重載為成員函數和非成員函數,并重載運算操作符:+,-,*,/ ,比較操作符:==,!= ,和指派操作符:=,實作了複數類。
在 【C++深度解析】22、數組操作符重載 中講解了了重載數組通路操作符,并在數組類中重載數組通路操作符,可以對函數對象直接使用下标通路數組元素。
在 【C++深度解析】23、函數對象分析(重載函數調用操作符())中實作了重載函數調用操作符(),使得對象可以像函數調用一樣使用。
1 指派運算符與深拷貝
我們現在有一個問題:什麼時候需要重載指派運算符?
- 編譯器為每個類預設重載了指派運算符,僅完成淺拷貝
- 需要深拷貝時必須重載指派操作符
- 指派操作符與拷貝構造函數有相同的存在意義,一起實作
下面我們進行程式設計實驗看一下深拷貝的意義:
#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;
}
上面的代碼很簡單,t2 = t1 時,使用編譯器預設的重載指派運算符,這裡是淺拷貝,t1 和 t2 中的 m_pointer 都指向同一塊記憶體空間,t1,t2 生存期結束時,調用析構函數,釋放兩次空間,會引起程式崩潰。
如何修改呢,就是自己實作拷貝構造函數和指派運算符,實作深拷貝。
#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) // 傳回值為類型引用,參數為const類型的引用
{
if (this != &obj) // 相等判斷,避免自我指派
{
delete m_pointer;
m_pointer = new int(*obj.m_pointer);
}
return *this; // 傳回 *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;
}
使用深拷貝,二者的位址空間不同,兩次析構釋放的是不同的位址空間
$ g++ 24-1.cpp -o 24-1
$ ./24-1
m_pointer = 0x90db008
m_pointer = 0x90db018
指派操作符與拷貝構造函數有相同的存在意義,使用場合不同,t2 = t1 使用指派操作符,t2(t1) 使用拷貝構造函數。意義是相同的。
1.1 注意事項
重載指派操作符需要注意四點(非常重要):
- 傳回類型為類名的引用,這樣可以連續指派
- 參數為 const 類名的引用
- 判斷相等,避免自我指派
- 傳回值為 *this
2 重載指派操作符優化數組類
在部落格【C++深度解析】12、構造函數、拷貝構造函數和析構函數中我們實作了數組類
在【C++深度解析】17、二階構造模式中使用二階構造模式進行改進,避免構造失敗而形成半成品對象。
在【C++深度解析】22、數組操作符重載中重載數組操作符,這樣可以直接對對象使用下标通路數組元素。
數組類中有指針會申請記憶體空間,拷貝時發生的是淺拷貝,容易造成記憶體錯誤,這裡我們重載指派操作符,實作深拷貝。另外這裡的拷貝構造函數為私有,類外無法調用,不需要重載了。
// IntArray.h
#ifndef _INTARRAY_H_
#define _INRARRAY_H_
class IntArray
{
private:
IntArray(int len);
bool construct();
IntArray(const IntArray& obj);
int m_length;
int* m_pointer;
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
#include"IntArray.h"
#include<stdio.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 = NULL;
}
return ret;
}
int IntArray::length()
{
return m_length;
}
bool IntArray::get(int index, int& value)
{
bool ret = (index >= 0 && index < m_length);
if (ret)
{
value = m_pointer[index];
}
return ret;
}
bool IntArray::set(int index, int value)
{
bool ret = (index >= 0 && index < m_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;
}
// 24-2.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;
}
重載指派運算符實作深拷貝,這樣對象使用指派符号指派就不會引起記憶體錯誤了。因為 不同對象指向的是不同的位址空間。
$ g++ 24-2.cpp IntArray.cpp -o 24-2
$ ./24-2
array.length = 5
brray.length = 10
array.length = 10
brray.length = 10
3 小結
1、需要深拷貝時必須重載指派操作符
2、指派操作符和拷貝構造函數有同等重要的意義