原书抄录:
- 在设计一个类时,程序员通常需要从这两个方面进行考虑。接口必须能够代表一致的抽象,而实现则必须使得对象在行为上与这个抽象保持一致。
- 任何时候,一个对象都是处于某种状态(state),这种状态是由对象中所有数据成员共同的值决定的。
接口和实现可以通过不同的模型来表示对象状态,这也分别称为逻辑状态和和物理状态。逻辑状态模型通常是物理状态模型的简化,多个物理状态可以对应于一个逻辑状态。
在创建一个类时,一致性是很重要的一个方面。
- 从外部来看,当客户通过类的公有借口来操作对象时,对象的行为必须是一致的。
- 从内部来看,类的实现在对象的状态上必须保持是一致的。
规则总结:
- 构造函数应该使得对象处于定义明确的状态
- 我们一个考虑使用默认参数的形式来代替函数重载的形式
- 用一致的方式来定义对象的状态—–这需要识别出类不变性
- 类的接口定义应该是一致的—–避免产生困惑
- 对于每个new操作,都要有对应的delete操作
- 避免对不使用的状态信息进行计算和存储(只有当信息在后续操作中需要用到时,才应该被存储)
- 在定义operator=时,我们要注意x= x 这种情况
- ‘用一个通用的函数来代替重复的表达式序列
章后练习题
原题如下:
程序清单 2-5 另一个string 类
(注:在代码中,因为类名string与标准库中的string类冲突,我改成了String,#include <iostream.h>
改成了#include <iostream>
)
#include <iostream>
#include <string.h>
#include <stdlib.h>
using namespace std;
class String {
struct srep {
char* s; // pointer to data
int n; // referenc count
srep() { n = 1; }
};
srep *p;
public:
String(const char *); // String x = "abc"
String(); // String x;
String(const String &); // String x = String ...
String& operator=(const char *);
String& operator=(const String &);
~String();
char& operator[](int i);
friend ostream& operator<<(ostream&, const String&);
friend istream& operator>>(istream&, String&);
friend int operator==(const String &x, const char *s)
{ return strcmp(x.p->s, s) == 0; }
friend int operator==(const String &x, const String &y)
{ return strcmp(x.p->s, y.p->s) == 0; }
friend int operator!=(const String &x, const String &y)
{ return strcmp(x.p->s, y.p->s) != 0; }
};
String::String()
{
p = new srep;
p->s = 0;
}
String::String(const String& x)
{
x.p->n++;
p = x.p;
}
String::String(const char* s)
{
p = new srep;
p->s = new char[ strlen(s) + 1 ] ;
strcpy(p->s, s);
}
String::~String()
{
if (--p->n == 0){
delete[] p->s;
delete p;
}
}
String& String::operator=(const char* s)
{
if (p->n > 1) { // disconnect self
p->n --;
p = new srep;
}
else // free old String
delete[] p->s;
p->s = new char[ strlen(s) + 1 ];
strcpy(p->s, s);
return *this;
}
String& String::operator=(const String& x)
{
x.p->n ++; // protect against ''st == st''
if (--p->n == 0) {
delete[] p->s;
delete p;
}
p = x.p;
return *this;
}
ostream& operator<<(ostream& s, const String& x)
{
return s << x.p->s << "\n";
}
istream& operator>>(istream& s, String& x)
{
char buf[256];
s >> buf; // unsafe, might overflow
x = buf;
return s;
}
char & String::operator[](int i)
{
if (i<0 || strlen(p->s)<i) abort();
return p->s[i];
}
// sample driver:
// read and echo words from input
// at "done", output words in reverse order
int main()
{
String x[100];
int n;
cout << "here we go\n";
for (n = 0; cin >> x[n]; n++) {
if (n==100)
abort();
String y;
cout << (y = x[n]);
if (y=="done") break;
}
cout << "here we go back again\n";
for (int i=n-1; i>=0; i--) cout << x[i];
return 0;
}
解答:
2.1:赋值运算符函数返回不确定, 因为出现“x=x”的情况, 导致内存先被释放掉了。之后再拷贝的行为是未定义的。
2.2:
首先我们看3个构造函数, 在构造函数String::String()
里面构造了一个空的String, 但在
String::String(const String& x)
和String::String(const char* s)
里面却不是空的,但在释放的和拷贝的时候, 默认String对象并不是空的, 因此应该将构造函数String::String()
与其他的构造函数保持一致。
最好的方法就是和将两个构造函数合并:
String::String(const char* s = 0)
{
p = new srep;
p->s = new char[ strlen(s) + 1 ] ;
strcpy(p->s, s);
}
另外在赋值函数中String& String::operator=(const char* s)
可能会存在“x=x”的情况, 因此改写为:
String& String::operator=(const char* s)
{
if(p->s == s){
return *this;
}
if (p->n > 1) { // disconnect self
p->n --;
p = new srep;
}
else // free old String
delete[] p->s;
p->s = new char[ strlen(s) + 1 ];
strcpy(p->s, s);
return *this;
}
我们还可以写一个小函数来代替重复的复制操作:
void String::copy(const char *s)
{
p->s = new char[ strlen(s) + 1 ] ;
strcpy(p->s, s);
}
修改后的代码:
#include <iostream>
#include <string.h>
#include <stdlib.h>
using namespace std;
class String {
struct srep {
char* s; // pointer to data
int n; // referenc count
srep() { n = 1; }
};
srep *p;
void copy(const char *);
public:
String(const char *s = ""); // String x = "abc"
String(const String &); // String x = String ...
String& operator=(const char *);
String& operator=(const String &);
~String();
char& operator[](int i);
friend ostream& operator<<(ostream&, const String&);
friend istream& operator>>(istream&, String&);
friend int operator==(const String &x, const char *s)
{ return strcmp(x.p->s, s) == 0; }
friend int operator==(const String &x, const String &y)
{ return strcmp(x.p->s, y.p->s) == 0; }
friend int operator!=(const String &x, const String &y)
{ return strcmp(x.p->s, y.p->s) != 0; }
};
void String::copy(const char *s)
{
p->s = new char[ strlen(s) + 1 ] ;
strcpy(p->s, s);
}
String::String(const String& x)
{
x.p->n++;
p = x.p;
}
String::String(const char* s)
{
p = new srep;
copy(s);
}
String::~String()
{
if (--p->n == 0){
delete[] p->s;
delete p;
}
}
String& String::operator=(const char* s)
{
if(p->s == s){
return *this;
}
if (p->n > 1) { // disconnect self
p->n --;
p = new srep;
}
else // free old String
delete[] p->s;
copy(s);
return *this;
}
String& String::operator=(const String& x)
{
x.p->n ++; // protect against ''st == st''
if (--p->n == 0) {
delete[] p->s;
delete p;
}
p = x.p;
return *this;
}
ostream& operator<<(ostream& s, const String& x)
{
return s << x.p->s << "\n";
}
istream& operator>>(istream& s, String& x)
{
char buf[256];
s >> buf; // unsafe, might overflow
x = buf;
return s;
}
char & String::operator[](int i)
{
if (i<0 || strlen(p->s)<i) abort();
return p->s[i];
}
// sample driver:
// read and echo words from input
// at "done", output words in reverse order
int main()
{
String x[100];
int n;
cout << "here we go\n";
for (n = 0; cin >> x[n]; n++) {
if (n==100)
abort();
String y;
cout << (y = x[n]);
if (y=="done") break;
}
cout << "here we go back again\n";
for (int i=n-1; i>=0; i--) cout << x[i];
return 0;
}
运行截图: