
Міністерство освіти і науки україни
Національний університет “Львівська політехніка”
ДИНАМІЧНЕ ВИДІЛЕННЯ ПАМ’ЯТІ
ІНСТРУКЦІЯ
до лабораторної роботи № 12 з курсу
“Основи програмування”
для базового напрямку “Програмна інженерія”
Затверджено
На засіданні кафедри
програмного забезпечення
Протокол № від
ЛЬВІВ – 2011
1. МЕТА РОБОТИ
Мета роботи – навчитися створювати нові типи даних у вигляді структур та об’єднань, а також розробляти алгоритми їх обробки засобами мови С++.
2. ТЕОРЕТИЧНІ ВІДОМОСТІ
2.1 Динамічні змінні
Бувають ситуації, коли заздалегідь невідомо, скільки даних потрібно обробити у програмі. Особливо це стосується роботи з масивами – при оголошенні статичного масиву для нього виділяється жорстко фіксована пам’ять. Однак, подекуди ця жорстко фіксована кількість елементів масиву може перевищувати реальну кількість даних або ж навпаки – бути недостатньою. Якщо масив не може вмістити потрібну кількість даних, доведеться переписувати і повторно компілювати програму. Один з типових підходів при використанні статичних масивів – виділити пам’ять з запасом, а заповнити даними лише частину масиву і в якійсь змінній зберігати фактичну кількість елементів масиву. У цьому випадку виділена пам’ять використовується неефективно. Крім того, статичні змінні “живуть” визначений час – або до кінця роботи програми, або до кінця її певного блока. Взагалі кажучи, часто статична змінна вже не є потрібною, а все ще займає пам’ять.
У С++ існує можливість динамічно розподіляти пам’ять у ході виконання програми, тобто, виділяти стільки пам’яті, скільки її насправді потрібно, і звільнити її тоді, коли вона вже не буде потрібною. Пам’ять виділяється з так званої вільної пам’яті. Розмір вільної пам’яті залежить від операційної системи та моделі пам’яті комп’ютера. Динамічні змінні не мають імен, а доступ до них здійснюється через вказівники. Динамічні об’єкти розміщаються у “кучі” (англомовний термін-відповідник: heap). Для управління вільною пам’яттю у С++ використовуються пара операторів new та delete та “успадкований” від мови С набір функцій malloc(), calloc(), realloc() та free().
2.2 Використання операторів new та delete
Оператор new у С++ дозволяє виділити потрібну кількість пам’яті. В якості операнда new використовує ім’я типу. Якщо пам’ять буде успішно виділена, то new поверне вказівник на початок виділеної ділянки пам’яті, а інакше new поверне значення NULL. Доброю практикою є пересвідчитися, чи результат new не дорівнює NULL, замість одразу звертатися до пам’яті, припускаючи, що вона була успішно виділена. Вказівник слід оголошувати відповідного типу.
Наприклад:
char* p = new char; /* створення динамічної змінної розміром, визначеним типом char, тобто, розміром в 1 байт */ int* p = new int; /* створення динамічної змінної розміром, визначеним типом int */
char* p = new char[100]; /* створення масиву розміром у 100 змінних типу char */
int* p = new int[100]; /* створення масиву розміром 100 змінних типу int */
struct student{
char surname[50];
char name[50];
int mark;
};
int k = 10;
student * p = new student[k]; /* створення масиву розміром у k змінних типу student; k є саме змінною, а не константою, що було б недопустимо при створенні статичного масиву */
Звільнення пам’яті здійснюється оператором delete:
delete p; /* де p є вказівником на початок раніше виділеної області пам’яті */
Приклад. В наступній програмі користувача запрошують ввести кількість елементів масиву, в динамічній пам’яті створюється масив вказаного розміру, який заповнюється даними, введеними з клавіатури, що згодом виводяться на екран у зворотному порядку.
#include "stdafx.h"
#include <iostream>
#include "conio.h"
using namespace std;
int main() {
int N;
cout<<"Haw many items does our array contain?"<<endl;
cin>>N;
int * pp = new int[N];
int *p = pp; /* за допомогою вказівника p будемо переміщатися по динамічному масиву, тоді як вказівник pp лишимо незмінним – він буде вказувати на початок блока пам'яті */
if(p) { /* якщо пам'ять була успішно виділена */
for (int i=0;i<N; i++) { /* ввід N елементів масиву */
cout<<"Enter "<<(i+1)<<"-th item"<<endl;
cin>>*p;
p++;
}
p = pp + (N-1); /* встановлюємо вказівник p на останній елемент масиву та у циклі виводимо на екран всі N елементів масиву*/
for (int i=N;i>0; i--) {
cout<<*p<<endl;
p--;
}
delete pp;
}
_getch();
return 0;
}
З динамічним масивом можна працювати які зі звичайним масивом – через індекси:
Та ж сама програма з використанням доступу до елементів динамічного масиву через індекси:
#include "stdafx.h"
#include <iostream>
#include "conio.h"
using namespace std;
int main() {
int N;
cout<<"Haw many items does our array contain?"<<endl;
cin>>N;
int * p = new int[N];
if(p) {/* якщо пам'ять була успішно виділена */
for (int i=0;i<N; i++)
{ /* ввід N елементів масиву */
cout<<"Enter "<<(i+1)<<"-th item"<<endl;
cin>>p[i];
}
for (int i=N-1;i>=0; i--)
{
cout<<p[i]<<endl;
}
}
delete p;
_getch();
return 0;
}
Приклад. В наступній програмі резервується 100 елементів масиву типу int, а використовується лише стільки, скільки користувач задає з клавіатури – ввід припиняється, коли введено нечислові дані.
#include "stdafx.h"
#include <iostream>
#include "conio.h"
using namespace std;
int main()
{
int * pp = new int[100];
int * p = pp;
if(p) {
char str[10];
int i = 1;
p = pp+1;
while(i<100)
{
cout<<"Enter "<<i<<"-th item"<<endl;
cin>>str;
if(!atoi(str)) break;
else
{
*p = atoi(str);
p++;
i++;
}
}
*pp = i - 1;
if(*pp)
for (p = pp+1;p!=pp+*pp+1; p++)
{
cout<<*p<<endl;
}
}
delete pp;
_getch();
return 0;
}
Приклад. У динамічній пам’яті виділяється пам’ять для зберігання 100 екземплярів структур student. Полям екземплярів структур присвоюються значення, задані з клавіатури. Кожного разу користувачеві пропонується ввести прізвище наступного студента або ж слово ‘end’. Введені дані розцінюються як рядки символів, а в поле структури mark, що є цілим числом, записується результат функції atoi. Якщо введено нечислові дані, відбудеться вихід з циклу. Далі, усі екземпляри структур виводяться на екран у тому ж порядку, в якому вони були задані.
#include "stdafx.h"
#include <iostream>
#include "conio.h"
using namespace std;
int main()
{
struct student{
char surname[50];
char name[50];
int mark;
};
int N;
student * pp = new student[100]; /* виділяється фіксована ділянка динамічної пам’яті з запасом */
if(pp){ /* якщо пам'ять була успішно виділена */
student * p = pp; /* вказівником p переміщатимемося по динамічному масиву екземплярів структур */
char str[50];
int i = 1;
while(i<100)
{
cout<<"Enter the surname of "<<i<<"-th student or the word 'end'"<<endl;
cin>>str;
if(!strcmp(str,"end")) break; /* якщо користувач ввів слово ‘end’, відбувається вихід з циклу */
else /* інакше продовжуємо задавати інші поля того ж екземпляра структури */
{
strcpy(p->surname,str);
cout<<"Enter the name of "<<i<<"-th student"<<endl;
cin>>str;
strcpy(p->name,str);
cout<<"Enter the mark of "<<i<<"-th student"<<endl;
cin>>str;
if(atoi(str))
p->mark = atoi(str);
else break;
i++;
p++;
}
}
N = (i-1);
for(p=pp; p!=pp+N;p++)
{
cout<<p->surname<<"\t";
cout<<p->name<<"\t";
cout<<p->mark<<"\t";
cout<<endl;
}
}
delete pp;
_getch();
return 0;
}
Приклад. В наступній програмі передбачається заповнення полів екземплярів структур даними, зчитаними з текстового файлу (один рядок файлу містить дані для одного екземпляра структури, розділені знаками табуляції). Спочатку зчитується кількість рядків у файлі (файл відкривається для читання і зчитується по рядках, причому кожного разу спеціально оголошена змінна збільшується на 1). Після завершення читання файл закривається. Тоді в динамічній пам'яті виділяється область для зберігання стількох екземплярів структур, скільки рядків знайдено у текстовому файлі. Файл знову відкривається для читання – у циклі while поки не досягнуто кінець файлу на кожній ітерації зчитується один рядок. Кожен зчитаний рядок розділяється на окремі дані за допомогою функції strtok. Відокремлені дані записуються на кожній ітерації у відповідні поля поточного екземпляра структури у динамічній пам’яті (там, де це потрібно, виконується приведення типів).
#include "stdafx.h"
#include <iostream>
#include <fstream>
#include "conio.h"
using namespace std;
int main() {
struct student{
char surname[50];
char name[50];
int mark;
};
char strfromfile[200]; /* у цю змінну будемо зчитувати вміст файлу по рядках */
ifstream f1("student.txt");
int k = 0; /* у змінній k зберігатимемо кількість рядків у текстовому файлі student.txt */
while(!f1.eof())
{
f1.getline(strfromfile,200);
k++;
}
f1.close();/* закриваємо файл */
student * pp = new student[k]; /* виділяємо рівно стільки вільної пам’яті, скільки потрібно для створення масиву екземплярів структури student */
if(pp) { /* якщо пам'ять була успішно виділена */
student * p = pp;
char str[50];
int i = 1;
ifstream f2("student.txt"); /* знову відкриваємо файл student.txt для читання */
char *pw; /* вказівник для розділення зчитаних рядків на окремі дані */
while(!f2.eof()) /* поки не досягнутий кінець файлу */
{
f2.getline(strfromfile,200); /* читаємо поточний рядок у змінну strfromfile, припускаючи, що довжина кожного рядка файлу не перевищує 200 символів; на кожній ітерації циклу курсор посувається на одну позицію */
int j = 0;
pw = strtok(strfromfile, "\t"); /* дані розділяються за символами табуляції */
while(pw !=NULL)
{
switch(j) /* на кожній ітерації циклу while зі зчитаного текстового рядка виділяється одна певна частина даних, яка записується у відповідне поле структури */
{
case 0: strcpy(p->surname,pw);break;
case 1: strcpy(p->name,pw);break;
case 2: p->mark=atoi(pw);break;
}
pw = strtok (NULL, "\t");
j++;
}
p++;
}
/* вивід полів усіх структур на екран; поля однієї структури розділяються знаками табуляції, а різні екземпляри структури записуються в різних рядках */
for(p=pp; p!=pp+k; p++)
{
cout<<p->surname<<"\t";
cout<<p->name<<"\t";
cout<<p->mark<<"\t";
cout<<endl;
}
}
delete pp;
_getch();
return 0;
}