■ TEST-PROG
Тестовая программа, позволяющая проверять работоспособ-
ность компьютеров SINCLAIR ZX-SPECTRUM любых конфигура-
ций: 16К, 48К и 128К.
Программа выполняет проверки:
Клавиатуры; цвета, яркости, мигания; звука; ULA;
ПЗУ и оперативной памяти;
Работы с магнитофоном.
Все эти проверки могут быть выполнены по отдельности или
комплексно.
■ ПРОГРАММИРОВАНИЕ НА ЯЗЫКЕ С
1. Введение в систему программирования Си.
Приведем первый пример:
main ()
{printf ("First program");
};
После набора программы Вы должны отметить ее конец (EOF),
нажав SS+1, после чего появится сообщение:
TYPE Y ТО RUN
К этому моменту программа будет скомпилирована и ответ Y
приведет к выполнению программы!!!
Произведя все вышеуказанные манипуляции, Вы, возможно,
увидите на экране:
FIRST PROGRAM
TYPE Y TO RUN:
Если вместо Y вы нажмете любую другую клавишу, то система
HISOFT перейдет в изначальное состояние ввода текста программы.
Описанный способ компиляции используется только для компи-
ляции простых программ. Значительная же часть программ подготав-
ливается сначала РЕДАКТОРОМ и только после этого компилирует-
ся.
Если система Hisoft находится в режиме компилирования, то для
вызова редактора нажмите EDIT (CS+1), а затем ENTER. При этом
на экране появится ">". Каждая строка в режиме редактирования
нумеруется. Последовательность ввода номеров роли не играет; ре-
дактор сам расположит их в порядке возрастания.
Процесс замены или удаления строки с каким-либо номером
аналогичен замене и удалению в Basic. Для получения листинга
наберите директиву L.
После того, как Вы отредактировали исходный текст, его можно
скомпилировать, набрав директиву С. После вызова компилятора
необходимо использовать специальную директиву #include - ее ин-
терпретация приведет к ее замене исходным текстом компилируемой
программы и непосредственной компиляции программы. Если про-
грамма не имеет ошибок, то после этих манипуляций нажмите SS+1
(EOF).
Если после компиляции Вы вернетесь в редактор и введете ди-
рективу L, то Вы опять увидите исходный текст программы, следова-
тельно, он не удаляется при компиляции. Для набора новой
программы необходимо старую удалить директивой DM, N - где M,N
- строки программы.
Пример:
200 main ()
210 {
220 printf (Second program");
230 }
После ввода директив С и #include компилятор выдаст Вам со-
общение: ERROR 37 - неопределенная переменная, в данном случае
- отсутствие первых кавычек ". Если после этого вызвать директиву
Q, то на экране Вы получите строку с ошибкой.
2. Структура программ.
В системе Hisoft зарезервированны следующие ключевые слова:
auto |
else |
long |
typedef |
break |
entry |
register |
union |
case |
extern |
return |
unsigned |
chare |
float |
short |
while |
continue |
for |
sizeof |
|
default |
goto |
static |
|
do |
if |
struct |
fortran |
double |
int |
switch |
asm |
Кроме того: inline и cast.
Идентификаторами в С являются отличные от ключевых слов
последовательности цифр и букв, начинающиеся с букв. Большие и
малые буквы распознаются как разные идентификаторы. Распозна-
ваемая длина идентификатора равна 8 знакам.
Литералами называются числовые (числа), символьные (симво-
лы) и строковые константы. Числовой литерал состоит из последова-
тельности десятичных цифр.
Символьный литерал имеет вид "С", где С является символом,
отличным от " и , или описанием символа ( - описание символа с
кодом равным нулю.
п - описание символа "новая строка",.
г - описание символа "возврат каретки",
t - табуляция,
" - апостроф,
\ - наклонная черта).
Строчный литерал имеет вид "s", где s - любая последователь-
ность символов или их описаний. Строковый литерал представляет
собой ссылку на первый символ последовательности символов в апо-
строфах, дополненную символом с кодом 0 в качестве ограничителя
этой последовательности.
В отличие от других языков, символьный литерал не является
частным случаем строкового. Так "J" представляет собой односим-
вольное данное, которое имеет величину, равную коду буквы J, в то
время как "J" представляет собой данное, указывающее на первый
символ последовательности символов, состоящей из знака J и символа
с кодом 0.
Таким образом пример:
int Age=44; /*Для 1987 года*/
main ()
{
printfC'Jan %с is %d" %s"", "В", Age, "now");
}
содержит коментарий /*...*/, ключевое слово int, идентифика-
торы Age, main и printf, символьный литерал "В", строковый литерал
"now", оператор = и т.д. В итоге получим JanB is 44 "now".
3. Описание переменных, функций и типов.
Данные могут быть переменными и константами и представля-
ются своими именами. Простейшими именами констант являются
литералы, а переменных - идентификаторы. В общем случае данные
могут представляться выражениями.
Тип данных, представленных литералами, следует из самой за-
писи литералов, следовательно описание литералов излишне. Пере-
менные же должны быть обязательно описаны перед их
использованием в программе.
Описание (декларация) имеет вид:
определение-типа список-деклараторов;
Например:
int а, Ь, с.
Список деклараторов может иметь и более сложный вид:
int*(*(*fun())[2]().
Правила конструирования декрараторов следующие:
Если некоторая запись Dec является декларатором, то деклара-
торами также являются записи: (Dec), означающая то же самое, что
и Dec; Dec [w], означающая массив из w элементов; *Dec, означаю-
щая указатель на объект и Dec О, означающая функцию. Наивысшим
приоритетом обладают круглые скобки, отделяющие сам декларатор,
а наинизшим - знак *.
Пример:
int (*NameO) [2 ] равносильно int (*(Name())) [2 ];
- декларация функции Name, результатом которой является
указатель двухэлементного массива данных типа (int). Ошибочной
декларацией является: char Fun()[3], эквивалентная char
(Fun ()) [3 ], т.к. она объявляет функцию, результатом которой явля-
ется трехэлементный массив данных типа (char), а результатом фун-
кции может быть только скалярная величина.
Остается отметить, что если некоторый объект программы объ-
явлен с помощью декларации: определение - типа декларатор, то он
принимается равным определению: (определение - типа псевдодек-
ларатор), в котором декларатор отличается от псевдодекларатора
тем, что последний не содержит идентификатора объекта.
Пример:
Переменная Ptr, объявленная как
int(*Ptr) [2 ] [3 ], имеет тип (int(*) [2 ] [3 ])
Объявление простых переменных.
# переменные типа (char) могут принимать значение 0...255;
# переменные типа (int) могут принимать значения - 32768...32767;
# переменные типа (uns igned) могут принимать значения
0...65535;
# указывающие переменные типа (char) могут принимать значе-
ния указателя на переменную.
Объявление переменных можно совместить с присвоением им
начального значения с тем условием, что это значение может быть
вычисленно еще перед выполнением программы.
Внимание: В данной реализации языка С присваивание на-
чальных данных допускается только во внешних по отношению к
определению функции декларациях:
char Initial-"J",
Name = "В";
int Age = 44;
mainO
{
int Year;
Year =1987;
printf("%c%c is %d in %d",Initial, Name, Age, Year);
}
Описание и присвоение значения переменной Year реализованы
разными операторами (т.к. она определяется внутри функции), а
переменной Age - одним.
Если переменной начальные данные не присваиваются, она при-
нимает значение 0.
Рассмотрим указывающие переменные. Ими являются данные,
определяющие положение других данных. Для преобразования вы-
ражений, представляющих определенные данные, в выражения,
представляющие указатели на эти данные, используется &. Обрат-
ным оператором является *.
К примеру, так как строковый литерал представляет данное,
указывающее на первый знак последовательности, определенной
этим литералом (литерал "JB" представляет данное, указывающее на
знак J), то выражение *"JB" представляет знак J последовательности,
состоящей из знаков J, В и знака с кодом ноль.
В С в выражениях с указателями допускаются операции сложе-
ния и вычитания. Так, если Ptr есть выражение, указывающее на i-ый
элемент последовательности данных, то Ptr + Num, где Num - целое,
указывает на i + Num элемент этой последовательности.
Prt + Num) эквивалентно Ptr [Num ] или Num [Ptr ].
Пару скобок [ ] в дальнейшем будем называть оператором ин-
дексирования.
Пример:
char *Ref="Jan";
main()
{
printf("%s=%c%c%c", Ref,*Ref, *<Ref+l),Ref [2]);
}
В этой программеRef объявлена как переменная, которой можно
поставить в соответствие указатель данных типа (char). Т.о., пере-
менная, представленная в программе идентификатором Ref, имеет
тип (char *). При объявлении переменной Ref присвоено значение,
указывающее на букву J 4-хзнаковой последовательности J,a,n и
знак с кодом ноль. В процессе выполнения команды, в которой вызыв-
вается функция printf, происходит связывание шаблона %s с указа-
телем, присвоенным переменной Ref.
Таким образом на экран выводется последовательность знаков,
йачиная с указанного места, до знака с кодом ноль, исключая послед-
ний, т.е. слова Jan. Если Ref представляет указатель на литеру J, то
*Ref представляет саму букву J. Следовательно, связывание первого
шаблона % с аргументом *Ref приведет к печати буквы J. Ref + 1
представляет указатель на букву a, a*(Ref + 1) - букву а. Выражение
Ref [2 ] является упрощенным представлением *(Ref + 2), а, следова-
тельно, представляет букву п. В итоге получим слово Jan = Jan.
Таким образом:
int Num; - декларация переменной типа (int)
int *Ptr; - (int*)
int **Ref; - (int**)
Объяление массивов.
Массив - переменная сложного типа, все элементы которой име-
ют тот же тип, что и сам массив. Можно объявлять одномерные и
многомерные массивы. Массивам, объявленным до определения фун-
кции, можно присваивать начальные значения. Запись, определяю-
щая начальные значения, называется инициатором и состоит из
списка литералов в { }. По умолчанию элементам массива присваи-
ваивается 0.
char Source [3][3] = {{'Е'>7а'}, {T,'z','a'},
{'JYaVn'}}
main ()
{
char Target [4 ];/*объявление второго массива */
int i;
for (i=0; i; i++)
Target [i ]=Source [2 ] [i ];
Target [3 К ";
printf ("%s", Target);
}
Таким образом Target [0 ]... Target [3 ] = J a n код 0
В языке принято, что каждое выражение, представляющее мас-
сив, сразу же неявно преобразуется в выражение, представляющее
первый элемент массива, следовательно:
printf("%s", Target); эквивалентно printf("%s",
&Target [0 ]);
Такой способ трактовки имен массивов, как имен указателей,
приводит к тому, что выполнение операций над массивами может
быть в общем случае заменено выполнением операций над указыва-
ющими данными.
Кроме того, элементами массивов могут быть структуры, унии
и указывающие переменные:
char *Family [3 ] = {"Ewa'VTza'V'Jan"}
main ()
{
printf("%c&%c", **Family, *Family[2]);
}
Здесь элементами массива являются указатели данных типа
(char). При определении массива, его элементу Family [0 ] присвоено
указание на первый знак последовательности Ewa, аналогично,
Family [1 ] - Iza, a Family [2] - Jan. Т.к. выражение Family представ-
ляет собой указатель на Family [0 ], выражение *Family представляет
элемент Family [0 ], а, следовательно, **Family представляет собой
объект, указанный выражением Family [0 ], т.е. букву Е. Выполнение
программы приводит к печати Е & J.
Можно сказать, что если сам массив Family имеет тип (char * [3 ])
(3-х элементный массив указателей данных типа (char)), то выраже-
ние Family имеет тип (char *), выражение *Family - (char *), а выра-
жение **Family - (char).
В отличие от предыдущей), в данном примере используется пе-
ременная, указывающая на массив:
int Array [2 ] [2 ] = {{3,2}, {1,0}},
(*Ptr) [2] = Array+1;
main ()
{
printf ("% d%cT, **Ptr, Ptr [-1 ][0]);
}
Здесь Array - 2-х элементный массив, элементами которого яв-
ляются 2-х элементные массивы с элементами типа int. Массив Array
можно рассматривать как двумерный массив с элементами 3,2,1,0. В
то же время Ptr является простой переменной, указывающей на 2-х
элементный массив типа int. Ей приписано значение Аггау+1 и она
имеет тип (int* [2 ]). Такое присвоение правомочно, т.к. Ptr и Аггау+1
имеют одинаковый тип (int * [2 ]).
Объявление структур и уний
Структурами и униями являются сложные переменные, состоя-
щие из компонентов разного типа. Эти компоненты называются по-
лями. Полями могут быть не только простые переменные, но и
массивы, структуры и унии.
Поля структур размещаются в памяти в порядке их объявления,
а каждое поле унии размещается, начиная от одного и того же места.
Следовательно, если в программе объявлена структура Record:
struct {
int Fix;
char Chr;
int Arr[3];
char *Ptr [2 ]
} Record;
с полями Fix, Chr, Arr и Ptr, то она займет столько места, сколько
занимают все поля в сумме. Тогда как, если объявлена уния:
union {
int Fix;
char Chr;
int Arr [3 ];
char *Ptr[2];
} Record
то она займет столько места, сколько занимает самое длинное
поле. Учитывая, что в Hisoft переменная типа (char) занимает один
байт, а типа (int) и указывающие - по 2 байта, то структура займет
11 байт, а уния - 6 байт ОЗУ. Т.о. различия между униями и структу-
рами заключаются только в способе размещения в ОЗУ и в том, что
униям нельзя приписать начальное значение.
Объявление структуры состоит из ключевого слова struct, после
которого в { } стоит перечень полей структуры, а после него - декла-
ратор и знак ;. Декларатор может состоять из идентификатора струк-
туры, а может иметь и более сложный вид:
struct {
int Fix;
char Chr [2] [2];
} Str, Arr[3 ], *Ptr[4 ];
- декларация структуры Str с полями Fix и Chr, декларация
Зх-элементной таблицы структур Arr, а так же декларация 4х-эле-
ментной таблицы указателей структур таких как Str и Arr [i ].
Если между ключевым словом struct и первой скобкой стоит
идентификатор, то в дальнейших объявлениях он может быть исполь-
зован для идентифицирования структуры данного вида. В частности,
это означает, что такое объявление, как:
struct Tag {
int Fix;
char Chr;
} One, Two [2 ];
в которой Tag является идентификатором, в дальнейшем назы-
ваемым описателем структуры, может с равным успехом быть заме-
нено объявлением:
struct Tag {
int Fix;
char Chr;
} One
struct Tag Two [2];
или парой объявлений:
struct Tag {
int Fix;
char Chr;
};
struct Tag One, Two [2 ];
Использование описателя структуры целесообразно в тех случа-
ях, когда в процессе объявления поля структуры необходимо обраще-
ние к типу уже объявленной структуры:
struct List {
int Data;
struct List *Link;
} Str;
где полю Link могут быть присвоены указатели структур типа
(struct List).
Полям структур (но не уний) могут быть присвоены исходные
данные. Инициатор структуры состоит, в общем случае, из списка в
{}. Элементами списка могут быть константы, чаще всего литералы,
или подсписки; константы для инициализации скалярных полей, а
подсписки для инициализации полей, которые являются массивами
и структурами. Подсписок, используемый для инициализации поля,
имеет вид идентификатора объекта с типом этого поля. Если некото-
рый подсписок содержит полный комплект константных выражений,
инициирующих данное поле, то { } могут быть опущены. Если для
определенных полей не определены явно начальные данные, а объяв-
ление структуры находится вне определения функции, то таким по-
лям присваивается литерал ноль. Полям структуры, объявленной
внутри функции, не могут быть присвоены начальные значения,
struct {
char One, Two;
struct {
char Uno, Due;
} InStr;
char Arr[2],
*Ref*
} OutStr={l,2{3,4},{5}};
- полям структуры OutStr присвоены данные таким образом, что
полю Arr [0 ] будет присвоено 5, а полю Ref - 0.
Внимание: В Hisoft для упрощения компилятора принято, что
инициатор структуры не может содержать подсписок. Из этого сле-
дует, что в приведенной выше декларации инициатор должен быть
изменен на следующий: {1,2,3,4,5}. Другим отступлением является
то, что присвоение данных относится не к отдельным полям структу-
ры, а к области памяти, выделенной структуре. Учитывая, что эле-
ментом списка инициатора является выражение со значением 0-255,
то инициируется один или два байта области памяти, присвоенной
структуре:
struct {
int Uno, Due;
} Str= {1,1};
- присвоение полю Uno данного, величиной 257, а полю Due - 0,
вместо 1 для обоих полей.
Объявление функций.
Объявление функций состоит из заголовка и тела функции.
Заголовок содержит имя функции, в круглых скобках список пара-
метров функции и объявление параметров. Тело функции является
групповой командой, начинающейся {и кончающейся }. Если выпол-
нение функции заканчивается командой:
return ехр;
где ехр - выражение, то в месте вызова функции станут доступ-
ными данные ехр. Тип данных определяется на основе анализа заго-
ловка функции. В следующем примере на экране печатается Bielecki:
main ()
{
char *Surname О;
printf ("%s", Surname ("Jan Bielecki",4));
}
char *
Surname (String, Number)
char *String;
int Number;
{ return String + Number;}
Здесь определяются две функции: функция main () без Ларамет-
ров и функция Surname с двумя параметрами. Параметр String типа
(char*) связан с аргументом "Jan Bielecki", а параметр Number типа
(int) связан с аргументом 4. Результатом функции является данное,
выступающее в команде return. Это данное имеет тип (char *) в
соответствии с объявленным в заголовке функции типом. Так как
вызов функции происходит перед ее определением, в функции main
производится предварительная декларация. Эта декларация имеет
вид заголовка определения, из которого изъят список имен парамет-
ров и их объявление.
Если выполнение функции не заканчивается командой return,
содержащей выражение, то определение типа результата излишне. В
Си можно опускать объявление типа результата или параметров фун-
кции, если они имеют тип (int).
int_13=-13;
int
main ()
{
int Negate 0;
printf ("%d", Negate(__13));
}
int
Negate (Par)
int Par;
{ return -Par;}
- упрощается до:
int__13 = -13;
main ()
{
printf ("%dM, Negate,(_13));
}
Negate (Par)
{
return -Par;
}.
В следующей программе определяются функции First и Second
с предварительной декларацией этих функций.
char Name [2] = "bj";
extern char ""First 0; /* First */
main ()
{
printf("%c", *First());
}
char *
First 0
{
char *Second(); /* Second */
printf ("%c", *Second (Name));
return Name;
}
char *
Second (Initials)
char Initial [2 ];
{
return Initial + 1;
}
Результат работы программы - печать jb.
Описание типов.
Объявление (описание, преобразование) типа аналогично опре-
делению переменной за исключением ключевого слова typedef.
typedef char *PTR,
VEC [3 ];
- определяет тип PTR, идентифицирующий указатель данных
типа (char) и тип VEC, идентифицирующий 3-х элементный массив
данных типа (char). После такого определения типов можно напи-
сать:
PTR Arr [2], ChrPtr;
VEC *Ref;
В первой строке объявляется 2-х элементный массив перемен-
ных типа (PTR), а; следовательно, массив указателей данных типа
(char), и переменная ChrPtr, указывающая на такие данные. Во вто-
рой - указатель данных типа VEC, следовательно, указатель 3-х
элементных массивов типа (char).
Легко заметить, что такое определение равносильно:
char *Arr[2], ChrPtr;
char (*Ref) [3 ];
Пример:
typedef struct List {
int Data;
struct List *Linck;
} LIST;
- LIST является именем типа (struct List). Таким образом деклара-
ция List Str [3 ], *Ref; аналогична struct List
Str[3], *Ref
4. Построение выражений.
Основным элементом языка Си являются первичные выраже-
ния. Ими являются:
- идентификатор : Volume, Fun и др.;
-литерал: 13, "JB", 'W и др.;
- некоторое выражение в круглых скобках: (а + Ь);
- выражение, представляющее функцию, за которой следует
список аргументов в круглых скобках: Fun (a,b);
- первичное выражение, после которого стоит выражение в
квадратных скобках: Arr [3+i ];
- первичное 1-выражение (т.е. выражение, которое стоит слева
от знака или символа =), представляющее собой структуру,
сразу после которой стоит знак . и идентификатор поля
структуры: Str.Chr;
- первичное выражение, представляющее указатель структуры,
непосредственно после которого стоит символ и
идентификатор поля структуры: Ptr - Chr.
Дополнительные правила:
-1 - выражением не является запись, представляющая указатель
объекта:
&Fix;
- в записи вида:
первичное-выражение (список-выражений)
Первичное выражение должно представлять функцию:
Fun(a,b); -в записи вида:
первичное-выражение [выражение ]
обязательно одно из выражений должно быть указателем на
элементы массива: 2 ["Jan" ].
Выражением является выражение с предшествующим операто-
ром конверсии (имя-типа). Если Ехр является выражением, пред-
ставляющим некоторое данное, то (имя-типа) Ехр является
выражением, представляющим данное типа (имя-типа). Именем ти-
па является запись, состоящая из описания типа и декларатора. Если,
к примеру, Ехр - выражение типа (int *), то (char *) Ехр представляет
данное типа (char *).
Внимание: В системе Hisoft запись (имя-типа), выступающая
в значении оператора конверсии, должна быть упреждена ключевым
словом cast: cast (char *) Ехр.
Выражением является имя типа в круглых скобках с предшест-
вующим оператором размера sizeof. Если Nam - имя типа, то
sizeof (Nam) является выражением, представляющим данные, вели-
чиной, равной числу байт, необходимых для хранения в ОЗУ внут-
реннего данного типа (Nam).
Внимание: В системе Hisoft имя типа должно быть ключевым
словом, означающим тип, к примеру, int или идентификатор типа.
Внимание: В системе Hisoft оператор sizeof не может отно-
ситься к выражениям.
Выражением является запись вида:
]-выражение оператор-присваивания выражение Оператор-
присваивания - =, *=, I =, %=, -=, <=, >=, &=, /ч= и !=.
Выполнение операции а@=Ь, где @ - один из вышеуказанных
символов, равносильно выполнению операции а=а@Ь.
. Выражением является запись вида:
выражение? выражение-1: выражение-2
Если выражение имеет значение, отличное от 0, то результатом
3-х аргументной операции ?: является данное, представленное выра-
жением-1, в противном случае - выражением-2.
Пример:
char Arr [2] [2] = {'J', 'а', 'п', 'В'},
* Ptr;
main ()
{
typedef char ""ChrPtr;
Ptr = cast (Chrtr) (Arr+1)+1;
printf ("%c%c", ♦♦Arr, *Ptr);
}
Конверсия может быть явной и неявной. Явная конверсия явля-
ется выражением с предшествующим ключевым словом cast (только
в Hisoft). В примере применен оператор коверсии к выражению (Arr
+ 1). При этом выражение (Arr +1), представляющее данное типа
(char(*) [2]), преобразуется в выражение, представляющее данное
типа (char *), т.е. в выражение такого же типа, как и Ptr.
Если данные типа (char) присваиваются переменной (int) или
(unsigned), то при этом самый старший байт этой переменной запол-
нится нулями. И, наоборот, если данные типа (int) или (unsigned)
будут присвоены переменной тип (char), то переменной присвоится
младший байт данного. А если данное типа (int) присвоить перемен-
ной типа (unsigned) и наоборот, то можно потерять данные.
Неявная конверсия может выполняться и при выполнении
арифметических операций: - если некоторый аргумент имеет тип
(char), то он будет преобразован к типу (int); - если только один
аргумент 2х-аргументной операции имеет тип (unsigned), то второй
будет преобразован к этому же типу; - в других случаях оба аргумента
должны быть типа (int), таким же будет и результат.