动态数组索引越界问题

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之前,不会出现导致速度降低的容量增长现象。

动态数组索引越界问题,布布扣,bubuko.com

时间: 2024-08-07 21:18:37

动态数组索引越界问题的相关文章

多维数组的索引越界问题

1.我们大都知道可以使用vector或array模板作为线性数组的实现,那么对于需要二维矩阵.三维数组(或者N维数组)时应该怎么解决. 由于N维数组的基本情况中的所有问题都可以用一个二维矩阵举例说明,因此以下的讨论仅限于此,并简单的称为矩阵. 如果矩阵的大小在编译时是已知的,可以很方便的把它实现为数组的数组,这个很简单.这里,我们主要把注意力集中在当矩阵的大小是在运行时计算产生,对于这种复杂的情况,我们可以很方便的使用vector或的vector来实现.事实上,如果不同的行必须必须具有不同的长度

JS 索引数组、关联数组和静态数组、动态数组

1 JS 索引数组.关联数组和静态数组.动态数组 2 3 数组分类: 4 5 1.从数组的下标分为索引数组.关联数组 6 7 var ary1 = [1,3,5,8]; 8 //按索引去取数组元素,从0开始(当然某些语言实现从1开始) 9 //索引实际上就是序数,一个整型数字 10 alert(ary1[0]); 11 alert(ary1[1]); 12 alert(ary1[2]); 13 alert(ary1[3]); 14 15 16 var ary2 = {}; 17 //存取时,以非

Delphi-基础(常量、集合、数组[动态数组、多维数组])

一.常量 1.常量定义:一开始定义好的值,以后在程序的运行过程中不允许改变 1 const 2 Pi : Double = 3.141592; //定义为常量 3 {常量的定义方式,可以不进行类型的声明,编译器会根据具体值决定常量的的类型} 4 Pi2 = 3.1415; 2.常量使用 枚举:常量集 type 枚举名=(标识符1,标识符2,标识符3,......) 1 type 2 {正常情况下,枚举类型的索引是从0开始} 3 EColors = (RED,GREEN,BLUE); 4 EWee

C# 动态数组(ArrayList)

动态数组(ArrayList)代表可单独被索引的对象的集合. 动态数组可以自动调整大小. 允许动态内存的分配,怎加,搜索,排序. using System; using System.Collections; namespace CollectionApplication { class Program { static void Main(string[] args) { ArrayList al = new ArrayList(); Console.WriteLine("Adding som

[转]delphi 删除动态数组的指定元素

type TArr = array of TPoint; {把数组先定义成一个类型会方便许多, 这里仅用 TPoint 测试} {删除动态数组指定元素的过程: 参数 arr 是数组名, 参数 Index 是要删除的索引} procedure DeleteArrItem(var arr: TArr; Index: Integer); var Count: Cardinal; begin Count := Length(arr); if (Count = 0) or (Index < 0) or (

简单动态数组的实现代码

以下是本人动态数组实现的源代码,仅供参考,不足之处请指正: import java.util.*; public class My_List implements Collection{ /***************************************************************/ //测试模块 //主方法,测试用 public static void main(String [] args){ /* My_List ma = new My_List(); /

线性表之顺序存储结构(C语言动态数组实现)

线性表的定义:N个数据元素的有限序列 线性表从存储结构上分为:顺序存储结构(数组)和 链式存储结构(链表) 顺序存储结构:是用一段连续的内存空间存储表中的数据 L=(a1,a2,a3....an) 链式存储结构:是用一段一段连续的内存空间存储表中每一行的数据,段与段之间通过一个引用(指针)相互连接来,形成一个链式的存储结构 看到顺序存储结构的图示,我们可能会马上联想到C语言的数组.是的,数组就是一种典型的顺序存储数据结构.下面我通过一个实例,来实现对顺序存储结构中的数据增.删.改.查的操作. 首

C基础 万能动态数组

引言 - 动态数组切入 开发中动态类型无外乎list 或者 vector, 这里就是在C中实现vector结构容器部分. 对于C中使用的数据结构, 可以参照下面感觉很不错框架源码学习 , 感觉是<<C接口与实现>>的标准Demo twemproxy  https://github.com/twitter/twemproxy/tree/master/src 写的很清楚易懂, 给人一种铺面而来的美感. 关于动态数组设计的总思路, 主要体现在下面的数组结构 struct array {}

动态数组类

在动态数组类中,通过类的成员函数访问数组元素,可以在每次访问之前检查一下下标是否越界,使得数组下标越界的错误能够及早被发现.这种检查,可以通过C++的assert来进行.assert的含义是"断言",它是标准C++的cassert头文件中定义的一个宏,用来判断一个条件表达式的值是否为true,如果不为true,则程序会中止,并且报告出错误,这样就很容易将错误定位. 以下是一个简单的动态数组类示例 #include<iostream> #include<cassert&