如果写成“int (*p)[]”的话,编译器必须能够在编译期推导p数组的长度才行,这个写法不等于变长数组,仅仅是让编译器自行推导长度而已。最常见的推导方式是直接给初始化数据。因此,你这个动态定长度的做法是不行的。如果是C++,可以用std::vector<std::vector<int> >做到这个效果,或者用int **p进行多次new也可以,但那样比较麻烦。
==== 分割线 ====
根据问题更新,下面是一个带解释的解法:
class Array2 {
int r, c;
int *p;
public:
Array2(int r, int c) : r(r), c(c) { // 比较优雅的方式是使用size_t,并通过异常机制报告错误参数
p = new int[r * c];
}
Array2() : r(0), c(0), p(nullptr) { } // 占位符。比较优雅的方式是通过异常机制进行容错检查,但鉴于本题甚至不提供stdexcept于是作罢
~Array2() { // 好习惯:及时回收空间
if (p) delete[] p;
}
int& operator()(int i, int j) { // 圆括号访问机制
return p[c * i + j]; // 二维数组偏移量机制:i行j列的元素相对于数组基址的偏移是c * i + j(字节偏移则要乘一个sizeof(value_type))
}
int* operator[](int i) { // 这里有点tricky,当使用类似a[i][j]的语法时,第一个方括号是调用本函数返回一个指针,第二个方括号则是原生语义
return p + c * i; // 和二维数组偏移机制同理,但第二个方括号由原生语义处理,我们重载的方括号运算符只负责返回第i行的一维数组,所以跟j没关系
}
Array2& operator=(const Array2& another) { // 值得注意的是,C++赋值的原生语义就是返回引用,自定义类中最好保持相同的习惯
if (p) delete[] p; // 记得释放之前的空间,虽然本题不这样写也无所谓
r = another.r;
c = another.c;
p = new int[r * c]; // 题目没说是要深拷贝还是浅拷贝,这里是按深拷贝来做的,但本题做浅拷贝也可以有相同效果
memcpy(p, another.p, r * c * sizeof(int)); // 深拷贝时记得对所有指针做内存复制操作,不能简单赋值
}
};
补充:上文提到r行c列二维数组的第i行第j列元素字节偏移是sizeof(value_type) * (i * c + j),这个结论成立的前提是:数组模型是按照从第一维存到最后一维的,也即每个靠前的维度的每个分量是连续的,在二维数组中表现为先行后列,但也有些语言的数组模型是先列后行。简单套用这个公式是不对的,尽管许多现代语言都有着和C++类似的先行后列的数组模型,但绝非所有语言都是如此。数组的具体存储方式和语言有关,和其他因素通常无关。在本题中表现为:如果在C++中使用先列后行的存储方式,那么方括号访问方式就不得不通过再封装一个内部类来实现,比较麻烦,但也不是做不到。