严格来讲不应该使用这个标题,因为我写这个代码只是练习了一下模板,这里并没有实现多少arraylist的东西,而且其中的原理也很不严谨。不过在这个代码中也学到了一些东西,在这里简单提及总结一下。
我的java_Arraylist类定义在一个hpp文件。首先是它的变量,一个T指针,用来指向数组,另外两个int,一个用来管理length,一个用来管理size。
这里之所以要使用指针,是方便后面数组扩容,可以方便的用指针指向扩容后的另一个数组。
template<typename T>
class java_Arraylist {
private:
T *arr;
int arr_len = 0;
int arr_size = 0;
然后是两个构造方法,指针指向一个初始化长度为10的数组,这里没什么好说的。
public:
java_Arraylist() {
arr = new T[10];
this->arr_len = 0;
this->arr_size = 10;
}
java_Arraylist(int length) {
arr = new T[length];
this->arr = arr[length];
this->arr_len = 0;
this->arr_size = length;
}
接着是get和size方法,在java中它们分别负责获取指定下标和获取length,此处没有做太多情况考虑,所以写得也很简单。
T get(int index) {
return this->arr[index];
}
int size() {
return arr_len;
}
最后是add方法,向list的最后一位插入数据。这里要考虑到数组容量的问题,进行扩容。
在将arr的值给到temp_arr后,arr指向的内存空间便可以释放了。随后将arr重新指向temp_arr指向的空间,所以此时不能delete[] temp_arr。只需要将它指向nullptr,将空间交由arr指针管理。
void add(T t) {
if (arr_len < arr_size) {
this->arr[arr_len] = t;
arr_len++;
}
else {
T* temp_arr = new T[arr_size * 2];
for (int i = 0; i < arr_len; i++) {
temp_arr[i] = arr[i];
}
delete[] arr;
arr = temp_arr;
temp_arr = nullptr;
arr[arr_len] = t;
arr_size = arr_size * 2;
arr_len++;
}
}
我在第一次写的时候,因粗心大意,在扩容部分T* temp_arr = new T[arr_size * 2]处,忘了*2,这样一来,新生成的数组和原来的容量一样大。然而通过main方法测试时,惊奇的发现,在add数据超过下标后,还可以继续add数据,甚至可以通过get打印出来。
这就很奇怪了,但是无论通过sizeof还是debug,都无法获取到new出来的数组的容量,难道说int* a = new int[10],它的容量不是10?
难道说我设置并维护的size变量,是自己跟自己过家家,没有起到应有的作用?
经过一番查询,我查到了这样一个资料:_msize()函数,可以通过指针获取到指针指向的空间内存大小,由于我创建的是int型,所以此处数组每个元素所占内存大小为4,根据_msize()得到40,果然对上了,但是为什么超过下标10还能继续读呢?
又测试了一番,得到一个关键字:缓冲区溢出。按照我的程序,编译器并不知道这里可能会越界,在此处甚至不会有任何提示,但如果直接在main函数中创建数组,并且在越界下标处插入数据,就会检测到下标越界,发出提示“缓冲区溢出”,但此时程序依然可以运行。
原来C++并不会检查数组的下标,指针只获得了数组的地址,没有获得数组的长度,所以程序可以运行成功。但这并不代表就可以这样做,因为数组越界,使用了不属于数组的未知的内存,如果这段内存中还有其他的数据,就可能造成不可预知的结果,程序员必须管理好数组的下标。