Константы 4 страница

 

4.2.1. Директива #include

Директива #include указывает препроцессору включить в компилируемый файл содержимое другого файла (подставить содержимое другого файла на место директивы).

Файлы, включаемые директивой #include, обычно называют заголовочными. Они содержит описания некоторых функций с традиционным расширением .h, .hpp или без расширения (С++).

Формат записи директивы #include имеет вид:

 

#include <имя_файла>

#include ''имя_файла''

 

Примечания:

— директива вставляет содержимое включаемого файла в ту точку, где она записана;

— включаемый файл может также содержать директивы препроцессора;

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

Первый формат записи предполагает, что заголовочный файл расположен в стандартном подкаталоге \include компилятора. Второй формат означат, что компилятор начнет поиск файла с текущего каталога, а затем в подкаталоге \include.

Пример:

#include <iostream.h>

#include <stdio.h>

#include <conio.h>

#include <math.h>

#include <string.h>

#include <dos.h>

4.2.1. Директива # define

Директива #define определяет подстановку в тексте программы:

— символьных констант;

— макросов;

— символов, условной компиляцией.

 

Формат определения символических констант:

 

#define имя текст_подстановки

 

Примечание:

Все вхождения имени заменяются на текст подстановки.

 

Пример:

#define WAIT 1; // заменяет ‘WAIT’ на значение 1

#define TX 2; // заменяет ‘TX’ на значение 2

#define RX 3; // заменяет ‘RX’ на значение 3

#define MSG “Текст замены”;

switch (state) {

case WAIT : … break;

case TX : … break;

case RX : … break;

}

 

Формат определения макросов:

 

#define имя (параметры) текст_подстановки

 

Примечание:

— макросы напоминают функции, но реализуются подстановкой их текста в текст программы;

— параметры макроса используются при макроподстановке.

Пример:

#define MAX(a, b) ((a>b) ? (a) : (b))

y=MAX(sum1, sum2) ;

 

Формат определения символов, управляющих условной компиляцией:

 

#define имя

 

Примечание:

Используются вместе с директивами условной компиляции.

Пример:

#define MODE_1

#define MODE_2

 

4.2.3. Директивы условной компиляции

Формат директив условной компиляции:

#if константное_выражение

#elif константное_выражение

#else константное_выражение

#endif константное_выражение

 

#ifdef константное_выражение

#ifndef константное_выражение

Формат проверки определения константы:

 

defined (имя_константы)

 

Пример:

1)

#if VER==1 // if

#define INC_FILE “inc_ver_1.h”

#elif VER==2 // else if

#define INC_FILE “inc_ver_2.h”

#elif VER==3 // else if

#define INC_FILE “inc_ver_3.h”

#else // else

#define INC_FILE “inc_ver_0.h”

#endif

 

#include INC_FILE

2)

#if define(__CPP__)

text = “C++ version”;

#elif define(__C__)

text = “C version” ;

#else

text = “Undefined version” ;

#endif

 

cout<<text<<“was compiled.”<<endl ;

 

3)

 

#ifdef __PARAM_1__ //if defined

// строки кода компилируются,

// если параметр определен

#endif

#ifndef __PARAM_2__ //if undefined

// строки кода компилируются,

// если параметр не определен

#endif

 

4.2.4. Директивы #undef

Формат директивы удаления определения символа:

 

#undef имя_управляющего_символа

 

Примечание:

Директива используется редко для отключения какой-либо опции компилятора.

4.3. Лекция 13. Пользовательские типы данных

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

4.3.1. Переименование типов

Формат записи:

 

typedef имя_типа новое_имя_типа [ размерность ] ;

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

 

Пример:

typedef unsigned int UINT ; // выделено 7 Байт.

typedef char SMS[100] ; // выделена область под 100 символов

typedef struct {

char FIO[30] ;

int Date, Code ;

double Salary ;

} Worker ;

// использование

UINT i, j ; // две переменные типа unsigned int

SMS str[10] ; // массив из 10 строк по 100 символов

Worker staff[100] ; // массив из 100 структур

 

4.3.2. Перечисления

Формат записи перечисления:

 

enum [ имя_типа ] { список_констант } ;

 

Здесь:

— имя_типа — задаётся, если в программе требуется определять переменные этого типа;

— список_констант — содержит перечисление констант, которые должны быть целочисленными.

Примечание:

При отсутствии инициализатора первая константа обнуляется, а значение любой последующей больше предыдущей на 1.

Примеры:

1)

enum Err_msg {ERR_NO, ERR_READ, ERR_WRITE, ERR_CONVERT} ;

/* =0 =1 =2 =3 */

switch (error) {

case ERR_NO : /*операторы*/ break ;

case ERR_READ : /*операторы*/ break ;

case ERR_WRITE : /*операторы*/ break ;

case ERR_CONVERT : /*операторы*/ break ;

default : /*операторы*/ break ;

}

2)

enum {two=2, three, four, ten=10, eleven, fifty=ten+40} ;

/* =2 =3 =4 =10 =11 =50 */

 

4.3.3. Структуры

Структуры могут содержать элементы различных типов

Формат записи структуры:

 

struct [ имя_типа ] {

тип_1 элемент_1 ;

тип_2 элемент_2 ;

тип_N элемент_N ;

} [ список_описателей ] ;

 

Здесь:

— элемент_1 … элемент_N — поля структуры;

— имя_типа — может быть использовано в дальнейшем наряду со стандартными типами;

— список_описателей — список описателей переменных, указателей или массивов, разделенных запятой.

Примечание:

1. Если имя_типа отсутствует, то должен быть указан список_описателей переменных, указателей или массивов.

Элементы структуры называют полями структуры.

 

Пример:

struct {

char FIO[30] ;

int Date, Code ;

double Salary ;

} staff[100], *ps;

/* *ps — указатель на структуру */

 

2. Если отсутствует список_описателей, то структура определяет новый тип, имя которого можно использовать в дальнейшем наряду со стандартными типами.

 

Пример:

struct Worker { // описание нового типа Worker

char FIO[30] ;

int Date, Code ;

double Salary ;

} ;

Worker staff[100], *ps; //объявление массива типа Worker

// и указателя на тип Worker.

 

3. Инициализация структуры производится перечислением значений элементов в фигурных скобках.

 

Пример:

struct {

char FIO[30] ;

int date, code ;

double salary ;

} worker={ ''Иванов Иван'' , 31 , 215 , 3500.45} ;

 

4. При инициализации массивов структур следует заключать в фигурные скобки { } каждый элемент массива.

 

Пример:

struct complex

{

float real, imaginary ;

} compl [2][3]= {

{ {1,1} , {1,1} , {1,1} }, // строка 0, т.е. массив compl[0][…]

{ {2,2} , {2,2} , {2,2} }, // строка 1, т.е. массив compl[1][…]

}

 

 

5. Доступ к полям структуры в программе осуществляют:

— через имя структуры с помощью операции выбора — . (точка);

— через указатель с помощью — –> .

 

Если элементом структуры является другая структура, то доступ осуществляется через две операции выбора

 

Пример:

struct Worker { // описание типа Worker

char FIO[30] ;

int Date, Code ;

double salary ;

} ;

struct A {int a; double x;} ; // описание типа А

struct B {A b; double x;} x[2] ; // описание типа B

Worker worker, // объявление переменной worker,

staff[100], // массива staff[] типа Worker

*pointer ; // и указателя на тип Worker

worker.FIO = ''Иванов Иван'' ;

staff[8].Code=301 ;

pointer –> Salary = 6400.37 ;

x[0].b.a=1 ;

x[1].x=0.1 ;

4.3.4. Битовые поля

Битовые поля — это особый вид полей структуры.

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

Флаг — признак того или иного события в программе, операционной системе (ОС) или аппаратной части ПК.

Примечания:

— минимальная адресуемая ячейка памяти — 1 Байт;

— минимальный размер поля — 1 Бит;

— битовые поля могут быть любого целого типа;

— адрес поля получить нельзя.

При описании битового поля его длина в Битах указывается через знак : (двоеточие).

Пример:

struct DialogOptions

{

bool centerX : 1 ; // через двоеточие

bool centerY : 1 ; // указана длина полей

unsigned int shadow : 2 ; // в Битах

unsigned int palette : 4 ;

} ;

 

4.3.5. Объединения

Объединение union — это частный случай структуры.

Примечания:

— все поля объединения располагаются по одному адресу;

— в каждый момент времени в переменной типа объединение хранится только одно значение;

— длина объединения равна длине наибольшего поля;

— объединения применяются для экономии памяти, когда известно, что более одного поля одновременно в программе не потребуется.

Формат записи объединения аналогичен формату структур:

 

union [ имя_типа ]

{

тип_1 элемент_1 ;

тип_2 элемент_2 ;

тип_N элемент_N ;

} [ список_описателей ] ;

 

Пример:

/* Фрагмент программы кассового аппарата. Вариант 1. */

#include <iostream.h>

int main()

{ enum PayType {CARD, CASH} ;

PayType ptype;

union payment

{

char card[25] ;

long cash ;

} info ;

/* Подпрограмма присваивания значений

переменных info и ptype */

switch (ptype) { // Вывод информации об оплате на экран

case CARD : cout << ''Оплата по карте : ''

<< info.card ; break ;

case CASH : cout << ''Оплата наличными : ''

<< info.cash ; break ;

}

return 0;

}

 

 

Union часто используют в качестве поля в struct, как показано в следующем примере.

 

Пример:

/* Фрагмент программы кассового аппарата. Вариант 2. */

#include <iostream.h>

int main()

{ enum PayType {CARD, CASH} ;

struct {

PayType ptype ;

union { char card[25]; long cash; }

} info;

/* Подпрограмма присваивания значений переменных info */

switch (info.ptype) { // Вывод информации об оплате на экран

case CARD : cout << ''Оплата по карте : ''

<< info.card ; break ;

case CASH : cout << ''Оплата наличными : ''

<< info.cash ; break ;

}

return 0;

}

 

 

Примечания:

Существует несколько ограничений:

— объединение может инициализироваться только значением его первого элемента;

— объединение не может содержать битовые поля, но может содержать структуры битовых полей (см. пример).

 

 

Пример:

struct DialogOptions

{

bool centerX : 1;

bool centerY : 1;

unsigned int shadow : 2 ;

unsigned int palette : 4 ;

}

union {

unsigned char ch ;

DialogOptions bit ;

} options = {0xC4} ;

cout<<options.bit.palette ;

options.ch &= 0xF0 ; // <– – наложение маски

 


 

5. Программирование графических изображений
в языке С

5.1. Лекция 14. Программирование графических изображений

5.1.1. Графический режим видеоадаптера

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

Программа, работающая в графическом режиме, использует графические драйверы — файлы, которые обеспечивают взаимодействие с видеоадаптером. Для разных типов видеоадаптеров используют различные графические драйверы. Основными характеристиками видеоадаптера являются разрешающая способность и количество цветов. Разрешающая способность задается парой чисел, первое из которых показывает количество пикселей в одной строке, а второе — число строк. Современные дисплейные адаптеры принадлежат к классу SVGA (Super Video Graphics Array). Такие адаптеры характеризуются разрешающей способностью более 640´480 пикселей и позволяют использовать не менее 256 цветов. Для работы с SVGA адаптерами используются драйверы svga256.bgi и egavga.bgi.

Графическая система языка Borland C состоит их обширной библиотеки GRAPHICS.LIB. Компилятор должен иметь доступ к этой библиотеке, иначе компиляция программы, содержащей вызовы функций графической библиотеки, будет заканчиваться выдачей множественных сообщений об ошибках. Для этого необходимо подключить графическую библиотеку:

1) В меню оболочки выбирать Options>Linker>Libraries…

2) На открывшейся вкладке Libraries в поле Libraries напротив строчки Graphic library поставить отметку выбора (курсором «мыши» или клавишей «пробел»).

3) На вкладке Libraries нажать кнопку-надпись «Ок».

При создании программы, обращающейся к функциям графической библиотеки, также необходимо подключить заголовочный файл <graphics.h>.

Графическая система содержит внутреннюю переменную, предназначенную для хранения кода завершения графических функций. В случае ошибки код завершения функции является отрицательным и определяется константой перечислимого типа graphics_errors. Текущее значение переменной возвращает функция graphresult. Прежде чем обращаться в прикладной программе к функциям графической библиотеки, необходимо выбрать графический драйвер, соответствующий установленному на персональном компьютере видеоадаптеру. Для выбора драйвера можно воспользоваться функцией detectgraph, которая тестирует аппаратуру видеоадаптера и автоматически выбирает необходимый драйвер и графический режим:

void detectgraph ( int far *graphdriver, int far *graphmode);

Эта функция через свои аргументы возвращает номер графического драйвера, обслуживающего обнаруженный ею дисплейный адаптер, и номер графического режима, обеспечивающего максимальное для данного адаптера разрешение. Возвращенные функцией detectgraph значения в дальнейшем могут быть переданы функции инициализации графической системы initgraph. Функция initgraph ищет на диске bgi-файл, содержащий требуемый драйвер, загружает и инициализирует соответствующий драйвер, переводит систему в графический режим и передает контроль вызванной программе. Прототип этой функции

void initgraph(int far *driver, int far *mode, char far *pathtodriver);

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

Если по указателю *driver перед вызовом было записано значение DETECT (или 0), то сначала запускается процедура автоматического тестирования аппаратуры видеоадаптера. В этом случае нет необходимости вызывать функцию detectgraph. Всю работу, связанную с переходом в графический режим, выполнит функция initgraph.

Ниже приведен фрагмент кода, позволяющий перевести систему в графический режим.

Пример:

#include <graphics.h> //подключение библиотеки графических

//средств

main()

{

int Driver, Mode;

Driver=DETECT;

initgraph(&Driver, &Mode, '' '');

closegraph();

return 0;

}

Здесь функция initgraph вызывается в режиме автоматического тестирования аппаратуры. Поиск файла с драйвером выполняется в текущей директории.

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

Используя функции restorecrtmode и setgraphmode, можно осуществлять переход в текстовый режим и обратно. При этом графический драйвер и графический буфер сохраняются в оперативной памяти. Синтаксис функций:

void far restorecrtmode(void);

void far setgraphmode(int mode);

Восстановить прежний графический режим можно, если запомнить его номер. Это можно сделать с помощью функции getgraphmode:

int far getgraphmode(void);

Эта функция возвращает текущее значение номер графического режима для активизированного драйвера.

5.1.2. Функции управления графическим окном

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

Смена графического окна может быть выполнена с помощью функции setviewport:

void far setviewport(int left, int top, int right, int bottom, int clip);

Её аргументами являются координаты окна, а также логическое выражение (clip), определяющее требуется ли отсекать части изображения, выходящие за пределы окна или нет. Все графические операции касаются текущего графического окна, а координаты графического курсора всегда отсчитываются от верхнего левого угла экрана. Графический курсор невидим, но его текущие координаты можно определить с помощью функций getx и gety. Переместить курсор в требуемую позицию можно с помощью функций:

void far moveto(int x, int y);

void far moverel(int dx, int dy);

Первая перемещает курсор в позицию. Заданную координатами x и y. Вторая перемещает курсор относительно текущей позиции на вектор ( dx , dy ). Для очистки графического окна используется функция:

void far clearviewport(void);

При построении графических образов необходимо помнить, что на экране дисплея содержится разное количество пикселей по вертикали и горизонтали, а также то, что вертикальный и горизонтальный размеры пикселя неодинаковы. Это приводит к тому, что горизонтальный и вертикальный отрезки, содержащие одинаковое количество пикселей, на экране будут выглядеть как отрезки разной длины. Для правильного отображения требуется знать коэффициент пропорциональности (aspect ratio). Его можно определить с помощью функции:

void far getaspectration(int far *xasp, int far *yasp);

Эта функция через свои аргументы возвращает искомое значение, причем *yasp всегда устанавливается равным 10000, величина *xasp<=*yasp. Отношение *xasp к *yasp и даёт коэффициент пропорциональности.

5.1.3. Управление цветом и стилем заполнения фигур

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

Цвета нумеруются от 0 до getmaxcolor(). Выбор номера цвета для изображения обеспечивает функция setcolor(int color). Цвет фона может быть изменен функцией setbkcolor(int color). После выполнения функции setcolor(n) все объекты изображаются в цвете, связанном с позицией n палитры цветов. Числа от 0 до 15, которые используются для обозначения цветов, определяют цветовые атрибуты пикселя или, как их ещё называют, «программные цвета». Каждому программному цвету присваивается аппаратный цвет из полной палитры в 256 цветов.

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

 

enum COLORS{BLACK, BLUE, GREEN, RED, MAGENTA, BROWN, LIGHTGRAY, DARKGRAY, LIGHTBLUE, LIGHTGREEN, LIGHTCYAN, LIGHTRED, LIGHTMAGENTA, YELLOW, WHITE};

 

Для управления соответствием между программными и аппаратными цветами используется ряд функций. Например, вызов setpalette(0,RED) присваивает первому цвету палитры красный цвет.

Для закрашивания геометрических фигур используются функции, заполняющие контур в соответствующем стиле:

setfillstyle(int pattern, int color);

floodfill(int x, int y,int border);

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

Например:

EMPTY_FILL — заливка цветом фона;

SOLID_FILL — заливка заданным цветом;

LINE_FILL — штриховка горизонтальными линиями;

LTSLASH_FILL — диагональная штриховка и др.

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

 

 

5.1.4. Рисование простейших графических фигур

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

Ниже представлены функции графических примитивов. Они имеют тип void far:

line(int x1, int y1, int x2, int y2) — изображение отрезка прямой;

linerel(int dx, int dy) — изображение отрезка прямой относительно текущей позиции ;

lineto(int x, int y) — изображение отрезка прямой из текущей точки в точку с заданными координатами ;

circle(int x, int y, int radius) — изображение окружности ;

arc(int x, int y, int stangle, int endangle, int radius) — изображение дуги радиуса radius с координатами (x , y) от угла stangle до endangle ;

ellipse(int x, int y, int stangle, int endangle, int xradius, int yradius) — изображение эллиптической дуги с аналогичными параметрами ;

rectangle(int x1, int y1, int x2, int y2) — изображение прямоугольника ;

rawpoly(int numpoints, int far *polypoints) — изображение ломанной, заданной набором координат некоторого множества точек .

В последней функции параметр numpoint — это количество точек ломанной. Параметр *polypoints указывает на массив, каждый элемент которого есть структура из двух полей, которые интерпретируются как координаты (x , y) очередной точки. Функцию drawpoly часто используют для вывода графиков функций, заданных таблично.

Доступ к отдельным пикселям активной страницы обеспечивают две функции:

unsigned far getpixel (int x, int y) ;

void far putpixel (int x, int y, int color) ;

Первая функция возвращает номер цвета пикселя с координатами (x , y). Вторая выводит пиксель с координатами (x , y) цветом color.

Стилем и толщиной линий можно управлять с помощью функции

setlinestyle (int linestyle, unsigned upattern, int thickness) ;

Аргумент linestyle устанавливает стиль рисования линий, upattern — определяет пользовательский шаблон, а thickness — толщину линий. Доступные значения для толщины линий: NORM_WIDTH (нормальная) и THICK_WIDTH (толстая). При этом аргумент thickness воздействует на все контурные графические примитивы, а аргументы linestyle и upattern — только на кусочно-линейные. Стиль рисования линий задается константами перечислимого типа:

enum line_styles

{

SOLID_LINE =0, // сплошная линия

DOTTED_LINE =1, // точечная

CENTER_LINE =2, // штрихпунктирная

DASHED_LINE =3, // пунктирная

USER_BIN =4 // пользовательский стиль

}

При выборе значений указанных констант от 0 до 3 параметр upattern в функции setlinestyle игнорируется. Шаблон upattern используется, если значение linestyle равно 4. Он представляет собой 16-битовое слово. Если бит шаблона равен 1, то соответствующий пиксель шаблона рисуется, в противном случае — нет. Так, пунктирная линия задается шаблоном 0x0F0F или 0x3333.

5.1.5. Отображение текстовой информации в графическом режиме

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

settextstyle (int font, int direction, int charsize) ;

Аргумент direction задает направление вывода текста (HORIZ_DIR, VERT_DIR), аргумент charsize — размер символов (от 1 до 10). Вид шрифта определяется значением аргумента font (от 0 до 4). Все шрифты, кроме шрифта DEFAULT_FONT=0, являются векторными. Т.е. их элементы формируются как совокупность векторов, которые характеризуются направлением и длиной.

Расположением выводимой строки текста относительно опорной точки управляет функция

settextjustify(int horiz, int vert) ;

Её аргументы принимают значения перечислимого типа:

enum text_just

{

LEFT_TEXT =0, // расположить слева

CENTER_TEXT, // расположить по центру

RIGHT_TEXT, // расположить справа

BOTTOM_TEXT // расположить внизу

TOP_TEXT // расположить вверху

}

Вывод текста выполняется фукциями

outtext (char far *textstring) ;

outtextxy (int x, int y, char far *textstring) ;

Обе функции в качестве аргумента получают указатель textstring на выводимую строку символов. За одно обращение к функции выводится одна строка. Первая функция выводит текст в текущую графическую позицию, вторая — в точку, заданную с помощью аргументов x и y.

 

 

5.1.6. Построение графиков функций

График функции строят по точкам . Для этого циклически задают значение абсциссы и вычисляют соответствующие ординаты . Затем отрезками соединяют точки с точками .

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

При построении графиков функций приведенные формулы используют в следующем порядке:

— циклически изменяют значение x_экрана от 0 до значения, равного максимальному числу пикселей в строке;

— для заданного значения x_экрана вычисляют математическую координату x по формуле

— вычисляют y_экрана по указанной выше формуле и получают экранную точку с координатами (x_экрана , y_экрана);

— полученные экранные точки соединяют отрезками и получают график функции.

5.1.7. Преобразование координат и анимационные эффекты








Дата добавления: 2015-10-21; просмотров: 663;


Поиск по сайту:

При помощи поиска вы сможете найти нужную вам информацию.

Поделитесь с друзьями:

Если вам перенёс пользу информационный материал, или помог в учебе – поделитесь этим сайтом с друзьями и знакомыми.
helpiks.org - Хелпикс.Орг - 2014-2024 год. Материал сайта представляется для ознакомительного и учебного использования. | Поддержка
Генерация страницы за: 0.109 сек.