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

来源:互联网 发布:淘宝卖家怎么品牌授权 编辑:程序博客网 时间:2024/06/06 07:11

二、一致性

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;}


原创粉丝点击