动态数组索引越界问题

1、在C++中,能够採用几种不同的方法创建一个某种类型T的对象的数组。3种经常使用的方法例如以下:

#define N 10 //数组的长度N在编译时已知
  T static_array[10];

  int n = 20; //数组的长度n是在执行时计算的
  T* dynamic_array = new T[n];

  std::vector<T> vector_array; //数组的长度能够在执行时进行改动

当然,我们仍然能够使用calloc()和malloc()函数,而且这种程序仍然能够通过编译并顺利执行。可是,混合C和C++代码并非良好的编程思路,除非因为依赖遗留的C函数库的原因而必须这样做。无论我们用什么方法分配数组,都能够用一个无符号整数作为索引訪问数组中的元素。

const T& element_of_static_array = static_array[index];
const T& element_of_dynamic_array = dynamic_array[index];
const T& element_of_vector_array = vector_array[index];

假设我们提供一个大于或等于数组长度的索引值,会发生什么情况呢?以上的代码都会安静的返回垃圾数据。假设我们决定在赋值符的左边使用[]操作符,情况就会变得更糟。

some_array[index] = x;
取决于运气,这个操作可能会重写其它某个不相关的变量,一个其它数组的元素甚至是一条程序指令。在最后一种情况下,程序非常可能会崩溃。每种错误都向恶意入侵者提供机会接管程序并产生不良后果。可是,std::vector提供了一个at(index)函数,它通过抛出一个out_of_range异常运行边界检查。它的问题在于假设我们想运行这样的安全检查,必须在訪问数组元素的每一个地方都严格地使用at()函数。显然,这样的做法会减少代码的效率。因此在完毕了測试之后,我们可能想用速度更快的[]操作符在每处对它进行替换。可是,这样的替换须要对代码进行大量的改动,工作量非常大,而且在此之后还要对代码进行又一次測试,由于在这个乏味的过程中,非常可能会不小心输错数据。

因此较之使用at()函数,使用下面的方法。虽然动态数组使[]操作符全然超出自己的控制,但STL的vector容器把它实现为一个C++函数,我们能够依据自己的缺陷捕捉目标对它进行改写,比如,又一次定义[]操作符:

T& operator [](size_type index)
{
	SCPP_TEST_ASSERT(index < std::vector<T>::size(),
		"Index "<<index<<" must be less than "
		<<std::vector<T>::size());
	return std::vector<T>::operator[](index);
}

const T& operator [](size_type index) const
{
	SCPP_TEST_ASSERT(index < std::vector<T>::size(),
		"Index "<<index<<" must be less than "
		<<std::vector<T>::size());
	return std::vector<T>::operator[](index);
}

使用上面定义的文件里的代码,举比例如以下:

#include <iostream>
#include "scpp_vector.hpp"

using namespace std;

int main()
{
	scpp::vector<int> vect;
	for(int i=0; i<3; i++)
		vect.push_back(i);

	cout<<"My vector = "<<vect<<endl;

	for(int i=0; i<=vect.size(); i++)
		cout<<"Value of vector at "<<i<<" is "<<vect[i]<<endl;

	return 0;
}
首先,我们并没有採用std::vector<int>或简单的vector<int>这种写法,而是採用了scpp::vector<int>的形式。这是为了把我们的vector与STL的vector区分开来。通过使用scpp::vector,我们把标准实现替换为我们自己的安全实现。scpp::vector还提供了一个<<操作符,因此仅仅要vector不要太大,而且类型T定义了<<操作符,就能够用这个操作符打印vector。

在第二个for循环中,我们没有使用i<vect.size()的写法,而是写成了i<=vect.size()。这是一个极为常见的编程错误,我们仅仅是为了观察当索引越界时会出现什么情况,事实上,程序的结果输出为:

My vector = 0 1 2
     Value of vector at 0 is 0
     Value of vector at 1 Value of vector at 2 is 2
     Index 3 must be less than 3 in file scpp_vector.hpp
仅仅要SCPP_TEST_ASSERT_ON符号已被定义,这个安全检查就会起作用,并能够方便的依据须要在编译时打开或关闭这个检查。这样的方法的问题是vector的[]操作符在循环内部的使用极为频繁,因此这样的安全检查会被大量使用,由于会显著地减少程序的运行速度,就像使用at()函数一样。假设认为这样的方法不适合自己的程序,能够定义一个新宏,比如SCPP_TEST_ASSERT_INDEX_OUT_OF_BOUNDS,它的工作方式与SCPP_TEST_ASSERT全然同样,但仅仅在scpp::vector::operator[]内部使用。SCPP_TEST_ASSERT_OUT_OF_BOUNDS与SCPP_TEST_ASSERT的差别应该是它的打开或关闭与SCPP_TEST_ASSERT宏无关,因此假设确信代码没有缺陷,就能够使它处于未激活状态,同一时候又让其它的安全检查继续有效。

除了同意捕捉索引越界错误之外,vector模板较之静态分配的数组和动态分配的数组另一个长处,即它的长度能够依据须要增长(仅仅要内存没有耗尽)。可是,这个长处是要付出代价的。假设一个vector预先并未声明须要多少内存,它就会分配一些默认内存(称为它的“容量”)。当这个vector的实际大小达到了这个容量时,它将分配一块更大的内存,把旧数据拷贝到这块新的内存区域,并用它替换旧的那块内存。因此,随着时间的推移,向vector模板加入一个新元素可能突然变得很缓慢。因此,假设预先知道须要多少元素,应该像静态数组和动态数组一样,在构造函数中告诉vector须要多少内存:

scpp::vector<int> vect(n);
这样就创建了一个具有指定元素数量的vector,事实上我们还能够写成例如以下:

scpp::vector<int> vect(n, 0);
它还把全部的元素初始化为一个指定的值。

还有一种替代方法是创建一个0个元素的vector,可是指定它所须要的容量。

scpp::vector<int> vect;
vect.reserve(n);

这个样例和前一个样例的差别在于,这个样例中的vector是空的(即vector.size()的返回值是0),可是当我们開始向它加入元素时,在它的长度达到n之前,不会出现导致速度减少的容量增长现象。




原文地址:https://www.cnblogs.com/mengfanrong/p/3955384.html