c++编程风格----读书笔记(2)

二、一致性

1、一致性示例,如下程序:

#include "stdafx.h"
#include "iostream"

class string
{
public:
	string()
	{
		str_ = new char[80];
		len_ = 80;
	}
	string(int len)
	{
		str_ = new char[len];
		len_ = len;
	}
	string(char *p)
	{
		len_ = strlen(p);
		str_ = new char[len_];
		strcpy(str_, p);
	}
	string(string &str);
	~string()
	{
		delete[] str_;
	}

public:
	void Assign(char *str)
	{
		strcpy(str_, str);
		len_ = strlen(str);
	}
	void Print()
	{
		std::cout << str_ << std::endl;
	}
	void concat(string &a, string &b);
private:
	char *str_;
	int  len_;
};

string::string(string &str)
{
	len_ = str.len_;
	str_ = new char[len_];
	strcpy(str_, str.str_);
}

void string::concat(string &a, string &b)
{
	len_ = a.len_ + b.len_;
	str_ = new char[len_];
	strcpy(str_, a.str_);
	strcat(str_, b.str_);
}

int _tmain(int argc, _TCHAR* argv[])
{
	char *str = "The wheel that squeaks the loudest\n";
	string a(str);
	string b;
	string author("Josh Billings\n");
	string both;
	string quote;
	b.Assign("Is the one that gets the grease\n");
	both.concat(a, b);
	quote.concat(both, author);
	quote.Print();

	return 0;
}

不足的地方

a、应该明确定义的状态

比如使用了string x, y(128); x.print();

那现在x的值是不确定的,输出的时候必要要等到’\0’才会停止(所有有构造函数应该使得对象处于明确的定义状态)

b、物理状态的一致性

看两段代码

	string(char *p)
	{
		len_ = strlen(p);
		str_ = new char[len_ + 1];
		strcpy(str_, p);
	}

void string::concat(string &a, string &b)
{
	len_ = a.len_ + b.len_;
	str_ = new char[len_];
	strcpy(str_, a.str_);
	strcat(str_, b.str_);
}

在这两个函数中len_所表示的含义有两个,一个是字符串的长度,另外一个是数组的长度;显然,len_的这两种含义都是有意义的,但在所有的构造函数及其他的成员函数中,必须只能有一种(所有的成员必须只有一个明确的定义,用一致的方式来定义对象的状态,这需要识别出类不变性)

c、动态内存的一致性

看这里的三段代码:

string()
{
	str_ = new char[80];
	len_ = 80;
}

string(int len)
{
	str_ = new char[len];
	len_ = len;
}

void string::concat(string &a, string &b)
{
	len_ = a.len_ + b.len_;
	str_ = new char[len_];
	strcpy(str_, a.str_);
	strcat(str_, b.str_);
}

    可以看到,上面动态分配的内存都是不一致的;要做到一致,有两种选择,一是只能是确保一开始分配的空间足够大,二是对每个字符串值都动态地决定数组的大小以保证安全。

    这两种方法都可以用在类中,但只能使用其中的一种,以保持类的一致性,而不应该将这两种方法混合使用。否则,在使用这个类时,不得不去了解在接口中不同操作之间的不用约定(类的接口定义应该是一致的--------避免产生困惑)

d、动态内存的回收

来看字符串连接的一个问题

void string::concat(string &a, string &b)
{
	len_ = a.len_ + b.len_;
	str_ = new char[len_];
	strcpy(str_, a.str_);
	strcat(str_, b.str_);
}

原来的空间显然被泄露掉了(对于每个new操作,都要有相应的delete操作)

看看重新设计的一个string

#include "stdafx.h"
#include "iostream"

class string
{
public:
	string();
	string(const char *pStr);
	string(string& str);
	~string();
	
	const char *content() const;
	string& operator=(const char *pStr);
	string& operator=(const string &str);

private:
	char *string_;
	int  length_;
};

char *Strdup(const char *pStr)
{
	char *pTemp = new char[strlen(pStr) + 1];
	strcpy(pTemp, pStr);

	return pTemp;
}

string::string()
{
	string_ = 0;
	length_ = 0;
}

string::string(const char *pStr)
{
	string_ = pStr ? Strdup(pStr) : 0;
	length_ = pStr ? strlen(string_) : 0;
}

string::string(string& str)
{
	if (str.string_)
	{
		string_ = Strdup(str.string_);
		length_ = strlen(string_);
	}
	else
	{
		string_ = 0;
		length_ = 0;
	}
}

string::~string()
{
	if (string_)
	{
		delete[] string_;
	}
}

const char *string::content() const
{
	return string_ ? string_ : 0;
}

string& string::operator=(const char *pStr)
{
	delete[] string_;
	string_ = pStr ? Strdup(pStr) : 0;
	length_ = pStr ? strlen(string_) : 0;
	return *this;
}

string& string::operator=(const string &str)
{
	delete[] string_;
	string_ = str.string_ ? Strdup(str.string_) : 0;
	length_ = string_     ? strlen(string_) : 0;
	return *this;
}

int _tmain(int argc, _TCHAR* argv[])
{
	string author("zengraoli");

	return 0;
}

该类使用了动态分配的形式,这样更能有效的避免数组的溢出

但是这里还有一些不足的地方:

a、冗余

length_的引入,一开始是为了表示字符串的长度,但是在这里这个信息却从来没有使用过。在string中,当每次需要字符串长度时,这个值都将在辅助函数Strdup中重新计算,所以这个状态信息是多余的(避免对从不使用的状态信息进行计算和存储)

b、在operator=中存在的一些问题

    可以看到在operator=(const string &str)上面,先是delete,虽然不太容易写出像x=x这样的表达式,但是在程序中可能会简接地导致这种复赋值运算的发生(如果ab碰巧都是引用了同一个string对象,纳闷呢a=b就等价于x=x)。虽然先delete就会导致操作未定义的内存。

所以最好的方式是首先处理自赋值

string& string::operator=(const string &str)
{
	if (&str == this)
	{
		return *this;
	}

	delete[] string_;
	string_ = str.string_ ? Strdup(str.string_) : 0;
	length_ = string_     ? strlen(string_) : 0;
	return *this;
}    

    对于operator=(const char *pStr)中的delete操作,可能会导致问题的情况是x=x.string(),或者虽然以a=b.string()的形式出现,但ab的字符指针指向的是同一个地址。在执行x=s的赋值运算时,如常量字符类型指针s的值的等于x.string(),那么将会产生和上面同样的问题。解决的办法是将delete操作符推迟到字符串被复制之后在进行

string& string::operator=(const char *pStr)
{
	char *prevString = string_;
	string_ = pStr ? Strdup(pStr) : 0;
	length_ = pStr ? strlen(string_) : 0;
	delete[] prevString;
	return *this;
}

c、最后一个细节的问题

关于Strdup函数,这个函数的作用是对字符串进行复制并存储在一块新分配的内存中。你会发现上面的代码,在调用该函数的时候都遵循着同样的形式:在每次调用之间,都要进行测试以保证参数指针是非空的;在从函数中返回之后,返回结果都是保存在string_中。因此可以看到简化的地方。

最终得到的修改版本:

#include "stdafx.h"
#include "iostream"

class string
{
public:
	string();
	string(const char *pStr);
	string(string& str);
	~string();
	
	const char *content() const;
	string& operator=(const char *pStr);
	string& operator=(const string &str);

private:
	void duplicate(const char *pStr);

private:
	char *string_;
};

void string::duplicate(const char *pStr)
{
	if (pStr)
	{
		string_ = new char[strlen(pStr) + 1];
		strcpy(string_, pStr);
	}
	else
	{
		string_ = 0;
	}
}

string::string()
{
	string_ = 0;
}

string::string(const char *pStr)
{
	duplicate(pStr);
}

string::string(string& str)
{
	duplicate(str.string_);
}

string::~string()
{
	if (string_)
	{
		delete[] string_;
	}
}

const char *string::content() const
{
	return string_ ? string_ : 0;
}

string& string::operator=(const char *pStr)
{
	char *prevString = string_;
	duplicate(pStr);
	delete[] prevString;
	return *this;
}

string& string::operator=(const string &str)
{
	if (&str == this)
	{
		return *this;
	}

	delete[] string_;
	duplicate(str.string_);
	return *this;
}

int _tmain(int argc, _TCHAR* argv[])
{
	string author("zengraoli");

	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值