北京大学程序设计MOOC作业详解-04-运算符重载
第五题:
第五题分析:
首先需要考虑补充哪些成员函数:
1)有代码s2=s1
,且成员变量是指针类型char* p
,为了避免浅拷贝,需要补充拷贝构造函数,如下:
MyString(const MyString& str) {
if (str.p) {
p = new char[strlen(str.p) + 1]();
strcpy(p, str.p);
}
else {
p = nullptr;
}
}
需要注意的是:如果str.p
是NULL,strlen函数会返回0,此时p原本也应该是空,却意外地初始化了1个空间,且只存放终止符’\0’。如果多次调用拷贝构造,则会白白开出一部分全是终止符的空间,且程序员并不知情,这是危险的。
2)有代码s3.Copy(w)
,这说明需要补充拷贝函数,这个拷贝函数的参数是一个指针类型,如下:
void Copy(const char* pStr) {
if (p) { delete[] p; }
if (!pStr) {
p = nullptr;
return;
}
p = new char[strlen(pStr) + 1]();
strcpy(p, pStr);
}
需要注意的是:在拷贝之前,为了避免内存泄露,要检查被赋值的变量p是否指向了内存空间,如果非空(即指向了内存空间),要及时释放掉;其他的和前面的构造/拷贝构造函数类似。
3)有代码cout << s1
,这说明要重载输出运算符,如下:
friend ostream& operator<<(ostream& os, const MyString& str) {
if (str.p) { os << str.p; }
return os;
}
需要注意的是:如果被输出的字符串是空,在这个实现里,是以NULL表示的,需要检查一下,非空才输出,否则啥也不输出。
4)有代码s2 = w2;s3 = s2;
,这意味着,需要重载赋值运算符函数。否则,在成员变量为指针类型时,可能会出现浅拷贝危害,如下:
MyString& operator=(const MyString& str) {
if (this == &str) { return *this; }
Copy(str.p);
return *this;
}
需要注意的是:首先,合理利用代码封装性,避免写重复冗余的代码,直接调用前面实现的Copy函数即可;然后,要先检查this是否就指向当前赋值的对象,避免this指向赋值的对象str。
之所以要这样,是因为:如果this指向被赋值的对象,在delete释放this->p的内存时,本质也释放了str.p,这样会在Copy函数中出现内存非法访问!
总结一些编码习惯:
1)写重载赋值运算符函数的时候,必须要有返回return *this;
的习惯,千万别忘了!
2)写拷贝函数的时候,如果拷贝的对象是NULL,给被拷贝的对象置空后立即返回,要么就if else语句写全,别只写if不写else。
完整的AC代码:
MyString(const MyString& str) {
if (str.p) {
p = new char[strlen(str.p) + 1]();
strcpy(p, str.p);
}
else {
p = nullptr;
}
}
void Copy(const char* pStr) {
if (p) { delete[] p; }
if (!pStr) {
p = nullptr;
return;
}
p = new char[strlen(pStr) + 1]();
strcpy(p, pStr);
}
friend ostream& operator<<(ostream& os, const MyString& str) {
if (str.p) { os << str.p; }
return os;
}
MyString& operator=(const MyString& str) {
if (this == &str) { return *this; }
Copy(str.p);
return *this;
}
第六题:
第六题分析:
首先分析需要补充哪些函数:
1)需要补充减法运算符函数,且返回值为引用类型,这是因为有代码objInt - 2 - 1 - 3;
,这是一种连续调用,且实际作用到了objInt对象上,因此返回引用,如下:
MyInt& operator-(int v) {
nVal -= v;
return *this;
}
2)需要补充强制类型转换运算符函数,这是因为有代码Inc(objInt);
,其中int Inc(int n);
函数的参数是整形,因此需要从类类型转换到整数类型,这在Java中叫解包,在C++中叫强制类型转换,如下:
operator int() {
return nVal;
}
需要注意的是:强制类型转换运算符的声明是operator T();
,这里不需要有返回值,但也并非void返回值,也并非无返回值,这是因为强制类型转换中声明的T类型,本身就是返回类型,无需再声明。
AC代码:
MyInt& operator-(int v) {
nVal -= v;
return *this;
}
operator int() {
return nVal;
}
第七题:
第七题分析:
这个题只需要补充重载输入输出运算符函数,这种函数的书写在前面已经介绍过了,函数声明如下:
friend istream& operator>>(istream& is, Point& p);
friend ostream& operator<<(ostream& os, const Point& p);
AC代码如下:
friend istream& operator>>(istream& is, Point& obj) {
is >> obj.x >> obj.y;
return is;
}
friend ostream& operator<<(ostream& os, const Point& obj) {
os << obj.x << "," << obj.y;
return os;
}
第八题:
第八题分析:
首先,分析需要补充哪些函数:
0)既然是二维数组,那私有成员变量应是一个二级指针,用来存储这个二维数组。此外,用指针必须配有对应的size_t类型的尺寸大小,例如行数和列数;
1)有代码Array2 a(3, 4);
,因此需要补充构造函数,且构造函数至少有2个参数,根据下面的二重循环可知,这2个参数分别代表二维数组的行和列数,完成代码如下。同时为了避免内存泄露,还需要及时析构释放内存,因此要有配套的析构函数:
private:
int** m_data;
size_t m_row, m_col;
public:
Array2(size_t row = 0, size_t col = 0)
: m_row(row), m_col(col) {
if (!row) {
m_data = nullptr;
return;
}
m_data = new int*[m_row];
for (size_t i = 0; i < m_row; ++i) {
m_data[i] = new int[m_col]();
}
}
~Array2() {
if (m_data && m_row > 0) {
for (size_t i = 0; i < m_row; ++i) {
delete[] m_data[i];
m_data[i] = nullptr;
}
delete[] m_data;
m_data = nullptr;
}
}
2)有代码a[i][j] = i * 4 + j;
,因此需要重载下标运算符,注意下标运算符只能有一个参数,具体的可以看前面博客的讲解。并且,这里是赋值语句,因此重载的运算符函数应是返回指针/引用类型,如下:
int* operator[](size_t idx) {
return m_data[idx];
}
需要注意的是:在C语言里面的数组,并没有越界一说,只有内存可访问和不可访问的区别。因此,不需要写对下标的检查代码。
3)有代码cout << a(i, j) << ",";
,因此需要重载括号运算符,可以看作仿函数,这是将对象当作函数去调用。然后,这里只有读,没有写的操作需求,因此不需要返回引用,直接返回int
即可。代码如下:
int operator()(size_t i, size_t j) {
return m_data[i][j];
}
4)有代码Array2 b; b = a;
,且成员变量是指针类型,为了避免浅拷贝,因此要写重载赋值运算符函数。此外,有默认构造的需求,这里需要注意一点点,如果是默认的,理应置空,但要区分size为0和默认的情况,一般出现在字符串的问题中。代码如下:
Array2& operator=(const Array2& arr) {
if (this == &arr) { return *this; }
if (m_data) {
for (size_t i = 0; i < m_row; ++i) {
delete[] m_data[i];
}
delete[] m_data;
}
m_row = arr.m_row, m_col = arr.m_col;
if (!arr.m_data) {
m_data = nullptr;
return *this;
}
m_data = new int* [m_row];
for (size_t i = 0; i < m_row; ++i) {
m_data[i] = new int[m_col]();
for (size_t j = 0; j < m_col; ++j) {
m_data[i][j] = arr.m_data[i][j];
}
}
return *this;
}
需要注意的是:本题虽然不要求拷贝构造,但是为了避免以后的开发者在使用的过程中,用到了拷贝构造,因成员变量是指针类型的,而造成浅拷贝危害,本节的分析还是给出拷贝构造的实现:
Array2(const Array2& arr)
: m_row(arr.m_row), m_col(arr.m_col) {
if (!arr.m_data) {
m_data = nullptr;
return;
}
m_data = new int* [m_row];
for (size_t i = 0; i < m_row; ++i) {
m_data[i] = new int[m_col];
for (size_t j = 0; j < m_col; ++j) {
m_data[i][j] = arr.m_data[i][j];
}
}
}
再次注意到:这里有代码的冗余,拷贝部分可以抽象一个私有的成员函数_Copy(const Array2& arr);
,提高代码的复用率。
AC代码如下:
private:
int** m_data;
size_t m_row, m_col;
public:
Array2(size_t row = 0, size_t col = 0) : m_row(row), m_col(col) {
if (!row) {
m_data = nullptr;
return;
}
m_data = new int* [m_row];
for (size_t i = 0; i < m_row; ++i) {
m_data[i] = new int[m_col]();
}
}
Array2(const Array2& arr) : m_row(arr.m_row), m_col(arr.m_col) {
if (!arr.m_data) {
m_data = nullptr;
return;
}
m_data = new int* [m_row];
for (size_t i = 0; i < m_row; ++i) {
m_data[i] = new int[m_col];
for (size_t j = 0; j < m_col; ++j) {
m_data[i][j] = arr.m_data[i][j];
}
}
}
~Array2() {
if (m_data && m_row > 0) {
for (size_t i = 0; i < m_row; ++i) {
delete[] m_data[i];
m_data[i] = nullptr;
}
delete[] m_data;
m_data = nullptr;
}
}
int* operator[](size_t idx) {
return m_data[idx];
}
int operator()(size_t i, size_t j) {
return m_data[i][j];
}
Array2& operator=(const Array2& arr) {
if (this == &arr) { return *this; }
if (m_data) {
for (size_t i = 0; i < m_row; ++i) {
delete[] m_data[i];
}
delete[] m_data;
}
m_row = arr.m_row, m_col = arr.m_col;
if (!arr.m_data) {
m_data = nullptr;
return;
}
m_data = new int* [m_row];
for (size_t i = 0; i < m_row; ++i) {
m_data[i] = new int[m_col]();
for (size_t j = 0; j < m_col; ++j) {
m_data[i][j] = arr.m_data[i][j];
}
}
}
第九题:
第九题分析:
本题要求写一个大整数类,首先我们明确用逆序整型数组来存储大数,然后我们要写构造函数,这里至少提供两种构造方法,一种是从字符串构造来,另一种是从10进制整数构造来,编写构造和析构代码:
void incMem(size_t factor) {
int* temp_data = new int[m_size]();
for (size_t i = 0; i < m_size; ++i) {
temp_data[i] = m_data[i];
}
m_mem *= factor;
m_data = new int[m_mem]();
for (size_t i = 0; i < m_size; ++i) {
m_data[i] = temp_data[i];
}
delete[] temp_data;
temp_data = nullptr;
}
CHugeInt(int nVal) : m_size(0), m_mem(16) {
m_data = new int[m_mem]();
while (nVal > 0) {
m_data[m_size++] = nVal % 10;
nVal /= 10;
if (m_size == m_mem) {
incMem(2);
}
}
}
CHugeInt(const char* strVal = nullptr) {
if (!strVal) {
m_data = nullptr;
m_size = m_mem = 0;
return;
}
m_size = m_mem = strlen(strVal);
m_data = new int[m_mem]();
for (size_t i = 0; i < m_size; ++i) {
m_data[i] = (strVal[i] - '0');
}
}
这里为了提高编码效率,提高代码复用率,编写了扩容代码:
void incMem(size_t factor) {
int* temp_data = new int[m_size]();
for (size_t i = 0; i < m_size; ++i) {
temp_data[i] = m_data[i];
}
m_mem *= factor;
m_data = new int[m_mem]();
for (size_t i = 0; i < m_size; ++i) {
m_data[i] = temp_data[i];
}
delete[] temp_data;
temp_data = nullptr;
}
具体的可以参考STL中vector的代码实现,主要是因为:① 以整数实例化大数时,不预知大数的大小;② 在加法过程中,可能带来进位,有扩容机制会好一些。虽然在本题中,数据规模不需要考虑这些,但也应该注意这些。
核心设计思路
为了减少加法过程中进位的次数,这里采用了不同的设计:只在输出时做进位,可以参考下面的代码:
class CHugeInt {
const size_t DEFAULT_SIZE = 16;
private:
int* m_data;
size_t m_size;
public:
CHugeInt(const char* sVal) : m_data(nullptr), m_size(0) {
if (!sVal) { return; }
if ("0" == sVal) {
m_size = 1;
m_data = new int[m_size]();
return;
}
m_size = strlen(sVal);
m_data = new int[m_size]();
for (size_t i = 0; i < m_size; ++i) {
m_data[i] = (sVal[m_size - i - 1] - '0');
}
}
CHugeInt(int iVal) : m_data(nullptr), m_size(0) {
if (0 == iVal) {
m_size = 1;
m_data = new int[m_size]();
}
m_data = new int[DEFAULT_SIZE]();
while (iVal > 0) {
m_data[m_size++] = iVal % 10;
iVal /= 10;
}
for (size_t i = 0; i < m_size; ++i) {
int temp = m_data[i];
m_data[i] = m_data[m_size - i - 1];
m_data[m_size - i - 1] = temp;
}
}
CHugeInt(const CHugeInt& bigint)
: m_data(new int[bigint.m_size]()), m_size(bigint.m_size) {
for (size_t i = 0; i < m_size; ++i) {
m_data[i] = bigint.m_data[i];
}
}
CHugeInt& operator++() {
m_data[0]++;
return *this;
}
CHugeInt operator++(int) {
CHugeInt ret = *this;
++(*this);
return ret;
}
CHugeInt& operator+=(const CHugeInt& bigint) {
size_t res_size = (m_size > bigint.m_size) ? m_size : bigint.m_size;
int* add_res = new int[res_size]();
for (size_t i = 0; i < res_size; ++i) {
int v1 = (i < m_size) ? m_data[i] : 0;
int v2 = (i < bigint.m_size) ? bigint.m_data[i] : 0;
add_res[i] = (v1 + v2);
}
delete[] m_data;
m_size = res_size;
m_data = new int[res_size]();
for (size_t i = 0; i < res_size; ++i) {
m_data[i] = add_res[i];
}
delete[] add_res;
add_res = nullptr;
return *this;
}
CHugeInt operator+(const CHugeInt& bigint) {
CHugeInt ret(*this);
ret += bigint;
return ret;
}
friend
CHugeInt operator+(int iVal, const CHugeInt& bigint) {
CHugeInt ret(iVal);
ret += bigint;
return ret;
}
friend
ostream& operator<<(ostream& os, const CHugeInt& bigint) {
int cur = 0, * temp = new int[bigint.m_size]();
for (size_t i = 0; i < bigint.m_size; ++i) {
temp[i] = bigint.m_data[i];
temp[i] += cur;
cur = temp[i] / 10;
temp[i] %= 10;
}
if (cur > 0) { os << cur; }
for (size_t i = bigint.m_size - 1; i > 0; --i) {
os << temp[i];
}
os << temp[0];
delete[] temp;
temp = nullptr;
return os;
}
};
**传统的思路,在后面再去写,这里先只记录我的AC答案。**也把之前第一次刷题时的答案放在下面:
const int MAX = 110;
class CHugeInt {
private:
int hugeSize;
int num[405];
void addOne() {
num[0] += 1;
int i = 0;
while (num[i] >= 0) {
num[i + 1] += num[i] / 10;
num[i] %= 10;
++i;
}
if (num[hugeSize] > 0) {
++hugeSize;
}
}
void setArrNum(int val) {
for (int i = 0; i < 400; ++i) {
num[i] = val;
}
}
public:
CHugeInt(int v = 0) {
setArrNum(0);
int nSize = 0;
while (v) {
num[nSize++] = v % 10;
v /= 10;
}
hugeSize = nSize;
}
CHugeInt(const char* strNum) {
setArrNum(0);
hugeSize = (int)strlen(strNum);
for (int i = hugeSize - 1; i >= 0; --i) {
num[hugeSize - i - 1] = strNum[i] - '0';
}
}
CHugeInt(const CHugeInt& hugeInt) : hugeSize(hugeInt.hugeSize) {
setArrNum(0);
for (int i = 0; i < hugeSize; ++i) {
num[i] = hugeInt.num[i];
}
}
CHugeInt operator+(const CHugeInt& hugeInt) {
int nSize1 = hugeSize, nSize2 = hugeInt.hugeSize;
int nSize = (nSize1 > nSize2) ? nSize1 : nSize2;
CHugeInt ret;
for (int i = 0; i < nSize; ++i) {
ret.num[i] += num[i] + hugeInt.num[i];
if (ret.num[i] >= 0) {
ret.num[i + 1] += ret.num[i] / 10;
ret.num[i] %= 10;
}
}
while (nSize > 0 && !ret.num[nSize]) {
--nSize;
}
ret.hugeSize = nSize + 1;
return ret;
}
CHugeInt operator+(int n) {
CHugeInt rhs(n);
return (*this + rhs);
}
CHugeInt& operator+=(int n) {
CHugeInt rhs(n);
int nSize = (hugeSize > rhs.hugeSize) ? hugeSize : rhs.hugeSize;
for (int i = 0; i < nSize; ++i) {
num[i] += rhs.num[i];
if (num[i] >= 10) {
num[i + 1] += num[i] / 10;
num[i] %= 10;
}
}
while (nSize > 1 && !num[nSize]) {
--nSize;
}
hugeSize = nSize + 1;
return *this;
}
CHugeInt& operator++() {
addOne();
return *this;
}
CHugeInt operator++(int) {
CHugeInt ret(*this);
addOne();
return ret;
}
friend ostream& operator<<(ostream& os, const CHugeInt& hugeInt) {
for (int i = hugeInt.hugeSize - 1; i >= 0; --i) {
os << hugeInt.num[i];
}
return os;
}
friend CHugeInt operator+(int n, const CHugeInt& hugeInt) {
CHugeInt lhs(n);
return (lhs + hugeInt);
}
};