программирование

«99 бутылок» на языке «Электроники МК-61»

Электроника МК-61 — советский программируемый калькулятор с ЖК-дисплеем, клавишами и надписями «ЭЛЕКТРОНИКА МК-61», лежащий на деревянной поверхности.
«Электроника МК-61» с запущенной на нём программой

59. Язык калькулятора Электроника МК-61. «Электроника МК-61» — устройство из класса «программирумых калькуляторов». Были когда-то такие гаджеты, позволявшие со всеми ограничениями калькуляторов (небольшая клавиатура, типичный для калькулятора экран) писать программы.

В детстве такие калькуляторы я видел только на картинках в журнале «Наука и жизнь», а программы для них были каким-то любопытным закорючками, в которых не разобраться.

Вчера мне в руки попал настоящая «Электроника МК-61» (для передачи компьютерному музею) и я наконец-то на обеде и потом вечером разобрался в этих странных значках.

Так выглядят мои «99 бутылок…» на этом калькуляторе:

8112000 Х→П c 8 + ИНВ Х→П b Вх 10 ÷ [X] Х→П d 8 + ИНВ Х→П e В/О ПРГ
П→Х 0 1 0 ÷ [x] Х→П 1 Вх {x} 1 0 × Х→П 2
1 1 Х→П 8 1 2 Х→П 7 П→Х 2 ПП 34 Х→П a
1 4 Х→П 8 1 3 Х→П 7 П→Х 1 ПП 34 П→Х a
^ С/П
6 - /-/ x<0 45 9 + К П→Х 7 + ИНВ В/О
1 + К П→Х 7 + ИНВ К П→Х 8 ^ В/О

Поскольку у меня в блоге скорее всего отсутствуют любители работать каждый день с калькуляторами советских времён, то я не буду придерживаться принятой нотации с номерами — не думаю, что кто-то будет это набирать.

Вкратце о языке. Больше всего это похоже на крайне примитивный ассемблер с гипертрофированным математическим сопроцессором. Всё то же — переходы на адрес, одно- и двухбайтные инструкции с операндами, регистры с определёнными назначениями, сильно ограниченная память (105 команд), наличествует косвенная адресация и подпрограммы (стек вызовов размером в пять вхождений).

До запуска программы нужно в регистр «0» поместить стартовое значение (например, «99», «Х→П», «0») и запустить программу «В/О», «С/П».

Это я сделал из-за того, что цикла в программе нет — из-за мельтешения разрядов совершенно не понять, что происходит. Хотя добавить его очень просто — до команды «С/П» надо вставить одну проверку с переходом и сдвинуть адреса.

Поэтому на вход подаётся нужное число, например, «42», а на выходе мы можем полюбоваться фразой «BEEr 42». Не весть что, но что вы хотели от калькулятора? Кстати, выводимая фраза — не строка (спойлер: а число), о том как мне пришлось помучаться я ещё расскажу.

6 комментариев

Подпилил переподсоединялку вайфая

Чуть поменял «переподсоединялку вайфая» для Мака — теперь пингуется не 8.8.8.8 (это ДНС Гугла), а адрес гейта, что более надёжно. Правда с конца октября, когда я её написал много воды утекло — вайфай у меня отваливается теперь очень редко, уж не знаю что изменилось.

2 комментария

Дерево в цепочках

Дерево в цепочках (120.20КиБ)

Я тут в блоге как-то намекал, что современные сервисы ведения родовых деревьев рассчитаны скорее на казуалов, чем на людей, для которых это серьёзное хобби. Даже подумывал написать свой, но оставил эту затею — я хорошо представляю объём работы, надо либо делать это основной своей работой, либо не делать вовсе.

В общем, там где мне совсем сильно жмёт, я пишу иногда небольшие скрипты. Недавно написал очередной и решил поделиться. Этот скрипт (написан на ПХП7) позволяет развернуть дерево в формате GEDCOM (это общепринятый формат для хранения информации о родословной) в цепочки — то есть простраивает все пути от указанного человека до каждого его предка.

Это позволяет быстро увидеть какие ветки у вас проработаны, а какие нет. Классическое дерево на моих объёмах это уже не показывает — его просто невозможно охватить взглядом и понять где есть проблема.

Для запуска требуются два параметра: нужно указать путь до файла дерева и идентификатор человека, от которого нужно простроить цепочки, его надо посмотреть внутри файла (GEDCOM текстовый формат). Для этого нужно найти внутри запись нужной персоны и выше неё — строку вида «0 @буквыцифры@ INDI», вот часть «@буквыцифры@» и будет требуемым идентификатором.

27 комментариев

strtr vs. str_replace

Когда-то я любил просматривать слайды по теме ускорения ПХП. Теперь это стало слишком скучно — основные промахи в этом плане команда его разработчиков устранила, а остальные принципы те же, что и в других языках.

Кроме того, часто выход какой-то новой версии обесценивал результаты большинства исследований в этой области. Недавний выход «семёрки» с блеском это продемонстрировал.

Среди подобных текстов есть и вредные. Речь об одном из них и пойдёт. Впервые на сравнение производительности функций str_replace и strtr (с одним параметром) я наткнулся очень много лет назад, тогда с ним разобрался и тему для себя закрыл.

Но такие исследования возникают постоянно, а авторы совершенно не утруждают себя ответом на вопрос «почему», так что я решил написать этот небольшой текст.

Итак, есть две функции, которые ведут себя очень похожим образом — заменяют в строке что-то одно на что-то другое. Велик соблазн объявить их совершенно одинаковыми (что часто и происходит), тем более их описание в руководстве по языку не акцентирует внимание на различиях. Но они есть, и очень важные.

Обратите внимание на этот код:

$r = ['xo' => 'xaxе', 'xa' => 'xo'];
$s = 'xoxo';

var_dump(str_replace(array_keys($r), array_values($r), $s)); // xoxеxoxе
var_dump(strtr($s, $r)); // xaxеxaxе

Очевидно результаты разные, но почему? Потому что str_replace делает замену «в лоб» — проходя строку несколько раз, по числу заданных замен. Поэтому получается «xoxo» → «xaxexaxe» → «xoxexoxe».

Функция strtr куда более хитрая, но работает более… естественным что ли образом — т. е. так как обычно человек ждёт от функции множественной замены. Самое главное — в сделанной замене она уже ничего не заменяет.

Есть и другое отличие:

$r = ['user1' => 'one', 'user11' => 'eleven'];
$s = 'user11';

var_dump(str_replace(array_keys($r), array_values($r), $s)); // one1
var_dump(strtr($s, $r)); // eleven

Мой пример выглядит несколько искусственно, но это часть случая из жизни, правда очень упрощённого. Тут беда в том, что str_replace применяет свои замены слева направо — в порядке перечисления, тогда как strtr сначала сортирует их по длине.

Как видите, эти функции совершенно разные по своему действию и сравнивать по производительности их просто некорректно.

3 комментария

Void в PHP

ПХП7 только-только вышел, а авторы языка уже приступили к следующим версиям. Если 7.0.1 будет лишь работой над ошибками, в 7.1 язык продолжит своё совершенствование. Первая ласточка — реализовано указание на отсутствие возвращаемого значения у функции (void).

Я сначала не понял зачем вводить в язык новое ключевое слово, можно было бы использовать для такого указания уже существующее ключевое слово null, но оказалось, что авторы под void имели ввиду, что функция не может вернуть никакого значения:

function returns_null(): void {
    return null; // Fatal error: A void function must not return a value
}

включая null, потому что это определённо значение, хоть и со специальным смыслом. При этом вот такое работает:

function returns_nothing(): void {
    return; // valid
}

Таким образом в языке произошло неявное изменение: раньше ситуации с пустым return и с return null не различались, сейчас они будут иметь разный смысл.

4 комментария

Отрицательное количество элементов (PHP)

Сегодня утром с разработчиками обсуждали фрагмент кода, где автор очень уж параноидально подошёл к проверке:

if (count($this->to) <= 0) {
    $this->validation_errors[] = "Неверный отправитель";
}

Проверять возвращает ли функция count значение меньшее нуля действительно странно, но я вдруг подумал — а можно ли в ПХП в припципе заставить эту функцию вернуть такое значение?

Дело в том, что функцию count можно использовать не только с примитивными типами (чаще всего её используют с массивами), но и с объектами, которые реализуют интерфейс Countable.

Оказалось вполне нормально работает:

$var = new class implements \Countable {
    public function count()
    {
        return -1;
    }
}

var_dump(count($var)); // выведет int(-1)

Выше код написан в синтаксисе ПХП7, который выйдет сегодня, но его можно переписать и на «пятёрку» — надо только дать имя классу.

2 комментария

99 бутылок: Эллочка

58. Эллочка. Мне написал автор языка программирования «Эллочка» с предложением написать на нём «99 бутылок…», как вы знаете, у меня есть хобби — писать такие программы на разных языках.

Для этого языка есть единственный, авторский интерпретатор под ДОС (и очень важно использовать в файле программы символы перевода ДОС/Виндоуз, иначе ничего не будет работать), а сам язык напоминает странноватый Бейсик, без поддержки функций и процедур. Их можно эмулировать, используя трюк со специальной переменной «@», которая содержит номер текущей строки.

В языке есть ряд интересных возможностей, но их затмевают недостатки разного калибра, что характерно для языков без сообщества. Даже в таком простом примере мне пришлось побороться за работоспособность программы. Например, перед числами зачем-то всегда выводится символ табуляции (я его срезаю функцией %MID), а когда вывод достигает размера экрана (80×25), то прокрутки не происходит, программа просто жалуется, что «операция не позволяется» (поэтому я просто жду нажатия клавиши после каждой итерации и очищаю экран).

!99 beer song by Evgeny Stepanischev
!Used variables: B, R, $1
B=99
@cycl
    R=@+2 
    goto @beer
    list ' of beer on the wall, '
    R=@+2
    goto @beer
    list '.'\
    list 'Take one down and pass around'\
    decr B
    R=@+2
    goto @beer
    list ' of beer on the wall.'\
    list ''\
    wait
    clsc
esli B >> 0; @cycl
list 'No more bottles of beer on the wall, no more bottles of beer.'\
list 'Go to the store and buy some more, 99 bottles of beer on the wall.'
exit
@beer
esli B >> 0; +3\
list 'No bottles'
goto R
esli B == 1; +5\
$1=B
$1=%MID($1,2,10)
list $1+' bottles'
goto R
list '1 bottle'
goto R

1 комментарий

Сложение массивов в PHP

Некоторые ребята не знают, что в ПХП есть операция «плюс» над массивами, а открыв её для себя случайным чтением инструкции по языку, не знают чем она отличается от array_merge. Между тем отличия разительные, если их не знать, легко наплодить ошибок в коде.

Операция сложения («плюс») с массивами работает проще всего: если в левом массиве уже есть значение с таким ключом, оно и остаётся:

["L"] + ["R"]; // будет ["L"]
["a" => "L"] + ["a" => "R", "b" => "R"]; // будет ["a" => "L", "b" => "R"]

Операция слияния массивов (array_merge) работает по-разному в зависимости от типа ключей массива. Значения со строковыми ключам правого массива перезаписывают значения левого с тем же ключём, а значения с целыми добавляются в конец левого.

array_merge(["L"], ["R", 4 => "R"]); // ["L", "R", "R"], значения целых ключей правого массива потерялись, сам массив добавился в конец
array_merge(["a" => "L"], ["a" => "R"]); // ["a" => "R"], совпадение строковых ключей
array_merge(["L", "a" => "L"], ["R", "a" => "R", 4 => "R"]); // ["L", "a" => "R", "R", "R"], разные типы ключей вместе

Операция «плюс» очень полезна для добавления группы ключей строковых со значениями (для объединения ассоциативных массивов):

$arr = ["key1" => "value1", "key2" => "value2"];

// вместо
$arr["key3"] = "value3";
$arr["key4"] = "value4";

// проще написать так:
$arr += ["key3" => "value3", "key4" => "value4"];

Но на этом её применение не заканчивается, конечно.

9 комментариев

99 бутылок: CMake

57. CMake. Писать «Песни про пиво» на различных языках программирования — давнее моё увлечение, ему столько же лет, сколько этому блогу. В этот раз «поёт» «Си-мэйк».

CMake — автоматизированная система сборки пакетов, имеющая развитый, хотя и несколько уродливый макроязык. Инструкции, большей частью, знакомые, разве что способ возврата значений несколько из макросов и функций несколько странный — через параметры и глобальные переменные.

Никакого ООП, само собой, и всё весьма примитивно по меркам развитых языков.

# 99.cmake
# to run: cmake -P 99.cmake
# Written by Evgeny Stepanischev, 2015

cmake_minimum_required(VERSION 3.0)

macro(bottles beer ret)
    if(${beer} EQUAL 0)
        set(${ret} "No bottles")
    elseif(${beer} EQUAL 1)
        set(${ret} "1 bottle")
    else()
        set(${ret} "${${beer}} bottles")
    endif()
endmacro()

foreach(beer RANGE 99 1 -1)
    bottles(beer bottles)
    math(EXPR beer "${beer}-1")
    bottles(beer bottless)

    message("${bottles} of beer on the wall, ${bottles}.")
    message("Take one down and pass it around, ")
    message("${bottless} of beer on the wall.")
    message("")
endforeach()

message("No more bottles of beer on the wall, no more bottles of beer.")
message("Go to the store and buy some more, 99 bottles of beer on the wall.")

Строка для запуска во второй строке файла.

3 комментария

Тег HYPE (Netscape HYPE tag sound)

Скриншот интерфейса SheepShaver с запущенной Netscape Navigator 2.01, окном Unix с папками и файлами, панелью Launcher с приложениями и рабочим столом Mac OS.
Скриншот MacOS 9 с открытым окном браузера

Пятнадцать лет назад, ковыряя исходники браузера «Нетскейп», я наткнулся, в частности, на тег HYPE — в интернете того времени писали, что он воспроизводит какой-то звук, но только в старых версиях браузера и не под «Виндоуз». «Мака» у меня тогда не было, как и доступа к «Линуксу» с графическим интерфейсом, поэтому безуспешно поискав по интернету подробности, я исследование забросил.

Сегодня за завтраком, по другому случаю наткнувшись на свою же статью об этом теге, я подумав, что теперь-то в интернете найдётся всё, попробовал разыскать тот самый звук. По-прежнему безуспешно. Есть только несколько чудом сохранившихся страничек, где этот тег упоминается в контексте перечня «пасхальных яиц» «Нетскейпа» и это всё.

Но времена же изменились! Теперь у нас есть виртуализация, образы виртуальных машин и быстрый интернет! Где-то полчаса ушло на поиск и скачивание второй версии «Нетскейпа», готовой виртуалки с девятой «МакОСью», установку всего найденного, разбирательство как всё запускается. Но при вводе заветного тега меня ждало препятствие, к счастью, устранимое — виртуалка заявила, что у меня нехватает какой-то Sound Machine и, вместо проигрывания звука, предложила скачать звуковой файл.

Файл оказался в формате .snd, который нормально открылся и проигрался уже в современном «Маке». Его содержимое — фраза «What is global hypermedia?» (из документа NCSA Mosaic Demo 1993 года, как объясняется на одной из страниц в интернете).

Я сконвертировал его в обычный МП3 и, если ваш браузер достаточно современный, можете послушать как звучит тот самый секретный тег:

Кстати, жаль, что я не догадался проиграть его на оригинальном «Макинтоше» в музее Эпл, в Москве. Если представиться случай, обязательно попробую!

2 комментария

Переподсоединялка вайфая

Переподсоединение (25.92КиБ)

Вышло очередное обноление «Эль Капитана» на «Маке», а проблема с вайфаем на «Макбуках Про» так никуда и не делась. Я планирую в какие-нибудь выходные переставить систему с нуля, но когда это будет, неясно — пока я не могу себе позволить оторваться от ноута так надолго, для меня он важный рабочий инструмент.

В общем, сделал себе на «Баше» утилиту переподключающую вайфай при пропадании пингов до 8.8.8.8 (это ДНС «Гугла»). Сегодня уже целый день её использую, так гораздо комфортнее.

17 комментариев

Блокировка экрана на «Маке» из Си

Как-то я программировал на Си под «Мак» блокировку жестом перед датчиком освещения. Сегодня показывал его коллегам, оказалось ничего не работает — метод, который я использовал для вызова экрана блокировки перестал принимать на вход NULL. Тогда как в интернетах только этот способ везде и упоминается:

CGSCreateLoginSession(NULL);
// теперь выдаёт
// Assertion failed: (sid != NULL), function CGSCreateLoginSession, file Services/Connection/CGSSession.c, line 418.

Возможно в «Эль-Капитане» (последней версии «МакОСи») что-то поменялось — функция CGSCreateLoginSession считается недокументированной, никакой информации о ней мне найти не удалось.

В общем, покапался я в заголовочных файлах, подбором и интуицией родил код, который блокирует экран и под «Эль-Капитаном», заодно и утилиту свою поправил:

// получаем словарик с атрибутами текущей сессии
CFDictionaryRef dict = CGSCopyCurrentSessionDictionary();
// выбираем из него идентификатор текущей сессии, получаем его в виде int
CFNumberRef number = (CFNumberRef) CFDictionaryGetValue(dict, CFSTR("kCGSSessionIDKey"));
CGSSessionID currentSession;
CFNumberGetValue(number, kCFNumberIntType, &currentSession);
// блокируем текущую сессию
CGSCreateLoginSession(&currentSession);

Обновлённая версия лежит там же — на «Гитхабе».

3 комментария

Где это я был?

Где я был (73.84КиБ)

Нацарапал утилитку на «Баше», которая помогает узнать к каким вайфай-сетям цеплялся ваш «Мак» в последнее время. Утилита требует подключения к интернету.

2 комментария

Чудны́ дела твои, PHP

var_dump(array_pop(range(0,5)));
// Strict Standards: Only variables should be passed by reference in Command line code on line 1
// int(5)

var_dump(array_pop( (range(0,5)) ));
// int(5)

В первом случае ПХП обращает наше внимание на то, что функция array_pop требует передачи значения по ссылке, что возможно только для переменной. Поскольку мы передаём результат работы функции, то логично, что ПХП в этом месте нам на это пеняет. Но если обернуть функцию в скобки, то интерпретатор уже всё устраивает.

Я не смотрел байт-код, поэтому не знаю причины точно, но мне кажется всё дело в том, что во втором случае примитивный парсер ПХП просто не узнаёт ситуацию, в которой должен выдавать предупреждение.

1 комментарий

65536

Если какие-то числа часто встречаются в работе, то всё, связанное с ними в остальной жизни, тоже очень эффективно запоминается. У программистов это, в основном, степени двойки, возможно марки старых процессоров и сопроцессоров Интела (например, 8080, 386, 387, 486), какие-то константные адреса (например, я со времён «Спектрума» помню адрес 23755 — начало программы на Бейсике в памяти).

Когда я начал заниматься историей, к этому всему добавились ещё числа. Пока у меня это только года. 1834 — второй год восьмой ревизии, 1850 — девятая ревизия и год, когда Бугульминский уезд перешёл из Оренбургской губернии в Самарскую и так далее.

Поэтому, увидев на часах 18:34, я улыбаюсь этому числу, как старому знакомому — я его за последние годы видел на тысячах, а то и десятках тысяч архивных листах. Номер 386 на авто так же привлекает моё внимание, так же как номер квартиры 256 (степень двойки). Для меня это очень особенные числа — события связаные с ними, как будто сами ложаться в память.

А в ваших профессии и хобби есть такие числа?

10 комментариев

«Кремниевая долина»

Пока отхожу от операции, смотрю сериал «Кремниевая долина» и дабы не заржавел мозг, сделал расшифровку надписи на футболке Эрлиха — одного из героев сериала, на трёх языках: Перле, Пайтоне и Тикле. Листинг один для трёх языков:

#\
"@{[$\=qq{\n},\
print map {chr $_} (0b1000010, 0b1101001, 0b1110100, 0b1100011, 0b1101111, 0b1101001, 0b1101110)]}";#\
__END__ = 1;\
print(''.join(chr(b) for b in (0b1000010, 0b1101001, 0b1110100, 0b1100011, 0b1101111, 0b1101001, 0b1101110))); #\
"""
foreach name {01000010 01101001 01110100 01100011 01101111 01101001 01101110} {
    puts -nonewline [binary format B* $name]
}
puts {} ;#"""

Дольше всего с Перлом возился, пока не вспомнил, что у него есть вычисление выражений внутри строк через конструкцию @{[…]}. Нужно рассказать как всё работает?

Оставить комментарий

Политкорректный Unicode 8

Я всё ждал, когда это появится, и вот наконец в восьмом Юникоде обнаружились модификаторы цвета кожи. Модификаторов в Юникоде — пруд пруди, это специальные символы, которые сами по себе ничего не значат — они привносят что-то новое в символ, с которым комбинируются. Простой пример — модификатор ударения, вот вам ударная буква «й»: «й́».

Модификаторы цвета (57.52КиБ)

Вот восьмой Юникод вводит модификаторы цвета (на картинке выше), при комбинировании с которыми цветные «эмоджи», которые уже давно входят в Юникод, могут менять свой цвет:

Смена цвета (12.86КиБ)

Юникод всё усложняется и усложняется, чувствую, однажды они придут к чем-то похожему на ХТМЛ+ЦСС — будут базовые элементы и стилевые таблицы для их изменения.

6 комментариев

Изучаю TCL

Учу «Тикль» потихонечку — вечерами, урывками. Язык нравится очень простым синтаксисом, который позволяет делать крутые штуки — всё в языке считается строкой, а вопросом интерпретации строки занимается код. Это даёт возможность, например, расширять язык по необходимости.

Попробую пояснить как это работает. В данном случае все три параметра, передаваемых команде puts эквивалентны:

puts Hello; # это строка
puts "Hello"; # это тоже строка
puts {Hello}; # и это строка!

То есть фигурные скобки — это не «блок кода», а строка, правда со своими особыми правилами интерполяции. Это, вместе с конструкциями изменения уровня выполнения и позволяет создавать собственные дополнения к языку. Например, добавил в язык конструкцию unless (это такой if, только проверяется не истинность, а ложность условия):

proc unless {condition body} {
    ;# uplevel — конструкция, позволяющая выполнить блок выше по стеку
    ;# то есть, в данном случае, мы выполняем код в контектсе вызвавшего
    ;# процедуру unless кода, что позволяет увидеть все переменные, которые там определены
    uplevel [list if !($condition) $body]
}

set var false

;# Это наша новая команда
unless { $var } {
     puts "False: $var" ;# напечатает «False: false»
}

set var true

;# А это обычный if, которые есть в языке
if { $var } {
    puts "True: $var" ;#  напечатает «True: true»
}

Надо бы, конечно, что-то пописа́ть, чтобы лучше понять язык, задумки есть, скоро приступлю.

11 комментариев

Tcl/tk и Retina

О, оказывается, можно «ретинизировать» интерпретатор Тикля — сделать так, чтобы программы на этом языке хорошо смотрелись на маковской ретине (см. скриншот).

Ретинизированный Тикль (24.29КиБ)

Для этого нужно скачать программу Retinizer и бросить в неё Wish.app, который находится в /System/Library/Frameworks/Tk.framework/Versions/Current/Resources/

Оставить комментарий

Странный PHP

А вот ПХП, в отличие от ДжаваСкрипта действительно странный. Давайте посмотрим на такой вот код и его результат:

$ php -a
Interactive shell

php > $a = 1; echo $a + $a++;
3
php > $a = 1; echo $a + $a + $a++;
3

Как видите, в том и другом случае у нас один результат — «3». Даже первая «тройка», казалось бы, противоречит здравому смыслу, а вторая — тем более. Что же происходит? Давайте разбираться.

Как работает первый пример?

Операция сложения левоассоциативна — разбор агрументов начинается слева направо. Двигаясь таким образом, парсер видит выражение в котором две операции — сложение и постинкремент, у постинкремента приоритет выше, поэтому вычисляется сначала он — возвращая в качестве значения «1» и увеличивая переменную на единицу, потом вычисляется операция сложения, складывая полученную единицу с двойкой (так как постинкремент увеличил значение переменной на единицу). Получается «три».

Похожим образом обрабатывается умножение вместе со сложением: 2 + 2 * 2 = 6, а не 8, потому что умножение имеет более высокий приоритет.

Во втором случае всё происходит похожим образом, но чуть иначе — парсер, обрабатывая левоассоциативное сложение, берёт первые два аргумента, складывает их, получает «двойку», двигается дальше, видит двойку, сложение и постинкремент переменной. Постинкремент более приоритетный, он его вычисляет раньше, возвращая в сложение «единицу», значение переменной увеличивается, но его уже никто не использует — складываются числа «2» (от предыдущего сложения) и «1» (вернул постинкремент). Получается «три».

В байт-кодах всё перечисленное выглядит следующим образом:

Байт-коды (23.90КиБ)

Тут с восьмой строки начинается второй пример (правда присваивание единицы во втором примере я опустил — как видите второй операции ASSIGN нет).

12 комментариев

← 1 24 25 26 27 28 67 →