北京大学程序设计MOOC作业详解-04-运算符重载(下)

文章详细分析了C++编程中如何处理运算符重载,包括拷贝构造函数、赋值运算符、输入输出流运算符的实现,并以具体题目为例,解释了二维数组的动态内存管理和运算符的正确使用。强调了防止浅拷贝的危害以及合理管理内存的重要性。
摘要由CSDN通过智能技术生成

北京大学程序设计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);
    }

};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值