Что вызывает ArrayIndexOutOfBoundsException
?
Если вы думаете о переменной как о «поле», где вы можете поместить значение, то массив представляет собой ряд ящиков, расположенных рядом с eachother, где число Ячейки представляют собой конечное и явное целое число.
Создание такого массива:
final int[] myArray = new int[5]
создает строку из 5 полей, каждая из которых имеет int
. Каждый из ящиков имеет индекс, позицию в ряду ящиков. Этот индекс начинается с 0 и заканчивается на N-1, где N - размер массива (количество ящиков).
Чтобы получить одно из значений из этой серии ящиков, вы можете обратиться к нему через его индекс, например:
myArray[3]
Который даст вам значение 4-го ящика в серии (так как в первом поле есть индекс 0).
ArrayIndexOutOfBoundsException
вызвано попыткой извлечь «ящик», который не существует, передав индекс, который выше индекса последнего «поля» или отрицательный.
В моем примере работы эти фрагменты кода приведут к такому исключению:
myArray[5] //tries to retrieve the 6th "box" when there is only 5
myArray[-1] //just makes no sense
myArray[1337] //waay to high
Как избежать ArrayIndexOutOfBoundsException
Чтобы предотвратить ArrayIndexOutOfBoundsException
, необходимо рассмотреть некоторые ключевые моменты:
Looping
При переходе по массиву всегда убедитесь, что индекс, который вы извлекаете, строго меньше длины массива (количество ящиков). Например:
for (int i = 0; i < myArray.length; i++) {
Обратите внимание на <
, никогда не смешивайте там =
.
Возможно, вам захочется сделать что-то вроде этого:
for (int i = 1; i <= myArray.length; i++) {
final int someint = myArray[i - 1]
Просто нет. Придерживайтесь одного выше (если вам нужно использовать индекс), и это сэкономит вам много боли.
По возможности используйте foreach:
for (int value : myArray) {
Таким образом, вы вообще не придется вообще обдумывать индексы.
Когда вы выполняете цикл, что бы вы ни делали, НИКОГДА не изменяйте значение итератора цикла (здесь: i
). Единственное место, которое должно изменить значение, это сохранить цикл. Изменение в противном случае просто рискует исключением и в большинстве случаев не является обязательным.
Retrieval / update
При извлечении произвольного элемента массива всегда проверяйте его действительность индекс по длине массива:
public Integer getArrayElement(final int index) {
if (index < 0 || index >= myArray.length) {
return null; //although I would much prefer an actual exception being thrown when this happens.
}
return myArray[index];
}
Этот дополнительный ответ в ответ на комментарий OP к моему более раннему ответу. Защитник спрашивает:
"Я имею некоторые вопросы в запасе. Если бы это ожидает массив интервала типа, и я отправил дважды, почему это изменило бы результат в 360 градусах. Я все еще не получил ответ на это".
будет легче проиллюстрировать это со столкновением типа между short int
и int
, но та же идея относится int
по сравнению с double
.
Смотрят на этот код:
#include <stdlib.h>
#include <stdio.h>
void functionA(short int[], int size);
int main(void) {
printf("sizeof short int : %lu\n", sizeof(short int));
printf("sizeof int : %lu\n", sizeof(int));
printf("\n\n");
printf("==== sending short int to functionA() ====\n");
short int shortdata[4] = {10, 20, 50, 100};
functionA(shortdata, 4);
printf("==== sending int to functionA() ====\n");
int intdata[4] = {10, 20, 50, 100};
functionA(intdata, 4);
}
void functionA(short int arr[], int size) {
int i;
char* ptr;
for (i = 0; i < size; ++i) {
ptr = &arr[i];
printf("bytes of 'arr[%d]' : %x %x\n", i, *ptr, *(ptr+1));
printf("value of 'arr[%d]' : %d\n", i, arr[i]);
printf("\n");
}
}
, который производит этот вывод:
sizeof short int : 2
sizeof int : 4
==== sending short int to functionA() ====
bytes of 'arr[0]' : a 0
value of 'arr[0]' : 10
bytes of 'arr[1]' : 14 0
value of 'arr[1]' : 20
bytes of 'arr[2]' : 32 0
value of 'arr[2]' : 50
bytes of 'arr[3]' : 64 0
value of 'arr[3]' : 100
==== sending int to functionA() ====
bytes of 'arr[0]' : a 0
value of 'arr[0]' : 10
bytes of 'arr[1]' : 0 0
value of 'arr[1]' : 0
bytes of 'arr[2]' : 14 0
value of 'arr[2]' : 20
bytes of 'arr[3]' : 0 0
value of 'arr[3]' : 0
первые две строки вывода показывают, что на моей машине, short int
берет 2 байта памяти, и int
берет 4 байта.
functionA()
ожидает short int
массив, и когда я отправляю его short int[]
, мы видим ожидаемый вывод. Байты, которые составляют первый элемент массива, 0x0a 0x00
, который в десятичном числе равняется "10"; байты, которые составляют второй элемент массива, 0x14 0x00
, который в десятичном числе равняется "20"; и так далее.
, Но когда я отправляю functionA()
int[]
, я отправляю 4 байта за элемент, поэтому когда это выполняет итерации через массив, это не видит элементы правильно. Это делает хорошо с первым элементом, но только потому, что это - небольшое число; когда это ищет второй элемент, это на самом деле смотрит на последние два байта первого элемента, таким образом, это видит 0x00 0x00
, или "0"; когда это ищет третий элемент, это смотрит на первые два байта второго элемента и видит 0x14 0x00
, или "20"; и так далее.
Другой способ показать случается так, что байты [1 120] являются этим:
0a 00 14 00 32 00 64 00
и байты [1 121] это:
0a 00 00 00 14 00 00 00 32 00 00 00 64 00 00 00
, поскольку functionA()
ожидает short int[]
, это рассматривает intdata
тот путь - два байта за элемент - и видит как его элементы:
arr[0] : 0a 00
arr[1] : 00 00
arr[2] : 14 00
arr[3] : 00 00
Это - подобная история с Вашим кодом. Ваш getAverage()
ожидает int[]
, поэтому когда Вы отправляете его double[]
, это не видит байты путем, Вы предназначаете.
(Столкновение между [1 128] и double
является еще более решительным, чем между [1 130] и int
; это вызвано тем, что в дополнение к тому, чтобы быть различными размерами (на большинстве современных машин, int
4 байта и double
, 8 байтов), числа с плавающей запятой хранятся как экспонента и мантисса - таким образом, значения байта имеют совершенно другое значение.)
я надеюсь, что это объяснение помогает, и я поощряю Вас экспериментировать с кодом выше и читать о внутренностях памяти C, поскольку это принадлежит выделению и обработке переменных. Как новый программист C, Вы вникнете в к основным принципам, которые помогут Вам столько, сколько Вы продолжаете программировать в C.
Объекты, определенные внутри функций без static
или _Thread_local
, не инициализируются автоматически и имеют неопределенные значения. Чтобы обеспечить инициализацию массива mrr
, определите его с помощью int mrr[2][3] = { 0 };
, который инициализирует его всеми нулями.
Код во втором вопросе является неполным, но, по-видимому, массив определен в вызывающей функции и не инициализирован, поэтому его значения являются неопределенными.
По умолчанию [значения] массива, определенного внутри функции (то есть локального массива), являются неопределенными. Глобальные (то есть статические) переменные по умолчанию инициализируются равными 0. Смотрите этот пост: Начальное значение массива int в C
. Для решения вашей проблемы просто инициализируйте ваш массив вручную, например,
for (int r=0;r<2;r++)
{
for (int c=0; c<3; c++) {
mrr[r][c] = 0;
printf("[%d],[%d]:%d\n",r,c,mrr[r][c]);
}
}
. Ваш вопрос о неинициализированных переменных хорошо освещен в комментариях и других ответах; Этот ответ для вашего второго вопроса, о неожиданном выводе из getAverage()
.
Вы определяете balance
как массив double
, но getAverage()
ожидает массив int
. Когда я изменяю:
double getAverage(int arr[], int size) { ... }
на:
double getAverage(double arr[], int size) { ... }
, я получаю ожидаемый результат: 215.2. Неявное преобразование типов [1] в вашем исходном коде вызывает неожиданное поведение.
Кроме того, я не знаю, какой компилятор вы используете, но когда я скомпилировал ваш оригинальный код [2], gcc
выдал это предупреждение:
warning: incompatible pointer types passing 'double [5]' to parameter of type 'int *'
... который мертв поддавайся тому, что назревает проблема. Если ваш компилятор не генерирует это предупреждение, или если у вас есть предупреждения, которые подавлены, или если они записаны в файл или что-то, что вы не просматриваете, я рекомендую снова сделать их видимыми; уделение внимания предупреждениям компилятора спасло меня от неисчислимых часов горя.
[1] «преобразование типов», возможно, не лучший термин здесь; преобразование типов обычно не вызывает затруднений, хотя есть некоторые ошибки; здесь происходит то, что байты, составляющие значение double
, читаются как int
, и структура байтов этих двух типов данных очень различна; вот почему при выводе вашего исходного кода создается впечатление, что он имеет дело с такими сумасшедшими большими числами: например, байты, используемые для представления «17» в виде двойного числа, когда читается как int
, становятся очень, очень разными числами. Хуже того, проходя через байты, составляющие массив значений double
, как если бы это был массив значений int
, он, вероятно, даже не смотрит на элементы массива на соответствующих границах элементов. Короче говоря, несоответствие типов вызывает хаос.
[2] Под «исходным кодом» я подразумеваю ваш код с достаточным количеством служебного кода для его компиляции, например, include
прагмы и тело main()
; Я в целом согласен с другими комментариями о предоставлении полного, минимального примера.