Как сделать массив с отрицательными числами c

Обновлено: 07.07.2024

Я преобразовываю большую и очень старую (более 25 лет!) программу чрезвычайно с C на С++.

В нем много (очень много) мест, где я обращаюсь к глобальному одномерному массиву UBYTE, используя множество целочисленных индексов. Иногда этот индекс может быть отрицательным.

Я иногда, но не всегда, оказался в ловушке этого случая и убедился, что ничего не получилось, но в качестве меры пояса и фигурных скобок я действительно постарался убедиться, что рядом с массивом появился еще один кусок памяти и заполнил его с правильными значениями, такими, что, если я случайно опущен, чтобы уловить условие отрицательного числа, тогда правильный правильный ответ будет получен в доступе к массиву. Это на самом деле отлично работало много лет.

Но теперь в С++ кажется, что обращение к массиву с отрицательным числом ведет себя по-другому, и теперь у меня плохо работает программа. Я зафиксировал один случай необработанного отрицательного числа, и программа, похоже, работает нормально, но я нервничаю, что не попал в ловушку всех отрицательных чисел, и могут быть проблемы впереди.

Итак, теперь мой вопрос: есть ли способ во время выполнения обнаружить все экземпляры доступа к массивам с отрицательными индексами? Я буду впечатлен, если кто-нибудь сможет ответить. Если вы совершенно уверены, что это невозможно сделать автоматическим способом, то я также скажу, что это тоже ценная информация.

Я должен просто добавить, что я на самом деле не программист на С++ (пока). Пока все, что я сделал, - это абсолютный минимум (почти ничего), чтобы программа могла скомпилироваться под компилятором С++. Поэтому, если ваш ответ включает в себя фантастические "только эксперты, решения на С++", попробуйте объяснить словами одного слога или дать мне ссылку, чтобы я мог ее найти.

ОТВЕТЫ

Ответ 1

Можно ли заменить глобальный одномерный массив ubyte объектом с перегруженным оператором []? Использование абсолютного значения ввода int может решить некоторые из ваших проблем.

Изменить: в зависимости от шаблона использования вашего массива (без указателя shenanigans) использование объекта с перегруженным оператором [] может быть полностью прозрачным для пользователей массива, поэтому мое предложение.

Ответ 2

Знаете ли вы границы индексов?

Считая комментарий в ответ Siyfion, кажется, что отрицательные индексы разрешены в вашей проблемной области, и поэтому нет смысла пытаться заставить все индексы быть положительными. Лучшее решение - разрешить отрицательные индексы, не нарушая каких-либо языковых правил (к которым также добавила ваша версия C. Вам просто повезло, что она не взорвалась;))

Итак, если вы знаете, что индексы, которые вы собираетесь использовать, находятся в диапазоне от -x до y, просто создайте массив размером x + y и используйте указатель на x-й элемент при доступе к массиву.

Например, если вам нужен массив с индексами от -4 до 5:

Конечно, то же самое могло (и должно) было сделано на C.

Другим советом было бы не напрямую обращаться к необработанному массиву. Вместо этого определите простые функции доступа. (например, но не обязательно, поместив массив в класс.Если вы поместите его в класс, вы можете перегрузить operator [], чтобы он даже выглядел как массив)

Накладные расходы в этом случае равны нулю (не "почти нулевое" или "так близко к нулю, что вы не заметите разницу", но ноль. Сгенерированный код будет точно таким же, как если бы вы обратились к массиву напрямую), поскольку компилятор будет выполнять короткие функции, но также позволяет вставлять дополнительные проверки, чтобы вы могли проверить в сборках отладки, которые никогда не проходят за границами массива.

Ответ 3

Может работать какой-то способ использования памяти, особенно если вы можете заставить массив перейти на свою собственную страницу. Я бы использовал valgrind в Linux, не уверен, что вы будете использовать в Windows - purify ?

Ответ 4

Оберните его. В альтернативном случае проверьте, поддерживает ли ваш компилятор проверку границ. В худшем случае я не ставил бы допустимые значения в вашем массиве на отрицательные числа. Я бы поставил откровенно неправильные значения, так что вы получите явно неправильное значение, когда это произойдет. Вы не будете определять проблему без отладчика, но, по крайней мере, у вас есть четкое предупреждение о том, что где-то это делает какой-то код.

Ответ 5

Есть ли способ, во время выполнения, для меня для обнаружения любых случаев доступа массивы с отрицательными индексами?

Замените свой глобальный массив std::vector. Замените использование синтаксиса array[index] на

Это делает именно то, о чем вы просите: он добавляет проверки привязки массива времени выполнения.

Ответ 6

единственный способ, которым я могу думать, - это обернуть массив в методе, который проверяет, что индекс положительный.

Ответ 7

Лично я попытаюсь разобраться, почему любой из ваших индексов может когда-либо быть отрицательным; очевидно, что они вернут неправильное значение, поэтому, конечно, вы не должны позволить индексу идти отрицательно?

Исправьте код, который позволяет им идти отрицательно, а не пытаться превратить квадратный штекер в круглое отверстие (так сказать);)

Ответ 8

Одна вещь, которую вы можете сделать, - это проверить, до какой минимальной ценности ваши индексы идут, и начать заполнять свой массив из этого индекса.

начните с (a-i) в качестве базового адреса вместо (a), чтобы вы просто сдвинули массив и получили желаемые значения.

Chrees

int main() \u00a0\u00a0\u00a0 int i, n;
\u00a0\u00a0\u00a0 cout<<\"\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432: \";
\u00a0\u00a0\u00a0 cin>>n;
\u00a0\u00a0\u00a0 int a[n];
\u00a0\u00a0\u00a0 for (i=0; i<n; i++) \u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 cout<<\"\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \"<<i+1<<\"-\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442: \";
\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 cin>>a[i];
\u00a0\u00a0\u00a0 >
\u00a0\u00a0\u00a0 int sump=0, koln=0;
\u00a0\u00a0\u00a0 for (i=0; i<n; i++) \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0 if (a[i]<0) koln++;
\u00a0\u00a0\u00a0 \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 else if (a[i]>0) sump+=a[i];
\u00a0\u00a0\u00a0 >
\u00a0\u00a0\u00a0 cout<<\"\u0421\u0443\u043c\u043c\u0430 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0440\u0430\u0432\u043d\u0430 \"<<sump<<endl;
\u00a0\u00a0\u00a0 cout<<\"\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043e\u0442\u0440\u0438\u0446\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0440\u0430\u0432\u043d\u043e \"<<koln<<endl;
\u00a0\u00a0\u00a0 system(\"PAUSE\");
\u00a0\u00a0\u00a0 return 0;
>

int main() int i, n;
cout >n;
int a[n];
for (i=0; i >a[i];
>
int sump=0, koln=0;
for (i=0; i 0) sump+=a[i];
>
cout

Давайте выведем в консоль все его элементы по очереди:

let arr = [1, 2, 3, 4, 5]; console.log(arr[0]); console.log(arr[1]); console.log(arr[2]); console.log(arr[3]); console.log(arr[4]);

Давайте теперь сделаем так, чтобы мы не вручную выводили каждый элемент, а это сделал за нас цикл.

Запустим цикл for от 0 до 4 и внутри этого цикла будем выводить элементы массива, обращаясь к ним как arr[i] :

Давайте теперь сделаем так, чтобы цикл сам определял, сколько итераций ему сделать. Сейчас условием окончания цикла служит команда i .

Откуда мы взяли эту четверку? Это количество элементов массива (пять) минус один. Почему мы отнимаем один? Потому что нумерация элементов начинается с нуля. То есть в нашем массиве 5 элементов и последний элемент имеет номер 4 .

Как вы уже знаете из предыдущих уроков, длину массива можно получить вот так: arr.length . Давайте подставим эту длину в условие, отняв от нее единицу по описанным выше причинам:

Теперь наш цикл получился универсальным и автоматически может перебирать массивы любой длины.

Можно не отнимать единицу, а вместо сделать :

Дан массив с элементами 'a', 'b' , 'c' , 'd' , 'e' . С помощью цикла for выведите все эти элементы на экран.

В следующем коде программист вывел в консоль все элементы массива:

В коде, однако, была допущена ошибка, которая привела к тому, что в последней итерации цикла почему-то выводится undefined , а не элемент массива. Исправьте ошибку программиста. Объясните, в чем он был не прав.

Перебор массива и if

Внутри цикла for можно использовать условие if . Давайте, например, при переборе массива, будем выводить в консоль только элементы с четными числами:

Дан массив с элементами 1 , 2 , 3 , 4 , 5 . С помощью цикла for и оператора if выведите в консоль нечетные элементы массива.

Нахождение суммы элементов

Давайте найдем в цикле сумму элементов массива. Для этого введем переменную (назовем ее result ) для накопления результата, подобно тому, как мы делали в предыдущих уроках:

Дан массив с элементами 1 , 2 , 3 , 4 , 5 . С помощью цикла for найдите произведение элементов этого массива.

Дан массив с элементами 1 , 2 , 3 , 4 , 5 . С помощью цикла for найдите сумму квадратов элементов этого массива.

Задачи

Дан массив с элементами 2 , 5 , 9 , 15 , 1 , 4 . С помощью цикла for и оператора if выведите в консоль те элементы массива, которые больше 3 -х, но меньше 10 .

Дан массив с числами. Числа могут быть положительными и отрицательными. Найдите сумму положительных элементов массива.

Дан массив arr . Найдите среднее арифметическое его элементов (сумма делить на количество). Проверьте задачу на массиве с элементами 1 , 2 , 3 , 4 , 5 .

Дан массив числами, например:

Выведите на экран только те числа из массива, которые начинаются на цифру 1 , 2 или 5 .

Дан массив с числами. Выведите элементы этого массива в обратном порядке.

Дан массив с числами. С помощью цикла выведите на экран все элементы, значение которых совпадает с их порядковым номером в массиве.

Дан массив с числами. С помощью цикла for и функции document.write выведите каждый элемент массива с новой строки. Используйте для этого тег br .

Дан массив с числами. С помощью цикла for и функции document.write выведите каждый элемент массива в отдельном абзаце.

Составьте массив дней недели. С помощью цикла for выведите все дни недели, а выходные дни выведите жирным.

Составьте массив дней недели . С помощью цикла for выведите все дни недели, а текущий день выведите курсивом. Номер текущего дня должен храниться в переменной day .

Часто в программах бывает надо работать с большим количество однотипных переменных. Например, пусть вам надо записать рост каждого человека в классе — это много целых чисел. Вы можете завести по одной переменной на каждого ученика, но это очень не удобно. Специально для этого придуманы массивы.

Общее представление о массиве

Массив (в питоне еще принято название "список", это то же самое) — это переменная, в которой хранится много значений. Массив можно представлять себе в виде такой последовательности ячеек, в каждой из которых записано какое-то число:

Соответственно, переменная теперь может хранить целиком такой массив. Создается такой массив, например, путем перечисления значений в квадратных скобках:

a = [7, 5, -3, 12, 2, 0]

Теперь переменная a хранит этот массив. К элементам массива можно обращаться тоже через квадратные скобки: a[2] — это элемент номер 2, т.е. в нашем случае это -3 . Аналогично, a[5] — это 0. В квадратных скобках можно использовать любые арифметические выражения и даже другие переменные: a[2*2-1] — это 12, a[i] обозначает "возьми элемент с номером, равным значению переменной i ", аналогично a[2*i+1] обозначает "возьми элемент с номером, равным 2*i+1", или даже a[a[4]] обозначает "возьми элемент с номером, равным четвертому элементу нашего массива" (в нашем примере a[4] — это 2 , поэтому a[a[4]] — это a[2] , т.е. -3 ).

Если указанный номер слишком большой (больше длины массива), то питон выдаст ошибку (т.е. в примере выше a[100] будет ошибкой, да и даже a[6] тоже). Если указан отрицательный номер, то тут действует хитрое правило. Отрицательные номера обозначают нумерацию массива с конца: a[-1] — это всегда последний элемент, a[-2] — предпоследний и т.д. В нашем примере a[-6] равно 7. Слишком большой отрицательный номер тоже дает ошибку (в нашем примере a[-7] уже ошибка).

С элементами массива можно работать как с привычными вам переменными. Можно им присваивать значения: a[3] = 10 , считывать с клавиатуры: a[3] = int(input()) , выводить на экран: print(a[3]) , использовать в выражениях: a[3+i*a[2]] = 3+abs(a[1]-a[0]*2+i) (здесь i — какая-то еще целочисленная переменная для примера), использовать в if'ах: if a[i]>a[i-2]: , или for a[2] in range(i) и т.д. Везде, где вы раньше использовали переменные, можно теперь использовать элемент массива.

Обход массива

Но обычно вам надо работать сразу со всеми элементами массива. Точнее, сразу со всеми как правило не надо, надо по очереди с каждым (говорят: "пробежаться по массиву"). Для этого вам очень полезная вещь — это цикл for . Если вы знаете, что в массиве n элементов (т.е. если у вас есть переменная n и в ней хранится число элементов в массиве), то это делается так:

например, вывести все элементы массива на экран:

или увеличить все элементы массива на единицу:

и т.п. Конечно, в цикле можно и несколько действий делать, если надо. Осознайте, что это не магия, а просто полностью соответствует тому, что вы знаете про работу цикла for.

Если же у вас нет переменной n , то вы всегда можете воспользоваться специальной функцией len , которая возвращает количество элементов в массиве:

for i in range(len(a)): .

Функцию len , конечно, можно использовать где угодно, не только в заголовке цикла. Например, просто вывести длину массива — print(len(a)) .

Операции на массиве

Еще ряд полезных операций с массивами:

  • a[i] (на всякий случай повторю, чтобы было легче найти) — элемент массива с номером i .
  • len(a) (на всякий случай повторю, чтобы было легче найти) — длина массива.
  • a.append(x) — приписывает к массиву новый элемент со значением x , в результате длина массива становится на 1 больше. Конечно, вместо x может быть любое арифметическое выражение.
  • a.pop() — симметричная операция, удаляет последний элемент из массива. Длина массива становится на 1 меньше. Если нужно запомнить значение удаленного элемента, надо просто сохранить результат вызова pop в новую переменную: res = a.pop() .
  • a * 3 — это массив, полученный приписыванием массива a самого к себе три раза. Например, [1, 2, 3] * 3 — это [1, 2, 3, 1, 2, 3, 1, 2, 3] . Конечно, на месте тройки тут может быть любое арифметическое выражение. Самое частое применение этой конструкции — если вам нужен массив длины n , заполненный, например, нулями, то вы пишете [0] * n .
  • b = a — присваивание массивов. Теперь в b записан тот же массив, что и в a . Тот же — в прямом смысле слова: теперь и a , и b соответствуют одному и тому же массиву, и изменения в b отразятся в a и наоборот. Еще раз, потому что это очень важно. Присваивание массивов (и вообще любых сложных объектов) в питоне не копирует массив, а просто обе переменные начинают ссылаться на один и тот же массив, и изменения массива через любую из них меняет один и тот же массив. При этом на самом деле тут есть многие тонкости, просто будьте готовы к неожиданностям.
  • b = a[1:4] ("срез") — делает новый массив, состоящий из элементов старого массива начиная со первого (помните про нумерацию с нуля!) и заканчивая третьим (т.е. до четвертого, но не включительно, аналогично тому, как работает range ); этот массив сохраняется в b . Для примера выше получится [5, -3, 12] . Конечно, на месте 1 и 4 может быть любое арифметическое выражение. Более того, эти индексы можно вообще не писать, при этом автоматически подразумевается начало и конец массива. Например, a[:3] — это первые три элемента массива (нулевой, первый и второй), a[1:] — все элементы кроме нулевого, a[:-1] — все элементы кроме последнего (!), а a[:] — это копия всего массива. И это именно копия, т.е. запись b = a[:] именно копирует массив, получающиеся массивы никак не связаны, и изменения в b не влияют на a (в отличие от b = a ).

Ввод-вывод массива

Как вам считывать массив? Во-первых, если все элементы массива задаются в одной строке входного файла. Тогда есть два способа. Первый — длинный, но довольно понятный:

Второй — покороче, но попахивает магией:

Может показаться страшно, но на самом деле map(int, input().split()) вы уже встречали в конструкции

когда вам надо было считать два числа из одной строки. Это считывает строку ( input() ), разбивает по пробелам ( .split() ), и превращает каждую строку в число ( map(int, . ) ). Для чтения массива все то же самое, только вы еще заворачиваете все это в list(. ) , чтобы явно сказать питону, что это массив.

Какой из этих двух способов использовать для чтения данных из одной строки — выбирать вам.

Обратите внимание, что в обоих способах вам не надо знать заранее, сколько элементов будет в массиве — получится столько, сколько чисел в строке. В задачах часто бывает что задается сначала количество элементов, а потом (обычно на следующей строке) сами элементы. Это удобно в паскале, c++ и т.п., где нет способа легко считать числа до конца строки; в питоне вам это не надо, вы легко считываете сразу все элементы массива до конца строки, поэтому заданное число элементов вы считываете, но дальше не используете:

Еще бывает, что числа для массива задаются по одному в строке. Тогда вам проще всего заранее знать, сколько будет вводиться чисел. Обычно как раз так данные и даются: сначала количество элементов, потом сами элементы. Тогда все вводится легко:

Более сложные варианты — последовательность элементов по одному в строке, заканчивающаяся нулем, или задано количество элементов и сами элементы в той же строке — придумайте сами, как сделать (можете подумать сейчас, можете потом, когда попадется в задаче). Вы уже знаете все, что для этого надо.

Как выводить массив? Если надо по одному числу в строку, то просто:

Если же надо все числа в одну строку, то есть два способа. Во-первых, можно команде print передать специальный параметр end=" " , который обозначает "заканчивать вывод пробелом (а не переводом строки)":

Есть другой, более простой способ:

Эта магия обозначает вот что: возьми все элементы массива a и передай их отдельными аргументами в одну команду print . Т.е. получается print(a[0], a[1], a[2], . ) .

Двумерные массивы

Выше везде элементами массива были числа. Но на самом деле элементами массива может быть что угодно, в том числе другие массивы. Пример:

Что здесь происходит? Создаются три обычных массива a , b и c , а потом создается массив z , элементами которого являются как раз массивы a , b и c .

Что теперь получается? Например, z[1] — это элемент №1 массива z , т.е. b . Но b — это тоже массив, поэтому я могу написать z[1][2] — это то же самое, что b[2] , т.е. -3 (не забывайте, что нумерация элементов массива идет с нуля). Аналогично, z[0][2]==30 и т.д.

То же самое можно было записать проще:

Получилось то, что называется двумерным массивом. Его можно себе еще представить в виде любой из этих двух табличек:

z содержит три элемента, и не важно, что каждый из них тоже массив), а len(z[2]) — длина внутреннего массива на позиции 2 (т.е. 2 в примере выше). Для массива x выше (того, у которого каждый подмассив имеет свою длину) получим len(x)==5 , и, например, len(x[3])==0 .

Аналогично работают все остальные операции. z.append([1,2]) приписывает к "внешнему" массиву еще один "внутренний" массив, а z[2].append(3) приписывает число 3 к тому "внутреннему" массиву, который находится на позиции 2. Далее, z.pop() удаляет последний "внутренний" из "внешнего" массива, а z[2].pop() удаляет последний элемент из "внутреннего" массива на позиции 2. Аналогично работают z[1:2] и z[1][0:1] и т.д. — все операции, которые я приводил выше.

Обход двумерного массива

Конечно, чтобы обойти двумерный массив, надо обойти каждый его "внутренний" массив. Чтобы обойти внутренний массив, нужен цикл for , и еще один for нужен, чтобы перебрать все внутренние массивы:

Создание пустого массива

Неожиданно нетривиальная операция на двумерных массивах — это создание двумерного массива определенного размера, заполненного, например, нулями. Вы помните, что одномерный массив длины n можно создавать как [0] * n . Возникает желание написать a = ([0] * m) * n , чтобы создать двумерный массив размера n x m (мы хотим, чтобы первый индекс массива менялся от 0 до n-1 , а второй индекс до m-1 , поэтому это именно ([0] * m) * n , а не ([0] * n) * m ). Но это сработает не так, как вы можете думать. Дело опять в том, что в питоне массивы по умолчанию не копируются полностью, поэтому то, что получается — это массив длина n , в котором каждый элемент соответствует одному и тому же массиву длины n . В итоге, если вы будете менять, например, a[1][2] , то так же будет меняться и a[0][2] , и a[3][2] и т.д. — т.к. все внутренние массивы на самом деле соответствуют одному и тому же массиву.

Поэтому массив размера n x m делается, например, так:

мы вручную n раз приписали к массиву a один и тот же массив.

Или еще есть магия в одну строчку:

a = [[0] * m for i in range(n)]

Я пока не буду объяснять, как это работает, просто можете запомнить. Или пользоваться предыдущим вариантом.

Обратите внимание, что тут важный момент — хотим мы, чтобы n соответствовало первому индексу или второму. В примерах выше n — размер первого индекса (т.е. размер "внешнего" массива), a m — размер второго индекса (т.е. размер каждого "внутреннего" массива). Если вы хотите, то можно делать и наоборот, но это вы сами должны решить и делать согласованно во всей программе.

Ввод-вывод двумерного массива

Обычно двумерный массив вам задается как n строк по m чисел в каждой, причем числа n и m вам задаются заранее. Такой двумерный массив вводится эдакой комбинацией двух способов ввода одномерного массива, про которые я писал выше:

Мы считываем очередную строку и получаем очередной "внутренний" массив: list(map(int, input().split())) , и приписываем его ( append ) ко внешнему массиву.

Обратите внимание, что здесь мы уже четко решили, что первый индекс нашего массива соответствует строкам входного файла, а второй индекс — столбцам, т.е. фактически мы уже выбрали левую из двух картинок выше. Но это связано не с тем, как питон работает с двумерными массивами, а с тем, как заданы входные данные во входном файле.

Вывод двумерного массива, если вам его надо вывести такой же табличкой, тоже делается комбинацией способов вывода одномерного массива, например, так:

Многомерные массивы

Аналогично двумерным, бывают и трехмерные и т.д. массивы. Просто каждый элемент "внутреннего" массива теперь сам будет массивом:

Здесь a[0] — это двумерный массив [[1, 2], [3, 4]] , и a[1] — двумерный массив [[5, 6], [7, 8]] . Например, a[1][0][1] == 6 .

Многомерные массивы в простых задачах не нужны, но на самом деле бывают полезны и не представляют из себя чего-то особо сложного. С ними все аналогично тому, что мы обсуждали про двумерные массивы.

Читайте также: