Книга: Песни о Паскале
Глава 39 Командная игра (массивы)
Разделы на этой странице:
Глава 39
Командная игра (массивы)
В чём сила компьютеров? В умении стремительно перемалывать огромные объёмы данных: сотни, тысячи, миллионы элементов! Под элементами данных мы разумеем числа, строки и тому подобное. Обратимся и мы к этой способности компьютера. Нет, с миллионом элементов погодим, начнем всего с нескольких: рассмотрим, к примеру, турнирную таблицу чемпионата.
Снежная лавина
Вот задача для болельщика: отсортировать команды в турнирной таблице чемпионата по убыванию набранных ими очков. Команд немного, всего 16. После каждого тура количество очков меняется, и таблица сортируется заново. Корпеть над этим вручную? – это не для нас! Итак, будущая программа должна принимать с клавиатуры очки, набранные командами, и распечатывать команды в порядке убывания этих чисел. При этом набранные очки мы будем вводить всегда в одном и том же порядке.
Сделаем это сначала для двух команд, пусть ими будут «Динамо» и «Спартак». Сортировка двух команд – что может быть проще?
{ ввод и сортировка двух команд (в программе 14 строк) }
var T1, T2 : integer;
begin
Readln (T1, T2); { Ввод очков для «Динамо» и «Спартак» }
if T1>T2
then begin
Writeln('1.Динамо');
Writeln('2.Спартак');
end
else begin
Writeln('1.Спартак');
Writeln('2.Динамо');
end;
Readln;
end.
Здесь для каждой из команд отведена переменная, хранящая набранные очки: T1 – для «Динамо» и T2 – для «Спартака». Вариантов расстановки всего два, поэтому и программа очень проста – всего 14 строк, не считая комментария.
Теперь добавим в чемпионат команду «Зенит». Вариантов расстановки стало втрое больше – шесть, и программа заметно усложнилась, вот она.
{ сортировка трех команд (в этой программе 45 строк) }
var T1, T2, T3 : integer;
begin
Readln (T1, T2, T3); { «Динамо», «Спартак», «Зенит» }
if (T1>T2) and (T1>T3)
then begin
Writeln('1.Динамо');
if T2>T3
then begin
Writeln('2.Спартак');
Writeln('3.Зенит');
end
else begin
Writeln('2.Зенит');
Writeln('3.Спартак');
end
end
else begin
if (T2>T1) and (T2>T3)
then begin
Writeln('1.Спартак');
if T1>T3
then begin
Writeln('2.Динамо');
Writeln('3.Зенит');
end
else begin
Writeln('2.Зенит');
Writeln('3.Динамо');
end
end
else begin
Writeln('1.Зенит');
if T1>T2
then begin
Writeln('2.Динамо');
Writeln('3.Спартак');
end
else begin
Writeln('2.Спартак');
Writeln('3.Динамо');
end
end
end;
Readln;
end.
Здесь уже 45 строк, что втрое больше, чем для двух команд. С добавлением последующих команд программа продолжит разбухать, как снежный ком. Для четырех команд она станет длиннее ещё в 4 раза (180 строк), для пяти – ещё в 5 раз (900 строк) и так далее. Дойдя до шестнадцати команд, мы насчитаем в программе триллионы строк. А ведь триллион – это «всего лишь» миллион миллионов! Скорей свернем с этой гибельной тропы, пока снежная лавина не накрыла нас с головой!
А где же волшебная палочка?
Вы ощущаете причину трудностей? В моих решениях нет циклов, способных выполнять огромное количество однообразных действий. Так, например, одним оператором цикла печатается хоть тысяча, хоть миллион чисел. Увы! Применить цикл к переменным с именами T1, T2 и T3 не получится. Хотя цифры в этих именах означают для нас порядковые номера команд, для компилятора они – всего лишь часть имени переменной, и не более. Как же втолковать компилятору то, чего мы добиваемся нумерацией переменных?
Для этого есть особый тип данных – массив переменных или, проще – массив. Вот она, спасительная волшебная палочка!
Массивы вокруг нас
Массив объединяет несколько однотипных переменных под одним общим именем. Отдельные переменные в массиве называют его элементами, и доступ к ним возможен по их номерам. Массивы придумали отнюдь не программисты. Возьмите любую спортивную команду – футбольную или хоккейную. Здесь, кроме фамилии, игрок снабжен номером, который лучше виден на поле. И это не единственный пример массива. Если отдельную переменную уподобить ящику с хранящейся в нём информацией, то массив переменных будет комодом с пронумерованными ящиками (рис. 88).
Рис.88 – Примеры простых переменных (слева) и массивов (справа)
Итак, массив – это собранные в одну команду переменные. Они получают общее на всех имя – имя своей команды. А внутри команды каждая переменная – элемент массива – обладает своим номером. Ну, чем не игроки?
Объявление массивов
Прежде, чем пользоваться массивом, его надо объявить: либо в секции VAR, либо через объявление пользовательского типа в секции TYPE.
Рассмотрим сначала первый способ, – объявим массив в секции VAR.
VAR Имя_Массива : ARRAY [<MIN>..<MAX>] OF <Тип элемента>
Здесь использована пара ключевых слов ARRAY… OF…, что переводится как «массив… из…». Имя массива – это обычный идентификатор, его программист придумывает сам; будем считать это имя названием команды переменных.
Справа, после двоеточия, указывают две характеристики массива: 1) диапазон для индексов и 2) тип элементов массива. Рассмотрим эти атрибуты массива подробней.
Диапазон для индексов определяет допустимые номера элементов внутри массива. Диапазон указывают в квадратных скобках после слова ARRAY, – это два выражения порядкового типа, условно обозначенные мною как MIN и MAX, они разделяются двумя точками. Говоря спортивным языком, здесь назначается диапазон номеров для «игроков команды».
После ключевого слова OF следует второй атрибут массива – тип данных для всех его элементов. Прибегнув вновь к спортивному языку, скажем, что здесь объявляют «вид спорта» для команды.
Вот пример объявления трех массивов: Names (фамилии), Ratings (оценки) и ChampShip (чемпионат).
VAR { объявления переменных-массивов }
{ 30 строковых переменных с фамилиями учеников класса }
Names : ARRAY [1..30] OF string;
{ 30 байтовых переменных с оценками учеников этого класса }
Ratings : ARRAY [1..30] OF byte;
{ 16 чисел с очками, набранными командами в чемпионате }
ChampShip : ARRAY [1..16] OF integer;
Как видите, массив можно составить из элементов любого типа. Так, массив Names содержит внутри себя 30 переменных строкового типа: Names[1], Names[2] и так далее (номера переменных указывают в квадратных скобках).
Объявление массивов в секции VAR не слишком удобно. Почему? Рассмотрим следующий пример.
var A : array [1..5] of integer;
B : array [1..5] of integer;
begin
A:= B; { здесь компилятор видит ошибку несовместимости типов}
end.
Мы объявили массивы A и B; на первый взгляд, это массивы одного типа, поскольку каждый из них содержит по пять целых чисел. Для однотипных переменных, включая массивы, Паскаль допускает операцию копирования. Например, оператором
A:=B
все элементы массива B копируются в элементы массива A. Увы, компилятор увидит здесь ошибку несовместимости типов. В чем дело? А в том, что он считает разнотипными массивы, объявленные в разных операторах. Даже если массивы совершенно одинаковы! Скажете, компилятор недостаточно умен? Может быть, но нам придётся как-то выкручиваться, и для этого есть два пути.
Во-первых, переменные A и B можно объявить в одном операторе.
var A, B : array [1..5] of integer;
Это устраняет проблему несовместимости типов.
Но есть и лучший способ – сначала объявить для массива пользовательский тип данных. Это делается в секции TYPE так:
TYPE Имя_Типа = ARRAY [<MIN>..<MAX>] OF <Тип элемента>
В сравнении с объявлением переменной разница мизерная: вместо двоеточия видим знак равенства, а вместо имени переменной – имя типа. Но каковы последствия! Объявите лишь однажды нужный вам тип, и тогда применяйте его, где угодно. Вот объявления типов для указанных выше переменных.
TYPE { примеры объявления типов-массивов }
{ тип для 30 строковых переменных с фамилиями учеников класса }
TNames = ARRAY [1..30] OF string;
{ тип для 30 байтовых переменных с оценками учеников }
TRatings = ARRAY [1..30] OF byte;
{ тип для 16 целых переменных с очками, набранными в чемпионате }
TChampionShip = ARRAY [1..16] OF integer;
Здесь буква «T» в имени типа напоминает о назначении этого идентификатора (помните наше добровольное соглашение об именах?). Теперь учрежденные типы данных можно употребить для объявления переменных и параметров в любом месте программы, вот пример.
TYPE { тип для 30 байтовых переменных с оценками учеников }
TRatings = ARRAY [1..30] OF byte;
VAR { 30 байтовых переменных с оценками учеников }
Ratings : TRatings;
procedure ABC (var arg: TRatings); { параметр процедуры }
var A, B, C : TRatings; { локальные переменные }
begin
...
end;
Здесь тип TRatings служит для объявления переменных и параметров в трех местах программы. В будущем мы всегда будем объявлять типы – как для массивов, так и для других сложных наборов данных.
Доступ к элементам (индексация)
Переменной-массивом можно ворочать как единым целым, например, при копировании одного массива в другой. Но чаще приходится работать с отдельными его элементами, как «выдернуть» их из массива?
Очень просто: воспользуйтесь индексацией, – она знакома вам по работе со строками. Как и для доступа к отдельному символу строки, для доступа к элементу массива надо указать его индекс, то есть порядковый номер в массиве. Индекс указывают в квадратных скобках, стоящих после имени массива, он представляет собой выражение порядкового типа. Кстати, сходство со строками не случайно, ведь строка – это особый род массива, составленного из отдельных символов.
Рассмотрим примеры доступа к элементам объявленных выше массивов.
Пример 1. Трем элементам массива Names присваиваем фамилии хоккеистов.
Names[1]:= ’Петров’;
Names[2]:= ’Михайлов’;
Names[3]:= ’Харламов’;
Пример 2. Сравниваем третий и четвертый элементы массива Ratings. Здесь индексы заданы через целочисленную переменную n.
…
Ratings[3]:= 12;
Ratings[4]:= 8;
n:=3;
if Ratings[n] > Ratings [n+1] then … else …;
Как видите, индекс в массиве можно вычислять, а это открывает дорогу к циклам. И мы двинемся ею немедленно!
Ввод и вывод массивов
Ввод и вывод – это те задачи, не решив которые, не стоит помышлять о применении массивов. Ни то, ни другое не сделать одним махом. Здесь, как и для множеств, нужны циклы, обрабатывающие отдельные элементы массива.
Взять, к примеру, массив Names, ввести который можно так:
for i:=1 to 30 do Readln(F, Names[i]);
Здесь F – это открытый для чтения текстовый файл, каждая строка которого содержит фамилию.
На первый взгляд все просто. Просто, да не гладко, – это будет работать лишь с файлом, в котором не менее 30 строк (по числу циклов). А иначе случится ошибка: противозаконное чтение за пределами файла. Как избежать её? Внимательней присматривайте за концом файла, вот так:
i:=1;
{ пока не конец файла и не введены все элементы }
while not Eof(F) and (i<=30) do begin
Readln(F, Names[i]);
i:= i+1;
end;
А вот ещё один хороший вариант.
for i:=1 to 30 do begin
if Eof(F) then break; { если конец файла, прервать цикл }
Readln(F, Names[i]);
end;
Вывод массива в файл не представляет труда, вот пример.
for i:=1 to 30 do Writeln(F, Names[i]);
Разумеется, что файловая переменная F должна быть открыта для записи.
Ошибки индексации
Объявление массива, как сказано, содержит границы для индексов: MIN – номер первого элемента, и MAX – номер последнего. А что случится при попытке обратиться к элементу с меньшим, чем MIN номером? Или наоборот – с большим, чем MAX? Иначе говоря, что случится при попытке доступа к несуществующему элементу массива? Такие ошибки преследуют даже опытных программистов, а последствия зависят от способа, которым вы совершите сей проступок.
Предположим, в программу вкрался такой оператор:
Names[200]:= ’Синичкин’;
Поскольку в массиве Names нет элемента с индексом 200, здесь вас остановит компилятор, – ошибка слишком явна, чтобы он промолчал. Вам не останется ничего иного, как исправить индекс, иначе программа не откомпилируется.
Но, когда индекс вычисляется при исполнении программы, нарушение границ проявляется и обрабатывается иначе, например:
Readln(N);
Writeln(Names[N]);
Нам не угадать, что введет пользователь в переменную N, – здесь ошибка нарушения границ может возникнуть при выполнении программы. В главе 27 мы рассматривали ошибки времени исполнения, – это как раз такой случай. Если указать индекс, выходящий за границы массива, то реакция программы будет зависеть от настройки компилятора, точнее, от опции контроля диапазонов. Напомню, что эта опция управляется директивой $R, а также доступна через меню по цепочке:
Options –> Compiler… –> Runtime Errors –> Range checking
Рассмотрим вариант компиляции при включенном контроле границ ($R+). Тогда, при нарушении границ индекса, программа выдаст аварийное сообщение «Range check error». То есть, она заметила нарушение границ индекса, «крикнула» об этом и прервала работу.
Теперь отключим контроль диапазонов ($R-) и перекомпилируем программу. Она станет «легче» и быстрее, и по ходу выполнения проверять границы не станет. Но ошибки не пройдут бесследно. Наоборот, последствия будут тяжелыми и непредсказуемыми! Отключать проверку диапазонов позволительно только в тщательно проверенной программе.
Лучший способ избежать нарушения границ индексов – взять проверку на себя. В данном случае это можно сделать так:
repeat
Readln(N);
if N in [1..30]
then Writeln(Names[N])
else Writeln(’Ошибка! Введите индекс от 1 до 30’);
until N in [1..30]
Этот цикл будет терзать пользователя, пока тот не введет допустимое значение индекса, или не выключит компьютер.
Итоги
• Массив – это сложный тип данных, объединяющий в себе несколько однотипных переменных – элементов массива.
• Все элементы массива носят одно общее имя – это имя самого массива. Внутри массива элементы различаются своими порядковыми номерами – индексами.
• В объявлении массива указывают две его характеристики: диапазон индексов и тип элементов.
• Индекс элемента может быть задан числом или выражением порядкового типа.
• Указание неверного индекса порождает ошибки либо при компиляции, либо при выполнении программы.
• Ввод массива из текстового файла и вывод в него возможен только поэлементно, для чего организуют цикл.
А слабо?
А) Массив A и переменная C объявлены так:
var A : array [’a’..’z’] of integer;
C: char;
Допустимо ли такое объявление массива и почему? Сколько элементов содержит массив? Какие из указанных ниже операторов будут (или могут) вызывать ошибки нарушения диапазонов?
A[’s’]:= 10;
A[’R’]:= 10;
C:=’d’; A[C]:= 10;
Readln(C); A[C]:= 10;
Проверьте свои решения на практике.
- Только для взрослых
- Детям до 16–ти
- Глава 1 Путь далек у нас с тобою…
- Глава 2 Вместо теории
- Глава 3 Консольный интерфейс
- Глава 4 Оружие – к бою!
- Глава 5 Программа номер один
- Глава 6 Подготовка к следующему штурму
- Глава 7 Развиваем успех
- Глава 8 Постоянные и переменные
- Глава 9 Переменные: продолжение знакомства
- Глава 10 Условный оператор
- Глава 11 Операторный блок
- Глава 12 Цикл с проверкой в конце
- Глава 13 Правда и кривда
- Глава 14 Дважды два – четыре
- Глава 15 Айда в Монте-Карло!
- Глава 16 Делу время, а потехе час
- Глава 17 И вновь за парту
- Глава 18 Аз, Буки
- Глава 19 Процедуры и функции: разделяй и властвуй
- Глава 20 Процедуры: первый опыт
- Глава 21 Отладка
- Глава 22 О передаче параметров
- Глава 23 Функции
- Глава 24 Криптография
- Глава 25 Текстовые файлы
- Глава 26 Я не читатель, – я писатель!
- Глава 27 Дайте кораблю минутный отдых!
- Глава 28 Редактор и справочная система
- Глава 29 Читайте по-новому
- Глава 30 Журнальная история
- Глава 31 Финал журнальной истории
- Глава 32 Порядковые типы данных
- Глава 33 Вещественные числа
- Глава 34 Структура программы
- Глава 35 Множества
- Глава 36 Множества в Паскале
- Глава 37 Ввод и вывод множеств
- Глава 38 Множества в «бою»
- Глава 39 Командная игра (массивы)
- Глава 40 Пристрелка на знакомых мишенях
- Глава 41 По порядку, становись!
- Глава 42 Кто ищет, тот всегда найдет
- Глава 43 Сортировка по-взрослому
- Глава 44 Строки
- Глава 45 Очереди и стеки
- Глава 46 Огромные числа
- Глава 47 Системы счисления
- Глава 48 Железная логика
- Глава 49 Сложные массивы
- Глава 50 Неспортивные рекорды (записи)
- Глава 51 Указатели в море памяти
- Глава 52 Динамические переменные
- Глава 53 Массив указателей
- Глава 54 Односвязные списки
- Глава 55 Слова, слова, слова…
- Глава 56 И снова очереди, и снова стеки…
- Глава 57 Графомания
- Глава 58 По графу шагом марш!
- Глава 59 Крупные проекты
- Глава 60 Мелкие хитрости
- Глава 61 «Кубики» программиста (ООП)
- Глава 62 Самое интересное только начинается!
- Приложение А Установка и настройка IDE Borland Pascal
- Приложение Б Консольная программа в среде Delphi
- Приложение В Особенности IDE Pascal ABCNet
- Приложение Г Зарезервированные слова
- Приложение Д Ошибки компиляции
- Приложение Е Ошибки исполнения
- Приложение Ж Директивы управления компиляцией
- Приложение З Назначение пунктов меню
- Приложение И Стандартная кодировка символов MS–DOS
- Приложение К Некоторые встроенные процедуры и функции
- Приложение Л Перечень программ
- Приложение М Пример олимпиадной задачи
- Библиография
- Содержание книги
- Популярные страницы
- Почему необходима миграция
- Сущность процесса миграции
- Миграция между различными версиями InterBase
- Карта миграции
- Прямая миграция
- Сохранение информации о пользователях при миграции
- Особый процесс, или обратная миграция
- Миграция баз данных на Yaffil и обратно
- Динамические массивы
- 3. Связи и миграция ключей
- 9.1 Массивы RAID
- Глава 14. Почему потребительский опыт играет важную роль в выстраивании клиентских взаимоотношений