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

Внимание! Данная страница находится в состоянии переработки и выноса части сведений на страницу Основы программирования на VBA. Также, с 2020 года здесь будет производиться сравнительное обобщение следующих языков: VBA, Visual Basic, Паскаль, Delphi, Python, Java, C++, JavaScript, PHP, FoxPro.

Целью данного проекта является помощь в выборе языка и облегчение перехода, когда можно наглядно сопоставить синтаксис уже знакомого языка с изучаемым. Лично мне приходится постоянно параллельно работать с четырьмя из перечисленных языков, и многие моменты просто путаются.

Условные обозначения:

  • | — вводится одно из нескольких возможных значений,
  • [ ] — необязательные параметры,
  • <информация, которая должна быть введена пользователем или программой>.

    Содержание
    Введение: основы программирования. Языки: выбор для ЕГЭ
    Операторы
    Переменные и типы данных
    Именование
    Типы данных
    Действия над данными
    Преобразование типов
    Ветвление программ: If, Case
    Циклы
    Безусловные переходы
    Функции
    Символьные
    Даты и времени
    Числовые и математические
    Задаваемые пользователем (UDF)

    Основы программирования

    Программирование, изучаемое в базовом школьном курсе информатики и ИКТ, носит исключительно декларативный характер. Это связано с ничтожным количеством часов, отводимых на изучение этой тематики и абсолютной несостоятельностью изучаемых языков (за исключением Visual Basic и некоторых современных языков, изучаемых исключительно в профильных учреждениях).

    Полное отсутствие учебников именно для базового курса также не способствует достижению приемлемого результата.

    На данной странице будет предпринята попытка обобщить только ту информацию, которая используется во всех без исключения языках высокого уровня. Вместе с тем, в некоторых случаях приводится подборка из нескольких языков, чтобы не была упущена важная идея. За основу берется перечень языков, используемых для ЕГЭ, который дополняется наиболее массово используемыми. Но, в любом случае, основой для изложения служит VBA, выносимый на отдельную страницу.

    В конце 2019 года начата переработка данной страницы для формирования не просто обобщенного, а конкретизированного описание некоторых возможностей в ограниченном наборе языков. С самых первых шагов начали «вылезать» многочисленные и крайне интересные закономерности. При желании, вы их легко обнаружите в сводных таблицах.

    Операторы

    Через косую черту приведены альтернативные варианты
    ОператорVBAVBПаскальDelphiPythonJavaC++JavaScriptPHPFoxPro
    Присваивание=/:==:=:=======
    Сложение++++++++++
    Вычитание----------
    Умножение**********
    Деление//////////
    Возведение в степень^^нет/Sqrнет/Sqr**Math.pow()pow()Math.pow()/****/pow()^/**
    Целая часть от деления\\divdiv//нетнет~~()%
    Остаток от деления
    (modulo of division)
    ModModmodmod%%%%fmod()%
    Конкатенация (слияние) строк&&+/Concat()+++/Concat()++./concat()+

    Ограниченная применимость.

    Операторы сравнения

    Сокращенные и комбинированные версии операторов не приводятся.
    ОператорVBAVBПаскальDelphiPythonJavaC++JavaScriptPHPFoxPro
    Присваивание=/:==:=:=======
    Равенство===============
    Идентичность (строгое совпадение)нетнетнетнетнетнетнет========
    Меньше<<<<<<<<<<
    Больше>>>>>>>>>>
    Меньше или равно<=<=<=<=<=<=<=<=<=<=
    Больше или равно>=>=>=>=>=>=>=>=>=>=
    Не равно<><><><><>/!=!=!=!=<>/!=#/!=/<>

    Логические операторы

    Также можно изучить материал, посвященный алгебре логики.

    Логические операторы располагаются между двумя логическими выражениями, каждое из которых так или иначе либо истинно, либо ложно. Для более сложных случаев используется группировка пар с помошью скобок. Приведенная ниже таблица содержит 4 основных логических оператора.
    ОператорНазвание оператора
    ANDИ. Означает, что оба соединяемых выражения должны быть истинны
    ORИЛИ. Хоть одно из выражений истинно
    XORИсключающее ИЛИ. Только одно из выражений должно быть истинно
    NOTНЕ. Ставится перед логическим выражением, значение которого меняется на противоположное

    Сводная таблица логических операторов в разных языках
    ОператорVBAVBПаскальDelphiPythonJavaC++JavaScriptPHPFoxPro
    И AndAnd andandand/& &/&&&/&&&&and/&&and
    ИЛИ OrOr ororor/| |/|||/||||or/||or
    Исключающее ИЛИXorXor xorxoroperator.xor()/^^^^xor#
    НЕ NotNot notnotnot/~ !!!!not/!
    ЭквивалентностьEqvнет нетнетнет нетнетнетнетнет
    Импликация Impнет нетнетнет нетнетнетнетнет
    Конъюнкция (&)нетAndAlso нетнетнет нетнетнетнетнет
    Дизъюнкция (+)нетOrElse нетнетнет нетнетнетнетнет

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

    Переменные и типы данных

    Именование переменных

    В математике принято обозначать переменные символами латинского алфавита. Для программирования такой подход неприемлем. Помимо того, что число переменных в большой программе может составлять несколько сотен, придется очень постараться, чтобы разобрать (запомнить), что описывает переменная «f» или «r». (Данное обозначение использовалось в учебных целях для сокращения слов Find и Replace, что, конечно же, является безобразием с точки зрения именования.)

    Имена переменных в большинстве языков программирования подчиняются примерно одинаковым правилам.

    • Имя должно начинаться с буквы (иногда можно и с символа подчеркивания).
    • Имя должно состоять из букв, цифр и символа подчеркивания.
    • Длина имени не должна превышать 255 символов (интересно, кому в голову может прийти такое имя?).
    • Регистр символов может учитываться либо нет, что полностью зависит от языка.
    • Имя должно быть понятным.

    О последнем пункте следует поговорить особо.
    Если назвать переменную «CV», то она мало отличается от шарады. Название «CurrentValue» позволит определить, что переменная содержит текущее значение вычислений или иной обработки информации. Но, что крайне важно, ничего нельзя сказать о типе хранящихся данных, а, значит, и о том, какими методами с ней нужно (можно) работать. Наоборот, «intCurrentValue» содержит в начале своего имени явное указание на целочисленное значение.

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

    Итоговый совет один: придерживаться одинаковых правил, которые кажутся понятными и разумными. Ведь речь идет не о профессиональном программировании, а создании продуктов для облегчения собственной жизни.

    Принципиальное замечание! Никогда не надо использовать для именования русские буквы, только английские!

    Основные типы данных

    Типы данных могут значительно отличаться в зависимости от среды программирования и/или используемой версии языка. Но некоторые из них используются повсеместно без каких-либо изменений и особенностей.
    А поскольку правила обработки информации в зависимости от типа практически применимы только для разработки баз данных или программирования, множество вопросов остается вне понимания учащихся.

    Хотя типов данных (точнее программных переменных, в которых данные хранятся) довольно много, можно выделить наиболее важные группы, имеющие общие правила обработки.

    В квадратных скобках указан размер в байтах. Кроме указания на место, занимаемое в памяти, для многих типов это определяет возможный диапазон значений. Так, 2 байта для целых чисел дают 22*8 (65536) значений. Но один бит используется для указания знака, что ограничивает диапазон значений 215 (до ±32768).

    • Логический (булевый, boolean) [1–16 битов]. Служит для хранения логической информации: True (истина) или False (ложь). Реально, по элементы управления попадает не два, а четыре варианта!
    • Символьный (text, character, string) [длина строки]. Понятно, что львиная доля информации хранится в виде символов. Но неочевидно, что цифры также являются символами, над которыми невозможно совершать математические действия.
    • Числовой (numeric). Один из наиболее разветвленных видов, что связано с множеством условий и возможностей для работы с числовой информацией, а также её особой важностью для обработки программными средствами.
      • Целочисленный (integer, byte, long) [1–8]. Может хранить только целые числа.
      • С плавающей точкой (float) [4–8].
      • Денежный (currency) [8]. Точность указания чисел — с обязательными четырьмя десятичными разрядами. Иногда называют банковским типом, хотя последний должен содержать 8 десятичных разрядов.
      • Десятичный (decimal) [14–число знаков].
    • Даты (date) [8]. Может создавать проблемы, связанные с вводом, выводом и восприятием формата даты, различающегося в разных странах.
    • Времени (time) [8]. Встречается довольно редко или объединяется с датой.
    • Объект (object). С учетом объектно-ориентированного программирования не стоит отказываться от выделения именно в качестве типа.
    • Другое. Сюда хотелось бы выделить многое из того, что не вошло в предыдущие пункты. Выделить именно потому, что эти данные по-разному называются, хранятся и обрабатываются в разных средах программирования. Мультимедийная информация чаще рассматривается в качестве объекта, на который создается ссылка. Memo-поле (поле примечаний в базах данных) может хранить от 28 байтов (от нуля) до условно неограниченного количества информации.

    Действия над данными

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

    Символьные данные обычно используются в операции сложения, когда нужно собрать из кусочков выражение. Все участвующие фрагменты должны быть только символьного типа.
    Сложение символов обычно вызывает только одну трудность: учет стыковочных пробелов. С одной стороны, пробелы не появляются сами по себе (и два слова могут склеиться). С другой, наличие множества пробелов в конце первого фрагмента также должно быть учтено (иначе возникнет большой пробел между фрагментами).

    Логические данные полностью подчиняются законам алгебры логики.

    Дата может вычитаться из даты (разница в днях), либо к ней можно прибавить целое число (будущая дата) или вычесть целое число (дата в прошлом).

    В связи с вышеизложенным, для того чтобы выполнить над данными определенные действия, во многих случаях необходимо произвести изменение (преобразование) их типа.

    Преобразование типов данных

    Во всех языках программирования существует большое число функций, предназначенных для преобразования одного типа данных в другой. Необходимость этого связана с тем, что многие действия можно производить только над данными одного типа. Например, нельзя сложить число 10 и строку из двух цифр: "20". Если первое превратить в строку, то результат будет представлять собой четырехсимвольную строку "1020". Наоборот, преобразовав "20" в число и опять сложив их, получим число 30.

    В других ситуациях просто предполагаются разнообразные математические операции над числами, а результат нужно вывести в виде строки, снабженными поясняющими словами. Например, обработали число 5 и получили число 120. Чтобы собрать строку: "Из числа 5 получен результат 120!", нужно провести преобразование чисел и их сложение с текстом:

    "Из числа" + Str(5) + "получен результат" + Str(120) + "!"

    Преобразования числа в строку: Str(<число>)

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

    Для того, чтобы избавиться от этого пробела, можно использовать одну из двух функций Trim(<строка>) или Ltrim(<строка>).

    Преобразования строки в число: Val(<строка>)

    Следует четко понимать, что любой текст, взятый из документа изначально будет набором символов, даже если они представляют собой цифры.

    Ветвление программ (условные операторы)

    Для осуществления ветвления программ используются два основных оператора: If... Else... End If и Case...

    Оператор If... Else... End If

    По существу, оператор следует переводить как Если... Иначе... Конец If.

    1. Первичным следует считать использование так называемого неполного If. В этом случае не используется часть Else.

    	If <условие>
    	    ...[блок команд]
    	End If
    

    Таким образом, блок команд будет выполнен только когда будет истинным условие, заданное на входе оператора.

    Пример, в котором переменная памяти x будет уменьшена в 10 раз, только если она превысила значение 10:

    	If x > 10
    	    x = x/10
    	End If
    

    2. Чаще встречается случай, когда нужно обработать данные одним из двух способов в зависимости от условия:

    	If <условие>
    	    ...[блок команд 1]
    	Else
    	    ...[блок команд 2]
    	End If
    

    Пример, в котором переменная памяти x возводится в квадрат, если она отрицательна, а иначе — в куб (ноль также будет возводиться в куб):

    	If x < 0
    	    x = x^2
    	Else
    	    x = x^3
    	End If
    

    Замечание. В некоторых языках окончание записывается слитно: EndIf. Внекоторых оно не используется.

    3. Наконец, ряд языков позволяет составлять многоуровневую конструкцию без использования вложения If.

    	If a = 1
    	    ...[блок команд 1]
    	ElseIf a = 2
    	    ...[блок команд 2]
    	ElseIf b > 0
    	    ...[блок команд 3]
    	Else
    	    ...[блок команд 4]
    	End If
    

    Таблица сравнения условного оператора IF в разных языках. Для смены языка щелкните мышкой по нужной кнопке для вывода: левой — в левую колонку, правой — в правую, левой+Ctrl/Alt/Shift — в центр.

    Функция IIf()

    Так как чрезвычайно часто встречается ситуация, когда единственное назначение условного оператора — изменение переменной памяти по условию, все развитые языки содержат функцию IIf() или ее аналоги. Она решает названную задачу в гораздо более компактном виде.

    Пример, в котором переменной х присуждается нулевое значение, если она отрицательна и единица во всех остальных случаях:

    x = IIf(x < 0, 0, 1)

    Оператор Case

    Данный оператор может иметь два варианта синтаксиса. В первом случае, в начале конструкции производится вычисление какого-либо выражение, значение которого проверяется на соответствие несколько раз Вместо выражения может присутствовать переменная, от содержания которой зависит дальнейших ход программы.

    	Select Case <проверяемое выражение>
    	Case <значение1>
    	    действия1
    	[Case <значение2>
    	    [действия2]]
    	...
    	[Case Else
    	    [действия для других случаев]]
    	End Select
    

    В других случаях расчеты и проверка осуществляется для каждого случая, что позволяет задавать разнообразные выражения, в том числе представленные множеством переменных условий. (В этом синтаксисе оператор соответствует If–ElseIf.)

    	Select Case
    	Case <проверяемое выражение1>
    	    действия1
    	[Case <проверяемое выражение2>
    	    [действия2]]
    	...
    	[Case Else
    	    [действия для других случаев]]
    	End Select
    

    Таблица сравнения условного оператора CASE в разных языках.

    Циклы

    Организация циклов в программах встречается довольно часто и применяется для двух основных случаев: когда нужно повторить фрагмент программы определенное количество раз и когда это повторение осуществляется до тех пор, пока выполняется некое условие.

    Цикл For...

    Классический образец цикла для случаев, когда известно, сколько раз он должен быть повторен, либо это можно вычислить.

    Общий синтаксис:

    	For счетчик = <start> To <end> [Step <шаг>]
    	    [программа]
    	    [Exit For]
    	    [LOOP]
    	    [программа]
    	Next [счетчик]
    

    Счетчиком является число (обычно целое), по умолчанию наращиваемое после выполнения каждого цикла на единицу (или на шаг), начиная со значения start, пока не будет достигнуто значение end. Сами значения счетчика могут быть использованы только для ограничения числа повторов, либо задействоваться для выполнения програмы. Например, для извлечения элементов массива, соответствующих текущему значению счетчика.

    Также следует обратить внимание, что команды внутри цикла могут дополнительно изменять (переприсваивать) значение счетчика. Наличие в синтаксисе оператора LOOP может прервать итерацию и начать новый цикл с наращением значения. (Своего рода безусловный переход.)

    В приведенном ниже примере находится произведение четных чисел (шаг = 2) от 2 до введенного пользователем значения y. Если произведение превысит 1 трлн, то произойдет выход из цикла. Нетрудно убедиться, что i достигнет только 24, а y будет равен 1,961,990,553,600.

    	Dim i As Byte, x As Double, y As Integer
    	x = 1
    	y = InputBox()
    	For i = 2 To y Step 2
    	    x = x * i
    	    If x > 1000000000000 Then
    	        Exit For
    	    End If
    	Next i
    

    Цикл While...Wend

    Один из важнейших в программировании циклов, завершение которого определяется выполнимостью условия. Таким образом, число итераций цикла может быть от нуля до бесконечности.
    Общий синтаксис:

    	While <условие>
    	    [тело цикла]
    	Wend
    

    Условие является обязательным и может представлять собой символьное или числовое выражение, анализ которого возвращает истинное или ложное значение (то есть логическое). Если результатом вычисления будет значение Null, то оно расценивается как ложное.

    Тело цикла выполняется при истинном значении условия, вплоть до оператора Wend, а затем вновь возвращается на строку While для повторной оценки условия. Если оно истинно, то происходит повтор тела цикла. Иначе происходит завершение цикла и исполнение программы продолжается со строки, идущей после Wend.

    Цикл While может быть вложенным.

    Обратите внимание, что принудительного выхода из цикла нет: только анализ исходного условия! Для того, чтобы его реализовать, следует воспользоваться циклом Do...Loop.

    Цикл Do (While...|Until...)... Loop (|End Do)

    Данный цикл в том или ином виде используется в большинстве языков программирования и исполняется, пока истинно условие (While) или пока условие не будет достигнуто (Until)

    Общий синтаксис:

    	Do {While|Until} <условие>
    	    [тело цикла]
    	    [Exit Do]
    	    [продолжение тела цикла]
    	Loop
    
    или
    	Do
    	    [тело цикла]
    	    [Exit Do]
    	    [продолжение тела цикла]
    	Loop {While|Until} <условие>
    

    Из синтаксиса следует, что условие можно задать в конце. Тогда цикл будет обязательно исполнен хотя бы один раз.

    Число команд Exit Do внутри тела не ограничено и обычно реализуется через ветвление с использованием If...Then. При таком завершении, исполнение программы продолжается со строки, идущей после Loop.

    Конструкция With...End with

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

    Она предназначена для случаев, когда объектные выражения с одинаковым началом (нередко довольно длинным) повторяются много раз.

    Понятно, что в следующем примере речь идет о поиске — всё, что внутри конструкции — строки которого не нужно выискивать в программе. Фраза «Selection.Find» записана только один раз. Все строки, которые должны получить её в начало, следует начинать с точки.

    	With Selection.Find
    	    .ClearFormatting
    	    .Replacement.ClearFormatting
    	    .Text = "?"
    	    .Replacement.Text = "!"
    	    ...
    	End With
    

    Без With это выглядело бы так:

    
    	Selection.Find.ClearFormatting
    	Selection.Find.Replacement.ClearFormatting
    	Selection.Find.Text = "?"
    	Selection.Find.Replacement.Text = "!"
    	...
    

    Безусловные переходы

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

    В связи с увеличением объема программного кода возросла и начала активно реализовываться вероятность логических ошибок. После чего данный подход был признан неприемлемым и нерекомендованным к использованию.

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

    ...

    Функции

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

    Вызов функций, как правило одинаковый:

    переменная = ИмяФункции([аргументы через запятую]).

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

    Порядок указания аргументов задается строго. Менять их местами нельзя! Если значение среднего аргумента опускается, то должна быть поставлена пустая запятая:

    MsgBox("Сообщение", , "Заголовок").

    Но! В описании функции может быть указано, что первый аргумент необязательный, а его значение присваивается по умолчанию (см. InStr()).

    Тип каждого аргумента также задается достаточно строго и в большинстве случаев не может быть изменен.

    Вместе с тем, многие функции могут не иметь аргументов вообще, так как обрабатывают определенную заранее системную информацию. Например, функция Date() во многих языках.

    Ниже приведены некоторые наиболее часто используемые функции с их разбивкой по принципу типа обрабатываемой информации (в алфавитном порядке).

    Символьные (строковые) функции

    • Asc(<строка>) Возвращает ASCII-код (0–256) первого символа строки.
    • Chr(<строка>) Преобразует ASCII-код (0–256) в символ (строку).
      Некоторые важные символы:
      СимволASCII-код
      Табулятор9
      Enter13
      Esc27
      Пробел32 (первый неслужебный знак)
      !33 (первый знак, который выводится на экран и на печать)
    • InStr([начало, ]<строка, в которой ищем>, <искомая строка>) Находит позицию первого вхождения одной строки внутри другой. InStr(3, "корова", "о") вернет значение 4. То есть, начиная с третьей буквы, «о» стоит на четвертой позиции в слове.
    • Left(<строка>, <число символов>) Возвращает число первых (слева) символов строки. Если их меньше, чем запрошено, возвращается вся строка.
    • Len(<строка>) Возвращает количество символов в строке.
    • Mid(<строка>, <номер начального символа>[, <число символов>]) выделяет фрагмент из строки. Mid("корова", 2, 3) вернет «оро»: три символа, начиная со второго.
    • Right(<строка>, <число символов>) Возвращает указанное число последних (справа) символов строки.
    • Trim(<строка>), LTrim(<строка>) и RTrim(<строка>) Удаляют из строки пробелы слева (LTrim), справа (RTrim), или с обеих сторон (Trim).
    • Val(<строка>) преобразует строку в число.

    Функции даты и времени

    • Hour(<время>) выделяет из строки времени (тип Time) часы (тип Integer).
    • Minute(<время>) Выделяет из строки времени (тип Time) минуты (тип Integer).
    • Second(<время>) Выделяет из строки времени (тип Time) секунды (тип Integer).
    • Time() Возвращает системное время компьютера. Тип — Time. В VBA используется без скобок.
    • Date() Возвращает системную дату компьютера. Тип — Date. В VBA используется без скобок.
    • Weekday(<дата>, [номер первого дня недели]) Возвращает номер дня недели указанной даты. По умолчанию, первый день недели — воскресенье. Чтобы получить в привычном нам порядке, следует задать второй аргумент — двойку: Weekday("01.01.2009", 2).

    Числовые и математические функции

    • Abs(x) абсолютное значение (модуль) числа x.
    • Atn(x) арктангенс угла x в радианах (от –π/2 до π/2).
    • Cos(x) косинус угла x в радианах.
    • Exp(x) константа e (основание натурального логарифма ~2.718282) в степени x.
    • Rnd([x]) генерация случайного числа от 0 до 1. Аргумент x может задавать правила выбора значения.
    • Round(x,[десятичных знаков]) округляет числа с заданной точностью.
    • Sgn(x) равно –1 для отрицательных чисел; 1 — для положительных и 0 для x = 0.
    • Sin(x) синус угла x в радианах.
    • Sqr(x) квадратный корень x.
    • Str(x) преобразует число x в строку. Для положительных чисел в начало добавляется пробел.
    • Tan(x) тангенс угла x в радианах.
    • Fix(x) Отбрасывает дробную часть числа и возвращает целое значение. В отличии от Int(), для отрицательных значений дает ближайшее большее целое (Fix(-3,3) = -3).
    • Int(x) Отбрасывает дробную часть числа и возвращает целое значение. В отличии от Fix(), для отрицательных значений дает ближайшее меньшее целое (Int(-3,3) = -4).

    Функции, задаваемые или определяемые пользователем (UDF)

    Использование UDF (User Defined Function) позволяет многократно расширить возможности языка программирования и, в гораздо большей степени, уменьшить трудозатраты на разработку.

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


  • Copyright © 1993–2020 Мацкявичюс Д.А. Все права защищены.
    Никакая часть сайта не может быть воспроизведена никаким способом без письменного разрешения правообладателя и явной ссылки на данный ресурс.