Компьютер и Программирование
(для начинающих программистов)

Эту статью я решил написать с целью дать некоторые понятия о реальной работе компьютера для тех людей, кто хочет научиться программировать на моем языке ACL или на языке Java по моим лекциям. Дело в том, что и описание языка ACL и лекции о Java написаны для людей, которые уже что-то знают о работе компьютера. Раньше без такого знания нельзя было сделать и шагу. А сегодня политика Билла Гейтса и его сторонников состоит в том, чтобы дать людям возможность работать на компьютере не понимая как он это делает. И многие люди так и работают не понимая. Хотя все написано в старых книгах, но их становится все меньше. А в новых толстых книгах пишут так: кликни кнопку такую-то, откроется строка ввода, введи то-то, затем опять кликни кнопку и так далее в том же духе. И вот человек может работать на компьютере многие годы и успешно, но не знать как компьютер работает на самом деле. А писать про то, как он работает, при описании конкретного языка программирования довольно скучное занятие, так как эта информация общая для всех языков. Для действительно нормальной работы на компьютере необходимо представлять себе возможности операционной системы компьютера и механизм его работы. В этой статье я по возможности попробую об этом написать.

При покупке нового компьютера обычно интересуются его характеристиками, каковыми являются скорость процессора, размер оперативной памяти и размер винчестера. Еще могут быть разные приборы типа CD и DVD дисководов и прочих приставок, но это для нас неинтересно. Мы обсудим первые два параметра - процессор и оперативная память. Эти два элемента присущи и разумным существам. Процессор можно назвать механизмом принятия решений, а память - это информация о событиях, которые имели место в прошлом. Оперируя этой информацией человек принимает решения. Человек без памяти неспособен думать, у него нет информации. Компьютер без памяти тоже не может работать. Но на этом сходство и заканчивается. Потому что и механизм принятия решений и память в компьютере очень и очень примитивные по сравнению с человеческой. Но зато быстродействие на много порядков выше. И за счет очень высокого быстродействия компьютер способен эффективно работать.

Память

Начнем с памяти. Что такое память компьютера? Это огромное число выключателей. Каждый выключатель имеет только два положения: включено и выключено. Приборов с двумя состояниями очень много. Самые распространенные их них - магниты. Как раз они лежат в основе магнитных носителей информации. Такие носители хороши тем, что информацию в них можно менять, то есть магниты перемагничивать. Магнитики могут быть очень маленькими и их число огромным. Но оторвемся от материи и перейдем к математике. В математике элемент с двумя состояниями называется битом. Он имеет два значения 0 и 1. Как известно, мы используем десятичную систему счисления, в которой числа записываются разрядами и каждый разряд может иметь значения от 0 до 9. А компьютер использует двоичную систему. В нем числа записываются разрядами, каждый из которых является битом, например, 100100111100. Такие числа на бумаге записываются очень длинными рядами, но можно группировать биты. Если разбить число на разряды из 3-х битов получаем 8-ричную систему счисления. Сорок лет назад она была популярна так как для ее использования хватает цифр десятичной системы и, кроме того, восемь рядов вполне умещалось на перфокарте. Но потом все же перешли на 16-ричную систему счисления, когда каждый разряд объединяет 4 бита. Причина простая. Такое число по длине равно половине байта. Правда для его записи используются буквы 0123456789abcdef. А что такое байт? Байт - это 8 битов вместе. Это единица измерения размеров файлов, и вообще носителей памяти, короче носителей. Байт может принимать значения от 0 до 255. Такие значения еще называются ASCII-кодами, так как они кодируют буквы текста. А в вычислениях байт имеет диапазон значений от -128 до 127. Почему отрицательных больше? Потому что 0 тоже положительное число. 1024 байта называется килобайтом или Kb, а часто пишут просто K. Не 1000, а именно 1024=2^10, то есть в килобайте 18 битов. Ну а 1024 Kb = 1 Mb - мегабайт. Но сегодня уже считают и в Gb - гигабайтах и в Tb - терабайтах. Каждая ступень в 1024 раз больше предыдущей. То есть малая величина бита не помеха, их можно записать очень много, и все дела.

Вообще говоря, процессор способен выполнять побитовые операции. Но скорость вычислений очень заметно зависит от длины процессора, то есть числа битов, которые он способен обработать за одну операцию. Первые домашние персональные компьютеры имели 8-битовый процессор. Революцией стало появление 16-битовых процессоров фирмы IBM. На их основе стали выпускаться IBM-PC серии 086, 186, 286. Затем появились 32-битовые процессоры. Сегодня это стандарт, но есть уже и 64-битовые процессоры. Соответственно формы представления чисел в компьютере тоже разные. Есть длина размером в 1 байт для целых чисел. Они могут быть беззнаковыми, это ASCII-коды, или знаковыми. Есть длина размером 2 байта для целых чисел, тоже числа могут быть только положительные или обоих знаков. Беззнаковые числа размером 2 байта можно назвать уникодами, так как они кодируют все символы всех языков в мире и эта кодировка обязательна для всех стран. Чисел размером 3 байта не бывает (не делится на 2). Бывают только 4-хбайтовые числа. Они могут быть как целыми, так и с порядком, то есть часть числа задает основу, как правило дробную часть числа от 0 до 0.99999999..., а другая, меньшая часть числа задает степень 10 для этого числа. Их можно назвать еще реальными или вещественными числами. Ну и могут быть числа с длиной 8 байтов, как целые так и с порядком. Только 64-битовый процессор способен обработать такое число за одну операцию, остальные это делают в несколько операций. Все эти длины чисел имеют названия. В языке Java (и ACL) используются следующие названия: уникоды называются char, 1-байт целые знаковые - byte, 2-байта целые знаковые - short, 4-байта целые знаковые - int, 4-байта реальные - float, 8-байтов целые знаковые - long, 8-байтов реальные - double. Соответственно при записи чисел на винчестер или другой носитель всегда необходимо указывать формат чисел. Форматы можно преобразовывать из одного в другой. Это надо знать при программировании, так как от этого зависит результат.

Но кроме чисел необходимо кодировать и тексты, это тоже информация. В эпоху 16-битовых процесоров была принята 1-байтовая система кодировки текстов. Она и сейчас еще широко используется. Это система ASCII кодов. Первые 127 кодов зарезервированы для букв американского языка, цифр, знаков препинания и разных важных знаков. Вторая половина содержит символы, используемые в других языках, которых нет в американском языке. Но таких символов явно недостаточно. В таких странах как Россия, Греция и других ее заменяют на альтернативную таблицу, которая кодирует русские (в России), греческие (в Греции) и так далее символы. Соответственно используется переключатель клавиатуры. Более того, даже русских альтернативных кодировок несколько. Есть кодировка ДОС, есть кодировка Виндовс и есть кодировка NET, принятая первоначально для пересылки писем по интернету. Из-за этого имеются серьезные трудности и необходимо иметь перекодировщики из одной кодировки в другую. Проблему решает система двухбайтовых уникодов. Она принята в Java и есть в ACL. Но проблема все же остается в том смысле, что уникодов так много, что не все шрифты поддерживают все уникоды. В программе vkACL.jar есть специальная утилита, которая сразу показывает все символы, поддерживаемые тем или иным шрифтом в виде удобной таблицы. Первые 127 уникодов совпадают с ASCII кодами, но дальше сходство заканчивается. В таблице есть и русские символы и греческие, но они имеют другие номера.

Есть и другие виды информации кроме текстов и чисел, которые тоже кодируются битами и байтами. К ним относятся цвета и картинки. Картинки кодируются по точкам, которые называются пикселями. Каждая точка описывается определенным количеством битов в зависимости от ее цветности. Сначала записывают точки первого ряда, затем второго и так далее. Запись может быть сделана сверху вниз, то есть первый ряд верхний или снизу вверх, то есть первый ряд нижний. Такой формат картинок называется raw (сырой). Сколько битов требуется на один пиксель зависит от цветности картинки. Бывают только черно-белые картинки, в которых пиксели имеют только два цвета черный (0) и белый (1) и достаточно одного бита для описания одной точки. Это хорошо для книжных текстов без картинок. Бывают 4-х цветные картинки (два бита) 16-цветные картинки (4 бита) 256-цветные картинки (1 байт) и 256*256*256-цветные картинки (3 байта). Только картинки последнего типа описываются универсально. У них первый байт задает градации красного, второй - градации зеленого и третий - градации синего. Всего 256 уровней яркости. Для картинок с меньшим размером пиксела поступают обычно по другому. Биты не описывают цвет непосредственно, а указывают номер цвета в специальной таблице цветов. Очевидно при записи в файл такой картинки необходимо перед самой таблицей пикселей еще записать и таблицу цветов, потому что одна и та же картинка будет выглядеть по разному если поменять таблицу цветов. К абсолютным картинкам еще принято относить серые картинки, когда при записи одного байта на пиксель этот байт просто описывает оттенки серого цвета от черного до белого. Каждый кто работал со сканером уже знает об этих понятиях.

Картинки появляются на экране компьютера каждый раз, когда их пиксели попадают в специальную часть памяти компьютера, которая так и называется - видеопамять. В ней всегда что нибудь записано и как раз это и видно на экране. Экран обновляется снова и снова с определенной частотой и как только в видеопамять записывается новая информация сразу меняется картинка на экране. От размера видеопамяти зависит максимальное разрешение экрана, но только в том случае, когда экран имеет большое разрешение. Иначе экран показывает не всю видеопамять, а только ее часть. Все режимы разрешения экрана можно настроить с помощью программ операционной системы. Такие программы называются драйверами и они как раз регулируют работу внешних приборов, каким и является экран монитора. Иногда картинка готовится по частям, то есть сначала рисуется один фрагмент, потом другой и так далее. Если все это сразу записывать в видеопамять, то пользователь будет видеть процесс рисования, то есть в какие-то моменты будут присутсвовать элементы и старой и новой картинки. Не всем это нравится и не всегда это хорошо. В таких случаях используют так называемую буферизацию. Выделяют в памяти компьютера другую часть, равную по размерам видеопамяти и рисуют картинку в ней. Чертят линии, кружочки, области, вставляют фотографии и тексты. Все это время на экране стоит старая картинка. И только после того как все нарисовано, сразу пересылают все пиксели в видеопамять. Такая пересылка много времени не занимает и пользователь видит мгновенную смену картинки. Она показывается, а в это время рисуется следующий кадр. Но иногда специально делают фасонную смену одного кадра другим, при которой фрагменты новой картинки заменяют старую плавно и постепенно.

В видео-память всегда записывают картинку в формате raw, то есть матрицу пикселей. А вот при записи в файл это уже неудобно, так как бывают картинки очень больших размеров, например фотографии, сделанные с высоким разрешением. И при такой записи размер файлов оказывается огромным. Поэтому используют различные форматы сжатия информации. Формат сжатия - это фактически алгоритм кодирования информации с целью сократить число байтов. Например, пусть в картинке первый ряд весь черный а ее ширина 1024. Если она имеет цветность 3 байта на пиксель - это значит 1024*3 нулевых байтов. Вместо них можно просто указать значение байта и сколько байтов подряд имеет это значение. Информация не потеряна, но запись намного компактнее. В интернете в последнее время широко используют два формата: png и jpg. Первый формат сжимает информацию о картинке без потери точности. И он даже бывает более эффективным для картинок типа чертежей или графиков. Второй формат с целью сжатия немного искажает картинку, но так, что она не становится хуже, если это фотография или картинка художника с многими цветами. В этом случае размер jpg-файла намного меньше, чем у png-файла. Но графики и чертежи этот формат, во-первых, просто портит, а во-вторых, записывает в файл большего размера. Это надо помнить и всегда учитывать при выборе формата записи картинки в файл. Фактически хорошо получается, если даже картинку в формате raw просто заархивировать каким-либо архиватором, например, zip перед пересылкой по интернету. Но это почему-то не принято. Хотя некоторые программы записывают файлы картинок в формате raw. Можно так записать и на языке ACL, то есть с помощью программы vkACL.jar.

Если уж писать о форматах, то можно вспомнить, что и числа тоже можно записывать в различной кодировке. В десятичной системе целые числа записыватся просто цифрами, а реальные - в виде -1.34e-5, то есть знак (плюс не обязательно), целая часть, точка, дробная, буква e (или E), знак (плюс не обязательно) и степень десяти. Но целые числа можно писать и в восьмеричной кодировке и в 16-ричной кодировке (hex-формат). В последней кодировке перед такой записью надо ставить знак #. Например, вместо 255 можно написать #ff. Такая запись иногда удобна, например, при показе всех байтов какого либо файла таблицей. Для каждого байта достаточно всего 2 разряда. Иногда так записывают яркости цветов в случае 3-байтовой пикселизации, например, #ff0000 - это ярко-красный. В языке ACL такие записи не используются, но они используются в HTML, Java и других языках.

Итак, мы теперь понимаем, что все на свете можно записать простой последовательностью битов, то есть нулей и единиц и такая простая структура памяти не помеха. На очереди следующий вопрос - как запоминать и как вспоминать. Для экономии времени я не буду описывать технические детали, кому интересно - сами поищите в интернете что-нибудь об этом. Я просто скажу, что у процессора есть способность изменить состояние любой последовательности битов, то есть скопировать информацию из регистров процессора в любой байт памяти и есть способность, наоборот, скопировать состояние любого байта в регистр. Процессор может работать и с отдельными битами внутри байта, но обмен осуществляется байтами или несколькими байтами сразу в зависимости от длины как самого процессора, так и длины его регистров. Но как процессор знает какие именно байты надо использовать? Для этого существует разметка памяти. Все байты оперативной памяти как бы стоят в очередь один за другим. Номер в очереди определяется конкретным расположением байта на носителе. Всю память можно представить как очень длинную очередь и каждый байт имеет в ней свой номер. Разметка памяти может делаться разными способами. Например, некоторые байты можно использовать как метки (флаги). Сначала надо найти крупный флаг, затем более мелкий, еще более мелкий, а потом и просто пересчитать. Неважно. Важно, что каждый байт имеет номер (на компьютерном языке говорят - адрес). Процессор способен найти блок байтов по адресу первого, скопировать его в первый регистр, затем найти второй блок байтов по адресу первого (если это необходимо), скопировать его во второй регистр. И после этого выполнить операцию. Какую именно? Об этом он узнает из другого куска памяти, где записаны и адреса. Я напишу об этом чуть ниже. В результате операции состояние одного из регистров (пусть это будет первый) изменяется. И процессор копирует результат опять в память. Вот так он вспоминает два события (числа) генерирует третье число (принимает решение) и записывает его опять в память (запоминает). Все, что умеет процессор - это производить операции над числами. Складывать их, вычитать, умножать и делить. Для всех этих операций придумываются алгоритмы. Но он также умеет сравнивать. Фактически это проверка нуля после операции вычитания. Он также умеет проверять условия типа больше или меньше - для этого достаточно посмотреть на знак результата после вычитания, который описывается одним битом. Короче говоря, логические операции - это те же математические операции, но с анализом результата.

Программа в коде компьютера

Следующий интересный вопрос - а как в программировании кодируются описанные выше операции. А по разному. Это зависит от языка программирования. Но во всех языках, как бы не записывался код программы, он сообщает одну и ту же информацию. По этой причине нет разницы между языками - они все говорят об одном и том же, только по разному. Я буду приводить примеры только из двух языков: ACL и Java. Этих языков достаточно, чтобы показать и различие и сходство всех языков. Итак числа записываются в байты памяти, а байты имеют адреса. Значит числа можно записывать по разным адресам памяти. Сам адрес памяти, куда запоминается число и откуда оно потом вспоминается - это тоже информация. Эта информация тоже записывается в памяти. Но та часть памяти, где она записывается, уже называется программой. В памяти как бы есть много непересекающихся кусков. Есть видео-память, о которой я уже писал выше. Есть память программы, то есть определенная последовательность команд, задающих код операции и адреса операндов. Есть еще и другие куски: память клавиатуры, память специальных (встроенных) программ, которые обеспечивают работу приборов (экрана, клавиатуры, модема и так далее).

Программа обычно не занимает всю память компьютера, а имеет строго определенный размер. В часть памяти, отведенную под программу записывается как сама программа, то есть поток байтов с командами (инструкциями), так и определенная совокупность ячеек памяти для хранения чисел, текстов, картинок и прочих объектов. Компьютер прочитывает первую команду программы, узнает из каких ячеек памяти надо взять числа, какую провести с ними операцию и куда послать результат. Затем он прочитывает следующую команду и тоже выполняет ее и так далее. Но среди команд есть такие, которые заставляют компьютер изменить порядок чтения команд. Команды тоже все пронумерованы и пока они выполняются последовательно - это неважно. Команда, которая заставляет компьютер перейти сразу к другой команде с определенным номером называется командой перехода. Бывает явный безусловный переход с указанным адресом команды. При этом компьютер обычно запоминает место, в котором он сделал этот переход. Это очень просто - адрес последней выполненной команды запоминается в ячейке памяти. Есть также команда вернутся назад в то место, откуда был сделан переход. Вот для этой команды и необходимо знать место. Это реализуется неявным безусловным переходом, когда команда сообщает адрес ячейки памяти, в которой записан адрес команды, на которую надо сделать переход. Казалось бы зачем нужен безусловный переход? Не проще ли выполнять все команды подряд да и все. Оказывается не проще. Есть определенные последовательности команд, которые надо выполнять много раз, причем это может понадобиться в разных местах кода. И вот вместо того, чтобы много раз писать один и тот же код, проще написать его один раз и делать безусловный переход на то место, где он записан. В программировании такие куски программ называются процедурами, подпрограммами. макросами и другими названиями такого же типа.

Существует и условный переход. Например, команда перехода при условии, что в предыдущей операции был получен нулевой результат. Если результат был ненулевой, перехода нет, если ноль - переход есть. На такую "мудрость" процессор способен. Но оказывается этого достаточно, чтобы распутать любые сколь угодно сложные логические цепочки. Можно без труда осуществить логические сложения и умножения простой математикой. Любые сколь угодно сложные каскады выбора одного из многих вариантов. Компьютер думает не так, как человек. Он все делает перебором вариантов. Другими словами, у него нет интуиции. Человек способен сразу выбрать один из тысячи вариантов, который ему каким-то образом симпатичен, а компьютер вынужден перебрать все возможные варианты, чтобы выбрать лучший. Так, например, он играет в шахматы. Просматривает все возможные комбинации на два, на три хода вперед. В зависимости от программы. Если шахматист тоже не видит дальше двух шагов, то он обязательно проиграет. Но если он способен заглянуть дальше, то иногда может и выиграть. Есть и еще некоторые операции непосредственно над битами числа. Иногда они приводят к интересным эффектам. Но так глубоко мы изучать компьютер не будем.

В коде компьютера есть также команды, которые тоже меняют порядок выполнения программы, но другим способом. Они как бы временно "забывают" о текущей программе и начинают выполнять другую программу, указанную в команде. Есть специальные программы операционной системы, которые имеют номера. Команды, вызывающие такие программы называются прерываниями с определенными номером. Иногда прерывания имеют номер, но выполняют программы, информация о которых записана в определенном месте, а не по номеру. Необходимость выполнения таких программ связана с наличием внешних приборов и необходимостью получать информацию от них. Это клавиатура, дисковод, принтер, сканер, модем, интернет-соединение и так далее. Программа должна периодически слушать эти приборы - нет ли сигналов от них. Если сигнал есть, то его нужно обработать. Получение сигнала - задача команды прерывания, а обработка сигнала реализуется через переход на какую либо процедуру программы.

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

Программа на языке ACL

Имея в виду потенциальных пользователей языка ACL, я далее покажу как конкретно надо понимать запись кода ACL программы с учетом сказанного выше. ACL - это язык для интерпретатора, то есть программы. которая его непосредственно выполняет, а не переводит в код компьютера. Поэтому я буду говорить про интерпретатор. Итак, во-первых, как мы уже знаем байты ячеек памяти записывают числа разных форматов и тексты. Поэтому вместо адресов самих байтов удобно перейти к нумерации конкретных объектов. В ACL используются не все объекты, а только целые знаковые числа, реальные знаковые числа и уникоды. Соответственно введены три массива i(123), r(123), t(123). Массивы имеют название из одной буквы и индекс в круглых скобках. Первый массив является массивом целых чисел длиной 4 байта. Индекс массива является целым числом, задающим порядковый номер элемента массива, как ячейки памяти для записи целых чисел. Второй массив является массивом реальных чисел длиной 8 байт. Индекс имеет тот же смысл. Третий массив является массивом уникодов размером 2 байта каждый. Остальные форматы хотя и не используются в вычислениях, но могут использоваться при записи чисел в файлы. Это необходимо для того, чтобы запись была более компактной. Эти массивы имеют конкретный размер для того, чтобы компьютер знал точно сколько места памяти надо под них выделить. Первый имеет размер 14999, второй 999999, третий 99999. Индексы у всех трех начинаются от единицы. Но индексы писать все же не всегда удобно. Поэтому есть еще два массива реальных чисел, каждый элемент которого имеет свое уникальное имя. Первый массив имеет 55 элементов. Элементы имеют имена a, ..., z, A, ..., Z, $, %, &. То есть это буквы латинского алфавита и три спецсимвола, маленькие и большие буквы обозначают разные элементы массива. И есть еще один массив размером 550 элементов, у него весь указанный выше набор букв сопровождается цифрами, сначала 0, затем 1 и так далее до 9. То есть имена имеют два символа - букву и цифру. Но цифра пишется слитно, а не как индекс. Элементы массивов с именами называются переменными, как в математике. Это на самом деле означает, что они указывают на ячейку памяти определенного формата, в которую можно перезаписывать числа, то есть менять значение переменной. Есть еще служебный массив целых чисел s(12), отдельные элементы которого имеют уникальные имена и могут присваиваться по своим уникальным именам в специальной части кода, заключенной в квадратные скобки. Это сделано исключительно для удобства чтения программы, так как имена обычно указывают на характеристику параметра. Многие команды являются вызовом целых программ с параметрами и имена элементов служебного массива как раз описывают эти параметры. Ну и удобно отделить параметры от переменных, чтобы не было конфликтов. Такая простая и жесткая структура элементов памяти удобна для простых программ, но не очень удобна для очень сложных и распределенных программ. Хотя ограничений в написании программ любой степени сложности нет, но нужно применять специальные методы программирования. Кроме того, ACL имеет много команд, являющихся готовыми программами. Фактически ACL - это язык компоновки готовых программ. Каждая команда языка запускает в работу определенную программу, некоторые из которых могут быть очень сложными.

Тем не менее, в разумных пределах можно делать и элементарные вычисления. Элементарные вычисления делаются в тексте программы после скобки в виде знака #. Этот знак указывает на начало очередного куска программы. А любой текст, который не начинается с этого знака, считается комментарием и компьютер его игнорирует. Элементарный код вычислений выглядит так
        # x=1; y=3.14; a=x*y; b=t(a)/x; a8=i(39)/10; r(105)=2*c;
Такая запись имеет левую часть, знак равенства и правую часть, а заканчивается знаком ";". В правой части могут быть числа, переменные, элементы массивов, соединенные знаком операции. А в левой части может быть только переменная или элемент массива, то есть ячейка памяти. Левая часть указывает куда надо записать результат операции, в то время как правая часть указывает саму операцию и адреса тех ячеек, числа из которых участвуют в операции. Это вполне соответствует тому что компьютер реально делает. Но в языке высокого уровня можно писать и сложнее, например,
        # x=(34/s+a8*r(3*d-2))/(a+b)-sin(x)-bj0(r9);
Здесь левая часть точно такая же, но правая часть описывает сразу много операций и дополнительно содержит круглые скобки и функции. Такую запись компьютер не может выполнить сразу. Но ничего страшного - интерпретатор сначала разложит эту сложную запись на цепочку простых операций типа написанных выше и затем выполнит. При разложении надо соблюдать приоритет, то есть правила, диктующие какие операции выполнять раньше, а какие позже. Это всегда оговаривается при описании языка. Такие правила едины почти во всех языках. Сначала все в скобках, в том числе и функции, затем возведение в степень, умножение и деление, затем сложение и вычитание. Равноприоритетные операции делаются слева направо.

В языке ACL есть дополнительные команды вычислений, которые обычно отсутсвуют в других языках, точнее записываются более сложным образом. Это операции получения минимального и максимального значения из двух чисел. Пусть нам нужно вычислить минимальное значение из "a" и "b". В языке Java для этого надо записать Math.min(a,b). Это наглядно, но не очень компактно и для нас неудобно, так как не может быть использовано непосредственно в выражениях. В языке ACL это записывается так: a<b. Это не условие, как обычно пишут в математике, в языке ACL вообще нет условий, а операция вычисления. В языке Java это условие и его надо записывать только в условных командах. А в ACL это операция вычисления минимального значения. Но ее можно читать и как условие. Примерно так: операция a<b дает "a" если "a" меньше "b" и "b" в противном случае. Аналогично для знака ">" , который означает вычисление максимального значения и читается примерно так же. Приоритет этих команд выше всех, кроме скобок. Поэтому выражение (a+b)<(a-b) даст совсем другой ответ, чем a+b<a-b. Это надо учитывать.

Рассмотрим как реализуется безусловный переход. В старых языках программирования были популярны метки. То есть какая-то строка кода метилась меткой и переход делался на эту метку. Но в современных языках предлагают от этого отказываться из-за того, что метки очень затрудняют чтение программы. Вместо этого рекомендуют явно указывать блоки программы, которые надо выполнить много раз. Или даже если и один раз, то не сразу и при условии. Иногда выделение части кода в отдельную единицу удобно просто для чтения и написания программы. Выделенный блок помещают в скобки и присваивают ему имя. Имя блока - та же метка, но разница в том, что внутри одного блока не может быть кусков другого блока, а при использовании меток можно наделать много самопересекаемых блоков и результат работы может оказаться непредсказуемым. В ACL такой блок начинается с команды #pro NAME, где вместо NAME может стоять любое имя из не более 4-х символов. А заканчивается он одним знаком @. Описание блока должно предшествовать его вызову. Это условие диктуется интерпретатором. Интерпретатор читает программу только один раз, с начала и до конца и он должен получить информацию о блоке раньше, чем ему будет приказано его выполнить. Но неявный безусловный переход по метке в ACL тоже есть. Только он используется в крайнем случае. Команда #% записывает адрес команды после себя в переменную %. Затем команда #go % может передать управление на адрес, записанный в переменную %.

Выполнение процедуры - это изменение порядка выполнения команд с переводом курсора в начало процедуры и с последующим возвратом в старое место после окончания процедуры. Это делается командой #e [c=1;] NAME, где вместо NAME должно стоять имя процедуры, можно ставить даже несколько имен сразу, процедуры будут выполнены одна за другой. Процедуру можно выполнять по условию. Условия в ACL записываются неявным образом. Реально никакой записи условия нет. Условием выполнения процедур является совпадение значений двух ячеек памяти. Одна ячейка памяти имеет имя переменной &. Вторая является элементом служебного массива s(8), который имеет имя code (или сокращенно одной буквой c). Элементы служебного массива определяются по именам внутри квадратных скобок после имени команды, а как элементы массива в любой команде вычислений. Они имеют только целые значения. Если два значения совпадают - все процедуры списка выполняются, если не совпадают - не выполняются. Например, команда #e [c=1;] NAME выполнит процедуру с именем NAME только в том случае, если значение переменной & будет равно 1, так как параметр "c" как раз задан равным 1 при вызове процедуры.

Есть еще одна многоцелевая команда, которая сама реализована почти как процедура, но ее не обязательно определять заранее и она не имеет имени. Она записывается так #case N . . . #end . Здесь вместо многоточия может находиться сколько угодно других команд, в том числе и другие команды #case N . . . #end . Многоточие будет выполняться при условии, что значение переменной N (это аргумент, он может быть в частности и числом) совпадает со значением переменной &. То есть условие опять состоит в совпадении, но вместо процедуры мы имеем дело с определенным набором команд, а сравниваются значение аргумента команды и все той же переменной &. Если команда вызова процедур имеет только один аргумент - имя процедур, то команда #case N как бы разделена на две части. Она работает в паре в другой командой #end , которая ограничивает блок команд, выполняемых по условию. Но этим ее функции не заканчиваются. Она тоже проверяет условие совпадения значения аргумента (при этом значение переменной N может уже измениться, но имеется в виду то число, которое было в ячейке N в момент вызова команды) и текущего значения переменной &. На этот раз она берет только целую часть аргумента, в то время как вначале аргумент мог иметь реальное значение. В этом отличие от вызова процедур. Однако, лучше в качестве аргумента всегда использовать только целые значения, этого достаточно. Если значения совпадают, то многоточие будет выполнено снова и так много раз. При этом, если в теле (многоточие) переменная & не будет изменена, но такой код будет выполняться бесконечно. Поэтому надо всегда предусматривать изменение переменной &. А если надо выполнить многоточие только один раз, то достаточно использовать запись #end |. Вертикальная черта через пробел автоматически изменяет значение переменной & на 12345. Если аргумент не равен этой величине, то совпадения не будет.

Полное описание языка ACL написано в отдельной книге. Я не буду ее здесь повторять целиком. Моя цель - показать как можно пользоваться условием совпадения двух переменных. В реальных вычислениях компьютер как раз и реализует такое условие. И записи условий во всех других языках так или иначе к нему сводятся. Но для начинающих программистов это не вполне понятный момент и его лучше показать на примерах. Первый пример. Пусть у вас есть код
        # a=1; b=1; c=1;
Здесь всего три операции, но реально вместо каждой их них может быть написано 30 операций, 300 операций и более. И вот вы в целях отладки не хотите выполнять вторую операцию, но и стирать ее тоже не хотите, так как она вам еще понадобится в будущем. Решение такое. Достаточно просто модифицировать код следующим образом
        # a=1; &=1; #case 0 # b=1; #end | # c=1;
Совпадения нет - аргумент у команды #case равен 0, а переменная &=1 и вторая операция не выполнится. Достаточно изменить &=1 на &=0 и она выполнится. Это, так сказать, примитивное использование условия.

Но таким же образом реализуется условие равенства какого-либо элемента символьного массива конкретному символу.
        # a=1; &=t(1); #case 88 # b=1; #end | # c=1;
Здесь вторая операция выполнится при условии, что первый символ текстового массива равен 'X'. Именно этот символ имеет уникод 88. Далее, допустим вы хотите выполнить вторую операцию по паролю. Вы предлагаете пользователю в строке ввода набрать 4 знака и получаете ответ в первые 4 элемента текстового массива. Пусть пароль равен 'kohn'. Решить эту задачу можно следующим образом
        # a=1; &=abs(t(1)-107)+abs(t(2)-111)+abs(t(3)-104)+abs(t(4)-110);
        #case 0 # b=1; #end | # c=1;
Наиболее прямым практическим применением этой условной команды является реализация кода по выбору. Допустим вы предложили пользователю выбрать какую либо кнопку из таблицы кнопок. Каждая кнопка предполагает выполнение определенной уникальной работы. Команда выбора возвращает вам решение пользователя в виде значения все той же переменной &. Это значение может быть равно 1, 2, и так далее. После этого вы просто пишете такой код
        #case 1 сделать то-то и то-то #end |
        #case 2 сделать то-то и то-то #end |
и так далее.
Но часто желательно после первого выбора снова предложить выбор и так далее до тех пор, пока пользователь не нажмет кнопку [Выход]. Пусть номер этой кнопки равен 5. Код может быть таким
        # &=0; #case 0
предлагаем выбор кнопок.
        # Z=&; #% запоминаем выбор пользователя
        #case 1 сделать то-то и то-то #end |
        #case 2 сделать то-то и то-то #end |
        #case 3 сделать то-то и то-то #end |
        #case 4 сделать то-то и то-то #end |
        # &=(4-Z)<0; #end
Здесь на входе условие выполняется всегда, а на выходе условие выполнится только если были выбраны первые 4 кнопки. В этом случае левая часть операции выбора минимального значения больше или равна нулю и минимум точно равен 0. Поэтому будет снова предложен выбор. А при пятом выборе левая часть равна -1, &=-1 и возврата не будет. Условие не выполнено.

Операции выбора минимального и максимального значений позволяют реализовать любые сколь угодно сложные условия, но это не так наглядно как в математике. Вот пример. Мы хотим что-то выполнить при условии, что ("a" больше 1.5 и одновременно "b" меньше 3.6) или "c" равно 25. Проще всего каждое отдельное условие свести к равентсву или неравенству нулю. Итак
        # &1=(a-1.5)<0; &2=(3.6-b)<0; &3=c-25; &=(abs(&1)+abs(&2))*&3; #case 0 ...
Задача решена. Действительно, если "a" больше 1.5, то левая часть в первой операции будет больше нуля, а нам надо взять минимум, то есть 0. А если не больше, то получим отрицательное число. Точно так же во второй операции - левая часть больше нуля при выполнении условия и минимум равен нулю. В конце концов нам надо сложить абсолютные значение первых двух минимумов и умножить на третье число, равное нулю при c=25. Можно было не разбивать на несколько переменных, а сразу написать все вместе.

Так как в языке ACL есть только три больших массива, то практически все команды и все операции выполняются над частями этих массивов. Часть массива в командах описывается парамерами begin (сокращенно b) и length (сокращеннно le). Первый из них определяет индекс первого элемента массива рассматриваемой части, а второй задает полное число элементов обрабатывамой части, то есть длину части. Разметку полных массивов по частям производит программист и эту разметку можно записать в переменные и затем использовать. Операции над матрицами делаются аналогично. Вы задаете три параметра: тот же begin, и дополнительно nx и ny, задающие размерности матрицы по двум индексам. В картинках часто используются параметры twidth, theight (полные ширина и высота) width, height (ширина и высота части) xshift, yshift (сдвиг части из начала). Короче имена параметров как бы подсказывают их назначение. Несмотря на ограниченность средств, в ACL можно писать супер-сложные программы, распределенные по многим файлам, поскольку есть операция вызова процедуры, записанной в файл, а не только определенной в тексте. Есть также операция вызова процедуры, записанной в текстовый массив t(). Это позволяет реализовывать динамическое программирование, то есть когда сам пользователь может в строке ввода дописать кусок программы и она сразу выполнится. Более того, есть возможность анализа введенного кода на ощибки и выдача диагностики. Благодаря таким возможностям, на ACL можно написать программу, которая будет вызывать весь код, написанный за десятки лет и распределенный по тысячам файлов в разных папках. А с другой стороны, простые вещи делаются очень просто. Достаточно написать одну или две строчки вызова какой-либо готовой программы и она сразу исполнится. Не нужно никаких преамбул и никакой компиляции. Не нужно формальных параметров и их передачи. Вся память всегда доступна всем процедурам. В заключение хочу заметить, что так как интерпретатор ACL написан на Java, то ACL способен делать все то, что в Java предлагается в готовом виде. В частности все возможности по графике. Кроме того, можно подцепить любые библиотеки Java классов и реализовать их использование методов запуска готовых программ.

Программа на языке Java

В этом разделе я хочу показать, что не все языки программирования похожи на ACL, более того, многие из них совсем непохожи. Если ACL удобен для простых программ, то есть языки, удобные именно для сложных программ, но неудобные для написания простых программ. К таким языкам можно отнести и язык Java. В этом разделе я не собираюсь научить программировать на Java. Читайте для этого мои лекции на сайте "http://vkacl.narod.ru" . Я просто хочу показать как организована память и вычисления. Очень сложно организована. Java является классическим языком так называемой объектной ориентации. В нем, кроме таких естественных объектов, как числа, символы текста, картинки можно оперировать с практически бесконечным числов других объектов самой различной природы. Такими объектами могут быть окна на экране компьютера, а на них строки ввода и кнопки. Но могут быть и другие объекты - дома, люди, звери. Фактически любой объект природы можно превратить в объект языка Java. Для этого просто нужно описать его свойства. Такие описания делаются в частях программы, называемых классами. Сам по себе класс - это кусок кода программы, вместе со всеми его переменными, массивами, текстами и так далее. Класс описывает объекты этого класса, которых может быть много. Программист заказывает объект какого-либо класса, когда ему это нужно и весь кусок кода программы, описывающий данный класс, копируется в память в новом месте с новыми стартовыми переменными и дальше живет своей жизнью. Разные объекты одного и того же класса - это разные программы с практически одинаковым кодом, записанные в разные места памяти. С одной стороны - это очень сложно, особенно для начинающего программиста. С другой стороны - очень удобно для программ, которые выполняют параллельно разные работы. Даже реализация сразу трех редакторов текста в трех окнах с возможностью все время переключаться из одного в другой при такой структуре программы кодируется очень просто. Вы просто объявляете три объекта одного и того же класса и все дела.

Что касается массивов и переменных, то их может быть сколько угодно с какими угодно именами, но все они имеют свою строго ограниченную область применимости. Ни одна переменная не может использоваться за пределами своего класса. Некоторые переменные имеют смысл только в пределах процедуры, где они введены. Вы можете вводить много переменных с одним и тем же названием и конфликта не будет. Те переменные которые введены в самый нижний уровень программы имеют приоритет. Можно вводить массивы любой длины и любой мерности. Точнее не так. Массив - это тоже объект класса. И вы можете делать массивы из массивов. При этом массивы могут быть не обязательно прямоугольные, а каждая строка может иметь свой размер. Могут быть массивы любых объектов любого класса. Ясно, что при такой системе программирования необходим компьютер с очень большой памятью, но и его можно заполнить очень быстро разным мусором. Однако интерпретатор языка Java, в отличие от языка ACL - это огромная и сверхсложная программа. И она умеет следить за тем, используются переменные или нет. Если нет, она освобождает память для других объектов динамически, то есть в процессе выполнения программы. Как это делается нам здесь знать необязательно, важно понимать, что это делается.

Что же касается самих вычислений, то они во многох такие же, как и во всех других языках, включая ACL. Только индексы массивов пишут в квадратные скобки, а блоки кода помещают в фигурные скобки. Как и в многих других языках, есть условные операторы, операторы условных циклов, логические переменные и многое другое, что несколько упрощает чтение программы, но зато заставляет учить больше понятий. Еще одной интересной чертой языка Java, которая тоже ставит в тупик новичков, является разделение доступа к классам и процедурам внутри класса. Есть процедуры имеющие статус "public", а есть "pvivate", "protected" и другие. Есть также и статические классы, не имеющие объектов. Они во многом похожи на необъектные языки типа ACL. Короче говоря, как часть памяти, предназначенная для программы, так и часть памяти, используемая в виде ячеек для запоминания чисел, очень структурированы и эта структура очень сложная. Для ее детального изучения надо изрядно попотеть, прежде чем удасться написать хоть какую-то простенькую программу. Да плюс еще компилятор все время будет писать вам сообщения об ошибках, многие из которых станут понятными только после прочтения нескольких толстых книг. Но зато после того, как все это останется позади и вы станете все понимать, вы будете чувствовать себя гигантом, способным на подвиги, как Геракл.

И все же часто необходимо быстро и качественно получить ответ на относительно простой вопрос. И каждый раз программировать на Java довольно утомительно. Именно поэтому я и придумал для себя свой язык ACL, который имеет возможности не хуже, чем у Java в тех областях, какими я сам занимаюсь, но его использование намного проще. А если нехватает возможностей - я могу их быстро сделать уже на Java. Интерпретатор ACL не совсем копирует возможности Java. Есть очень много команд в виде готовых программ, которые в Java не написаны. Я сам написал соотвествующие классы. И в этом смысле программу из ACL сразу конвертировать на Java не получится. Многого не хватит. Хотя часто я отлаживаю какой-либо алгоритм именно на ACL, а потом переписываю его на Java в виде готовой команды ACL. Так удается быстро программировать, а потом быстро считать.

...

Hosted by uCoz