剑指Offer: 面试题1:赋值运算符函数

题目

如下为类型CMyString的声明,请为该类型添加赋值运算符函数。

class CMyString
{
public:
     CMyString(char* pData = nullptr);
     CMyString(const CMyString& str);
     ~CMyString(void);
private:
     char* m_pData;
};

考查知识点

  • 是否把返回值的类型声明为该类型的引用,并在函数结束的时候返回*this。只有返回一个引用,才可以允许连续赋值。
  • 是否把参数类型声明为常量引用。不是引用,将会调用复制构造函数,造成不必要的浪费。因为赋值运算符不会改变实例状态,因此需要声明为const
  • 是否释放实例自身内存,否则会引起内存泄露(memory leak)。
  • 是否能够实现自身赋值而不会引起错误。
  • 是否考虑到内存分配失败抛出bad_alloc,导致异常退出,还能保证原实例不会被改变。

答案

CMyString.h

#pragma once
#ifndef _01_ASSIGNMENTOPERATOR_CMYSTRING_H_
#define _01_ASSIGNMENTOPERATOR_CMYSTRING_H_
#include <string.h>

class CMyString {
	friend void myStrcpy(char* dest, char* src);
public:
	// Attention!!! We cannot define CMyString() = default 
	// whereas we have defined CMyString(char * pData = nullptr) 
	// with default value. Otherwise, which constrctor should be
	// called would be unclear when we use CMyString cmStr;
	CMyString(char * pData = nullptr) : m_pData(nullptr)  // constructor with default value
	{
		if (pData == nullptr)
		{
			pData = new char[1];
			*pData = '\0';
		}
		else
		{
			m_pData = new char[strlen(pData) + 1];
			myStrcpy(m_pData, pData);
		}
	};
	CMyString(const CMyString& str) : m_pData(new char[strlen(str.m_pData) + 1]) // copy constructor
	{
		myStrcpy(m_pData, str.m_pData);
	} 
	CMyString& operator=(const CMyString&); // assignment operator
	~CMyString(void) { 
		if(m_pData != nullptr)
			delete []m_pData; 
	}; // destructor
	int getLen() { return strlen(m_pData); };
private:
	char* m_pData;
};

/** One way to implement assignment operator.
 *  1. Self-assignment is considered. (The if condition)
 *	2. Continuous assignment is considered. (Return a reference)
 *	3. Exception is considered. (Create a new char* temp at first. 
 *	     Then delete orignal memory. Finally assign temp variable 
 *		 to m_pData. If an exception occurs at the process of new
 *		 operation, this code can guarantee the original object 
 *		 is not changed.)
 */
CMyString& CMyString::operator=(const CMyString& str)
{
	if (this != &str)
	{
		char * temp = new char[strlen(str.m_pData) + 1];
		myStrcpy(temp, str.m_pData);
		delete []m_pData;
		m_pData = nullptr;
		m_pData = temp;
	}
	return *this;
}

/** Another way to implement assignment operator.
 *  1. Self-assignment is considered. (The if condition)
 *	2. Continuous assignment is considered. (Return a reference)
 *	3. Exception is considered. (bad_alloc) 
 */

/*CMyString& CMyString::operator=(const CMyString& str)
{
	if (str.m_pData != m_pData)
	{
		CMyString tempStr(str);
		char * pTemp = m_pData;
		m_pData = tempStr.m_pData;
		tempStr.m_pData = pTemp;
		// Note: Exchanging tempStr.m_pData and this.m_pData
		// makes sure that the orignal memory can be freed
		// in tempStr's destructor.
	}
	return *this;
}*/

void myStrcpy(char* dest, char* src)
{
	if (src == dest)
		return;
	while (*src) *dest++ = *src++;
	*dest = '\0';
}

#endif

main.cpp

#include <iostream>
#include <string>
#include "CMyString.h"

int main() 
{
	char ac[] = "AssignmentOperator Class Demo";
	char *pc = ac;
	CMyString cmStr(pc);
	CMyString cmStr2;
	CMyString cmStr3;
	CMyString cmStr4;
	int i = cmStr.getLen();
	// int j = strlen(nullptr); // undefined
	cmStr = cmStr;   // self assignment
	cmStr2 = cmStr;  // assign to other object
	cmStr4 = cmStr3 = cmStr; // continuous assignment
	return 0;
}

注意事项

  1. 释放内存是应该使用delete [] m_pData,不能使用delete m_pData,因为m_pData是一个char数组。
  2. 最好在所有的构造函数中都让m_pData不为nullptr,最少有一个字符,且值为'\0'。这是为了避免在释放内存的时候,m_pData为空(这是第一层保障);以及将一个值为nullptrm_pData使用赋值运算符函数时,语句strlen(str.m_pData)出错。
  3. 在析构函数释放内存之前,判断m_pData是否为空,为避免释放空指针再一次提供了一层保障。
  4. nullptr进行strlen以及delete的行为都是未定义的,应当在代码中避免这两个操作。
  5. strlen的求值结果不包括'\0',因此在new一个新数组的时候应该在strlen求值结果的基础上加1

发表评论

电子邮件地址不会被公开。 必填项已用*标注

3 × 2 =

3 + 5 =