Контроль стану вводу - виводу

В системі вводу - виводу C++ підтримується інформація про стан після кожної операції вводу - виводу. Поточний стан потоку вводу - виводу, який зберігається в об'єкті типу iostate, є переліком, визначеним в класі ios і містить наступні значення:

goodbit – помилок немає;

eofbit – досягнутий кінець файлу;

failbit – не фатальна помилка;

badbit – фатальна помилка.

Є два способи отримання інформації про стан вводу - виводу. По-перше, можна викликати функцію rdstate(), що є членом класу ios. Прототип цієї функції:

 

iostate rdstate();

 

Функція повертає поточний стан прапорів помилки. Функція rdstate() повертає прапор goodbit тільки за відсутності якої б то не було помилки. Інакше вона повертає прапор помилки.

Іншим способом визначення того, чи мала місце помилка, є використання однієї або декількох наступних функцій — членів класу ios:

 

bool bad();

bool eof();

bool fail();

bool good();

 

Робота функції eof() була описана раніше. Функція bad() повертає істину, якщо встановлений прапор badbit. Функція fail() повертає істину, якщо встановлений прапор failbit. Функція good() повертає істину за відсутності помилок. У протилежних випадках функції повертають неправду.

Після появи помилки може виникнути необхідність очистити цей стан перед тим, як продовжити виконання програми. Для цього використовується функціясlеаr(), що є членом класу ios. Нижче приведений прототип цієї функції:

 

void clear(iostate flags = ios::goodbit);

 

Якщо аргумент flags рівний goodbit (значення за умовчанням), то очищаються прапори всіх помилок. Інакше очищаються тільки вказані прапори.

В наступній програмі за допомогою функції good() у файлі перевіряється наявність помилки:

 

#include <iostream>

#include <fstream>

using namespace std;

int main(int argc, char *argv[])

{

char ch;

if(argc!=2)

{

cout << "Вміст: <ім'я_файлу>n";

return 1;

}

ifstream in(argv[1]);

if(!in)

{

cout << "Файл відкрити неможливо. n";

return 1;

}

while(!in.eof())

{

in.get(ch); // контроль помилки

if(!in.good() && !in.eof())

{

cout << "Помилка вводу - виводу. Робота припиняється. n";

return 1;

}

cout << ch;

}

in.close();

return 0;

}

9.8 Системи вводу – виводу користувача

Одним з доводів на користь використання операторів вводу - виводу C++ замість аналогічних їм функцій вводу - виводу C є можливість перевантаження операторів вводу - виводу для створюваних нових класів. Далі описуються правила перевантаження операторів << та >>, а також програмування маніпуляторів користувача.

9.8.1 Функції виводу користувача

В мові C++ вивід іноді називається вставкою (insertion), а оператор << — оператором вставки (insertion operator). Коли для виводу інформації перевантажується оператор <<, створюється функціявставки (inserter function або inserter). Раціональність цим термінам дає те, що оператор виводу вставляє (inserts) інформацію в потік.

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

 

ostream &operator<<(ostream &stream, class_name ob)

{

// інструкції тіла функції виводу користувача

return stream;

}

 

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

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

Функціявиводу користувача повертає посилання на потік stream, який має тип ostream. Це необхідно для того, щоб перевантажений оператор << міг використовуватися в ряді виразів вводу - виводу, що йдуть один за одним в одній інструкції:

 

cout << ob1 << ob2 << ob3;

 

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

Функціявиводу користувача не може бути членом класу, для роботи з яким вона створена, з наступної причини.

Якщо функція-оператор будь-якого типу є членом класу, то лівий операнд, який неявно передається через вказівник this, є об'єктом, від імені якого викликається функція-оператор. Це має на увазі, що лівий операнд є об'єктом цього класу. Тому, якщо перевантажена функція-оператор була б членом класу, тоді лівий операнд повинен бути об'єктом цього класу. Проте, коли створюється функція виводу користувача, лівий операнд повинен бути потоком, а не об'єктом класу, а правий операнд — об'єктом, який повинен виводитися. Саме тому функція виводу користувача не може бути функцією-членом класу.

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

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

 

// Використання дружньої функції виводу

// для об'єктів класу coord – координати точки

#include <iostream>

using namespace std;

// оголошення класу

class coord

{

int x, у; // координати точки

public:

coord(){x = 0; у = 0;} // конструктор за умовчанням

coord(int i, int j){x = i; у = j;}

friend ostream& operator<< (ostream &stream, coord ob);

};

 

// реалізація функції-оператора

ostream& operator<< (ostream &stream, coord ob)

{

stream << ob.x << ", " << ob.y << 'n';

return stream;

}

 

int main()

{

coord а(l, 1), b(10, 23);

cout << а << b;

return 0;

}

 

В результаті виконання програми на екран буде виведено наступне:

 

1, 1

10, 23

 

Функціявиводу користувача цієї програми ілюструє одну дуже важливу особливість: функції користувача потрібно розробляти якомога більш узагальненими.

В даному конкретному випадку інструкція вводу - виводу всередині функції вставляє значення х та у в потік stream, який і є переданим у функцію потоком.

9.8.2 Функції вводу користувача

За тими ж правилами, за якими перевантажується оператор виводу <<, можна перевантажувати і оператор вводу >>. В мові C++ оператор вводу >> іноді називають оператором вибирання (extraction operator), а функцію, що перевантажує цей оператор — функцією вибирання (extractor). Сенс цих термінів у тому, що при вводі інформації дані вибираються з потоку.

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

 

istream& operator>>(istream &stream, class_name &ob)

{

// інструкції тіла функції вводу користувача

return stream;

}

 

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

Так само, як і функціявиводу користувача, функція вводу користувача не може бути функцією-членом класу. Хоча всередині такої функції може бути виконана будь-яка робота, краще обмежити її роботу вводом інформації.

Як демонстрація того, як програмувати функції вводу користувача, нижче приводиться допрацьована версія класу coord:

 

// Використання дружньої функції вводу

// для об'єктів класу coord – координати точки

#include <iostream>

using namespace std;

// оголошення класу

class coord

{

int x, у; // координати точки

public:

coord() {x = 0; у = 0;} // конструктор за умовчанням

coord(int i, int j){x = i; у = j;}

friend ostream& operator<< (ostream &stream, coord ob);

friend istream& operator>>(istream &stream, coord &ob);

};

// реалізація функції-оператора виводу

ostream& operator<<(ostream &stream, coord ob)

{

stream << ob.x << ", " << ob.y << 'n';

return stream;

}

// реалізація функції-оператора вводу

istream& operator>>(istream &stream, coord &ob)

{

cout << “Введіть координати: ”;

stream >> ob.x >> ob.y;

return stream;

}

int mainO

{

coord а(l, 1), b(10, 23);

cout << а << b;

cin >> а;

cout << b;

return 0;

}

 

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

9.8.3 Маніпулятори користувача

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

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

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

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

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

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

Є декілька важливих відмінностей в способі створення маніпуляторів з аргументами і без них. Більш того, створення маніпуляторів з аргументами є істотно важчим завданням, чим створення маніпуляторів без аргументів. У зв'язку з цим обмежимося створенням останніх, тобто маніпуляторів без аргументів.

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

 

ostream& ім'я_маніпулятора(ostream &stream)

{

// інструкції програми маніпулятора

return stream;

}

 

Тут ім'я_маніпулятора — це ідентифікатор створюваного маніпулятора користувача, а &stream — посилання на потік виводу. Значенням, що повертає функція, також є посилання на потік виводу. Останнє необхідно у разі, коли маніпулятор має використовуватися у великих виразах вводу - виводу.

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

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

 

istream& ім'я_маніпулятора(istream &stream)

{

// інструкції програми маніпулятора

return stream;

}

 

Маніпулятор вводу одержує як аргумент посилання на потік, для якого він викликається. Маніпулятор обов’язково має повертати посилання на цей же потік.

Маніпулятори користувача не обов'язково мають бути складними. Наприклад, запрограмовані нижче прості маніпулятори atn() та note() забезпечують простий і зручний спосіб виводу слів і фраз, що часто зустрічаються:

 

#include <iostream>

using namespace std;

// Маніпулятор “ Увага:”

ostream& atn(ostream &stream)

{

stream << "Увага: ";

return stream;

}

 

// Маніпулятор “Будь ласка, не забудьте:”

ostream& note(ostream &stream)

{

stream << "Будь ласка, не забудьте: ";

return stream;

}

 

int main()

{

cout << atn << "Висока напруга n";

cout << note << "Вимкнути світло n";

return 0;

}

 

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

9.8.4 Ввід – вивід користувача і файли

Оскільки всі потоки C++ однакові, то, наприклад, однаково перевантажена функціявиводу може використовуватись без яких-небудь змін для виводу як на екран, так і в файл. Це одна з найбільш важливих і корисних можливостей вводу - виводу в C++.

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

10 КЕРОВАНА МОВА C++ ТА ІНШІ МОВИ ПРОГРАМУВАННЯ ПЛАТФОРМИ MICROSOFT .NET

В системі програмування Microsoft Visual C++ з'явилися язикові розширення, звані керованим C++, які дозволяють програмісту створювати і використовувати компоненти середовища, що реалізовує вимоги загальної специфікації мов програмування CLS (Common Language Specification) для платформи .NET.

В цьому розділі дається інформація про основні| особливості підмножини мови Microsoft Visual C++ - керованого C++ [8, 9], а також інших мов платформи Microsoft .NET - сучасних мов програмування C# [10] та Visual Basic .NET [11]. Останні дві мови по своїх функціональних можливостях практично ідентичні керованому C++ і разом з мовою C++ можуть використовуватися для створення системних програм. Зокрема, вони можуть застосовуватися для розробки окремих програм і програмних комплексів в областях інформаційної безпеки, компіляторів, розподілених інформаційних систем, Web – програм, графічних інтерфейсів програмних систем, паралельного програмування і обробки аудіовізуальної інформації.

10.1 Керований C++

Керований С++ працює в середовищі .NET Runtime – системі підтримки керованих програм платформи Microsoft .NET.

В цьому середовищі автоматично виконується знищення об'єктів збирачем сміття в якісь моменти часу після того, як об'єкти перестають використовуватися. Деструктори (також звані завершувачами) мають можливість виконувати деякі завершальні операції, але вони використовуються зовсім не так, як деструктори некерованого C++.

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

Відпала необхідність і в звичайних бібліотеках C/C++. Практично всі функції (операції з рядками, файловий ввід - вивід і т. д.), що раніше містилися в бібліотеках, виконуються засобами .NET Runtime і знаходяться в просторах імен, що починаються з рядка System.

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

Створювати на C++ сумісні з .NET програми досить просто: достатньо розмістити в потрібних місцях програми декілька нових для С++ ключових слів – перемикачів, що змінюють сенс існуючих конструкцій C++. Наприклад, ключове слово __gc перед оголошенням класу є ознакою керованого класу і забороняє застосування конструкцій, що не підтримуються в середовищі .NET (наприклад, множинного спадкування). Крім того, в керованому C++ можна використовувати системні класи .NET.

В табл. 10.1 приведені основні| ключові слова – перемикачі з описом виконуваних ними дій в програмах на C++. Всі ці ключові слова були перелічені раніше в табл. 2.2 і мали виноску 2.

Таблиця 10.1

Ключові слова – перемикачі керованого C++

Ключове слово Призначення
__abstract Оголошує абстрактний клас, об'єкти якого не можна оголошувати безпосередньо.
__box Створює в динамічній пам'яті (купі) об'єкт вказівного типу на основі об'єкту розмірного типу.
__delegate Оголошує делегат - посилання на унікальний метод (вказівник на функцію) керованого класу.
__event Оголошує метод для подій керованого класу.
__finally Оголошує блок finally для попереднього блоку try.
__gc Оголошує керований тип.
__identifier Дозволяє використовувати ключове слово C++ як звичайний ідентифікатор.
__interface Оголошує інтерфейс.
__nogc Оголошує звичайний клас C++, об'єкти якого не зачіпаються автоматичною збіркою сміття.
__pin Забороняє переміщати звичайний об'єкт або керований об'єкт при автоматичній збірці сміття.
__property Оголошує член керованого класу як властивість.
public, protected або private Визначають доступність типів і методів поза збіркою.
__sealed Забороняє використовувати клас як базовий для похідних класів.
__try_cast Виконує контроль приведення типів і викликає виключення у разі помилки приведення.
__typeof Повертає тип об'єкту з простору імен System::Type.
__value Оголошує розмірний тип.

 

10.2 Мова програмування C#

Мова програмування C# створена на основі мови C++. Тому програма, написана на С#, буде здаватися знайомою програмісту, що в тій чи іншій мірі знає мову C++. Практично всі оператори, оголошення і інструкції мови C++ є також і в мові C#, причому вони мають той же синтаксис. Проте між цими мовами існує декілька великих і багато дрібних відмінностей. Короткий перелік цих відмінностей приведений нижче.

У мові С# немає вказівників. Вірніше, в небезпечному (unsafe) режимі вони є, але застосовуються рідко. Замість вказівників використовуються посилання, які схожі на посилання C++, але позбавлені властивих їм недоліків.

10.2.1 Робота з об'єктами

Всі об'єкти С# є похідними від загального предка object. Множинне спадкування не допускається, хоча можлива реалізація декількох інтерфейсів.

Полегшені об'єкти, наприклад, змінні типів користувача, можуть оголошуватися у вигляді розмірних типів. Це означає, що вони створюються в стеку, а не в динамічній пам'яті (купі). Вказівні типи оголошуються і використовуються практично так само, як і в мові C++.

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

В мові С# підтримуються властивості і індексатори, що відокремлюють модель об'єкту користувача від його реалізації, а також делегати і події, що інкапсулюють можливості вказівників на функції і функцій зворотного виклику (callbacks).

10.2.2 Інструкції

Інструкції і оператори мови С# мають багато загального з інструкціями і операторами мови C++. Істотних відмінностей не так вже й багато.

Ключове слово new означає „одержати новий екземпляр класу”. Об'єкти вказівних типів створюються в купі, а об'єкти розмірних типів створюються в стеку або безпосередньо в програмному коді (inline allocated).

В усіх інструкціях, які перевіряють логічні умови, повинні використовуватися змінні типу bool. Не існує автоматичного приведення типу int в bool, тому конструкції виду if (i) неприпустимі.

В інструкціях switch для скорочення числа помилок заборонено наскрізне виконання. Інструкція switch може використовуватися для вибору, використовуючи рядкові значення.

В мові C# з'явилися декілька нових інструкцій. Інструкція fогeach забезпечує перебір об'єктів в колекціях. Інструкції checked та unchecked управляють перевіркою математичних операцій і приведень на предмет переповнення.

В мові C# правило однозначного присвоювання вимагає, щоб перед використанням об'єкту йому було присвоєно певне значення.