[ Миронченко ] Императивное и объектно-ориентированное програмирование на Turbo Pascal и Delphi
.pdf81
Глава 5: Несколько операторов на закуску
В этой главе мы рассмотрим еще несколько операторов и типов данных, встроенных в ТР а также обсудим, что мы уже знаем о программировании.
5.1.Оператор case
Спомощью оператора if можно проверять любое условие, поэтому введение дополнительного оператора выглядит несколько искусственным. Но в ряде случаев использование оператора if не очень удобно, поэтому для упрощения программного кода был введен оператор case.
Синтаксис:
case выражение of
список значений 1: begin операторы; end;
список значений 2: begin операторы; end;
. . . . . . . . . . . .
список значений n: begin операторы; end;
else begin
операторы; end;
end;
Список значений – это набор констант, разделенных запятыми и диапазонов чисел (например 1,2,5,7..15,18..29)
Алгоритм работы:
1.Вычисляется значение выражения, следующего за словом case
2.Полученное значение последовательно сравнивается с константами из списков констант перед двоеточием.
3.Если значение нашего выражения попало в какой-то список констант, то выполняются операторы, следующие за двоеточием, и затем оператор case завершается.
4.Если значение выражения не находится ни в одном списке констант, то выполняются операторы ветви else (если она в операторе есть). Если же ее нет, то оператор case заканчивает свою работу.
Пример 1: Определение дня недели.
1 : var
2 : i:integer;
3 : begin
4 : writeln('Введите день недели (1-7)');
5 : readln(i);
82
6 : case i of
7 : 1..5:writeln('Рабочий день');
8 : 6:writeln('Суббота');
9 : 7:writeln('Воскресенье');
10:else
11:writeln('Вы ввели неверный день недели');
12:end;
13:readln;
14:end.
Заметьте, что оператор case должен обязательно заканчиваться словом end (открывающий begin не нужен). Аналогичная программа, написанная с помощью оператора if, выглядит так:
Пример 2: case против if.
1 : var
2 : i:integer;
3 : begin
4 : writeln('Введите день недели (1-7)');
5 : readln(i);
6 :
7 : if (i>=1) and (i<=5) then
8 : writeln('Рабочий день')
9 : else
10:if (i=6) then
11:writeln('Суббота')
12:else
13:if (i=7) then
14:writeln('Воскресенье')
15:else
16:writeln('Вы ввели неверный день недели');
17:readln;
18:end.
Очевидно, что с помощью оператора case программа выглядит гораздо понятнее.
5.2. Символьный тип
Тип char занимает в памяти 1 байт. С помощью одного байта можно закодировать 256 различных чисел. Для того чтобы закодировать символы, надо каждому из целых чисел от 0 до 255 поставить в соответствие определенный символ. Кодировки могут быть различны, поэтому для того, чтобы все программы могли стыковаться друг с другом, требуется ввести некоторую стандартную кодировку.
Стандартной кодировкой (т.е. соответствием между символами и числами)
является кодировка ASCII (American Standard Code for Information Interchange –
американский стандартный код для обмена информацией). Это 7-битный двоичный код, с помощью которого можно закодировать 128 чисел в диапазоне от 0 до 127.
Например, символу ‘a’ (латинской букве a) ставится в соответствие число 97, большой латинской букве ‘A’ – число 65 и т.д.
83
Вторая половина символов с кодами от 128 до 255 - может меняться на PC разных типов. Эти символы могут использоваться для кодирования специальных символов и букв других алфавитов, например, кириллицы.
Переменные символьного типа можно присваивать друг другу, вводить с помощью read, readln, а выводить с помощью write, writeln.
Подпрограммы для работы с типом char
Ord(ch) Возвращает ASCII-код символа сh.
Chr(n) Возвращает символ, код которого – число n типа byte.
Pred(ch) Возвращает символ, предшествующий символу сh.
Succ(ch) Возвращает символ, следующий за символом сh.
Замечание: не путайте символы с числами: символ ‘1’ и число 1 имеют разное машинное представление.
Пример 3: Демонстрация работы встроенных функций pred, ord, succ, chr.
1 : var
2 : c:char;
3 : n:byte;
4 : BEGIN
5 : writeln('введите символ');
6 : readln(c);
7 : writeln('ASCII-код символа с = ',ord(c));
8 : writeln('Символ, стоящий перед ',c,' - ',pred(c)); 9 : writeln('Символ, стоящий после ',c,' - ',succ(c));
10:writeln('Введите код символа');
11:readln(n);
12:writeln('Символ, код которого n: ',chr(n));
13:end.
Пример 4: Вывод на экран всей таблицы ASCII.
1 : uses Crt;
2 : var
3 : i:integer;
4 : begin
5 : Clrscr;
6 : for i:=0 to 255 do
7 : write(chr(i));
8 : readkey;
9 : end.
5.3. Работа с клавиатурой
Есть специальная область памяти, называемая буфером клавиатуры, в котором может храниться до 127 символов. Когда этот буфер переполняется, то динамик начинает издавать характерное звучание. Если вы когда-либо включали компьютер, а в это время на клавиатуре лежала книга, то вы его наверняка слышали. Если нет, то вы можете этот эксперимент провести в гораздо менее экстремальных условиях: просто, когда вам предлагают ввести символ с помощью readln, то зажмите любую клавишу, и
84
после того, как на экране появится 127-й символ, дальнейшее отображение символов на экране прекратится, а вместо этого динамик начнет издавать терзающие его динамическую душу звуки.
В модуле Crt есть полезная функция Readkey, которая возвращает первое число из буфера клавиатуры, если он не пуст и ждет нажатия клавиши в противном случае. При этом вводимые символы на экране не отображаются, что очень удобно, например, при использовании пароля.
Для перевода букв в верхний регистр используется функция UpCase(ch). Если символ сh – латинская буква, то функция вернет заглавную букву. Любой другой символ (в том числе и буквы кириллицы) функция изменять не будет.
Все клавиши и их комбинации можно разбить на 3 группы:
1.Клавиши и комбинации клавиш, которые посылают в буфер клавиатуры ASCII-код (буквенные и цифровые клавиши, а также комбинации Ctrl+некоторые другие клавиши).
2.Клавиши и комбинации клавиш, которые посылают в буфер клавиатуры расширенный код (F1..F12, комбинации Ctrl, Alt, Shift, с клавишами F1..F12, а также некоторые комбинации Alt+другая клавиша).
3.Клавиши и комбинации клавиш, не посылающие в буфер клавиатуры коды (Shift, Ctrl, Alt, NumLock, CapsLock, ScrollLock и некоторые комбинации клавиш).
Расширенный код состоит из символа 0 и следующего за ним кода клавиши.
Пример 5: Программа, которая по нажатию клавиши выдает ее код.
1 |
: Uses Crt; |
|
|
2 |
: var |
|
|
3 |
: |
c: char; |
|
4 |
: begin |
|
|
5 |
: |
repeat |
|
6 |
: |
c:=ReadKey;{Ждем нажатия клавиши} |
|
7 |
: |
if ord(c)<>0 then{Если клавиша посылает ASCII-код} |
|
8 |
: |
writeln(ord(c)) |
|
9 |
: |
else |
{Если клавиша посылает расширенный код} |
10:writeln('0',ord(Readkey):8)
11:until ord(c)=27 {27 - ASCII-код клавиши Esc}
12:end.
Вэтой программе мы на каждом витке цикла просим пользователя нажать клавишу. После этого программа анализирует, посылает ли данная клавиша ASCII-код или расширенный код (если нажата клавиша, не посылающая код, то программа просто пропустит ее). Если клавиша посылает расширенный код, то выводится сначала 0, а затем второе число расширенного кода (строка 10).
Давайте подробнее рассмотрим случай, когда нажатие на клавишу посылает расширенный код. В этом случае в буфер клавиатуры посылаются 2 числа: 0 и второе число. При этом в переменную с (см. строку 6) будет записан лишь символ с кодом 0, а второй символ (с порядковым номером, равным второй части кода) останется в буфере клавиатуры. Для того чтобы его извлечь оттуда, надо применить еще раз функцию Readkey. Чтобы выйти из программы, надо нажать клавишу Esc (ASCII-код – 27).
85
Следующая программа отображает все вводимые пользователем строчные латинские буквы как заглавные. Ввод символов можно прекратить, нажав клавишу 1.
Пример 6: Отображение букв в верхнем регистре.
1 |
: |
uses Crt; |
2 |
: |
var |
3 |
: |
c:char; |
4 |
: |
begin |
5 |
: |
while c<>'1' do |
6 |
: |
begin |
7 |
: |
c:=readkey; |
8 |
: |
writeln(UpCase(c)); |
9 |
: |
end; |
10: |
end. |
|
5.4.Генератор псевдослучайных чисел
Впрограммах часто надо иметь возможность генерировать случайные числа. ПК, однако, не может генерировать полностью случайные числа, поэтому используются специальные алгоритмы, при помощи которых достигаются примерно одинаковые вероятности выпадения любого числа из заданного диапазона. В идеале вероятности появления всех чисел должны быть равны. В ТР есть встроенная функция, генерирующая псевдослучайные числа.
Random(n) - Возвращает целое псевдослучайное число от 0 до n-1. Random - Возвращает вещественное псевдослучайное число от 0 до 1.
Также вам понадобится процедура Randomize, которая инициализирует генератор псевдослучайных чисел случайным значением от системного таймера. Это число записывается в предопределенной переменной RandSeed.
Если перед началом работы с генератором случайных чисел (ГСЧ) не вызвать процедуру Randomize, то после каждого запуска программы вы будете получать те же значения случайных чисел.
Вследующем примере вы увидите, насколько случайные числа выдаются встроенным генератором. мы заставим ГСЧ 1000000 раз выдать случайное число от 0 до 2, и подсчитаем, сколько раз он выдал каждое из этих чисел. Т.к. количество испытаний велико, то сильных отклонений от равномерного распределения быть не должно.
Пример 7: Использование генератора псевдослучайных чисел.
1 : const
2 : n=1000000;
3 : var
4 : i,x,y,z:longint;
5 :
6 : begin
7 : randomize;
8 : for i:=1 to n do
9 : case random(3) of
10:0:inc(x);
86
11:1:inc(y);
12:2:inc(z);
13:end;
14:writeln('Количество испытаний ',n);
15:writeln('Ноль выпал ',x,' раз');
16:writeln('Единица выпала ',y,' раз');
17:writeln('Двойка выпала ',z,' раз');
18:readln;
19:end.
Рассмотрим еще один пример использования процедуры randomize.
Пример 8: Проблемы с randomize.
1 |
: var |
|
2 |
: |
i:integer; |
3 |
: begin |
|
4 |
: |
randomize; |
5 |
: |
for i:=1 to 5 do |
6 |
: |
writeln(random(50)); |
7 |
: |
writeln; |
8 |
: |
randomize; |
9 |
: |
for i:=1 to 5 do |
10:writeln(random(50));
11:writeln;
12:end.
Запустив этот пример, вы увидите, что две последовательности по 5 чисел полностью совпадают, т.е. процедура randomize после второго вызова сгенерировала то же самое число, что и после первого вызова. По идее randomize должна обращаться к системному таймеру и, следовательно, должны быть сгенерированы различные числа; но этого, как мы видим, не происходит. По всей видимости, просто процедура randomize работает неправильно.
Совет: не используйте randomize по несколько раз в одной и той же программе (например, не стоит эту процедуру вызывать внутри других подпрограмм).
5.5. Директивы компилятора
Директивы компилятора – это специальные выражения, позволяющие влиять на генерацию компилятором машинного кода. Например, можно менять размер стека, который выделяется программе во время выполнения, отключить проверку переполняемости стека и т.д.
Чтобы объявить директиву компилятору, надо написать {$Dir}, где вместо Dir надо поставить название директивы. Например, чтобы подключить возможность обработки вещественных чисел типа extended, надо указать компилятору в начале программы директиву {$N+}.
Некоторые директивы компилятор использует по умолчанию. Для того, чтобы узнать их, можно набрать комбинацию клавиш Ctrl+O+O.
87
5.6. Что мы уже знаем о структурном программировании
Вы знаете, что программа (на машинном языке) – это последовательность элементарных команд. Давайте теперь проанализируем структуру программного кода, написанного на ТР (все то же самое может быть перенесено на другие процедурные языки).
Во-первых, раздел описаний в программе на машинном языке отсутствует. Давайте теперь разбираться с исполняемым разделом. Исполняемый раздел – последовательность операторов. В принципе, простой оператор можно принять за высокоуровневую команду, поэтому если бы не было составных операторов, то исполняемая часть программного кода была бы подобна программе на машинном языке.
Настоящих нововведения в процедурных языках два:
1.Типизированность
2.Наличие структурных операторов
Структурные операторы разделяют исполняемый код на уровни, причем из внутреннего оператора можно переходить только к тому, в котором он непосредственно находится (например, из нельзя из цикла третьей степени вложенности перейти сразу к первому циклу).
Сразу напрашивается вопрос, как же реализуются повторяющиеся действия на ассемблере (или машинном языке). Оказывается, для этого есть специальная команда, которая позволяет перескочить от одной команды к другой без проверки каких бы то ни было условий. В ТР есть специальный оператор goto, выполняющий те же действия.
Мы посмотрим, как работает goto на примере вычисления факториала числа.
В разделе Label объявляются метки. Сами по себе они ничего не делают. Они нужны только для того, чтобы использовать оператор goto. Если выполнение программы дойдет до строки 13, то оператор goto M просто говорит ПК, что следующим оператором будет первый оператор после метки M, т.е. f:=f*i.
Пример 9: Использование goto.
1 : label
2 : M;
3 : var
4 : i,n,f:longint;
5 : begin
6 : readln(n);
7 : f:=1;
8 : i:=1;
9 : M:
10:f:=f*i;
11:i:=i+1;
12:if (i<=n) then
13:goto M;
14:writeln('f= ',f)
15:end.
Т.е. с помощью оператора goto мы можем организовать цикл. Ясно, что при программировании с использованием меток о разделенности уровней говорить не
88
приходится: с помощью goto можно выскочить из цикла любой степени вложенности. А в ассемблерах метки – необходимость.
Использовать оператор goto не рекомендуется, т.к. любая программа может быть написана без него, а он – лишь запутывает программный код.
Почему оператор goto вообще оставили в ТР?
- Я думаю, что в то время, когда создавался Паскаль (70-е годы) еще много программистов писало программы с использованием goto, и Никлаус Вирт (разработчик Pascal) не рискнул убрать этот оператор. А в дальнейших версиях Pascal goto оставляли для совместимости с предыдущими версиями.
Низкоуровневой замены условному оператору в ТР нет, хотя команда сравнения на ассемблере, разумеется, есть.
Думаю, что сейчас, после того, как вы изучили 1-й отдел книги, было бы полезно еще раз заглянуть в 0-ю главу и посмотреть уже опытным глазом на коды программ на разных языках программирования. Думаю, что вы сможете легко отыскать в ассемблерной программе и аналог goto, а может быть, и аналог if.
89
Глава 6: Массивы
С помощью циклов мы обрабатывали последовательности однотипных данных, но при этом мы не могли эту информацию сохранять; с помощью массивов можно осуществить нашу мечту: мы сможем хранить данные о котировках акций, дневные температуры за последние 100 лет, моделировать многочлены, быстро искать информацию и т.д.
6.1.Что такое массив?
•Массив – набор последовательно идущих переменных одного типа.
ВТР массив описывается так:
имя типа = array [диапазон значений] of тип;
тип – любой тип ТР (возможно, другие массивы).
В качестве индексных типов можно использовать любые порядковые типы, кроме longint и диапазонов с базовым типом longint.
Примеры: type
Mas= array [1..6] of real;
Mas2= array [’a’..’s’] of integer; var
a,b:Mas;
В памяти массив A типа Mas представлен следующим образом:
A[1] A[2] A[3] A[4] A[5] A[6]
Однородность массива позволяет получить доступ к любому его элементу зная лишь тип переменных и адрес начала массива. Роль адреса начала массива играет его идентификатор (например, в предыдущем примере А указывает на начало массива9). Прибавляя смещение, умноженное на размер одной переменной, мы получим как раз адрес нужного элемента.
Массивы статичны, т.е. их размер не может изменяться во время работы программы. Это следует из того, что переменные размещаются в памяти компилятором и не могут быть затем удалены из программы. Поэтому если во время работы программы могут понадобиться массивы разных размеров, то надо объявлять массив наибольшего размера, либо использовать динамические структуры данных, которые мы изучим в главе 13.
Можно объявлять массивы и без непосредственного указания типа:
var
a,b:array [1..6] of real;
Ограничения:
• Диапазон значений не может выходить за границы типа word. Например:
Arr=array[0..6000] of integer; |
{допустимое объявление} |
ArrErr=array[70000..80000] of integer; |
{недопустимое объявление} |
9 С точки зрения компилятора название переменной – это адрес первого байта, занимаемого ею в памяти компьютера.
90
•Общий размер массива, как и любой другой переменной не должен превышать 64 Кб – 16 байт (65520 байт)10
Например, размер массива arrErr=array[10000..60000] of integer;
составляет 100000 байт, и его использовать нельзя.
Массивы одного типа можно присваивать один другому.
В качестве индексов можно использовать и целые числа, и символы (символьный тип будет изучаться позднее). Но начинать индексацию с -4, а тем более с ‘a’ в большинстве случаев крайне неудобно, поэтому мы будем работать с вами преимущественно с индексацией [1..n].
6.2. Примеры работы с массивами
Пример 1: Ввод массива с клавиатуры, печать на экран и нахождение суммы его элементов.
1 : const n=5; {n - количество элементов массива}
2 : type Mas=array[1..n] of integer; {Объявление нового типа} 3 : var
4 : i:byte;
5 : A:Mas; {Объявление переменной типа Mas}
6 : S:integer;
7 : BEGIN
8 : writeln('Введите, пожалуйста, ',n,' элементов массива');
9 : for i:=1 to n do {В этом цикле user вводит элементы массива}
10:read(A[i]);
11:writeln('А вот и наш массив:');
12:for i:=1 to n do {Печать массива А на экран}
13:write(A[i],' ');
14:writeln;
15:for i:=1 to n do {Этот цикл находит сумму элементов массива}
16:S:=S+A[i];
17:writeln('Сумма элементов массива равна ',S);
18:END.
Вы видите, что размер массива объявлен константой n. Такой подход позволяет убить сразу двух зайцев: если мы захотим изменить размер массива, то можем поменять лишь значение константы n (если бы мы ее не использовали, то мы должны были бы пройтись по всему коду, и везде заменить одни числа на другие). Кроме того, могут быть другие константы, используемые в программе, которые совпадают со значением размера вашего массива. Поэтому сам процесс замены одних чисел на другие может стать источником трудно находимых ошибок.
Read(A[i]) позволяет пользователю ввести i-й элемент массива А. Поэтому когда i=1 вы будете вводить 1-й элемент массива, на следующем шаге, когда i=2, - элемент A[2] и т.д. Аналогично происходит вывод массива на экран и вычисление суммы элементов массива.
Замечание: ввести элементы массива, написав read(A) нельзя, так как с помощью процедуры read можно вводить лишь переменные простых типов.
10 Это ограничение возникает из того, что ТР работает под управлением MS-DOS. В Delphi, которая использует функции 32-разрядных операционных систем Windows такого ограничения на размер массива нет.
