同不带指针的类一样,防卫式声明、前置声明都要有。然后进行类的定义、函数(成员函数和全局函数)的声明和定义。
这部分内容的大体框架和不带指针的类相同,可以参考:C++学习4:详解不带指针的类(侯捷Complex类为例)。下面就不同之处加以剖析。
为了清楚看出与不带指针的类主要的不同之处,下面请直接看main函数:
int main()
{
String s1();
String s2("hello");
String s3(s1); //第一次出现,为了构造:拷贝构造
cout << s3 << endl;
s3 = s2; //第二次出现,为了赋值:拷贝赋值
cout << s3 << endl;
}
可见,这里有两个主要的操作:拷贝构造、拷贝赋值。其所做的工作很简单:就是一个bit一个bit的进行复制及赋值,Complex类中没有定义,这时编译器会给它一套,所以也有。我们要在有指针的类中自己定义拷贝构造和拷贝赋值,因为默认的那一套拷贝一个指针,只是单纯的指向了一个位置,不是真正的构造,故会出现问题。所以默认的版本不一定够用,起到的效果并不是我们期望的。
因此:只要类中带有指针,一定要自己写出拷贝构造和拷贝赋值操作!
class String
{
public:
String(const char* cstr = 0); //构造函数
String(const String& str); //拷贝构造函数 -> 1
String& operator= (const String& str); //拷贝赋值函数 -> 2
~String(); //析构函数 -> 3
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
设计方案:因为不确定其字符串的大小,故让字符串里面拥有一根指针,需要内存的时候才去创建。这种动态分配的方式要更好些,把这跟指针放在private里面。
get_c_str()函数:传回一个char类型的指针,并没有改变这个类中的数据,所以加上了const(详见最上方链接)。
Big Three:
1、拷贝构造函数:首先是构造函数,以自己类型名作为函数无返回值。但他接受的是和自己类型相同的东西。如本例中参数也是String类型,这就是拷贝构造。
2、拷贝赋值函数:对赋值操作符进行重载,可见这是个赋值的操作。但他接受的也是和自己类型相同的东西。如本例中参数也是String类型,这就是拷贝赋值。
3、析构函数:当这个类的对象死亡的时候(如离开其作用域时),会被调用。
inline
String::String(const char* cstr = 0)//拷贝构造函数,默认初始值为0,即若没有则为0
{
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)//拷贝赋值,这里有个隐藏的第一参数this
{
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;
}
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
以上就是全部的定义。要注意,有构造函数就要有析构函数。析构过程如下例(这里还有一种新的创建方法):
{
String s2("hello");
String *p = new String("world");
delete p;
}
s2在离开花括号时就自动调用了析构函数~String(),但这里必须要delete掉p。具体delete的原因见本文第3部分。
String s2(s1);//s2是新创建出来的,用构造函数,故用拷贝构造
String s2 = s1;//s2是有的,要把s1指向的内容拷贝赋给s2,故用拷贝赋值
以下两条语句表示,a这个指针指向“hello”,b这个指针指向“world”。
如果用默认版本的拷贝赋值,将a指向的值赋给b,则根据bit对bit的赋值,结果是:b的指针指向a的指针所指的位置。
String a("hello");
String b("world");
这种情况称为“浅拷贝”造成的后果:
1、“world”这个位置内存泄漏
2、a和b指向相同位置,对指针a的操作直接影响这段内存,b也会受到影响。如a改为“HELLO”,b也变成“HELLO”,出现“别名”现象。
3、两个指针指向一个内存,如果free掉一个,另一个变成野指针,非常危险。
深拷贝:做到真正的拷贝,就是前面定义的那种拷贝构造和拷贝赋值:
拷贝构造:以s1为蓝本,构造和s1相同的s2,而且在不同的内存空间。
把要被赋值的内存先清空,然后分配一段和要赋值的内容一样大的空间,最后把应该存入的东西存进去。
1、检测自我赋值(功力深厚);
2、先delete掉自己。
3、再开辟空间。
4、最后拷贝进去。