Студопедия.Орг Главная | Случайная страница | Контакты | Мы поможем в написании вашей работы!  
 

Оператор defined



Оператор препроцесора defined може використовуватися в спеціальних константних виразах. Синтаксис таких виразів один з двох:

defined(ідентифікатор)

defined ідентифікатор

Значення такого виразу дорівнює true (не нуль), якщо вказаний ідентифікатор оголошений в програмі за допомогою директиви #define, та false (0) – інакше. Ідентифікатор, якому приписаний порожній рядок (тобто оголошений без літерала або макроозначення), вважається також оголошеним.

Оператор defined може використовуватися тільки в директивах умовної компіляції #if та #elif.

В наступному прикладі забезпечується включення в програму тільки одного з трьох викликів функцій:

#if defined(CREDIT)

credit();

#elif defined(DEBIT)

debit();

#else

printerror();

#endif

6.4.2 Використання директив умовної компіляції

Директива препроцесора умовної компіляції #if багато в чому схожа на інструкцію if. Розглянемонаступний фрагмент програми:

#if!defined(NULL)

#define NULL 0

#endif

Перша з цих трьох директив визначає, чи не був оголошений раніше іменований літерал NULL. Вираз defined(NULL) дає значення 1, якщо літерал NULL оголошений, і 0 - інакше. Якщо результат рівний 0, то вираз!defined(NULL)дає значення 1 і в наступному рядку проводиться оголошення іменованого літерала NULL.Інакше директива #define пропускається. Кожен блок тексту з директивою #if має закінчуватися своєю директивою #endif.

Директиви:

#ifdef ідентифікатор

#ifndef ідентифікатор

є скороченим записом директив:

#if defined(ідентифікатор)

#if!defined(ідентифікатор)

і тому не потребують додаткових коментарів.

Можна використовувати складні конструкції умовних директив препроцесора, включаючи в ці конструкції директиви #else (еквівалент else в конструкції звичайної інструкції if) та #elif (еквівалент else if в конструкції звичайної інструкції if).

При розробці програми часто виявляється зручним тимчасово коментувати великі фрагменти програми і не компілювати їх. Якщо в коді вже використовуються коментарі в стилі C, то знаки багаторядкових коментарів /* та */ не допоможуть вирішити ці проблеми. В такому разі можна використовувати в програмі наступну конструкцію директив препроцесора:

#if 0

Фрагмент коду, який не потрібно компілювати

#endif

Для того, щоб цей фрагмент включити у процес компіляції, достатньо замінити 0 в приведеній конструкції на 1.

Умовна компіляція зазвичай використовується як засіб налагодження. Практично всі системи програмування на C++ включають той або інший налагоджувач програм. Проте розробнику спочатку потрібно вивчити цей налагоджувач та навчитися його використовувати, що часто викликає труднощі у програмістів - початківців.

Замість налагоджувача можна використовувати інструкції виводу значень змінних, що дозволить контролювати процес виконання програми. Ці інструкції обрамляються умовними директивами препроцесора і компілюються до тих пір, поки процес налагодження програми не буде завершений.

Наприклад, в наступному фрагменті:

#ifdef DEBUG

cerr << "Змінна х = " << х << endl;

#endif

інструкція виводу в потік cerr компілюватиметься тільки у випадку, якщо ідентифікатор DEBUG був оголошений (директивою #define DEBUG)до директиви #ifdef DEBUG.Після завершення процесу налагодження директива #define DEBUG може бути просто видалена з початкового файлу, і інструкції виводу, потрібні тільки для цілей налагодження, ігноруватимуться під час компіляції. У великих програмах можливо потрібно буде оголошувати декілька різних ідентифікаторів, які можуть керувати умовною компіляцією різних частин початкового файлу.

6.5 Директива #error

Директива #error забезпечує видачу користувачу повідомлень під час компіляції, точніше, під час роботи препроцесора. Вона має наступний синтаксис:

#error текст_повідомлення

6.6 Директива #import

Директива #import використовується в системі програмування Visual C++ для включення в проект інформації з бібліотеки типів. При цьому вміст бібліотеки конвертується до класів C++, що представляють інтерфейси COM. Синтаксис використання директиви один з наступних:

#import “ім'я_файлу” атрибути

#import <ім'я_файлу> атрибути

6.7 Директива #line

Ця директива використовується для зміни нумерації рядків початкового тексту програми, яка ведеться всередині компілятора, і на які посилаються повідомлення про помилки. Синтаксис директиви наступний:

#line номер “ім'я_файлу”

Номер задає цілочисельне константне початкове значення номера рядка для нумерації наступних за директивою рядків початкового тексту програми. Наприклад, директива:

#line 100

задає початкове значення номера рядка, рівне 100, і все подальші рядки початкового тексту програми нумеруватимуться, починаючи з цього номера.

6.8 Директива #pragma

За допомогою директиви #pragma компілятор одержує вказівки (прагми|) по його роботі, пов'язані, можливо, з особливостями реалізації мови даним компілятором і використовуваною операційною системою. Синтаксис директиви наступний:

#pragma прагма|

Компілятор C++, що працює у складі Visual Studio.NET, підтримує наступні прагми| – вказівки по його роботі:

alloc_text

auto_inline

bss_seg

check_stack

code_seg

comment

component

conform

const_seg

data_seg

deprecated

function

hdrstop

include_alias

init_seg

inline_depth

in1ine_recursion

intrinsic

managed

message

once

optimize

pack

pointer_to_members

pop_macro

push_macro

runtime_checks

section

setlocale

unmanaged

vtordisp

warning

6.9 Директива #using

Ця директива імпортує в програму метадані, необхідні для роботи керованих розширень мови C++. Формат використання директиви:

#using <файл>

Файл, метадані якого імпортуються, має бути типу.dll,.exe,.netmodule або.obj.

Наприклад:

#using <mscorlib.dll>

6.10 Програмування на Асемблері

За допомогою інструкцій на машино – орієнтованій мові Асемблера можна збільшити швидкість виконання програми, особливо її циклічних ділянок, зменшити об'єм оперативної пам'яті, необхідної для роботи програми, і, нарешті, запрограмувати роботу спеціальних пристроїв вводу – виводу.

Для компіляції асемблерних інструкцій в системі програмування Visual C++ немає необхідності використовувати окрему програму – асемблер, таку як MASM (Microsoft Macro Assembler). Натомість потрібно скористатися асемблером, вбудованим у компілятор Visual C++. При цьому виключаються додаткові етапи асемблювання і компоновки роздільно компільованих програмних одиниць.

Компілятор Visual C++ забезпечує трансляцію всіх асемблерних інструкцій, зокрема тих, що містять всі коди операцій процесорів Pentium 4 і AMD Athlon. Слід при цьому відмітити, що асемблерні програми не завжди правильно працюватимуть на комп'ютерах з іншою архітектурою.

6.10.1 Інструкція __asm

Інструкція мови C++, яка починається з ключового слова __asm на початку рядка і включає в роботу вбудований асемблер, може використовуватися в програмі скрізь, де можуть кодуватися будь-які інші інструкції мови C++.

За ключовим словом __asm повинна слідувати інструкція асемблера або список інструкцій асемблера, взятих у фігурні дужки:

__asm інструкція_асемблера;

__asm

{

інструкція_асемблера1

інструкція_асемблера2

інструкція_асемблераN

};

Список інструкцій у фігурних дужках може бути порожнім. Рядок з однією асемблерною інструкцією або ж блок асемблерних інструкцій може завершуватися крапкою з комою, хоча це і необов'язково.

Замість ключового слова __asm з двома символами підкреслення може використовуватися його варіант з одним символом підкреслення - _asm. Ключове слово asm без символів підкреслення, вживане для цих же цілей відповідно до стандарту на мову C++, може використовуватися в програмах на Visual C++, але воно не призводить до трансляції асемблерних інструкцій.

Приклад блоку асемблерних інструкцій в програмі на мові C++:

__asm

{

mov al, 2

mov dx, 0xD007

out dx, al

}

Цей блок може бути замінений послідовністю інструкцій __asm:

__asm mov al, 2

__asm mov dx, 0xD007

__asm out dx, al

Оскільки ключове слово __asm є роздільником інструкцій мови C++, то останній список інструкцій можна помістити в один рядок:

__asm mov al, 2 __asm mov dx, 0xD007 __asm out dx, al

Всі три варіанти інструкцій генерують один і той же двійковий код в програмі.

7 ОБ'ЄКТНО-ОРІЄНТОВАНЕ ПРОГРАМУВАННЯ

Об'єктно-орієнтоване програмування (ООП|) – це відносно новий, але вже перевірений десятирічною практикою підхід до створення програм. В процесі розвитку обчислювальної техніки виникали різні методики програмування: структурне програмування, процедурне програмування і, нарешті, попередник ООП – це модульне програмування.

Об'єктно-орієнтоване програмування в мові C++ є розвитком двох концепцій: концепції модульного програмування, як воно з'явилося в більш ранніх мовах програмування, таких як C, та концепції типів даних користувача, представлених в тій же мові C структурами, що об'єднують в собі різнотипні складові елементи.

Так само як і при модульному програмуванні, при об'єктно-орієнтованому програмуванні дані та функції, що їх оброблюють, об'єднуються разом, утворюючи клас. Але на відміну від програмного модуля клас не виконує ніяких реальних дій ні самостійно, ні за запитом від інших частин програми.

Клас є штампом, за допомогою якого виробляються однотипні функціональні модулі програми, в сукупності звані об'єктами. Дані та функції кожного об'єкту тісно зв'язані один з одним. Саме об'єкти виконують всю реальну роботу при виконанні об'єктно-орієнтованих програм.

Об'єкти володіють властивістю приховування інформації.Це означає, що хоча об'єкти можуть знати, як зв'язуватися один з одним за допомогою добре визначеного інтерфейсу, їм зазвичай не дозволено знати, як реалізуються інші об'єкти — деталі реалізації заховані всередині самих об'єктів. Поза сумнівом, можна їздити на автомобілі, не знаючи технічних деталей його внутрішнього функціонування — трансмісії, вихлопної труби та ін.

При процедурно-орієнтованому програмуванні орієнтуються на дії, тоді як при використанні класів наголос робиться на властивості, поведінку та взаємозв'язки об'єктів, тобто програмування стає об'єктно-орієнтованим.

7.1 Класи. Інкапсуляція

Як було відзначено вище, класи в мові C++ введені як природне продовження і розвиток стандартних структур struct мови C, які є типами користувача.

Тому при оголошенні класу створюється новий тип даних, який можна використовувати подібно до вбудованого типу. Проте, на відміну від вбудованих типів, класи містять як дані, так і функції. Клас дозволяє інкапсулювати всі функції та дані, необхідні для управління приватними компонентами програми (наприклад, вікном на екрані; малюнком, побудованим за допомогою графічної програми; пристроєм, підключеним до комп'ютера; завданням, виконуваним операційною системою).

Клас оголошується за допомогою ключового слова class. Синтаксис оголошення класу наступний:

class class_name

{

// закриті_члени_класу

public:

// відкриті_члени_класу

};

Клас мови C++ дуже схожий на стандартну структуру, хоча засоби, що надаються класами C++, значно перевершують можливості структур.

Структури, як вони були описані в розділі 2, дозволяють згрупувати в одне ціле набір зв'язаних змінних - членів. Наприклад, якщо в програмі маємо прямокутник, зручно зберігати його координати у вигляді структури, оголошеної таким чином:

struct Frame

{

int Left;

int Top;

int Right;

int Bottom;

};

Далі можна визначити функцію малювання прямокутника:

void DrawFrame(Frame *Frm)

{

Line (Frm->Left, Frm->Top, Frm->Right, Frm->Top);

Line (Frm->Right, Frm->Top, Frm->Right, Frm->Bottom);

Line (Frm->Right, Frm->Bottom, Frm->Left, Frm->Bottom);

Line (Frm->Left, Frm->Bottom, Frm->Left, Frm->Top);

}

В даному прикладі Line() - гіпотетична функція, яка дозволяє малювати лінію від точки, заданої першими двома координатами, до точки, заданої другими двома координатами. Така функція може бути визначена де-небудь в програмі або викликана з бібліотеки функцій.

Нарешті, щоб намалювати прямокутник в заданому місці екрану, потрібно оголосити та ініціалізувати змінну типу Frame, а потім передати її у функцію малювання прямокутника DrawFrame:

Frame Frm = {25, 25, 100, 100};

DrawFrame (&Frm);

Клас мови C++, на відміну від структури мови C, визначає не тільки сімейство компонентів даних, але й функції, що працюють з цими даними. У C++ можна сумістити координати прямокутника та функції малювання прямокутника всередині єдиного оголошення класу, як показано в наступному прикладі:

class CFrame

{

int Left;

int Top;

int Right;

int Bottom;

void Draw ()

{

Line (Left, Top, Right, Top);

Line (Right, Top, Right, Bottom);

Line (Right, Bottom, Left, Bottom);

Line (Left, Bottom, Left, Top);

}

};

Компоненти даних, оголошені всередині класу, називаються змінними - членами класу (іноді їх називають також полями даних або властивостями класу). Функції, оголошені або визначені всередині класу, називаються функціями - членами або методами класу.

В приведеному прикладі змінними – членами класу є цілі змінні Left, Top, Right і Bottom, а функцією – членом класу є функціяDraw().

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

7.1.1 Створення об'єктів класу

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

Створити об'єкт класу можна, оголошуючи його подібно до того, як оголошується змінна вбудованого типу. Як альтернативний варіант можна використовувати оператори new та delete для динамічного створення та видалення об'єктів.

У програмі наступне оголошення структури передає компілятору повідомлення про її форму, але не резервує місце в пам'яті і не створює змінну, яку можна використати для зберігання даних:

struct Frame

{

int Left;

int Top;

int Right;

int Bottom;

};

Щоб зарезервувати пам'ять і створити структурну змінну, потрібно зробити оголошення:

Frame Frm;

Як було описано, аналогічно структурі оголошується клас, наприклад, CFrame, оголошений раніше. При цьому компілятору надається проект класу, але насправді місце в пам'яті не резервується. Як і структурна змінна, змінна класу повинна оголошуватися інструкцією виду:

CFrame Frm;

Це оголошення створює об'єкт класу CFrame, який також іноді називають екземпляром, представником або копією класу.

Об'єкт Frm класу CFrame займає власний блок пам'яті і може використовуватися для зберігання даних і виконання операцій над ними. Як і змінна вбудованого типу, об'єкт існує, поки потік управління не виходить за межі області видимості його оголошення. Наприклад, якщо об'єкт оголошений усередині функції, то знищується при виході з неї.

Точно так, як і для структур мови C, в початковому файлі оголошення класу має передувати оголошенню та використанню об'єктів класу.

Об'єкт класу можна створити, використовуючи наявний в мові C++ оператор new, наприклад:

CFrame *PFrm = new CFrame;

Ця інструкція виділяє блок пам'яті достатнього розміру, щоб розмістити в ньому об'єкт класу, повертає вказівник на даний блок, який потім заноситься у вказівник. Об'єкт залишатиметься в пам'яті, поки не буде явно звільнений за допомогою оператора delete:

delete Frame;

Можна створити довільне число об'єктів кожного класу.

7.1.2 Доступ до членів класу

Доступ до членів класу організується операторами "." або "->", подібно доступу до елементів структури мови С. Доступом до членів класу можна керувати, використовуючи специфікатори доступу public (відкриті) або private (закриті). При цьому треба пам'ятати, що на відміну від структур змінні класу закриті за умовчанням.

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

Перш ніж йти далі, слід зазначити, що за наявності раніше приведеного оголошення класу Frame програма не зможе звернутися ні до одного з його членів, оскільки за умовчанням всі змінні та функції, що належать класу, є закритими (private). Це означає, що вони можуть використовуватися тільки всередині функцій-членів самого класу. Так, для функції Draw() дозволений доступ до змінних-членів Top, Left, Right і Bottom, тому що Draw() — функція-член класу. Для інших частин програми, таких як функція main(), доступ до змінних-членів або виклик функції-члена Draw() заборонений.

Специфікатор доступу public використовується, щоб створити відкритий член класу (іноді званий загальнодоступним або публічним), доступний для використання всіма функціями програми (як усередині класу, так і за його межами). Наприклад, в наступному варіанті оголошення класу Frame всі члени класу є відкритими:

class CFrame

(

public:

int Left;

int Top;

int Right;

int Bottom;

void Draw ()

{

Line (Left, Top, Right, Top);

Line (Right, Top, Right, Bottom);

Line (Right, Bottom, Left, Bottom);

Line (Left, Bottom, Left, Top);

}

};

Специфікатор доступу застосовується до всіх членів, розташованих після нього в оголошенні класу, і діє до тих пір, поки не зустрінеться інший специфікатор доступу або закінчиться оголошення класу.

Тепер, коли всі члени класу CFrame відкриті, доступ до них, як і доступ до полів структури в мові C, можливий з використанням оператора "."

Наприклад:

CFrame Frm; // оголошення об'єкту класу CFrame

Frm.Left = 5; // присвоювання значень змінним-членам

Frm.Top =10; // для визначення координат прямокутника

Frm.Right = 100;

Frm.Bottom = 150;

Frm.Draw(); // малювання прямокутника

З іншого боку, можна створити об'єкт класу оператором new, а потім використати вказівник на об'єкт для доступу до змінних-членів класу. Для доступу до членів класу через вказівник, так само як і для структур, використовується оператор "->":

CFrame *PFrm = new CFrame;

PFrm->Left = 5;

PFrm->Top = 10;

PFrm->Right = 100;

PFrm->Bottom = 150;

PFrm->Draw();

7.1.3 Інкапсуляція

Згідно принципу інкапсуляції внутрішнє представлення даних, використане в реалізації класу, не повинно бути доступне користувачу класу безпосередньо.

Відповідно до цього принципу специфікатори доступу в мові C++ необхідно використовувати для запобігання безпосередньому доступу користувача до змінних-членів усередині класу. Можна створити функцію-член |, що змінює змінні-члени, але тільки після перевірки правильності значення, заданого користувачем.

Знову ж таки, наша поточна версія класу CFrame очевидно порушує цей принцип, оскільки користувач може безпосередньо читати або модифікувати будь-які змінні-члени класу.

З метою дотримання принципу інкапсуляції клас CFrame повинен бути оголошений так, щоб мати доступ до функцій-членів (у нашому випадку до Draw()), але не мати доступу до внутрішніх змінних-членів, використовуваних цими функціями (Left, Top, Right і Bottom):

class CFrame

(

private:

int Left;

int Top;

int Right;

int Bottom;

public:

void Draw ()

{

Line (Left, Top, Right, Top);

Line (Right, Top, Right, Bottom);

Line (Right, Bottom, Left, Bottom);

Line (Left, Bottom, Left, Top);

}

};

Специфікатор доступу private робить змінні, оголошені пізніше, закритими. Таким чином, вони доступні тільки функціям-членам класу. Подібно специфікатору доступу public, розглянутому раніше, специфікатор private впливає на всі оголошення, що стоять після нього, поки не зустрінеться інший специфікатор або кінець оголошення.

Отже, нове оголошення робить змінні Left, Top, Right і Bottom закритими, а функцію Draw() - відкритою.

Насправді не потрібно поміщати специфікатор private на початку оголошення класу, тому що члени класу за умовчанням є закритими. Проте включення специфікатора private полегшує читання програми.

Тут слід відмітити, що мова C++ надає ще й третій специфікатор доступу - protected. Цей специфікатор буде розглянутий пізніше, оскільки вимагає розуміння спадкування класів.

Наступний фрагмент програми ілюструє як коректне, так і некоректне звернення до членів чергового варіанту класу CFrame:

void main()

{

CFrame Frm; // оголошення об'єкту CFrame

Frm.Left = 5; // помилка: немає доступу до закритого члена

Frm.Top = 10; // помилка: немає доступу до закритого члена

Frm.Right = 100; // помилка: немає доступу до закритого члена

Frm.Bottom = 150; // помилка: немає доступу до закритого члена

Frm.Draw (); // допускається, але координати прямокутника

// не визначені

}

Тепер, коли користувачу класу заборонений прямий доступ до змінних-членів, клас повинен надати альтернативний засіб запису координат перед створенням прямокутника. Хороший спосіб для цього – включення в класс відкритої функції-члена, що приймає необхідні значення координат і використовує ці значення для установки змінних-членів. Наприклад:

void SetCoord (int L, int T, int R, int B)

{

L = __min (__max (0, L), 80);

T = __min (__max (0, T), 25);

R = __min (__max (0, R), 80);

B = __min (__max (0, B), 25);

R = __max (R, L);

B = __max (B,T);

Left = L; Top = T; Right = R; Bottom = B;

}

Ця функціядодається в розділ public оголошення класу СFrame. Тому її можна викликати з будь-якої функції програми.

Потрібно звернути увагу, що при необхідності, перед присвоюванням аргументів змінним класу, функціяSetCoord() організовує перевірку приналежності аргументів діапазону коректних значень і стежить, щоб права координата була більша за ліву, а нижня| — більша за верхню.

Макроси __max та __min надаються стандартною бібліотекою мови C++. Для їх використання в програму потрібно включити файл заголовків stdlib.h.

Тепер клас CFrame можна використовувати для створення прямокутників:

void main ()

{

CFrame Frm;

Frm.SetCoord (25, 25, 100, 100); // установка координат

// прямокутника

Frm.Draw (); // малювання прямокутника

}

Може виявитися необхідним додати в наш клас функцію-член|, що дозволяє іншим частинам програми читати поточні значення координат прямокутника. Ось приклад такої функції:

void GetCoord (int *L, int *T, int *R, int *B)

{

*L = Left;

*T = Top;

*R = Right;

*B = Bottom;

}

Ця функція, так само як і функція SetCoord(), повинна бути додана в розділ public оголошення класу.

Нижче приведено остаточне оголошення класу СFrame, що містить нові функції-члени SetCoord() і GetCoord():

#include <сstdlib>

class СFrame

{

private:

int Left;

int Top;

int Right;

int Bottom;

public:

void Draw ()

{

Line (Left, Top, Right, Top);

Line (Right, Top, Right, Bottom);

Line (Right, Bottom, Left, Bottom);

Line (Left, Bottom, Left, Top);

}

void GetCoord (int *L, int *T, int *R, int *B)

{

*L = Left;

*T = Top;

*R = Right;

*B = Bottom;

}

void SetCoord (int L, int T, int R, int B)

{

L = __min (__max (0, L), 80);

T = __min (__max (0, T), 25);

R = __min (__max (0, R), 80);

В = __min (__max (0, B), 25);

R = __max (R, L);

В = __max (B,T);

Left = L; Top = T; Right = R; Bottom = B;

}

};

Тепер за допомогою функцій-членів SetCoord() та GetCoord() клас CFrame згідно принципу інкапсуляції надає доступ до закритих змінних тільки за допомогою чітко визначеного інтерфейсу, контролюючого коректність нових значень, що присвоюються, і, при необхідності, коректуючого ці значення.

7.1.3.1 Переваги інкапсуляції

Очевидна перевага інкапсуляції полягає в тому, що вона змушує розробника класу перевіряти правильність будь-яких значень, що присвоюються змінним-членам, і тим самим запобігати помилкам програмування.

Інша перевага управління доступом до внутрішнього представлення даних полягає в тому, що автор класу може вільно змінювати спосіб представлення цих даних, не змінюючи інші частини програми, що використовують клас, до тих пір, поки зберігається інтерфейс викликів загальнодоступних функцій-членів. Для демонстрації цієї переваги наведемо простий приклад.

Припустимо, що автор класу CFrame вирішив зберігати координати лівого верхнього кута прямокутника разом з його шириною та висотою замість координат правого нижнього кута. В цьому випадку змінні-члени могли б бути оголошені таким чином:

private:

int Left;

int Top;

int Width;

int Height;

Поки інтерфейс виклику функцій SetCoord() і GetCoord() залишається тим самим, внутрішні зміни представлення даних класу CFrame не впливають на інші частини програми або будь-які інші програми, що використовують цей клас. Природно, реалізацію наших двох функцій-членів потрібно змінити так, щоб виконувати перетворення між значеннями координат та значеннями ширини й висоти.

Інкапсуляція виключає залежність користувача класу від специфіки внутрішнього представлення даних класу.





Дата публикования: 2014-11-03; Прочитано: 802 | Нарушение авторского права страницы | Мы поможем в написании вашей работы!



studopedia.org - Студопедия.Орг - 2014-2024 год. Студопедия не является автором материалов, которые размещены. Но предоставляет возможность бесплатного использования (0.043 с)...