C++中如何支持多维数组:产生一个class,用以表现我们需要却被语言遗漏的对象,我们可以这样定义一个class template:
template
class Array2D
{
public:
Array2D(int dim1,int dim2);
...
};
我们可以这样定义数组:
Array2D data(10,20);
Array2D* data = new Array2D(10,20);
void processInput(int dim1,int dim2)
{
Array2D data(dim1,dim2);
...
}
但是,在数组对象的使用并不十分直截了当,为了保持C和C++一致性,我们希望能够方括号表现数组索引cout << data[3][6],我们必须重载operator[][],但是C++中并没有operator[][]操作符。
或许愿意以括号表现数组索引,必须重载operator(),但是这样做起来,必须这样使用:cout<
当调用data[10][20],变量data并非真正的二维数组,它其实是10个元素组成的一维数组,每个元素本身又是20个元素所组成的一维数组。 我们可以在Array2D class中玩相同把戏:将operator[]重载,令它返回一个Array1D对象。然后我们再对Array1D重载operator[],令它返回原来二维数组中的一个元素: 于是下面的动作就合法了: 每个Array1D对象象征着一个一维数组,观念上它并不存在与Array2D的用户心中,凡“用来代表(象征)其他对象”,ch昂被称为proxy objects,而用以表现proxy objects者,我们称为proxy classes。proxy objects有时候被称为surrogates(替代品)。 proxy classes用途很多,例如用来阻止单参constructors执行隐式转换,在proxy classes各种用于中,最明显的就是区分operator[]的读写动作了。 考虑上章reference-counted字符串类型,它支持operator[]。我们即将产生proxies来区别读写动作,对于一个proxy,你只有3件事情可做: 下面是一个reference-counted String class。其中利用proxy class来区分operator[]的左值运用和右值运用: 除了增加了CharProxy class以外,这个String class和上章那个String class之间唯一的区别是,此处的两个operator[]都返回CharProxy对象: 有趣的不是它能够有效运作,有趣的是它如何运作: 表达式s1[5]产生一个CharProxy对象,此对象没有定义output操作符,所以寻找隐式转换,使用operator char() const来执行,于是CharProxy所表现的字符串被打印出来,这是典型的CharProxy-to-char转换,发生在所有“被用来作为右值”的CharProxy对象身上。 左值运算处理方式又不同: 表达式s2[5]导出一个CharProxy,但这次调用的对象assignment动作的操作符。 同样道理,以下句子: 调用的是两个CharProxy对象的assignment操作符,在该操作符中我们知道左端对象用作左值,右端对象用作右值。 下面是String operator[]实现: 每个函数都只是产生并返回“被请求字符”的一个proxy。我们延缓此等行为,直到直到该行为是读取或者写。 operator[]返回的每一个proxy都会记住它所附属的字符串,以及它所在的索引位置: 将proxy转换为右值,是非常直接了当的事情——我们只需要返回该proxy所表现的字符串副本就行: 接下来实现CharProxy的assignment操作符。其间必须面对一个事实:proxy所代表的字符将被作为赋值动作的目标,也就是一个左值: 第二个CharProxy assignment操作符和上述版本几乎雷同: Proxy class很适合区分operator[]的左值和右值运算,但是对象也可能在其他情况下被当做左值使用,如果String::operator[]返回时个CharProxy而非char&将不能编译通过,因为“对proxy取址所获取的指针和对真实对象取址获取的指针类型不同”如下代码: 为了消除这个难点,我们需要在CharProxy class内将取址操作符重载: 实现如下: 如果我们有一个template用来实现reference-counted数组,其中利用proxy classes来区分operator[]被调用时的左值运算和右值运算,那么chars和其替代CharProxys第二个不同点是不支持+= ++这样的运算符,需要在下面的Proxy类中重载: 另一个问题就是“通过proxies调用真实对象的member functions”,如果你直接那么做,会失败。 原因:operator[]返回的是一个proxy对象而不是实际的Rational对象。成员函数只存在于Rational对象上,如果你想成功必须对proxy对这两个函数重载。 另一个proxy对象替代对象失败的情况作为非const的引用传递给函数: String::operator[]返回的是CharProxy对象,但swap却要求的使用char&,一个CharProxy对象可以隐式转换成char并不能转换成char&,因为这个char是一个临时对象(它是operator()返回值),所以拒绝将临时对象绑定为非const的引用形参是有原因的。 最后一种proxy对象不能无缝替换成实际对象的情况是隐式类型转换。当proxy对象隐式转换成它所扮演的实际对象时候,一个用户自定义转换函数被调用了。例如CharProxy对象可以通过operator()转换成char。编译器在调用函数而将参数转换成该函数需要的类型时,只调用了用户自定义类型的转换函数。于是,很有可能在函数调用的时候,传实际对象成功而传proxy对象失败。如下代码: Proxy类可以用来完成一些其他方法很难甚至不可能实现的问题:多维数组、区分左值和右值,限制隐式类型转换。 同时,proxy类也有缺点,作为函数的返回值,proxy对象是临时对象,它必须被构造和析构,这不是免费的。Proxy增加的软件的复杂度,因为额外增加的类使得事情更难设计、实现、理解和维护。 最后,从一个实际对象类改换到处理proxy对象类改变的类的语意。因为proxy对象通常表现出的行为和实际对象的微妙区别,需要针对proxy对象进行隐式类型转换。 template
Array2D
class String //reference-counted strings
{
public:
class CharProxy
{
public:
CharProxy(String& str,int index); //构造
CharProxy& operator = (const CharProxy& rhs); //左值运算
CharProxy& operator = (char c); //右值运算
operator char() const;
private:
String& theString; //这个proxy附属的字符串
int charIndex; //这个proxy所代表的字符
};
const CharProxy operator[](int index) const; //针对const strings
CharProxy operator[](int index); //针对non-const strings
...
friend class CharProxy;
private:
RCPtr
String s1,s2;
...
cout << s1[5]; //合法
s2[5] = 'x'; //合法
s1[3] = s2[8]; //合法
cout << s1[5];
s2[5] = 'x';
s1[3] = s2[8];
const String::CharProxy String::operator[](int index) const
{
return CharProxy(const_cast
String::CharProxy::CharProxy(String& str,int index)
:theString(str),charIndex(index){}
String::CharProxy::operator char() const
{
return theString.value->data[index];
}
String::CharProxy&
String::CharProxy::operator = (const CharProxy& rhs)
{
//如果字符串与其他String对象共享实值
//将实值复制一份,供本字符串单独使用
if(theString.value->isShared())
theString.value = new StringValue(theString.value->data);
//现在进行赋值动作:将rhs所代表的字符值
//赋值*this所代表的字符
theString.value->datap[charIndex] =
rhs.theString.value->data[rhs.charIndex];
return *this;
CharProxy& String::CharProxy::operator = (char c)
{
if(theString.value->isShared())
theString.value = new StringValue(theString.value->data);
theString.value->data[charIndex] = c;
return *this;
}
String s1 = "Hello";
char* p = &s1[1];
class String
{
public:
class CharProxy
{
public:
...
char* operator&();
const char* operator() const;
...
};
};
const char* String::CharProxy::operator() const
{
return &(theString.value->data[charIndex]);
}
char* String::CharProxy::operator&()
{
//确定“标的字符”所属的字符串实值不为任何其他
//String对象共享
if(theString.value->isShared())
theString.value = new StringValue(theString.value->data);
//我们不知道clients会将本函数返回的指针保存多久,所以
//“目标字符”所属的字符串绝不可以被共享
theString.value->markUnshanreable();
return &(theString.value->data[charIndex]);
}
template
class Rational
{
public:
Rational(int numerator = 0,int denominator = 1);
int numerator() const;
int denominator() const;
...
};
Array
void swap(char& a,char& b);
String s = "+C+";
swap(s[0],s[1]); //有问题
class TVStation
{
public:
TVStation(int channel);
...
};
void watchTV(const TVStation& station,float hourToWatch);
//借助int到TVStation隐式类型转换
watchTV(10,2.5); //成功
Array