Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Стандартные диалоги при работе с файлами

Поиск

Чтобы открыть файл для реализации действий с его содержимым, используют компонент TOpenDialog вкладки Dialogs, а для сохранения изменений в файле можно применить другой объект этой же вкладки – TSaveDialog . Названные компоненты относятся к невизуальным, поэтому на форме необходимо предусмотреть соответствующие интерфейсные элементы в виде пунктов меню или кнопок. За отображение диалога отвечает метод Execute, поэтому его следует привязать в обработчике события к интерфейсному эле-менту. На рисунке представ-лены две кнопки управления действиями с файлами. Свой-ство Title обоих диалогов задает текст в заголовке окна (если значение свойства не задано, то отображается “Открыть” или “Сохранить” в соответствии с видом окна диалога). Свойство Filter задает список фильтров имен файлов. Если щелкнуть по многоточию справа от свойства, то появится окно редактора фильтра, пред-ставленное на следующем рисун-ке. В левую колонку заносят име-на фильтров, а в правую – маску имен. С помощью свойства FilterIndex в группе Database задают индекс фильтра по умолчанию (в нашем примере для текстовых файлов – 0). Свойство InitialDir определяет каталог, содержимое которого будет отображаться при появлении окна диалога (по умолчанию – папка “Мои документы”). Имя выбранного пользователем файла представляет свойство FileName. Ниже приведены обработчики кликов по кнопкам Button1 (“Открыть”) и Button2 (“Сохранить”) при работе с текстовыми файлами (см. п. 7.2).

procedure TForm1.Button1Click(Sender: TObject);

begin

if OpenDialog1.Execute then

Memo1.Lines.LoadFromFile(OpenDialog1.FileName);

end;

 

procedure TForm1.Button2Click(Sender: TObject);

begin

if SaveDialog1.Execute then

Memo1.Lines.SaveToFile(SaveDialog1.FileName);

end;

 

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

 

Отладка приложения

Отладка заключается в локализации ошибки (нахождении ее места в коде) и ее устранении. Таким образом, восстанавливается правильная логика работы программы. Среда содержит встроенный отладчик (debugger), который позволяет выполнять действия (строки кода) по шагам и при этом наблюдать за изменением объектов программы, ответственных за конечный результат. Рассмотрим процесс отладки на конкретном примере.

Задача: подсчитать сумму элементов некоторого вектора.

Создаем соответствующую форму, представленную ниже.

Пусть элементы вектора являются целыми числами, вводимыми с клавиатуры, а разделителем между ними служит один пробел. Числа можно занести при создании формы и изменять в процессе работы программы. Количество суммируемых элементов, начиная с первого, задают в элементе ввода, обозначенном n. При создании обработчика клика по кнопке “Вычислить” следует закодировать получение элементов вектора (чисел) из строки символов, вводимых в компонент Edit2. Текст процедуры имеет следующий вид. В нем присутствует ошибка (выделена цветом), которая обнаруживается на этапе выполнения программы, но не распознается на стадии компиляции. Она обусловлена копированием из строки s в переменную element не только символов, соответствующих разрядам числа, но и разделителя – пробела.

procedure TForm1.Button1Click(Sender: TObject);

var

v: array[1..20] of integer; // Вектор с запасом

s: string[100]; // Вектор в виде строки

element: string[10]; // Элемент вектора в виде строки

i, n, summa: integer;

begin

n:= StrToInt(Edit1.Text); // Число суммируемых элементов

i:= 1;

s:= edit2.Text; // Исходная строка для получения чисел

repeat // Цикл обработки чисел в строке

element:= copy(s, 1, pos(' ', s) {-1}); // Копия с пробелом - ошибка

v[i]:= StrToInt(element); // i-й элемент вектора - число

delete(s, 1, pos(' ', s)); // Удаление элемента с пробелом

inc(i);

until i > n;

summa:= 0;

for i:= 1 to n do // Цикл суммирования элементов вектора v

summa:= summa + v[i];

Edit3.Text:= IntToStr(summa);

end;

 

Чтобы произвести отладку, выполняем команду Run/Add Watch … (< Ctrl> + <F5>). Она приводит к открытию в центре экрана окна диалога Watch Properties и за ним – окна Watch List для наблюдения за изменениями введенных в него параметров. В окне диалога в строке Expression вводят имя переменной (можно выражение), например i, которое после нажатия кнопки <Ok> попадает в список просмотров. В этот список после нажатия клавиши <Insert> добавляют другую переменную и т.д. На следующем рисунке представлен вид созданного окна просмотров. Против каждой переменной в колонке Value (значение) стоит метка о недоступности, т.к. все эти переменные являются локальными. Далее производим трассировку программы с захо-дом в подпрограммы, для чего нажимаем <F7> (команда Run/Trace Into). Первое нажатие вызовет появление окна с текстом программы, в котором будет выделено слово begin. Каждое последующее нажатие приводит к выполнению строки программы. После выполнения строки Application.Run отображается окно программы. В нем вводим n, например 3, и щелкаем по кнопке “Вычислить”. В результате выделяется слово begin в процедуре. Нажимая <F7>, доходим до строки element:= copy(s, 1, pos(' ', s){-1}). После ее выполнения (очередное нажатие <F7>) наблюдаем (рисунок справа), что первый элемент вектора (число 11) скопировал-ся с пробелом. Это ошибка, она приведет при следующем нажатии <F7> к невозможности преобразования такого элемента строки в число. Таким образом, ошибка локализована. Очевидно, чтобы копирование было корректным, необходимо исправить строку кода следующим образом: element:= copy(s, 1, pos(' ', s)-1). Для выхода из процесса отладки выполняют команду Run/Program Reset (<Ctrl> + <F2>), а затем делают исправления в коде.

 

Исключения и их обработка

Мы уже обращали внимание на ошибки, возникающие в процессе выполнения программы, на так называемые исключения (с. 13). Это динамические ошибки, причиной которых чаще всего являются недопустимые исходные данные для совершаемых операций. При выполнении программы такие ошибки вызывают генерацию средой объектов специального вида, характеризующих возникшую исключительную ситуацию. Сразу после их обработки соответствующими процедурами объекты-исключения уничтожаются. Если исключение нигде в программе не перехвачено для адекватной обработки (как это сделать, будет сказано ниже), то оно вызывает стандартную реакцию с выдачей на экран сообщения. После запуска содержащей ошибки программы на выполнение командой Run/Run (<F9>) Debugger (отладчик) выведет сообщение об этой ошибке. Применительно к

 
 

примеру, рассмотренному на с. 43, может выводиться следующее окно с сообщением о том, что '11 ' не является целым числом. Окно не выводится, если стоял флажок Ignore this exception type (Игнорировать этот тип исключения) или сделана настройка в среде, а сразу появится окно, представленное справа. Это же окно появится при нажатии кнопки Continue (Продолжить) в диалоге с отладчиком, т.к. выполнение приложения будет продолжено, что приведет к исключительной ситуации. Щелчок по кнопке Break прерывает работу программы и открывает окно с кодом, в котором активна (выделена) строка, при выполнении которой произошло исключение.

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

try

< Исполняемые операторы >

except

< Операторы, исполняемые при генерации исключения >;

end;

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

on < Класс исключения > do < Оператор >;

Таблицы классов исключений, базовым для которых служит класс Exception, приведены в [1, 3]. Рассмотрим простой пример возможной обработки некоторых исключений.

var

A, B, q: real;

begin

try

A:= StrToFloat(Edit1.Text); // Возможен ввод с разделяющей точкой

B:= StrToFloat(Edit2.Text);

q:= sqrt(A/B); // Возможно деление на ноль

Edit3.Text:= FloatToStr(q);

except

on EConvertError do ShowMessage('Неверный ввод данных');

on EZeroDivide do ShowMessage('Деление на ноль');

end;

end;

 

Лабораторный практикум

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

 

10.1. Простое приложение

Приведем еще один пример достаточно простого по интерфейсу приложения. Воспользуемся компонентом TMemo для ввода значений элементов матрицы, которую затем преобразуем в вектор. Конструируем форму, представленную на следующем рисунке. При создании матрицы будем разделять ее элементы в строках одним пробелом. Каждую строку матрицы обрабатываем процедурой StrToVector. Ее следу-ет описать вне типа TForm1, а за-тем использовать в процедуре, реагирующей на щелчок по кноп-ке “В вектор”. Ниже приводится фрагмент соответствующего кода.

 

//Заголовок процедуры в интерфейсной секции модуля

procedure StrToVector(s: string);

 

implementation

 

{$R *.dfm}

procedure StrToVector(s: string);

var

element: string[5]; // Элемент строки матрицы

i, n: integer;

begin

n:= StrToInt(Form1.Edit1.Text);

i:= 1;

repeat // Цикл обработки чисел в строке

if pos(' ', s)<> 0 then

begin

element:= copy(s, 1, pos(' ', s));

delete(s, 1, pos(' ', s));

end

else element:= s + ' ';

Form1.Edit2.Text:= Form1.Edit2.Text + element;

inc(i);

until i > n;

end;

 

procedure TForm1.Button1Click(Sender: TObject);

var

ms: TStrings; // К этому типу относятся строки объекта TMemo

i, n: integer;

begin

Edit2.Text:= '';

n:= StrToInt(Edit1.Text);

Edit2.Width:= n * n *15;

ms:= Memo1.Lines; // Все строки из Memo1 – в массив ms

for i:= 0 to n - 1 do // Строки Lines пронумерованы от нуля

StrToVector(ms[i]);

end;

 

procedure TForm1.Button2Click(Sender: TObject); // Процедура очистки

begin

Edit1.Text:= '';

Memo1.Lines.Clear; // Метод, очищающий все строки в Memo1

Edit2.Text:= '';

Edit2.SetFocus; // Этот компонент получает фокус после очистки

end;

 

Для защиты от ввода в компоненты Edit1 и Memo1 недопустимых символов следует написать обработчики события OnKeyPress, аналогичные процедуре, реализованной в примере на с. 13. При этом в множество допустимых символов для Memo1 включить символы пробел и #13, #10.

 

Варианты задания

Сконструировать оконный интерфейс для решения задачи. Предусмотреть защиту от недопустимых действий.

1. Установить, является ли строка палиндромом (перевертышем).

2. Отсортировать вектор из n чисел по убыванию или нарастанию.

3. Найти сумму элементов матрицы случайных чисел размером N*N с помощью подпрограммы, суммирующей элементы вектора.

4. Преобразовать натуральное число в код.

5. Отсортировать элементы строки текста.

6. В списке из n сотрудников отдела подсчитать количество ветеранов (стаж работы > 25). Записи таблицы имеют также поля “ФИО” и “№ документа”.

7. Заполнить массив из трех элементов случайными значениями типа “цвет” (красный, синий, зеленый). Показать результат.

8. Получить таблицу из n значений функции ln(x) – x + 1.8 на отрезке [2; 3.5].

9. Отсортировать по убыванию стажа работы список из n сотрудников.

10. Получить скалярное произведение для двух векторов из n элементов, вводимых с клавиатуры.

11. Вывести через два пробела все цифры натурального числа.

12. Матрицу N*N случайных чисел переписать в вектор.

13. Получить вектор из n случайных значений и найти их среднее.

14. Получить по итерационной формуле q:= (q + n/q)/2 с погрешностью 0.001. Отобразить на форме также три последних приближения к результату.

15. На основе матрицы N*N получить вектор, элементы которого равны 0, если сумма четных чисел в строке больше суммы нечетных, а иначе равны 1.

16. Установить, равно ли натуральное число сумме факториалов его цифр. Использовать подпрограмму вычисления факториала. (Пример такого числа – 145 = 1! + 4! + 5!).

17. Установить, равно ли число сумме своих делителей, включая единицу (совершенное число).

18. Заполнить два вектора случайными числами из диапазонов [33; 100] и [0; 28] соответственно. Сформировать третий вектор из символов с порядковыми номерами, равными сумме аналогичных элементов первых векторов.

19. Вычислить: для x = 1.2, n = 5, a = (1.3, -0.8, 1.8, 4.1, -7.4, 6.7) (вектор коэффициентов начат с ) по схеме Горнера .

20. Сформировать матрицу N*N из целых чисел. Переписать ее строки в обратном порядке: первая строка становится последней, вторая – предпоследней и т.д. Показать обе матрицы.

 

Рекурсивные вычисления

Рекурсивные подпрограммы основаны на их обращении к самим себе [6]. Описание действий в таких подпрограммах обязательно содержит терминальную ситуацию, в которой результат получается непосредственно без рекурсивного вызова. Покажем пример реализации действий для следующей задачи: методом слияния объединить два вектора из целых чисел, отсортированных по нарастанию значений их элементов.

Метод слияния двух отсортированных массивов A и B из n и m элементов соответственно, что называется, интуитивно понятен. В последнюю позицию результирующего вектора C сначала помещают наибольший из элементов векторов A и B. Номер следующего элемента (n или m) в том векторе, из которого его взяли, уменьшается на 1. Этот элементарный шаг можно повторять рекурсивно. Если один из векторов уже переписан в вектор C, то в C помещают остатки другого. Терминальная ситуация наступает, когда все элементы векторов A и B будут переписаны в C, чему соответствует условие n + m = 0.

Сначала сконструируем основную форму, представленную на рисунке. Затем создадим модуль с рекурсивной процедурой слияния и вспомогательной процедурой перевода строки в вектор. Для этого выполним команду File/New/Unit – Delphi for Win32. В результате появится заготовка модуля с заголовками интер-фейсной секции и секции реализации. Заносим в них код, представленный ниже.

unit Unit2;

 

interface

const

k = 100;

type

vector = array[1..k] of integer;

procedure Sliyanie (n, m: byte; a, b: vector; var c: vector);

procedure StrToVector(s: string; var n: integer; var v: vector);

 

implementation

uses SysUtils;

procedure Sliyanie (n, m: byte; a, b: vector; var c: vector);

{a и b - исходные векторы, c - результат их слияния}

{сначала n - размер вектора a, m - размер вектора b}

begin

if m + n = 0 then exit {Терминальная ситуация - когда все элементы}

else begin {из вектора a и вектора b помещены в вектор c}

if n = 0 then begin {Если из вектора a все элементы взяты в вектор c,}

c[n + m]:= b[m]; {то помещаем в него элемент вектора b}

m:= m-1

end

else if m = 0 then begin

c[n + m]:= a[n]; n:= n-1;

end

else if a[n] > b[m] then begin

c[n + m]:= a[n]; n:= n-1;

end

else begin

c[n + m]:= b[m]; m:= m-1;

end;

sliyanie(n, m, a, b, c); // Рекурсивный вызов с новыми значениями n и m

end;

end;

 

procedure StrToVector(s: string; var n: integer; var v: vector);

var // Процедура перевода строки s в вектор v из n элементов

element: string[5]; // Элемент строки

i: integer;

begin

i:= 1;

repeat // Цикл обработки чисел в строке

element:= copy(s, 1, pos(' ', s)-1); // Элемент строки как число

v[i]:= StrToInt(element);

delete(s, 1, pos(' ', s));

inc(i);

until pos(' ', s) = 0;

v[i]:= StrToInt(s);

n:= i;

end;

end.

 

Сообщение об ошибке Undeclared identifier: ' StrToInt ' при компиля-ции потребует добавления строки uses SysUtils; после слова implementation. Созданный модуль необходимо подключить к проекту, для чего выполняют одну из команд: File/Use Unit… или Project/Add to Project…. И наконец, сам обработчик щелчка по кнопке “Объединить” имеет вид:

procedure TForm1.Button1Click(Sender: TObject);

var

v1, v2, v3: vector; i, n, m: integer;

begin

StrToVector(Edit1.Text, n, v1);

StrToVector(Edit2.Text, m, v2);

Sliyanie(n, m, v1, v2, v3);

for i:= 1 to n + m do

Edit3.Text:= Edit3.Text + ' ' + IntToStr(v3[i]);

end;

Варианты задания

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

1. Вычислить и показать первые 10 чисел Фибоначчи: F(1) = 1, F(2) = 1, F(n) = F(n-1) + F(n-2).

2. Вычислить значения первых 5 полиномов Лежандра для x = 1 и x = 0.6: , , .

3. С погрешностью 0.001 уточнить методом дихотомии корень уравнения = 0 на отрезке [0; 4].

4. С погрешностью 0.001 уточнить по методу Ньютона корень уравнения ln(x) – x + 1.8 = 0 на отрезке [2; 3].

5. Уточнить корень уравнения ln(x) – x + 1.8 = 0 на отрезке [2; 3] методом простой итерации с погрешностью 0.001.

6. Используя итерационную формулу , вычислить с погрешностью 0.001 и .

7. Найти с погрешностью 0.001 и , по формуле q:= (q + n/q)/2, в которой n – исходное число, а q – приближение к результату.

8. Определить наибольший элемент в векторе из n чисел.

9. Методом прямоугольников вычислить с погрешностью 0.001 .

10. С погрешностью 0.001 вычислить .

11. С погрешностью 0.001 вычислить .

12. Для x = 0.8 вычислить значения первых 6 полиномов Эрмита: , , .

13. Вычислить и , использовав следующее представление квадратов чисел: , , ,

14. Вычислить сумму элементов вектора из n чисел.

15. Для x = 0.5 вычислить значения первых пяти полиномов Лагерра: , , .

16. Вычислить значения первых пяти многочленов Чебышева: , , для x = 0.5.

17. Подсчитать количество переносов колец ханойской башни с одного столба на другой. Можно переносить только по одному кольцу, класть меньшее на большее, используя третий столб. Исходная и результирующая башни имеют на вершинах самое маленькое кольцо.

18. Вычислить p + (p + 1) + (p + 2) + … + (p + n) для p = 0.7 и n = 10, а также для p = 0.5 и n = 15.

19. С погрешностью 0.0001 вычислить e = 1 + 1/1! + 1/2! + 1/3! + …

20. Вычислить для n = 7 и n = 10 выражение .

 

Обработка текстовых файлов

Текстовые файлы представляют собой поименованную последовательность символов на внешнем носителе, разбитую на строки [6]. В п. 7.2 был приведен пример действий над такими файлами с использованием методов класса TStrings, обрабатывающих массив строк как одно целое. Рассмотрим другой способ взаимодействия с файлом – с помощью файловой переменной (ФП). Язык Delphi, в отличие от языка Turbo Pascal, использует следующие процедуры для работы с файлами.

AssignFile(<ФП>, <Имя файла на диске>) – связывает имя файловой переменной ФП с именем файла на диске.

CloseFile(<ФП>) – закрывает файл.

 
 

Файловая переменная для работы с текстовым файлом описывается так: <ФП>: TextFile. Остальные языковые средства сохраняются. Покажем на простом примере, как обрабатывать текстовые файлы. Создадим форму, представленную ниже. На ней присутствуют интерфейсные элементы для организации диалогов при работе с файлами и пунктов меню.

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

f: TextFile; // ФП для действий с файлом

FileName, s: ShortString; // Имя файла на диске и строка файла.

Коды обработчиков событий имеют следующий вид.

procedure TForm1.FormCreate(Sender: TObject);

begin // При открытии формы удаляются ненужные надписи

Label6.Caption:= ''; Label7.Caption:= ''; Label8.Caption:= '';

Label9.Caption:= ''; Label10.Caption:= '';

end;

 

procedure TForm1.N1Click(Sender: TObject); // Пункт меню "Открыть"

begin

if OpenDialog1.Execute then // Если выполняется диалог, то

begin

FileName:= OpenDialog1.FileName; // из него берут имя файла

AssignFile(f, FileName); // Связывание ФП с именем файла

reset(f); // Открываем файл для чтения

repeat

readln(f, s); // Чтение из файла строки в переменную s

Memo1.Lines.Add(s); // Добавление строки s в Memo1

{Memo1.Text:= Memo1.Text + s; --> Возможный вариант действий}

until eof(f); // до тех пор, пока не кончится файл

CloseFile(f); // Закрываем файл

end;

end;

 

procedure TForm1.N2Click(Sender: TObject); // Пункт меню "Записать"

var

c: char; // Переменные (выражения)

n: integer; // этих типов

x: real; // можно писать

q: boolean; // в файл

begin

if SaveDialog1.Execute then

begin

FileName:= SaveDialog1.FileName;

AssignFile(f, FileName);

if not FileExists(FileName) then rewrite(f) // Создаем файл, если его нет

else begin

Append(f); // Иначе открываем файл для дозаписи в него

writeln(f);

end;

c:= Edit1.Text[1];

writeln(f, 'Символ --> ', c);

n:= ScrollBar1.Position;

writeln(f, 'Целое число --> ', n);

x:= StrToFloat(Edit2.Text);

writeln(f, 'Дробное число --> ', x: 4: 2); // Форматный вывод в файл

q:= StrToBool(Edit3.Text);

writeln(f, 'Булево значение --> ', q);

CloseFile(f);

end;

end;

 

procedure TForm1.N3Click(Sender: TObject); // Пункт меню "Очистить"

begin

Edit1.Text:= ''; Edit2.Text:= ''; Edit3.Text:= ''; Edit4.Text:= '';

Memo1.Lines.Clear; // Очистка текста в компоненте Memo1

end;

 

procedure TForm1.ScrollBar1Change(Sender: TObject);

begin // Получение целого числа

Edit4.Text:= IntToStr(ScrollBar1.Position);

end;

На следующем рисунке показано окно выполняемой программы.

 
 

Были введены исходные данные в блоке “В файл можно записать”, затем выполнено добавление данных в файл командой меню “Записать” и произведено чтение из файла по команде “Открыть”.

Варианты задания

Создать текстовый(е) файл(ы), содержащий(е):

 

1) информацию о трех книгах: автор, название, год издания. Вывести ее на экран в алфавитном порядке по фамилии автора;

2) операнды большого размера и результат их перемножения в отдельных файлах;

3) данные о трех сотрудниках: ФИО, пол, возраст. По ним определить число мужчин;

4) координаты трех точек на плоскости. Установить их принадлежность кругу радиуса R с центром в начале координат;

5) таблицу истинности для функций XÇY и XÈY (соответственно функции логическое И и логическое ИЛИ);

6) координаты трех пар точек на плоскости. Найти пару с наименьшим расстоянием;

7) список группы с данными о каждом студенте: ФИО, средний балл. Найти средний балл для всей группы;

8) строки, содержащие ‘ d ’ вместо ‘ д ’. Исправить ошибки программно;

9) оценки и фамилии в разных файлах. Создать результирующий файл;

10) несколько строк. Сделать последнюю строку первой, предпоследнюю – второй и т.д.;

11) строки с именами и рейтингами работников. Вывести в файл данные о работнике с наивысшим рейтингом с комментарием;

12) три строки. Вставить после каждого символа пробел;

13) пять строк. С помощью подпрограммы, определяющей количество цифр в строке файла, подсчитать их число во всем файле;

14) матрицу 3х3 целых чисел. Вывести в файл суммы элементов строк;

15) построчно матрицу 4х5 с помощью подпрограммы записи в файл вектора целых чисел. Содержимое файла вывести на экран;

16) матрицу 2х6 вещественных чисел. Скопировать ее в другой файл;

17) вектор из 10 случайных вещественных значений. Отсортировать числа в файле по нарастанию;

18) пять строк. Вывести в другой файл латинские буквы строк;

19) вектор из 10 вещественных чисел. Записать в другой файл числа, которые не превышают среднего значения в исходном векторе;

20) случайным образом созданные 5-элементные векторы. Сравнить соответствующие компоненты файлов и записать результаты в файл.

 



Поделиться:


Последнее изменение этой страницы: 2016-06-19; просмотров: 434; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.222.164.176 (0.01 с.)