Міністерство освіти та науки України
Національний університет “Львівська політехніка”
Бітова арифметика. Алгоритмічна реалізація коду Грея. Створення консольних Windows-програм
на основі Microsoft Visual Studio .NET
Інструкція до лабораторної роботи № 1
з дисципліни “Основи збору, передачі та оброблення інформації”
для студентів базового напрямку 6.0914
“Комп’ютеризовані системи, автоматика і управління”
та базового напрямку 050201 “Системна інженерія”
Затверджено
на засіданні кафедри
“Комп’ютеризовані
системи автоматики”
Протокол № 6 від 07.02.2008
Львів 2008
Бітова арифметика. Алгоритмічна реалізація коду Грея. Створення консольних Windows-програм на основі Microsoft Visual Studio .NET: Інструкція до лабораторної роботи № 1 з дисципліни “Основи збору, передачі та оброблення інформації” для студентів базового напрямку 6.0914 “Комп’ютеризовані системи, автоматика і управління” та базового напрямку 050201 “Системна інженерія” / Укл.: А.Г. Павельчак, Р.В. Проць, В.В. Самотий – Львів: НУЛП, 2008. – 44 с.
Укладачі: А.Г. Павельчак, к.т.н., асистент
Р.В. Проць, к.т.н., доцент
В.В. Самотий, д.т.н., професор
Відповідальний за випуск:
А.Й. Наконечний, д.т.н., професор
Рецензент: З.Р. Мичуда, д.т.н., професор
Мета роботи: отримати навики по розробленню консольних Windows-програм CLR за допомогою інструментарію Visual C++ 2005, алгоритмічно реалізувати кодер/декодер Грея, дослідити основні бітові операції алгоритмічної мови С++ та на їх основі навчитися виконувати різноманітні маніпуляції над бітовими послідовностями.
1. Основні поняття Microsoft Visual Studio .NET
Microsoft .NET Framework – платформа, яка побудована на верхньому шарі операційної системи та призначена для розроблення та виконання програм. Основними компонентами платформи є загальномовне виконуюче середовище (Common Language Runtime, CLR) та бібліотека класів .NET Framework (FCL). CLR абстрагує сервіси операційної системи та є механізмом для виконання керованих програм (managed applications), будь-яка дія яких повинна отримати дозвіл зі сторони CLR. FCL являє собою об’єктно-орієнтований API (Application Programming Interface, інтерфейс прикладного програмування), до якого звертаються керовані програми. Керовані CLR програми використовують розширену версію С++, що має назву С++/CLI.
Загальномовне виконуюче середовище CLR.
CLR – це стандартизоване середовище виконання програм, написаних на високорівневих мовах С++, С#. Visual Basic. CLR розташована поверх операційної системи та представляє собою віртуальне середовище для керованих програм. При запуску керованої програми CLR завантажує модуль програми та виконує її код. Код, призначений для CLR, називається керованим кодом та складається з команд псевдомашинної мови – проміжної мови IL (Microsoft Intermediate language, MSIL, чи просто IL). Команди IL компілюються в машинний код (переважно код процесора х86) по запиту (just-in-time) у період виконання. Переважно компіляція будь-якого методу відбувається лише один раз – при першому його виклику, а потім результат компіляції кешується в пам’ять, щоб при повторному виклику він міг бути виконаний без затримки. Код, який ніколи не викликається, ніколи і не компілюється. Хоча компіляція по запиту, звичайно, знижує продуктивність, однак це компенсується тим, що на протязі виконання програми кожен метод компілюється не більше одного разу. Теоретично продуктивність коду, скомпільованого по запиту, може бути вищою від продуктивності звичайного коду, оскільки JIT-компілятор виконує оптимізацію для задіяного процесора.
Специфікації CLR відображаються в стандарті ECMA інфраструктури загальної мови CLI (Common Language Infrastructure) – ECMA-335, і тому С++ для CLR так і називається С++/CLI.
Бібліотека класів .NET Framework FCL.
.NET Framework представляє собою повністю новий API – бібліотеку класів .NET Framework, що містить понад 7000 типів: класів, структур, інтерфейсів, переліків та делегатів (так називаються оболонки функцій зворотного виклику, що забезпечують безпеку типів). Деякі FCL містять до 100 методів, властивостей та інших членів.
З метою спрощення вивчення та використання FCL ця бібліотека розділена на ієрархічні простори імен. Всього в FCL близько 100 таких просторів. У кожному з них містяться класи та інші типи, що мають певне спільне призначення.
Таблиця 1.1. Деякі простори імен бібліотеки .NET
Простір імен
Вміст
System
Фундаментальні типи даних та допоміжні класи, що використовуються у всіх програмах CLR. Тут містяться також класи подій та обробників подій, виключень та класи, що підтримують функції загального призначення.
System::Collections
Класи колекцій, що призначені для всеможливої організації даних, у тому числі класи визначення списків, черг, словників (карт) та стеків.
System::ComponentModel
Класи, які підтримують роботу компонентів графічного інтерфейсу користувача в програмах CLR
System::Windows::Forms
Класи для програм з графічним інтерфейсом користувача (Windows Forms).
System::Data
Класи ADO.NET для доступу до даних.
System::Drawing
Класи для виводу графіки (GDI+) на форму чи компоненту.
System::IO
Класи файлового та потокового вводу-виводу.
System::NET
Класи для роботи з мережевими протоколами.
Як видно з таблиці 1.1, базовим простором імен для .NET CLR є System. Він, у свою чергу, є розділений на ряд підлеглих йому підпросторів, наприклад, System::Console, у якому містяться всі функції для вводу-виводу у вікно консолі. Деякі підпростори імен також мають ще вкладені «підпідпростори» і т.д.
2. Елементи мови С++
2.1. Поняття класу.
Клас є найбільш важливим вдосконаленням мови С++ та представляє собою потужну платформу для створення різних об’єктних типів, визначених користувачем. Класи в деякій мірі є нащадками таких елементів мови С як структури. Вони містять в собі найрізноманітніші поля даних: прості змінні (типів int, char, double, тощо), масиви, вказівники, масиви вказівників, структури, об’єкти інших класів та крім того, на відміну від структур, класи мають в наявності методи для роботи з цими даними. Поля даних прийнято називати даними-членами або полями класу. Відповідно, методи класу мають ще назву функції-члени чи члени-функції.
Розглянемо простий приклад оголошення класу.
Рис. 2.1. Синтаксис визначення класу
За допомогою ключових слів private:, protected:, public: реалізується механізм керування доступом до членів (полів та функцій) класу. Будь-яка програма може отримати доступ до членів класу, які описані у загальнодоступній частині класу (після ключового слова public:). Однак отримати доступ до закритих даних (область private:) можуть лише загальнодоступні функції-члени класу (у наведеному прикладі функція setdate()). Таким чином, загальнодоступні функції-члени виступають як посередники між програмою та власними членами об’єкту цього класу, іншими словами, вони забезпечують інтерфейс між об’єктом та програмою.
Ключове слово protected: призначене для керування доступом до даних при успадкуванні класів. Воно працює ідентично private: при зовнішньому звертанню до членів класу, а для успадкованих класів забезпечує доступ до цих членів.
Конструктор – це особлива функція, що викликається при оголошенню об’єктів для цих класів. За її допомогою здійснюється ініціалізація полів та динамічне виділення пам’яті для покажчиків. Ім’я конструктора точно співпадає з ім’ям класу, що відрізняє його від інших функцій-членів. У нього відсутній тип значення, що повертається.
Деструктор – функція, що викликається при знищенні об’єкту. Основна його задача – це знищення пам’яті, виділеної конструктором при створенні об’єкту. Ім’я деструктора співпадає з ім’ям конструктора (а відповідно і класу), лише перед ним стоїть ~.
Якщо функція-член є громіздкою, то в середині класу прописується лише її прототип, наприклад
void setdate(int, double);
а визначення функції виконується за межами класу
void Apple::setdate(int m, double n)
{ a=m; value=n; }
:: – оператор визначення діапазону доступу, який вказує якому саме класу належить ця функція.
Розглянемо приклад оголошення та ініціалізації об’єктів.
Прокоментуємо цей приклад по кожному рядку.
Рядок 1: оголошення першого об’єкту Ap1 з передачею значення для конструктора, оголошення другого Ap2 як покажчик на тип Apple.
Рядок 2: виділення пам’яті для другого об’єкту Ap2 за допомогою операції new з передачею значення для конструктора.
Рядок 3: Виклик функції setdate для першого об’єкту Ap1. Доступ до функції-члена здійнсюється через операцію крапки ( . ).
Рядок 4: присвоєння значення полю count першого об’єкту. Доступ до поля здійснюється через операцію крапки ( . ).
Рядок 5: присвоєння полю count другого об’єкту Ap2 значення такого ж поля першого об’єкту Ap1. Доступ до поля першого об’єкту здійснюється через операцію непрямого вибору члена ( -> ).
Рядок 6: за допомогою операції delete знищується динамічно виділена пам’ять для другого об’єкту Ap2.
Отже, операція доступу до поля об’єкту або як її ще називають операція крапки ( . ) використовується для роботи зі статичними об’єктами, а операція непрямого вибору члена ( -> ) – для вказівників на об’єкти.
Вказівник this. Цей вказівник визначає адрес рідного об’єкту. Таким чином, будь-яка функція може дізнатися про адрес, образно кажучи, будинку, в якому вона прописана, тобто адрес свого об’єкту. Таким чином, коли під час виконання функції setdate() (див. приклад) здійснюється звертання до членів value та а, то насправді відбувається звертання до this->value та this->а. Автоматичне добавлення вказівника this до імен полів виконує компілятор. При необхідності вказівник this можна використовувати явно.
void setdate(int m, double n)
{ this->a=m; this->value=n; }
2.2. Оператори new та delete.
Операція new – це універсальна операція, що отримує пам’ять в операційної системи та повертає покажчик на початок виділеного блоку.
Операція delete вивільнює пам’ять виділену оператором new, тобто повертає її в загальну «купу».
2.3. Простори імен.
У великих програмах можуть виникнути ситуації, коли деякі імена елементів програми (імена глобальних змінних, переліків, об’єднань, структур, масивів, класів, функцій тощо) можуть співпадати, особливо, коли в основний модуль підключаються бібліотеки різних розробників. Тому для уникнення конфліктів імен елементів програми використовують простори імен (іменовані області).
Розглянемо приклад задання простору імен.
Рис. 2.2. Синтаксис простору імен
Простори імен мають бути оголошені глобально, тобто вони не можуть оголошуватися всередині функції. Їх призначення бути контейнерами для глобальних змінних, функцій й інших іменованих елементів.
Створимо ще один схожий простір, але з іншою назвою.
Всередині простору імен всі зазначені імена є глобально видимими. Для доступу до цих імен ззовні використовується оператор визначення діапазону доступу (::).
Як бачимо з прикладу, ми можемо використовувати поряд змінні та функції з однаковими іменами, які є описані в різних просторах. Функції cut() виконують при цьому різні обчислення.
Для спрощення оброблення імен з простору імен передбачено спеціальну директиву using namespace, яка дає доступ до імен напряму (без оператора ::).
Припустимо, що визначені нами простори імен cow та horse розміщені у файлі “animals.h”. Покажемо на прикладі, як зробити напряму доступними імена з простору cow.
У цьому прикладі проста змінна legs «живе» у просторі імен cow, і доступ до неї здійснюється як до звичайної змінної типу int. А от доступ до аналогічної змінної з простору імен horse здійснюється через оператор визначення діапазону доступу (::).
У середині простору імен можуть розміщуватися вкладені у них простори імен.
Доступ до змінних здійснюється знову ж таки за допомогою оператора (::) поступово від верхнього простору імен до вкладеного.
animals::part = 10;
animals::chicken::count = 5;
Можемо також використати директиву using namespace для спрощеного доступу до цих змінних.
2.4. Розширений символьний тип даних.
Тип wchar_t (wide char type) містить 2-байтні символьні коди зі значеннями від 0 до 65 535. Оголошення та ініціалізація для цього типу ідентичні як і для даних типу char.
wchar_t letter = L’A’;
wchar_t *str = L”My name is Vova”;
Символ L, що стоїть перед символом чи стрічкою, це літерал, який повідомляє компілятору, що це 16-ти бітні значення коду символу чи стрічки (у форматі Unicode).
3. Елементи розширеної мови С++/CLI
3.1. Типи даних.
У С++/CLI всі типи даних відображаються у вигляді об’єктів класів – як класів типів значень, так і класів вказівникових типів (про ці класи згадаємо нижче). Фундаментальні типи (int, char, double) також відображаються як об’єкти класу типу значень, що визначений в просторі імен System. В С++/CLI імена фундаментальних типів є скороченнями для асоційованих з ними класових типів. Це дає можливість трактувати значення фундаментального типу як просте значення або, при необхідності, як автоматично перетворений об’єкт асоційованого типу класу.
Таблиця 3.1. Фундаментальні типи С++/CLI
Фундаментальний тип Розмір (у байтах) Клас значень CLI
bool 1 System::Boolean
char 1 System::Sbyte
signed char 1 System::Sbyte
unsigned char 1 System::Byte
short 2 System::Int16
unsigned short 2 System::Uint16
int 4 System::Int32
unsigned int 4 System::Uint32
long 4 System::Int32
unsigned long 4 System::Uint32
long long 8 System::Int64
unsigned long long 8 System::UInt64
float 4 System::Single
double 8 System::Double
long double 8 System::Double
wchar_t 2 System::Char
Оскільки імена фундаментальних типів простого С++ є псевдонімами для імен класів у програмі С++/CLI, то в принципі можна використовувати у коді С++/CLI і ті, і інші.
int count1;
double value1;
System::Int32 count2;
System::Double value2;
Варто зазначити, що всі об’єкти у середовищі .NET є похідними від одного базового класу – системного класу Object, що знаходиться в просторі імен System.
Центральне місце у .NET Framework займає клас String з простору імен System. Клас String містить у собі стрічку символів Unicode та має набір методів для роботи з цим символьним об’єктом. Найцікавішим є метод ToString(), який забезпечує перетворення любого типу даних до об’єкту String. Тобто цей метод доступний також і для фундаментальних типів (int, double), бо компілятор, коли це необхідно, автоматично їх упаковує (boxing) і, відповідно, розпаковує (unboxing).
int count = 243;
double value = 2344.4534;
String ^Str1 = count.ToString();
String ^Str2 = value.ToString();
Об’єкт Str1 отримує в кінцевому результаті стрічку "243", а Str2 "2344,4534". Як бачимо, для числа типу double ціла частина від дробової розділяється комою (,). Це пов’язано з регіональними налаштуваннями операційної системи.
3.2. Відслідковувані дескриптори.
Середовище CLR має свою власну «купу» пам’яті, яка є повністю незалежною від «купи» рідного С++. Робота з цією пам’яттю для об’єктів CLR є автоматизована. Спеціальний прибиральник сміття CLR здатний знищувати об’єкти та звільняти задіяну під них пам’ять, коли необхідність у цих об’єктах відпадає. Тобто CLR відслідковує кожну змінну, що містить адрес цього об’єкту, і коли цей адрес стає «нічий», то прибиральник очищує динамічно виділену пам’ять з під об’єкту. Таким чином відпадає необхідність явно використовувати операцію delete. При необхідності, прибиральник також фрагментує купу пам’яті, а це означає, що адреси вказівників для об’єктів змінюються. Тому використовувати звичайні вказівники С++ не можна. Для цього використовуються так звані відслідковувані дескриптори. Ці дескриптори в дечому схожі на вказівники С++. Вони містять адрес об’єкту, який автоматично обновлюється прибиральником сміття при його зміні підчас фрагментації купи. Для дескрипторів не можна застосовувати арифметику адресів та здійснювати їх приведення.
Оголошення дескриптора здійснюється аналогічно вказівникам, тільки замість зірки (*) використовується символ «капелюха» (^).
String ^Str1;
int ^count;
При оголошенні дескриптора можна здійснювати його явну ініціалізацію
String ^Str1 = L”Ми з вами знайомі?”;
int ^count = 55;
Варто пам’ятати, що при цьому створюється різновид вказівника, і для арифметичних операцій з ним необхідно його розіменовувати за допомогою операції *.
int value;
value = 5 * (*count) +3;
Ще один нюанс при використанні дескрипторів полягає у тому, що якщо дескриптор знаходиться зліва від операції присвоювання (=), то можна його явно не розіменовувати, це автоматично зробить компілятор.
int ^count = 0;
count = 5*2+3;
*count = 5*2+3;
Останні дві стрічки є ідентичними. Однак такий крок можна здійснити лише при умові, коли дескриптору виділена пам’ять з купи при його явній ініціалізації чи за допомогою оператора gcnew (аналогу оператора new для звичайних вказівників).
int ^count = gcnew int;
double ^value;
value = gcnew double;
3.3. Масиви.
Подібно дескрипторам, масиви CLR відрізняються від масивів рідного С++. Пам’ять для масивів виділяється у керованій купі, і тому змінні масивів є відслідковуваними дескрипторами.
Рис. 3.1. Синтаксис оголошення одновимірного масиву
Створення масиву CLR здійснюється за допомогою операції gcnew одночасно з оголошенням або після нього.
array<int> ^data = gcnew array<int> (100);
array<String^> ^names;
names = gcnew array<String^> (5);
У першій стрічці створюється одновимірний масив по імені data. Змінна масиву є відслідковуваним дескриптором, і тому після специфікації типу елементу в кутових скобках йде символ «капелюха» (^). У самому кінці в круглих дужках вказується розмірність масиву (100). У другій стрічці оголошується масив стрічок names. Оскільки об’єкти класу String розміщуються в купі CLR, і тому типом елементів цього масиву є тип відслідковуваних дескрипторів – String.
При створенні багатовимірного масиву CLR його розмірність вказується в кутових скобках одразу ж після типу елементів.
array<int, 2> ^data2 = gcnew array<int, 2> (5, 6);
Аналогічно створюються масиви і більшої розмірності.
array<int, 4> ^data4 = gcnew array<int, 4> (5, 5, 5, 5);
Нумерація масивів починається з нуля. При створенні масиву він автоматично компілятором ініціалізується значеннями нулів.
Доступ до елементів масиву CLR організований дещо відмінно від звичайного С++. Доступ до одновимірного здійснюється згідно індексу у квадратних скобках [ ], а от до багатовимірного – згідно індексів записаних через кому в цих скобках відповідно до рангу багатовимірної матриці.
int i=1, j=2, m=1, n=2;
data[i] = 15;
data4[i, j, m, n] = 25;
Відмінність запису доступу до елементів масивів у С++/CLI від запису у звичайному С++ пов’язана з відмінністю самої структури масивів. У той час, коли у С++ багатовимірні масиви представляються як вектори векторів, то у С++/CLI масиви відображаються згідно їх рангу. Масиви рідного С++ завжди мають ранг рівний 1. Правда у С++/CLI також можна оголошувати масиви масивів, і тоді доступ до їх елементів буде ідентичним як у С++.
array<array<double>^> ^multivector = gcnew array<array<double>^>(3);
У цій стрічці створюється одновимірний масив дескрипторів на одновимірні масиви. Тепер після цього необхідно створити ці одновимірні масиви. Це можна зробити, наприклад, у циклі for.
for (int i=0; i<3; i++)
multivector [i] = gcnew array<double> (3);
У такий спосіб ми створимо масив 3х3. Хоча можемо створити і поелементно ці вектори, при цьому різної розмірності, одночасно й проініціалізувавши їх.
multivector [0] = gcnew array<double> (3) {1,2,3};
multivector [1] = gcnew array<double> (5) {1,2,3};
multivector [2] = gcnew array<double> {1,2,3,4};
У першій стрічці створюється перший вектор розмірності 3 з одночасною його ініціалізацією числами {1,2,3}. Другий вектор створюється розмірності 5 та перші його три елементи ініціалізуються значеннями, а решта автоматично нулями. Третій вектор створюється по замовчанню згідно кількості ініціалізованих значень, тобто розмірності 4.
Можна також створити та проініціалізувати весь такий складний масив одночасно.
array<array<double>^> ^multivector = gcnew array<array<double>^>(3)
{ gcnew array<double> (3) {1,2,3}, gcnew array<double> (3) {1,2,3},
gcnew array<double> {1,2,3,4} };
Варто тут звернути увагу на один нюанс. Якщо при ініціалізації для одного з внутрішніх векторів записати так
gcnew array<double> (5) {1,2,3}
то в дійсності створиться вектор розмірності не 5, а 3, який проініціалізується значеннями {1, 2, 3}.
Доступ до елементів масиву масивів у такому випадку здійснюється так
int i=1, j=2;
multivector [i][j] = 25;
3.4. Класи.
Розширення мови C++/CLI має набір власних класів. Це класи значень value class, вказівникові класи ref class, інтерфейсні класи interface class та класи переліків enum class.
Різниця між класами значень та вказівниковими класами полягає у тому, що змінні типів значень містять свої власні дані, в той час, коли змінні вказівникових типів повинні бути дескрипторами, і тому вони містять адрес. На відміну від простих класів С++ класи значень чи вказівникові класи не можуть містити поля, що є масивами чи типами класів рідного С++, а також не можуть містити членів, що представляють собою бітові поля.
Клас значень використовується для створення простих об’єктів, які будуть використовуватися аналогічно фундаментальним типам.
Оголошення простого класу значень виглядає так:
value class Apple
{
private:
int a, b;
double value;
public:
void setdate(int m, double n)
{ a=m; value=n; }
};
Створення таких об’єктів та доступ до їх полів виглядає так.
Apple APP, ^APP2;
APP2=gcnew Apple2;
APP.setdate(2,3.24); APP2->setdate(6,12.98);
Як бачимо, створювати об’єкти класу значень можна простим оголошенням як для фундаментальних типів (APP), так і через дескриптор (APP2). Доступ до полів виконується або через операцію (.), або через операцію (->).
При присвоюванні змінній класу значень іншої такої змінної відбувається просте копіювання значень змінних з одного об’єкту в інший.
Apple AD, AD2;
AD = APP; // AD.a=APP.a, AD.b=APP.b, AD.value = APP.value
AD2 = *APP2;
Об’єкти, які оголошені через дескриптор, необхідно розіменовувати при присвоюванні їх іншим об’єктам.
Вказівникові класи більше схожі на звичайні класи С++ та не мають обмежень, які притаманні класу значень. Їх відмінність від звичайних класів – це відсутність конструктора копій по замовчанню та операції присвоювання по замовчанню. Наведемо приклад оголошення вказівникового класу.
ref class Tree
{
private:
int a, b;
double ^value;
array <int,2> ^matrix;
public:
Tree(int c, int d)
{ matrix = gcnew array <int,2> (c,d); }
void setdate(int m, int n)
{ this->a = m; b = n; }
};
Даний клас містить такі поля: 2 змінні типу int, один дескриптор типу double та один дескриптор на двовимірний масив типу int, пам’ять для якого виділяється під час виконання конструктора. Доступ до полів в межах класу здійснюється або безпосередньо через ім’я змінної, або через вказівник this, так як це відбувається у звичайних класах С++.
Створення об’єктів вказівникових класів та доступ до їх полів такий ж як і для класів значень.
Tree TR1(3,5) , ^TR2;
TR2 = gcnew Tree (4,4);
TR1.setdate(2,5); TR2->setdate(2,5);
У класах C++/CLI, на відміну від звичайних класів С++, передбачено ряд додаткових засобів. По-перше, це літеральні поля за допомогою яких є можливість створювати числові константи як поля класу. Позначаються вони ключовим словом literal.
value class Flower
{
private:
int a, b;
literal double value=25.65;
};
За допомогою ключового слова property позначаються властивості класу значень або вказівникового класу. Хоча до властивості звертання відбувається як до поля класу, однак воно таким не є. В той час, коли до значення поля звертаються за адресом, то для властивості викликається спеціальна функція. Властивість має функції доступу get() та set() – відповідно для отримання та встановлення значення.
value class Flower2
{
public:
double inches;
property double meters
{
double get ()
{ return 2.54/100*inches; }
void set (double value)
{ inches = 100/2.54*value; }
}
};
Тепер ми можемо звертатися до поля meters як до звичайного поля, отримуючи його значення та присвоюючи йому інше значення.
double lenght = 5;
Flower2 FL;
FL.meters = lenght + 2;
lenght = FL.meters;
4. Створення консольних Windows-програм CLR
Створення консольного варіанту програми CLR будемо здійснювати поетапно, від створення скелету програми до його наповнення програмним кодом.
Запускаємо Visual Studio 2005. Вибираємо пункт меню File ( New ( Project для того, щоб відкрити діалог New Project.
Рис.4.1. Створення нового проекту консольної програми
Тепер виконуємо по пунктах: 1 – серед типів проектів мишею вибираємо пункт CLR; 2 – серед шаблонів вибираємо пункт CLR Console Application; 3 – вводимо назву проекту, наприклад, consol; 4 – вибираємо місце розташування проекту; 5 – натискаємо кнопку OK та отримуємо готовий скелет програми. По замовчанню він виглядає так:
#include "stdafx.h"
using namespace System;
int main(array<System::String ^> ^args)
{
Console::WriteLine(L"Hello World");
return 0;
}
Це основний файл проекту з назвою "consol.cpp". Директива #include "stdafx.h" підключає заголовний файл "stdafx.h", який необхідний для попередньої компіляції. У другій стрічці дається доступ до простору імен System, в якому знаходяться основні типи даних та стандартні функції, також там розміщується клас Console, що відповідає за роботу в консолі операційної системи (ОС). У наступній стрічці розміщується основна функція main(). Параметри стрічки приймаються нею у вигляді масиву CLR. У тілі функції main() здійснюється вивід стрічки "Hello World" у консоль ОС за допомогою функції WriteLine() класу Console.
Якщо вихідний файл "consol.cpp" ще не відображається у вікні редактора, тоді двічі клікніть на імені файлу в панелі Solution Explorer (рис. 4.2), що знаходиться з лівого боку екрану.
Рис.4.2. Файли консольного проекту CLR
Як видно з рис.4.2 у нашому проекті також наявні додаткові файли: файл .ico містить піктограму програми, що відображається при мінімізації програми; файл .rc містить ресурси програми – у даному випадку тільки посилання на піктограму; у файлі AssemblyInfo.cpp розміщується інформація про наявні зборки (assembly) програми, використовувані ними специфікації типів даних, версію коду і т.п.
У цій лабораторній роботі ми працюватимемо з класами Console, String та Convert з простору імен System.
Клас System::Console забезпечує підтримку стандартного вводу-виводу. Серед основних його методів та властивостей зазначимо такі:
Console::Write() – вивід форматованої стрічки у консоль без переходу на нову стрічку;
Console::WriteLine() – вивід стрічки у консоль з символом нової стрічки;
Console::Read() – читає один символ з клавіатури;
Console::ReadLine() – читає цілу стрічку, завершену натиском клавіші <Enter>;
Console::ReadKey() – повертає натиснуту клавішу у вигляді об’єкту класу ConsoleKeyInfo;
Console::Clear() – виконує очистку консолі;
Console::BackgroundColor – властивість, що встановлює чи повертає колір фону консолі. Кольори є визначені у класі переліку ConsoleColor {Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White};
Console::ForegroundColor – властивість, що встановлює чи повертає колір переднього плану консолі;
Console::Title – властивість, що встановлює чи повертає назву у стрічці заголовку вікна консолі.
Методи WriteLine() та Write() мають можливість форматування виводу. Це здійснюється за допомогою цифри у фігурних дужках {0}, тобто у це місце підставляється значення другого аргументу функції.
Console::WriteLine (L“Масив має набір значень: {0}, {1}, {3}”, a1, a2, a3);
Цифри у фігурних дужках вказують на аргумент по порядку, після форматної стрічки. Розташування фігурних дужок з цифрами може бути довільним, але першому аргументу після форматної стрічки завжди асоціюється 0, другому – 1, і т.д. Взагалі у загальному випадку специфікація формату має такий вигляд: {n, w:Axx}, де n – це порядковий номер аргументу, що розташований після форматної стрічки; w – необов’язкова специфікація ширини поля; A – односимвольна специфікація формату значення; xx – необов’язкове одно- чи двозначне число, що задає точність виводу значення. Специфікація ширини поля – ціле число зі знаком. Значення буде вирівняне вправо в полі шириною |w|, якщо ширина додатна, і вліво – якщо від’ємна. Якщо значення займає менше знаків, аніж вказано у w, то вивід доповнюється пробілами, якщо ж значення не вміщається в ширину w, то специфікація ширини поля ігнорується.
Таблиця 4.1. Основні специфікатори формату
Специфікатор Опис
C або c Виводить значення у грошовому форматі
D або d Виводить ціле у десятковому вигляді. Якщо вказати більш високу точність, кількість десяткових знаків в числі, то воно буде доповнене нулями зліва.
E або e Виводить значення з плаваючою комою в експоненційному вигляді. Значення точності вказує кількість десяткових розрядів після крапки.
F або f Виводить значення з плаваючою комою, як число з фіксованою крапкою у формі ±ddd.dd
G або g Виводить значення у найбільш компактному вигляді, у залежності від його типу та вказаної точності. Якщо точність не вказана, то приймається значення точності по замовчанню.
N або n Виводить десяткове значення з плаваючою комою, використовуючи при необхідності розділювач – кому між групами по три розряди.
X або x Виводить ціле число у шістнадцятковому вигляді. Шістнадцяткові цифри виводяться у верхньому чи ниж- ньому регістрі в залежності від того, що вказано X чи x.
Клас System::String представляє стрічку символів Unicode, що складається з послідовності символів типу System::Char (wchar_t), та багатий набір методів та властивостей для роботи з нею.
Length – властивість, що повертає довжину стрічки;
String::Compare (String^ strA, String^ strB) – порівнює дві стрічки у алфавітному порядку, та повертає нуль, коли стрічки рівні, від’ємне значення, коли strA менше від strB, та додатнє, коли більше.
Trim() – очищає стрічку від пробілів (або окремих символів чи їх набору) на її початку та кінці;
TrimStart() – працює аналогічно Trim(), але очищує лише на початку стрічки;
TrimEnd() – працює аналогічно Trim(), але очищує лише на кінці стрічки;
PadLeft() – доповнює стрічку зліва визначеною кількістю пробілів чи зазначених символів;
PadRight() – доповнює стрічку справа визначеною кількістю пробілів чи зазначених символів;
ToUpper() – перетворює усю стрічку у верхній регістр символів;
ToLower() – перетворює усю стрічку у нижній регістр символів;
Replace() – виконує заміну у стрічці зазначеного символу чи підстрічки на вказаний символ чи підстрічку;
StartsWith() – перевіряє, чи починається дана стрічка з вказаної підстрічки;
EndsWith() – перевіряє, чи закінчується дана стрічка вказаною підстрічкою;
IndexOf() – шукає у стрічці перше входження вказаного символу та повертає індекс входження, якщо такого нема, то -1;
LastIndexOf() –шукає з кінця стрічці перше входження вказаного символу та повертає індекс входження, якщо такого нема, то -1;
Для об’єднання стрічок можна використовувати операцію +, щоб сформувати новий об’єкт String. Однак перший доданок обов’язково має бути стрічкою, наприклад пустою "".
N.B. Будь-яка модифікація об’єкту типу String призводить до створення нового об’єкту String, і тому всі модифікації необхідно присвоювати або тому ж об’єкту, або іншому типу String, бо базовий об’єкт залишається без змін.
Клас System::Convert забезпечує різноманітні перетворення значень одних типів в інші. Дає можливість зчитати значення з об’єкту типу String у змінну довільного типу. Основні методи такі:
Convert::ToBoolean() – конвертує до типу System::Boolean (bool);
Convert::ToByte() – конвертує до типу System::Byte (unsigned char);
Convert::ToChar() – конвертує до типу System::Char (wchar_t);
Convert::ToDateTime() – конвертує до типу System::DateTime;
Convert::ToDouble() – конвертує до типу System::Double (double);
Convert::ToInt16() – конвертує до типу System::Int16 (short);
Convert::ToInt32() – конвертує до типу System::Int32 (int);
Convert::ToInt64() – конвертує до типу System::Int64 (long long);
Convert::ToSByte() – конвертує до типу System::SByte (char);
Convert::ToSingle() – конвертує до типу System::Single (float);
Convert::ToString() – конвертує до типу System::String;
Convert::ToUInt16() – до типу System::UInt16 (unsigned short);
Convert::ToUInt32() – конвертує до типу System::UInt32 (unsigned int);
Convert::ToUInt64() – до типу System::UInt64 (unsigned long long).
Проілюструємо роботу з цими класами на прикладах.
У основному файлі проекту "consol.cpp" замість існуючої основної функції main(), вписуємо свою. Тобто повністю витираємо функцію main() разом з її тілом та записуємо таку:
void main(void)
{
Console::BackgroundColor = ConsoleColor::Yellow;
Console::ForegroundColor = ConsoleColor::Blue;
Console::WriteLine(L"Лабораторна робота №1 з предмету ОЗПІ");
Console::ReadKey(true);
Console::Clear();
Console::Title=L"Моя перша програма";
Console::ReadKey(true);
}
Зберігаємо проект програми та запускаємо його на виконання . Спостерігаємо кольоровий вивід стрічки. Команда Console::ReadKey(true) виконує роль затримки до натисненя довільної клавіші. Після затримки відбувається очищення екрану та змінюється надпис на заголовку вікна.
У кінець функції main() додаємо такий код:
int i1=28, i2=-54000000;
double d1=21.123456, d2=-5.098765;
Console::ResetColor();
Console::Clear();
Console::WriteLine(L"Простий вивід:");
Console::Write(L"цілі числа: {0}, {1}",i1,i2);
Console::WriteLine(L" числа з плаваючою комою: {0}, {1}",d1,d2);
Console::WriteLine(L"Форматований вивід:");
Console::Write(L"цілі числа: {0,10:D5}, {1,4:N2}",i1,i2);
Console::WriteLine(L" числа з плаваючою комою: {0,5:G3},
{1,5:F3}",d1,d2);
Console::WriteLine(L"шістнадцядкова форма: {0,5:X4}, {1,5:x4}",i1,i2);
Console::ReadKey(true);
Зберігаємо проект програми та запускаємо його на виконання . Здійснюємо аналіз отриманого форматованого виводу результатів.
Тепер спробуємо щось зчитати з консолі. Для цього у кінець функції main() додаємо такий код:
String ^line;
wchar_t ch;
Console::Clear();
Console::Write(L"Введіть число з плаваючою комою ");
line = Console::ReadLine();
d1 = Convert::ToDouble(line);
Console::Write(L"Введіть друге число з плаваючою комою ");
line = Console::ReadLine();
d2 = Convert::ToDouble(line);
Console::WriteLine(L"Сума чисел {0} та {1} = {2}",d1,d2,d1+d2);
Console::WriteLine();
Console::Write(L"Введіть дві цифри ");
ch = Console::Read();
i1 = Convert::ToInt32(ch.ToString());
ch = Console::Read();
i2 = Convert::ToInt32(ch.ToString());
Console::WriteLine(L"Добуток цифр {0} та {1} = {2}", i1, i2, i1*i2);
Console::ReadKey(true);
Зберігаємо проект програми та запускаємо його на виконання . Здійснюємо аналіз отриманих результатів.
Реалізуємо відслідковування натиснення комбінацій клавіш у консолі, наприклад Ctrl+Shift+Alt+G. Для цього у кінець функції main() додаємо такий код:
Console::Clear();
Console::WriteLine(L"Нажміть комбінацію клавіш - для виходу нажміть ESC");
ConsoleKeyInfo keyPress;
do
{
keyPress = Console::ReadKey(true);
Console::Write(L"Ви натиснули");
if(safe_cast<int>(keyPress.Modifiers)>0)
Console::Write(L" {0},", keyPress.Modifiers);
Console::WriteLine(L" {0} що дає символ {1} ", keyPress.Key, keyPress.KeyChar);
}
while(keyPress.Key != ConsoleKey::Escape);
Console::ReadKey(true);
Зберігаємо проект програми та запускаємо його на виконання .
Тепер проілюструємо відмінності між звичайними покажчиками та відслідковуваними дескрипторами, а також між звичайними масивами С++ та масивами CLR, та доступом до їх елементів. Для цього у кінець функції main() додаємо такий код:
Console::Clear();
int count1 = 2, *count2, *pvector1, vector[2][2]={{1,2},{3,4}};
count2 = new int (25);
pvector1 = new int [5];
for (int i=0;i<5;i++)
pvector1[i]=i;
int ^count_d;
count_d = gcnew int (15);
array<int> ^pvector1_d;
pvector1_d = gcnew array <int> (5){1,2,3,4,5};
array<int,2> ^pvector2_d;
pvector2_d = gcnew array <int,2>(2,2) {{1,2},{3,4}};
Console::WriteLine(L"Вивід цілих значень");
Console::WriteLine(L"count1={0}, count2={1}, count_d={2}",count1,*count2,*count_d);
Console::WriteLine();
Console::WriteLine(L"Вивід одновимірних масивів");
for (int i=0;i<5;i++)
Console::WriteLine(L"pvector1[{0}]={1}, pvector1_d[{0}]={2}",i,pvector1[i],pvector1_d[i]);
Console::WriteLine();
Console::WriteLine(L"Вивід двовимірних масивів");
for (int i=0;i<2;i++)
for (int j=0;j<2;j++)
Console::WriteLine(L"vector[{0}][{1}]={2}, pvector2_d[{0},{1}]={3}",i,j,vector[i][j],pvector2_d[i,j]);
Console::ReadKey(true);
Зберігаємо проект програми та запускаємо його на виконання . Аналізуємо виведену інформацію.
5. Бітова арифметика
5.1. Порозрядні операції та цілочисельні типи даних.
Для аналізу вхідної інформації, кодування чи декодування її різноманітними кодерами, архіваторами тощо необхідно оперувати з окремими бітами. У алгоритмічній мові С++ для роботи з цілими числами у двійковому представленні є шість порозрядних операцій: & (І), | (АБО), ^ (виключне АБО чи сума за модулем 2), ~ (НЕ), >> (зсув вправо) та << (зсув вліво). Результат їх виконання для двох бітів наведений у таблиці 5.1 та таблиці 5.2.
Таблиця 5.1. Таблиця істинності для порозрядних операцій
Результат
~
Біт 1
Біт 2
&
|
^
Біт
Результат
0
0
0
0
0
0
1
0
1
0
1
1
1
0
1
0
0
1
1
1
1
1
1
0
Таблиця 5.2. Порозрядні операції зсуву
>>
зсув
вправо
Зсуває біти лівого операнда на число розрядів, що вказане правим операндом. При цьому праві біти втрачаються. Якщо лівий операнд представляє собою ціле число без знаку, то ліві біти, що звільнилися, заповнюються нулями, в іншому випадку, вони заповнюються символом знаку, тобто 1.
<<
зсув
вліво
Зсуває біти лівого операнда на число розрядів, що вказане правим операндом. При цьому ліві біти втрачаються, а праві заповнюються нулями.
Результати цих операцій для цілих чисел виглядають так:
Від’ємні цілі числа інтерпретуються у двійковій формі наступним чином. У прикладі маємо число -121, тобто знак «-» свідчить, що у старшому розряді є 1. Для однобайтного від’ємного числа мінімальне значення -128. Отже перетворюємо решта сім молодших розрядів у десяткове число (1112 ( 710) та додаємо до мінімального, і в результаті отримуємо число -128 + 7 = -121.
Операція порозрядного зсуву вліво << цілого числа на n розрядів еквівалентна множенню числа на , при умові що крайні біти не губляться. Наприклад, при зсуві числа 5 (1012) на 3 розряди вліво отримуємо число 40 (1010002), що тотожно множенню числа 5 на 8=.
Операція порозрядного зсуву вправо >> цілого числа на n розрядів еквівалентна цілочисельному діленню цього числа на . Наприклад, при зсуві числа 53 (1101012) на 3 розряди вправо отримуємо число 6 (1102), що тотожно цілочисельному діленню числа 53 на 8= (53 / 8 = 6,625).
Зазначимо, що біти нумеруються справа наліво, причому крайній справа (молодший) біт має номер 0. Довжина біта, півбайта, байта, півслова, слова та подвійного слова рівні, відповідно, 1, 4, 8, 16, 32 та 64 (рис. 5.1).
Рис. 5.1. Внутрішня структура числа та нумерація його бітів
У табл. 5.3 подані основні цілочисельні типи С++. У залежності від поставленої задачі потрібно вміти вибрати необхідний тип даних. Як правило, при порозрядних операціях знакові цілі типи не використовуються, а лише беззнакові (unsigned).
Таблиця 5.3. Цілочисельні типи С++
Тип Розмір у байтах Діапазон значень
char 1 (8 біт) від -128 до +127
unsigned char 1 (8 біт) від 0 до 255
short 2 (16 біт) від -32 768 до +32 767
unsigne...