C++学习5:详解带指针的类(侯捷String类为例)

同不带指针的类一样,防卫式声明、前置声明都要有。然后进行类的定义、函数(成员函数和全局函数)的声明和定义。
这部分内容的大体框架和不带指针的类相同,可以参考:C++学习4:详解不带指针的类(侯捷Complex类为例)。下面就不同之处加以剖析。

1 代码部分

1.1 main函数

为了清楚看出与不带指针的类主要的不同之处,下面请直接看main函数:

int main()
{
	String s1();
	String s2("hello");

	String s3(s1);		//第一次出现,为了构造:拷贝构造
	cout << s3 << endl;

	s3 = s2;			//第二次出现,为了赋值:拷贝赋值
	cout << s3 << endl;
}

可见,这里有两个主要的操作:拷贝构造拷贝赋值。其所做的工作很简单:就是一个bit一个bit的进行复制及赋值,Complex类中没有定义,这时编译器会给它一套,所以也有。我们要在有指针的类中自己定义拷贝构造和拷贝赋值,因为默认的那一套拷贝一个指针,只是单纯的指向了一个位置,不是真正的构造,故会出现问题。所以默认的版本不一定够用,起到的效果并不是我们期望的。

因此:只要类中带有指针,一定要自己写出拷贝构造和拷贝赋值操作!

1.2 类的定义

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、析构函数:当这个类的对象死亡的时候(如离开其作用域时),会被调用。

1.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部分。

2 类中带指针必须有copy_ctor和copy_op=的原因

String s2(s1);//s2是新创建出来的,用构造函数,故用拷贝构造
String s2 = s1;//s2是有的,要把s1指向的内容拷贝赋给s2,故用拷贝赋值

2.1 拷贝构造

以下两条语句表示,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,而且在不同的内存空间。

2.2 拷贝赋值

把要被赋值的内存先清空,然后分配一段和要赋值的内容一样大的空间,最后把应该存入的东西存进去。
1、检测自我赋值(功力深厚);
2、先delete掉自己。
3、再开辟空间。
4、最后拷贝进去。

你可能感兴趣的:(侯捷老师C++)