3、程序问题分析
A. 编译错误; B 编译成功,运行时程序崩溃; C 编译运行正常,输出10.
Class A
{
private:
int value;
public:
A(int n) {value = n;}
A(A other) {value = other.value;}
void print(){std::cout << value << std:: endl;}
}
int main()
{
A a = 10;
A b = a;
b.print();
return 0;
}
在上述代码中,复制构造函数A(A other)传入的参数是A的一个实例。
由于是传值参数, 我们
把形参复制到实参会调用复制构造函数。
因此,如果
允许复制构造函数传值,就会在复制构造函数内调用复制构造函数,就会形成永无休止的递归调用
从而导致栈溢出。因此C++的标准不允许复制构造函数传值参数。在Visual Studio和GCC中,豆浆编译出错。
可以改为A(const A& other),也就是把传值参数改成常量引用。
================================================================================
面试题1 : 赋值运算符函数
如下为类型CMyString的声明,请为该类型添加赋值运算符函数。
class CMyString
{
public:
CMyString(char * pData = NULL);
CMyString(const CMyString& str);
~ CMyString(void );
private:
char * m_pData;
}
面试者考察的关键点:
1、是否把返回值的类型声明为该类型的引用,并在函数结束前返回实例自身的引用(即*this)。
只有返回一个引用,才可以允许连续赋值。
否则,如果函数返回值是void,应该该复制运算将不会做连续赋值。
假设有3个
CMyString的对象:str1、str2和str3,在程序中str1=str2=str3将不能通过编译。
2、是否把传入的参数声明为常量引用。
如果传入的是实例,会造成复制构造函数。
同时要为传入的引用参数加上const关键字。
3、是否释放实例自身已有的内存。
否则会出现内存泄露。
4、是否判断传入的参数和当前的实例(*this)是不是同一个实例。
如果是一个,则不能进行赋值操作,直接返回。
如果不判断,那么在释放实例自身的内存时,就会导致严重的问题:当*this和传入的参数时同一个实例时,那么
一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到需要赋值的内容了。
CMyString& CMyString :: operator = (const CMyString &str)
{
// 如果传入的参数与当前的实例是同一个实例,则直接返回自身
if(this == &str) return *this;
// 释放实例自身已有内存
delete []m_pData;
m_pData = NULL;
// 在删除自身以后重新new一个长度为len+1的字符数组,类似拷贝构造函数
m_pData = new char[strlen(str.m_pData) + 1];
strcpy(m_pData, str.m_pData);
return *this;
}
上述中,在分配内存之前先用delete释放了实例m_pData的内存。如果此时内存不足导致
new char 抛出异常, m_pData将是一个空指针,这非常容易导致程序崩溃。
也就是说一旦在赋值运算符函数内部抛出一个异常, CMyString的实例不再保持有效的状态,
违背了异常安全性的原则,有两个解决方法:
1、先用new分配出新内容再用delete释放已有的内容,只有在分配内存成功之后再释放原来的内容,也就是当分配内存
失败时我们能够保证CMyString的实例不会被修改。
2、更好的方法:先创建一个临时实例,再交换临时实例和原来的实例,下面是代码:
CMyString& CMyString :: operator = (const CMyString &str)
{
if(this != &str)
{
CMyString strTemp(str); // 使用构造函数创建一个临时对象, 出了strTemp的作用域,
// 该实例会被自动调用析构函数,把其所指向的内存释放掉。
// 交换临时对象与当前对象的m_pData值
char* pTemp = strTemp.m_pData;
strTemp.m_pData = m_pData;
m_pData = pTemp;
}
return *this;
}