HISOFT C Compiler V1.1
Copyright C 1984 HISOFT
Итак, первый пример:
main()
{ printf ("Pierwszy program");
};
После набора программы Вы должны отметить ее конец (EOF), нажав
SS+I, после чего появится сообщение:
Type y to run
К этому моменту программ будет скомпилирована и ответ y приведет к
выполнению программы!!!
Произведя все вышеуказанные манипуляции, Вы ,возможно, увидите
на экране:
Pierwszy program
Type y to run:
Если вместо y Вы нажмете любую другую клавишу, то система HiSoft
перейдет в изначальное состояние ввода текста программы.
Описанный способ компиляции используется только для компиляции
простых программ. Значительная же часть программ подготавливается
сначала РЕДАКТОРОМ и только после этого компилируется.
Если система HiSoft находится в режиме компилирования, то для
вызова редактора нажмите EDIT (CS+1), а затем ENTER. При этом на
экране появится '>'. Каждая строка в режиме редактирования нумеруется.
Последовательность ввода номеров роли не играет; редактор сам распо-
ложит их в порядке возрастания.
Процесс замены или удаления строки с каким-либо номером анало-
гичен замене и удалению в Basic. Для получения листинга наберите
директиву L.
После того, как Вы отредактировали исходный текст, его можно
скомпилировать, набрав директиву С. После вызова компилятора необхо-
димо использовать специальную директиву #include - ее интер-
претация приведет к ее замене исходным текстом компилируемой
программы и непосредственной компиляции программы. Если программа
не имеет ошибок, то после этих манипуляций нажмите SS+I (EOF).
Если после компиляции Вы вернетесь в редактор и введете директиву
L, то Вы опять увидите исходный текст программы, следовательно, он
не удаляется при компиляции. Для набора новой программы необходимо
старую удалить директивой Dm,n - где m,n - строки программы.
Пример:
200 main ()
210 {
220 printf(Trzeci 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 знакам.
Литералами называются числовые (числа), символьные (символы) и
строковые константы. Числовой литерал состоит из последовательности
десятичных цифр. Символьный литерал имеет вид 'с', где с является
символом, отличным от ' и , или описанием символа ( - описание
символа с кодом равным нулю, n - описание символа 'новая строка' ,
r - описание символа 'возврат каретки', t - табуляция, ' -
апостроф, \ - наклонная черта). Строчный литерал имеет вид 's',
где s - любая последовательность символов или их описаний. Строковый
литерал представляет собой ссылку на первый символ последовательности
символов в апострофах, дополненную символом с кодом 0 в качестве огра-
ничителя этой последовательности.
В отличие от других языков, символьный литерал не является
частным случаем строкового. Так 'J' представляет собой односимволь-
ное данное, которое имеет величину, равную коду буквы J, в то время
как "J" представляет собой данное, указывающее на первый символ
последовательности символов, состоящей из знака J и символа с кодом
0.
Таким образом пример:
int Age=44; /*Для 1987 года*/
main ()
{
printf("Jan %c is %d" %s"",'B', Age, "now");
}
содержит комментарий /*...*/, ключевое слово int, идентификаторы
Age, main и printf, символьный литерал 'B', строковый литерал
"now", оператор = и т.д. В итоге получим JanB is 44 "now".
3. Описание переменных, функций и типов
Данные могут быть переменными и константами и представляются
своими именами. Простейшими именами констант являются литералы, а
переменных - идентификаторы. В общем случае данные могут представ-
ляться выражениями.
Тип данных, представленных литералами, следует из самой записи
литералов, следовательно описание литералов излишне. Переменные же
должны быть обязательно описаны перед их использованием в программе.
Описание (декларация) имеет вид:
определение-типа список-деклараторов;
к примеру; int a,b,c. Список деклараторов может иметь и более сложный
вид; int*(*(*fun())[2]().
Правила конструирования деклараторов следующие:
если некоторая запись Dec является декларатором, то деклараторами
также являются записи: (Dec), означающая то же самое, что и Dec;
Dec[w], означающая массив из w элементов; *Dec, означающая указа-
тель на объект и Dec(), означающая функцию. Наивысшим приоритетом
обладают круглые скобки, отделяющие сам декларатор, а наинизшим -
знак *.
Пример:
int (*Name())[2] равносильно int(*(Name()))[2];
- декларация функции Name, результатом которой является указатель
двухэлементного массива данных типа (int). Ошибочной декларацией
является: char Fun()[3], эквивалентная сhar (Fun())[3], т.к.
она объявляет функцию, результатом которой является трёхэлементный
массив данных типа (char), а результатом функции может быть только
скалярная величина.
Остаётся отметить, что если некоторый объект программы объявлен
с помощью декларации:
определение-типа декларатор,
то он принимается равным определению:
(определение-типа псевдодекларатор),
в котором декларатор отличается от псевдодекларатора тем, что
последний не содержит идентификатора объекта.
Пример: Переменная Ptr, объявленная как int(*Ptr)[2][3], имеет
тип (int(*)[2][3])
-1Объявление простых переменных-0
# переменные типа (char) могут принимать значения 0...255;
# переменные типа (int) могут принимать значения -32768...32767;
# переменные типа (unsigned) могут принимать значения 0...65535;
# указывающие переменные типа (char) могут принимать значения
указателя на переменную.
Объявление переменных можно совместить с присвоением им
начального значения с тем условием, что это значение может быть
вычисленно еще перед выполнением программы.
-1UWAGA-0: В данной реализации языка С присваивание началь-
ных данных допускается только во внешних по отношению к
определению функции декларациях:
char Initial = 'J',
Name = 'B';
int Age = 44;
main()
{
int Year;
Year = 1987;
printf("%с%с is %d in %d", Initial, Name, Age, Year);
}
Описание и присвоение значения переменной Year реализованы
разными операторами (т.к. она определяется внутри функции), а
переменной Age - одним.
Если переменной начальные данные не присваиваются, она принимает
значение 0.
Рассмотрим указывающие переменные . Ими являются данные,
определяющие положение других данных. Для преобразования выражений,
представляющих определенные данные, в выражения, представляющие
указатели на эти данные, используется &. Обратным оператором является *.
К примеру, так как строковый литерал представляет данное,
указывающее на первый знак последовательности, определенной этим
литералом ( литерал "JB" представляет данное, указывающее на знак
J ), то выражение *"JB" представляет знак J последовательности,
состоящей из знаков J, B и знака с кодом ноль.
В С в выражениях с указателями допускаются операции сложения и
вычитания. Так, если Ptr есть выражения, указывающее на i-ый элемент
последовательности данных, то Ptr + Num, где Num - целое, указывает
на i + Num элемент этой последовательности.
*(Ptr + Num) эквивалентно Ptr[Num] или Num[Ptr].
Пару скобок [ ] в дальнейшем будем называть оператором индекси-
рования.
Пример:
char *Ref="Jan";
main()
{
printf("%s=%c%c%c", Ref,*Ref,*(Ref+1),Ref[2]);
}
В этой программе Ref объявлена как переменная, которой можно
поставить в соответствие указатель данных типа (char). Т.о.,
переменная, представленная в программе идентификатором Ref, имеет
тип (char *). При объявлении переменной Ref присвоено значение,
указывающее на букву J 4-х знаковой последовательности J,a,n и знак
с кодом ноль. В процессе выполнения команды, в которой вызывается
функция printf, происходит связывание шаблона %s с указателем,
присвоенным переменной Ref. Т.о. на экран выводится последователь-
ность знаков, начиная с указанного места, до знака с кодом ноль,
исключая последний, т.е. слова Jan. Если Ref представляет указатель
на литеру J, то *Ref представляет саму букву J. Следовательно,
связывание первого шаблона % с аргументом *Ref приведет к печати буквы
J. Ref + 1 представляет указатель на букву а, а *(Ref + 1) - букву
а. Выражение Ref[2] является упрощенным представлением *(Ref + 2), а,
следовательно, представляет букву n. В итоге получим слово Jan =
Jan.
Таким образом:
int Num; - декларация переменной типа (int)
int *Ptr;- (int *)
int **Ref;- (int **)
-1Объявление массивов-0
Массив - переменная сложного типа, все элементы которой имеют
тот же тип, что и сам массив. Можно объявлять одномерные и многомерные
массивы. Массивам, объявленным до определения функции, можно
присваивать начальные значения. Запись, определяющая начальные зна-
чения, называется инициатором и состоит из списка литералов в { }.
По умолчанию элементам массива присваивается 0.
char Source [3][3] = {{'E','w','a'}, {'I','z','a'},
{'J','a','n'}}
main()
{
char Target [4]; /* объявление второго массива */
int i;
for (i=0; i<4; 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", "Iza", "Jan"};
main ()
{
printf("%c&%c", **Family, *Family[2]);
}
Здесь элементами массива являются указатели данных типа (char).
При определении массива, его элементу Family[0] присвоено указание на
первый знак последовательности Ewa, аналогично, Family[1] - Iza, а
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%d", **Ptr, Ptr[-1][0]);
}
Здесь Arrаy - 2-х элементный массив, элементами которого являются
2-х элементные массивы с элементами типа int. Массив Arrаy можно
рассматривать как двумерный массив с элементами 3,2,1,0. В то же время
Ptr является простой переменной, указывающей на 2-х элементный массив
типа int. Ей приписано значение Array+1 и она имеет тип (int *[2]).
Такое присвоение правомочно, т.к. Ptr и Array+1 имеют одинаковый тип
(int *[2]).
-1Объявление структур и уний-0
Структурами и униями являются сложные переменные, состоящие из
компонентов разного типа. Эти компоненты называются полями. Полями
могут быть не только простые переменные, но и массивы, структуры и
унии.
Поля структур размещаются в памяти в порядке их объявления, а
каждое поле унии размещается начиная от одного и того же места.
Следовательно, если в программе объявлена структура 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, декларация
3х-элементной таблицы структур Arr, а так же декларация 4х-элемент-
ной таблицы указателей структур таких как Str и Arr[i].
Если между ключевым словом struct и первой скобкой стоит
идентификатор, то в дальнейших объявлениях он может быть использован
для идентифицирования структуры данного вида. В частности, это
означает, что такое объявление, как:
struct Tag {
int Fix;
char Chr;
} One, Two [2];
в которой Tаg является идентификатором, в дальнейшем называемым
описателем структуры, может с равным успехом быть заменено
объявлением:
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={1,2,{3,4},{5}};
-полям структуры OutStr присвоены данные таким образом, что полю
Arr[0] будет присвоено 5, а полю Ref - 0.
-1UWAGA-0: В Hisoft для упрощения компилятора принято, что
инициатор структуры не может содержать подсписок. Из этого следует,
что в приведенной выше декларации инициатор должен быть изменен на
следующий: {1,2,3,4,5}. Другим отступлением является то, что
присвоение данных относится не к отдельным полям структуры, а к
области памяти, выделенной структуре. Учитывая, что элементом списка
инициатора является выражение со значением 0 - 255, то инициируется
один или два байта области памяти, присвоенной структуре:
struct {
int Uno, Due;
} Str = {1,1};
-присвоение полю Uno данного, величиной 257, а полю Due - 0, вместо
1 для обоих полей.
-1Объявление функций-0
Объявление функций состоит из заголовка и тела функции.
Заголовок содержит имя функции, в круглых скобках список параметров
функции и объявление параметров. Тело функции является групповой
командой, начинающейся { и кончающейся }. Eсли выполнение функции
заканчивается командой:
return exp;
где exp - выражение, то в месте вызова функции станут доступными
данные exp. Тип данных определяется на основе анализа заголовка
функции. В следующем примере на экране печатается 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 ();
printf ("%d", Negate(_13));
}
int
Negate (Par)
int Par;
{ return -Par; }
- упрощается до:
int _13 = -13;
main ()
{
printf ("%d", Negate (_13));
}
Negate (Par)
{
return -Par;
}.
В следующей программе определяются функции First и Second с
предварительной декларацией этих функций.
char Name [2] = "bj";
extern char *First (); /* First */
main ()
{
printf("%c", *First());
}
char *
First ()
{
char *Second(); /* Second */
printf ("%c", *Second (Name));
return Name;
}
char *
Second (Initials)
char Initial [2];
{
return Initial + 1;
}
Результат работы программы - печать jb.