数组类模板中的[ ]运算符重载问题
分类:C++
丝毫不夸张,这真的是我在大便的时候想出来的...[speechless]
注:在《C++ Primer Plus》6th P577,书上光是使用了如下代码,但是没有分析为什么:
//arraytp.h -- Array Template
#ifndef ARRAYTP_H_
#define ARRAYTP_H_
#include <iostream>
#include <cstdlib>
using namespace std;
template <typename Type, int n>
class ArrayTP
{
public:
ArrayTP() {}
explicit ArrayTP(const Type & v);
virtual Type & operator[](int i);
virtual Type operator[](int i) const;
private:
Type ar[n];
};
template <typename Type, int n>
ArrayTP<Type, n>::ArrayTP(const Type & v)
{
for (int i = 0; i < n; ++i)
{
ar[i] = v;
}
}
template <typename Type, int n>
Type & ArrayTP<Type, n>::operator[](int i)
{
if (i < 0 || i >= n)
{
cerr << "Error in array limits: " << i << " is out of range\n";
exit(EXIT_FAILURE);
}
return ar[i];
}
template <typename Type, int n>
Type ArrayTP<Type, n>::operator[](int i) const
{
if (i < 0 || i >= n)
{
cerr << "Error in array limits: " << i << " is out of range\n";
exit(EXIT_FAILURE);
}
return ar[i];
}
#endif
首先请注意,为什么在类中重载了两次[ ]运算符,而且实现的功能“完全相同”,除了一个为返回引用为非const版本,一个返回值为const版本。两个函数的返回都不是局部变量,返回引用和值有区别吗?如果追求效率那就都用返回引用不就好了吗?为什么引用对应的是非const版本而值对应的是const版本?这是巧合吗?
然后,又不由分说的使用了如下代码:
//twod.cpp -- making a 2-d array
#include <iostream>
#include "array.h"
int main(void)
{
using std::cout;
using std::endl;
ArrayTP<int, 10> sums;
ArrayTP<double, 10> aves;
ArrayTP<ArrayTP<int, 5>, 10> twodee;
int i, j;
for (i = 0; i < 10; ++i)
{
sums[i] = 0;
for (j = 0; j < 5; ++j)
{
twodee[i][j] = (i + 1) * (j + 1);
sums[i] += twodee[i][j];
}
aves[i] = (double) sums[i] / 10;
}
for (i = 0; i < 10; ++i)
{
for (j = 0; j < 5; ++j)
{
cout.width(2);
cout << twodee[i][j] << ' ';
}
cout << ": sum = ";
cout.width(3);
cout << sums[i] << ", average = " << aves[i] << endl;
}
cout << "Done.\n";
return 0;
}
先说个题外话,这里使用了模板的递归多功能性:ArrayTP<ArrayTP<int, 5>, 10> twodee; 。这使得 twodee 是一个包含10个元素的数组,其中每个元素都是一个包含5个 int 元素的数组。与之等价的常规数组声明:int twodee[10][5]; 。请注意,在模板语法中,维的顺序与等价的二维数组相反,想清楚了原因就很容易记忆了,就用这个例子来说,这是声明的一个10行5列的数组,在模板数组中,先看内层:声明一个5维数组(一行有5个元素,即我们把它看成5列),再看外层,声明一个10维数组(一列有10个元素,即我们把它看成10行,每一行都是一个5维数组),所以与常规数组的声明顺序相反。但是!在使用时,与常规数组的使用方法无二异,即:twodee[i][j],先是
i 行,然后是 j 列。
等等等等,有个疑点,为什么声明的顺序相反,而调用的顺序相同,而且调用的时候又是使用的[
]运算符,想到这里,我就觉得找到了突破口。
Brainy is new sexy.
- 为什么有const和非const两个版本?
因为在程序中有的地方将 ar[i] 作为右值(cout << sums[i];),有的地方将 ar[i] 作为左值(sums[i] = 0;),有的地方两种版本都使用了(sums[i] += twodee[i][j];、aves[i] = (double) sums[i] / 10;)。作为右值时不需要修改数据,是const版本;作为左值时需要修改数据,是非const版本。
- 为什么引用对应的是非const版本而值对应的是const版本?
因为返回值时(const)不需要修改成员变量(右值),返回数组的副本,所以使用const修饰符限制成员函数;而返回引用时(非const)需要修改成员变量(左值),所以只能用引用和非const。而且返回引用只能对应非const(需要修改成员数据,不能加const限定符),返回值只能对应const(如果为了追求效率,const版本加上&变成返回引用,则不能使用const限定符,因为编译器无法将带有const限定符的数据转换为非const数据,这也是引入const限定符作为保护数据不被随意修改的初衷,详见我的《const修饰符的作用》;如果为了编译通过去掉const的话,不就变成了返回引用的非const版本了吗,哈哈,这里就有了两个相同的函数声明,没必要声明两次,当然编译器也不允许。)
- 为什么声明的顺序相反而调用的顺序相同?二维模板数组的使用究竟是怎样实现的?(以左值为例)
twodee[i][j] <==> (twodee.operator[ ](i)).operator[ ](j)
首先是内层调用,这里使用的是返回引用非const版本,调用结束之后,函数返回的是一个对象的引用,所以可以再次调用运算符成员函数;
其次是外层调用,由于内层调用函数的返回值为一个对象的引用,所以可以再次调用运算符成员函数,两个都应该是返回引用的非const版本,因为是左值需要修改数据。
具体的调用哪个版本的运算符成员函数,最终还是取决于编译器对具体语句功能的解析。
到此,基本讲清了这里面的关系,yeah!
如果有收获,可以请我喝杯咖啡!