- •Міністерство освіти і науки україни
- •Для студентів напряму підготовки 0804 "Комп’ютерні науки" всіх форм навчання
- •Харків, Вид. Хнеу, 2009
- •Загальні положення
- •Вимоги до оформлення звіту
- •Лабораторна робота №1 Підготовка і розв’язання на пк задач обробки масивів з використанням покажчиків
- •Поняття покажчика і основні операції з покажчиками ·взаємозв'язок між покажчиками і масивами ·основи динамічного розподілу пам'яті.
- •Теоретичний матеріал
- •Завдання на лабораторну роботу
- •Лабораторна робота №2 Підготовка і рішення на пк завдань з використанням рядків і макросів.
- •Теоретичний матеріал
- •Функції введення з вказівкою заголовного файлу і вхідного потоку
- •Функції виведення з вказівкою заголовного файлу і вхідного потоку
- •Завдання на лабораторну роботу
- •Лабораторна робота №3 Підготовка і рішення на пк завдань з використанням рядків і макросів.
- •Теоретичний матеріал
- •Завдання на лабораторну роботу
- •Лабораторна робота №4
- •Теоретичний матеріал
- •Завдання на лабораторну роботу
- •Лабораторна робота №5 Підготовка й рішення на пк завдань обробки масивів структур з використанням контейнерів
- •Завдання на лабораторну роботу
- •Лабораторна робота № 6 Дослідження структури windows-додатка
- •Завдання на лабораторну роботу
- •Лабораторна робота № 7 Дослідження взаємодії додатка з користувачем
- •Читання об'єктів з ресурсів (LoadCursor(), Loadlcon(), LoadString(), LoadResource(), LoadBitmap(), LoadMenu());
- •Завдання на лабораторну роботу
- •У головне меню додатка, виконане в завданні № 1, додати пункти, що відповідають таким діалоговим вікнам.
- •Діалоговому вікну настроювання виду меню.
- •Лабораторна робота №8
- •Короткі теоретичні відомості й історичний аспект
- •Створення простого керованого консольного додатка.
- •Стандартний ввід-вивід
- •Часто використовувані специфікатори формату
- •Багатомірні масиви
- •Рекомендована література
- •5. Лабораторна робота №5. Підготовка й рішення на пк завдань обробки масивів структур з використанням контейнерів ………………. 66
- •Навчальне видання
Вимоги до оформлення звіту
Поля сторінки: ліве – 2,5 см, праве – 1,5 см, верхнє й нижнє – 2 см; шрифт Times New Roman (висота – 14 пт), міжрядковий інтервал – множник 1,1.
Номери сторінок повинні знаходитися у правому верхньому куті, титульний лист не нумерується.
Листи звіту мають бути з´єднані скріпкою або іншим загальноприйнятим способом.
Лабораторна робота №1 Підготовка і розв’язання на пк задач обробки масивів з використанням покажчиків
Мета лабораторної роботи - освоїти основні прийоми використання покажчиків в програмах на мові C++
Перед виконанням лабораторної роботи студент повинен знати:
Поняття покажчика і основні операції з покажчиками ·взаємозв'язок між покажчиками і масивами ·основи динамічного розподілу пам'яті.
Після виконання лабораторної роботи студент повинен уміти: використовувати покажчики при розробці програм на мові C++.
Теоретичний матеріал
Покажчики
Коли компілятор обробляє оператора визначення змінної, наприклад, int i =10, він виділяє пам'ять відповідно до типу (int) і ініціалізував її вка-заним значенням (10). Всі звернення в програмі до змінної по її імені (i) замінюються компілятором на адресу області пам'яті, в якій зберігається значення змінної. Програміст може визначити власні змінні для зберіган-ня адрес областей пам'яті. Такі змінні нази-ваються покажчиками. Отже, покажчики призначені для зберігання адрес областей пам'яті. У C++ розрізняють три види покажчиків – покажчики на об'єкт, на функцію і на void, що відрізняються властивостями і набором допустимих операцій. Покажчик не є самостійним типом, він завжди пов'язаний з яким-небудь іншим конкретним типом.
Покажчик на функцію містить адресу в сегменті коду, по якому розташовується виконуваний код функції, тобто адреса, по якій передається управління при виклику функції. Покажчики на функції використовуються для непрямого виклику функції (не через її ім'я, а через звернення до змінної, що зберігає її адресу), а також для передачі імені функції в іншу функцію як параметр. Покажчик функції має тип покажчик функції, що повертає значення заданого типа і що має аргументи заданого типа:
тип (*ім'я) ( список_типов_аргументов );
Наприклад, оголошення:
int (*fun) (double, double);
задає покажчик з ім'ям fun на функцію, що повертає значення типу int і що має два аргументи типу double.
Покажчик на об'єкт містить адресу області пам'яті, в якій зберігаються дані певного типа (основного або складеного). Просте оголошення покажчика на об'єкт (надалі званого просто покажчиком) має вигляд:
тип *имя;
де тип може бути будь-яким, окрім посилання і бітового поля, причому тип може бути до цього моменту тільки оголошений, але ще не визна-чений (отже, в структурі, наприклад, може бути присутнім покажчик на структуру того ж типа).
Зірочка відноситься безпосередньо до імені, тому для того, щоб ого-лосити декілька покажчиків, потрібно ставити її перед ім'ям кожного з них. Наприклад, в операторі
int *a, b *с;
описуються два покажчики на ціле з іменами а, с, а також ціла змінна b.
Розмір покажчика залежить від моделі пам'яті. Можна визначити покажчик на покажчик і т.д.
Покажчик на void застосовується в тих випадках, коли конкретний тип об'єкту, адресу якого потрібно зберігати, не визначений (наприклад, якщо в одній і тій же змінній в різні моменти часу потрібно зберігати адреси об'єктів різних типів).
Покажчику на void можна привласнити значення покажчика будь-якого типа, а також порівнювати його з будь-якими покажчиками, але перед виконанням яких-небудь дій з областю пам'яті, на яку він посилається, потрібно перетворити його до конкретного типа явним чином.
Покажчик може бути константою або змінною, а також указувати на константу або змінну. Розглянемо приклади:
int i: // ціла змінна
const int ci = 1; // ціла константа
int * pi: // покажчик на цілу змінну
const int * pci; // покажчик на цілу константу
int * const cp = &i; // покажчик-константа на цілу змінну
const int * const cpc = &ci; // покажчик-константа на цілу константу
Як видно з прикладів, модифікатор const, що знаходиться між ім'ям указу теля і зірочкою, відноситься до самого покажчика і забороняє його зміну а const зліва від зірочки задає постійність значення, на яке він указує. Для ініціалізації покажчиків використана операція отримання адреси &. Величини типу покажчик підкоряються загальним правилам визначення області дії, видимості і часу життя.
Ініціалізація покажчиків
Покажчики найчастіше використовують при роботі з динамічною пам'яттю, званою деякими естетами купою (переклад з англійської мови слова heap). Це вільна пам'ять, в якій можна під час виконання програми виділяти місце відповідно до потреб. Доступ до виділених ділянок динамічної пам'яті, званих динамічними змінними, проводиться тільки через покажчики. Час життя динамічних змінних - від точки створення до кінця програми або до явного звільнення пам'яті. У C++ використовується два способи роботи з динамічною пам'яттю. Перший використовує сімействі функцій mallос і дістався в спадок від З, другий використовує операції new і delete.
При визначенні покажчика треба прагнути виконати його ініціалізацію, тс є привласнення початкового значення. Ненавмисне використання неініціалізованих покажчиків - поширене джерело помилок в програмах. Ініціалізація записується після імені покажчика або в круглих дужках, або після знаку рівності.
Існують наступні способи ініціалізації покажчика:
1. Привласнення покажчику адреси існуючого об'єкту:
за допомогою операції отримання адреси:
int а = 5; // ціла змінна
int* p = &а; //в покажчик записується адреса а
int* p(&а); // те ж саме іншим способом
за допомогою значення іншого покажчика, що ініціалізував:
int* г = р;
за допомогою імені масиву або функції, які трактуються як адреса:
int b[10]; // масив
int* t = b; // привласнення адреси почала масиву
void f(int а ) { /* ... */ } // визначення функції
void (*pf)(int); // покажчик на функцію
pf = f; // привласнення адреси функції
2. Привласнення покажчику адреси області пам'яті в явному вигляді:
char* vp = (char *)0xB8000000;
Тут 0хВ8000000 - шестнадцатерічная константа, (char *) - операція приведення типу: константа перетвориться до типу покажчик на char.
3. Привласнення порожнього значення:
int* suxx = NULL;
int* rulez = 0;
У першому рядку використовується константа NULL, визначена в деяких заголовних файлах С як покажчик, рівний нулю. Рекомендується використовувати просто 0, оскільки це значення типу int буде правильно перетворено стандартними способами відповідно до контексту. Оскільки гарантується, що об'єктів з нульовою адресою немає, порожній покажчик можна використовувати для перевірки, посилається покажчик на конкретний об'єкт чи ні.
4. Виділення ділянки динамічної пам'яті і привласнення її адреси покажчику:
за допомогою операції new:
int* n = new int; III
int* m = new int (10); // 2
int* q = new int [10]; // 3
за допомогою функції mallос:
int* u = (int *)malloc(sizeof(int)); // 4
У операторі 1 операцію new виконує виділення достатнього для розміщення величини типу int ділянки динамічної пам'яті і записує адресу почала цієї ділянки в змінну n. Пам'ять під саму змінну n (розміру, достатнього для розміщення покажчика) виділяється на етапі компіляції.
У операторі 2, окрім описаних вище дій, проводиться ініціалізація виділеної динамічної пам'яті значенням 10.
У операторі 3 операція new виконує виділення пам'яті під 10 величин типу int (масиву з 10 елементів) і записує адресу почала цієї ділянки в змінну q, яка може трактуватися як ім'я масиву. Через ім'я можна звертатися до будь-якого елементу масиву. Якщо пам'ять виділити не вдалося, за стандартом повинне породжуватися виключення bad_alloc. Старі версії компіляторів можуть повертати 0.
У операторі 4 робиться те ж саме, що і в операторі 1, але за допомогою функції виділення пам'яті malloc, успадкованої з бібліотеки З. У функцію передається один параметр - кількість пам'яті, що виділяється, в байтах. Конструкція (int*) використовується для приведення типу покажчика, що повертається функцією, до необхідного типа. Якщо пам'ять виділити не вдалося, функція повертає 0.
Операцію new використовувати переважно, чим функцію malloc, особливо при роботі з об'єктами.
Звільнення пам'яті, виділеної за допомогою операції new, повинно виконуватися за допомогою delete, а пам'яті, виділеною функцією malloc - за допомогою функції free. При цьому змінна-покажчик зберігається і може ініціалізуватися повторно. Приведені вище динамічні змінні знищуються таким чином:
delete n; delete m; delete [] q; free (u);
Якщо пам'ять виділялася з допомогою new[], для звільнення пам'яті необхідно застосовувати delete[]. Розмірність масиву при цьому не указується. Якщо квадратних дужок немає, то ніякого повідомлення про помилку не видається, але помічений як вільний буде тільки перший елемент масиву, а інші опиняться недоступні для подальших операцій. Такі елементи пам'яті називаються сміттям.
За допомогою комбінацій зірочок, круглих і квадратних дужок можна описувати складені типи і покажчики на складені типи, наприклад, в операторі
int *(*р[10])();
оголошується масив з 10 покажчиків на функції без параметрів, що повертають покажчики на int.
За умовчанням квадратні і круглі дужки мають однаковий пріоритет, більший, ніж зірочка, і розглядаються зліва направо. Для зміни порядку розгляду використовуються круглі дужки.
При інтерпретації складних описів необхідно дотримуватися правила з середини назовні:
1) якщо праворуч від імені є квадратні дужки, це масив, якщо дужки круглі - це функція;
2) якщо зліва є зірочка, це покажчик на проінтерпретовану раніше конструкцію;
3) якщо справа зустрічається закриваюча кругла дужка, необхідне пріменіть приведені вище за правило усередині дужок, а потім переходити назовні;
4) в останню чергу інтерпретується специфікатор типу.
Операції з покажчиками
З покажчиками можна виконувати наступні операції: разадресация, або непряме звернення до об'єкту (*), привласнення, складання з константою, віднімання, інкремент (++), декремент (--), порівняння, приведення типів. При роботі з покажчиками часто використовується операція отримання адреси (&).
Операція разадресації, або разименування, призначена для доступу до величини, адреса якої зберігається в покажчику. Цю операцію можна використовувати як для отримання, так і для зміни значення величини (якщо вона не оголошена як константа):
char а; // змінна типу char
char * р = new char; /* виділення пам'яті під покажчик і під динамічну змінну типу char */
*р = 'А'; а = *р; // привласнення значення обом змінним
Як видно з прикладу, конструкцію *имя_указателя можна використовувати в лівій частині оператора привласнення, оскільки вона є L-значенням, тобто визначає адресу області пам'яті. Для простоти цю конструкцію можна вважати ім'ям змінної, на яку посилається покажчик. З нею допустимі всі дії, визначені для величин відповідного типа (якщо покажчик ініціалізував). На одну і ту ж область пам'яті може посилатися декілька покажчиків різного типа. Застосована ним операція разадресациі дасть різні результати. Наприклад, програма
#include <stdio.h>
int main()
{
unsigned long int A = 0Xcc77ffaa;
unsigned short int* pint = (unsigned short int*) &A;
unsigned char* pchar = (unsigned char *) &A;
printf(" | %x | Xx | Xx |", A *pint, *pchar);
return 0;
}
виведе на екран рядок:
| cc77ffaa | ffaa | аа |
Значення покажчиків pint і pchar однакові, але разадресація pchar дає в результаті один молодший байт за цією адресою, а pint - два молодші байти. У приведеному вище прикладі при ініціалізації покажчиків були використані операції приведення типів. Синтаксис операції явного приведення типу простий: перед ім'ям змінної в дужках указується тип, до якого її потрібно перетворити. При цьому не гарантується збереження інформації, тому в загальному випадку явних перетворень типу слід уникати.
При змішуванні у виразі покажчиків різних типів явне перетворення типів потрібне для всіх покажчиків, окрім void*. Покажчик може неявно перетворюватися в значення типу bool (наприклад, у виразі умовного оператора), при цьому ненульовий покажчик перетвориться в true, а нульовий в false. Привласнення без явного приведення типів допускається в двох випадках:
покажчикам типу void*;
якщо тип покажчиків справа і зліва від операції привласнення один і той же.
Таким чином, неявне перетворення виконується тільки до типу void*. Значення 0 неявно перетвориться до покажчика на будь-який тип. Привласнення покажчиків на об'єкти покажчикам на функції (і навпаки) неприпустимо. Заборонено і привласнювати значення покажчикам-константам, втім, як і константам будь-якого типа (привласнювати значення покажчикам на константу і змінним, на які посилається покажчик-константа, дозволяється).
Арифметичні операції з покажчиками (складання з константою, віднімання, інкремент і декремент) автоматично враховують розмір типу величин, що адресуються покажчиками. Ці операції застосовні тільки до покажчиків одного типа і мають сенс в основному при роботі із структурами даних, послідовно розміщеними в пам'яті, наприклад, з масивами.
Інкремент переміщає покажчик до наступного елементу масиву, декремент - до попереднього. Фактично значення покажчика змінюється на величину sizeof(тип). Якщо покажчик на певний тип збільшується або зменшується на константу, його значення змінюється на величину цієї константи, помножену на розмір об'єкту даного типа, наприклад:
short * р = new short [5]:
р++; // значення р збільшується на 2
long * q = new long [5];
q++; // значення q збільшується на 4
Різниця двох покажчиків - це різниця їх значень, що ділиться на розмір типу в байтах (у застосуванні до масивів різниця покажчиків, наприклад, на третій і шостий елементи рівна 3). Підсумовування двох покажчиків не допускається.
При записі виразів з покажчиками слід звертати увагу на пріоритети операцій. Як приклад розглянемо послідовність дій, задану в операторі
*р++ = 10;
Операції разадресації і інкремента мають однаковий пріоритет і виконуються справа наліво, але, оскільки інкремент постфіксний, він виконується після виконання операції привласнення. Таким чином, спочатку за адресою, записаною в покажчику р, буде записане значення 10, а потім покажчик буде збільшений на кількість байт, відповідне його типу. Те ж саме можна записати докладніше:
*р = 10; р++;
Вираз (*р)++, навпаки, інкрементіруєт значення, на яке посилається покажчик.
Унарна операція отримання адреси & застосовна до величин, що мають ім'я і розміщеним в оперативній пам'яті. Таким чином, не можна одержати адресу скалярного виразу, неіменованої константи або регістрової змінної. Приклади операції наводилися вище.
Ідентифікатор масиву є константним покажчиком на його нульовий елемент. Наприклад, для масиву з попереднього лістингу ім'я b - це те ж саме, що &b[0], а до i-му елементу масиву можна звернутися, використовуючи вираз *(b+i). Можна описати покажчик, привласнити йому адресу почала масиву і працювати з масивом через покажчик. Наступний фрагмент програми копіює всі елементи масиву а в масив b:
Int a[100], b[100];
int *pa = а;
int *pb = b:
for (int i = 0; i<100; i++)
*pb++ = *pa++; // або pb[i]= pa[i];
Динамічні масиви створюють за допомогою операції new, при цьому необхідно вказати тип і розмірність, наприклад:
int n = 100;
float *р = new float [n];
У цьому рядку створюється змінна-покажчик на float, в динамічній пам'яті відводиться безперервна область, достатня для розміщення 100 елементів речовинного типа, і адреса її початку записується в покажчик р. Динамічні масиви не можна при створенні ініціалізувати, і вони не обнуляються.
Перевага динамічних масивів полягає в тому, що розмірність може бути змінною, тобто об'єм пам'яті, що виділяється під масив, визначається на етапі виконання програми. Доступ до елементів динамічного масиву здійснюється точно так, як і до статичних, наприклад, до елементу номер 5 приведеного вище масиву можна звернутися як р[5] або *(р+5).
Альтернативний спосіб створення динамічного масиву - використання функції malloc бібліотеки C:
int n = 100:
float *q = (float *) malloc(n * sizeof(float));
Операція перетворення типу, записана перед зверненням до функції malloc, потрібна тому, що функція повертає значення покажчика типу void*, а ініціалізувався покажчик на float.
Пам'ять, зарезервована під динамічний масив за допомогою new [], повинна звільнятися операцією delete []. а пам'ять, виділена функцією mallос - за допомогою функції free, наприклад:
delete [] p; free (q);
При невідповідності способів виділення і звільнення пам'яті результат не визначений. Розмірність масиву в операції delete не указується, але квадратні дужки обов'язкові.
Багатовимірні масиви задаються вказівкою кожного вимірювання в квадратних дужках, наприклад, оператор
int matr [5][6];
задає опис двовимірного масиву з 5 рядків і 6 стовпців. У пам'яті такий масив розташовується в послідовних осередках відрядковий. Багатовимірні масиви розміщуються так, що при переході до наступного елементу найшвидше змінюється останній індекс. Для доступу до елементу багатовимірного масиву указуються всі його індекси, наприклад, matr[i][j], або більш екзотичним способом: *(matr[i]+j) або *(*(matr+i )+j). Це можливо, оскільки matr[i] є адресою почала i-й рядки масиву.
Для створення динамічного багатовимірного масиву необхідно вказати в операції new все його розмірності (найлівіша розмірність може бути змінною) наприклад:
int nstr = 5;
int ** m = (int **) new int [nstr][10];
Більш універсальний і безпечний спосіб виділення пам'яті під двовимірний масив, коли обидві його розмірності задаються на етапі виконання програми приведений нижче:
int nstr, nstb;
cout << " Введіть кількість рядків і стовпців :"; cin >> nstr , nstb; int **a = new int *[nstr]; // 1 for(int i = 0; i<nstr; i++) // 2 а[i] = new int [nstb]; // 3
У операторі 1 оголошується змінна типу покажчик на покажчик на int і виделяєтся пам'ять під масив покажчиків на рядки масиву (кількість рядків nstr). У операторі 2 організовується цикл для виділення пам'яті під кожною строь масиву. У операторі 3 кожному елементу масиву покажчиків на рядки привласнюється адреса почала ділянки пам'яті, виділеного під рядок двовимірного масиву. Кожен рядок складається з nstb елементів типу int.
Звільнення пам'яті з-під масиву з будь-якою кількістю вимірювань виконується за допомогою операції delete []. Покажчик на константу видалити не можна.
