一个类中只要带有指针类型的成员,就必须自己写出big three(构造函数、拷贝构造函数,拷贝赋值函数),如果没有指针类型的成员,大部分情况下可以用默认的。
字符串类是一个经典的例子:
#ifndef __MYSTRING__
#define __MYSTRING__
#include <cstring>
#include <iostream>
using namespace std;
class String
{
public:
String(const char* cstr=0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
1.构造函数、析构函数和拷贝构造函数
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
inline
String::~String() /析构函数
{
delete[] m_data;
}
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
根据代码规范,不超过10行的函数可以定义为内联函数,所以在构造和析构函数前都加了inline,上面构造函数超过了10行,但是内联不内联是由编译器决定的,所以加上inline也没啥毛病。
构造函数中,如果传入的参数有效的话则new一个strlen(cstr)+1大小的数组给成员变量,并将参数拷贝到成员变量中;
如果传入的参数无效,则将’\0’传给成员变量,当然,也需要new一个字节的空间来保存。
构造函数的使用:
String s1();
String s2("world");
对于析构函数,由于构造函数是new了一个数组,所以析构函数也需要使用delete []。
拷贝构造函数的参数类型就是类的类型,初始化步骤如代码所示。
拷贝构造函数的使用:
String s2("world");
String s3(s2);
2.拷贝赋值函数
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
①拷贝赋值函数的返回值类型应是by reference(传引用),因为拷贝赋值函数是将一个对象赋值给另一个对象,即要复制的目的端已经存在,所以可以用引用;还有一个原因是调用拷贝赋值函数是有可能是连续赋值的,如s1 = s2 = s3; ,这也需要返回值类型为引用才可以。
②拷贝赋值函数必须要有检测自我赋值这一步,检测自我赋值一个好处是当是自我赋值时后面的几步不需要再执行,从而节省了时间。另一个必要原因就是如果没有检测自我赋值这一步,并且遇到自我赋值时,代码逻辑会出错。
构造函数在赋值前会delete自己的成员变量,如果是自我赋值的话,会清空当前对象的值,后面赋值时对象已经被delete了,所以对象中保存的内容已经不能确定是什么了。
关于返回值类型必须使用值传递的函数的问题
如下面的程序:
class complex
{
public:
complex (double r = 0, double i = 0): re (r), im (i) { }
double real () const { return re; }
double imag () const { return im; }
private:
double re, im;
};
inline double
imag (const complex& x)
{
return x.imag ();
}
inline double
real (const complex& x)
{
return x.real ();
}
inline complex
operator + (const complex& x, const complex& y)
{
return complex (real (x) + real (y), imag (x) + imag (y));
}
上面的“+”操作符重载函数的返回值就必须使用值传递,因为当函数返回后,函数中的变量就会随着函数返回,其生命周期也就随之结束,如果返回类型使用引用的话,那么引用指向的内存也是不存在的,所以这种函数的返回类型不能使用引用传递。
注:上面的“+”操作符重载函数应该有三个,此处只写了一个。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)