Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Всі відповіді СП.docx
Скачиваний:
10
Добавлен:
28.01.2023
Размер:
217.28 Кб
Скачать
    1. Розкрити сутність адресної арифметики при роботі з вказівниками.

Адресна арифметика в мові програмування С++ закладається в одному правилі – при арифметичних діях з вказівниками вказівники змініють адрес на який взають пропорційно розміру свого типу даних, наприклад ptr + 1 не повертає наступну будь-яку адресу пам'яті, яка знаходиться відразу після ptr, а він повертає адресу пам'яті наступного об'єкта, тип якого збігається з типом значення, на яке вказує ptr, іншими словами здвигає своє значення адреси на яку вказує на розмір типу даних. Якщо ptr вказує на адресу пам'яті цілого значення (розмір якого 4 байти), то ptr + 3 повертатиме адресу пам'яті третього цілого значення після ptr. Якщо ptr вказує адресу пам'яті значення типу char, то ptr + 3 повертатиме адресу пам'яті третього значення типу char після ptr. Цю особливість можна використовувати при роботі з масивами, так як зазначення масиву в пам’яті розміщаються один за одним, то маючи вказівник на одним з елементів масиву можна отримати доступ до інших елементів цього масиву. Також потрібно занати, що коли компілятор бачить оператор індексу [], він, насправді, конвертує його в покажчик з операцією додавання та розіменування, тобто, array [n] - це те ж саме, що і * (array + n), де n є цілим числом. Оператор індексу [] використовується з метою зручності, щоб не потрібно завжди пам'ятати про дужки. Також в С++ використовуючи арифметику вказівників можна вийти за область пам’яті масиву, що може призвести до помилок.

    1. Проаналізувати можливості динамічного виділення пам’яті під масиви в мові С++.

Спочатку варто загадати про функції malloc , calloc та realloc.

void *malloc(size_t size);

Функція malloc повертає вказівник типу void на виділений простір або NULL, якщо пам'яті недостатньо. Щоб повернути вказівник на тип, відмінний від void, потрібно використати приведення типів для значення, що повертається.

Щоб виділити пам'ять під цілочисельний масив розміром 10:

int *mass;

size_t len = 10;

mass = malloc( sizeof(int)*len );

void *calloc(size_t number, size_t size);

Calloc так само як і malloc повертає вказівник типу void на виділену пам'ять, проте кожний виділений байт пам’яті ініціалізується 0.

Щоб виділити пам'ять під цілочисельний масив розміром 10:

int *mass;

size_t len = 10;

mass = calloc ( sizeof(int), len );

void *realloc(void *memblock, size_t size);

Функція realloc змінює розмір виділеного блоку пам'яті. Аргумент memblock вказує на початок блоку пам'яті. Якщо параметр memblock має значення NULL, функція realloc поводиться так само, як і функція malloc, виділяючи новий блок розміром size байтів. Якщо параметр memblock не має значення NULL, він має бути вказівником, повернутим попереднім викликом функції calloc, malloc або realloc.

Щоб виділити пам'ять під цілочисельний масив розміром 10, а потім ще додати пам’яті на 10:

long *buffer, *oldbuffer;

size_t size = 10;

oldbuffer = (long *)malloc( 10 * sizeof( long ) )

buffer = realloc( buffer, size + (10 * sizeof( long ))

Слід зауважити що підтримка realloc() уже припинена, і на нових версіях операційної системи Windows ця функція не працює.

Пам'ять яка виділена за допомгою функцій malloc , calloc та realloc потрібно звільнювати за допомогою free(), наприклад free(buffer), де buffer – вказівник на виділену пам'ять.

В С++ також зявився новий, більше безпечний спосіб виділення пам’яті динамічно – оператор new. При вдалому виділенні памяті повертає вказівник на виділену пам'ять, інакше  генерується виключна ситуація bad_alloc.

оператор new може виділяти пам'ять лише для одного екземпляру типу, а для виділення памяті під масив потрібно використати new[].

Приклад використаня:

float * ptrArray;

try

{

// спробувати виділити пам'ять для 10 елементів типу float

ptrArray = new float[10];

}

catch (bad_alloc ba)

{

return -1; // вихід з функції

}

delete[] ptrArray;

Слід зауважити, що для спустошення виділеної пам’яті оператором new потрібно використати оператор delete, а для пам’яті виділеної new[] - delete[]

    1. Проаналізувати роботу з класами пам’яті в мові С++.

Об’єкти класів та класи можуть оголошуватись з такими класами пам’яті:

  • автоматичний (auto);

  • регістровий (register);

  • зовнішній (extern);

  • статичний (static).

  • мінливий (mutable).

Для кожного класу пам’яті визначається власний специфікатор: auto, register, extern, static. Специфікатори класів пам’яті визначають, як повинна зберігатися змінна.

Клас пам’яті auto визначає автоматичні змінні. За замовчуванням, це локальні змінні, які розміщуються у стеку чи внутрішніх регістрах процесора. Час “життя” автоматичної змінної обмежений часом виконання функції (методу) чи блоку, в якому ці змінні описані. При звичайному оголошенні локального об’єкту класу або локальної змінної за зразком

int a;

double x;

char c;

CMyClass MC; // об’єкт класу

ці змінні отримують клас пам’яті auto.

Задавання ключового слова register – це є вказівка компілятору виділити для збереження даних об’єкта не комірки стеку, а внутрішні регістри процесора. Але це не означає, що компілятор обов’язково розмістить дані об’єкта у регістрах процесора. Крім того, компілятор може розмістити дані у кеш-пам’яті. Основною метою оголошення змінної (об’єкту) з ключовим словом register є забезпечення максимально швидкого доступу до цієї змінної та обробки цієї змінної.

register int a;

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

#include "struct.h"

extern int a; // оголошення, що змінна а зовнішньою

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

Клас пам’яті mutable використовується у поєднанні з так званими константними функціями (функціями, які оголошені з ключовим словом const). Якщо константна функція оголошена в класі, то ця функція не може змінювати дані класу. Це є основною метою оголошення константної функції в класі – не допустити модифікацію об’єкту, який її викликає. Але, бувають випадки, коли потрібно змінювати значення даних з допомогою константної функції. Наприклад, у випадку, коли даними класу може бути також складний об’єкт іншого класу, в якому частину даних потрібно змінювати. Щоб вийти з цієї ситуації потрібно використати ключове слово mutable при оголошенні даних класу.

    1. Обгрунтувати алгоритм та представити програмний код для розміщення чотирьох однобайтових змінних у змінній типу int (код мовою С++)

Алгоритм є дуже простим: розіб’ємо 4 байта int на 4 умовниих комірки, де 1 байт – 0 комірка, 4 – 3 комірка. Для запису даних виконає здвиг int до відповідної комірки, потім виконаємо побітове «і» з однобайтовим числом, що дозвилость нам занулити непотрібні біти, а потім виконаємо побітове «або» для запису потрібних «1». Для зчитування потрібно виконати побітове «і» з набором одниниць, що розташовані навпроти потрібної комірки.

Код:

#include <iostream>

using namespace std;

class for_byte_in_int

{

public:

for_byte_in_int()

{

number = 0;

}

void set(char num,char comirka)

{

if (comirka < 4&& comirka >=0)

{

int temp = (((number >> (8 * comirka)) & num) | num) << (8 * comirka);

number |= temp;

}

}

unsigned char get(char comirka)

{

if (comirka < 4 && comirka>=0)

{

int temp;

temp = number & (255 << (8 * comirka));

temp >>= (8 * comirka);

return temp;

}

else

{

return 0;

}

}

private:

int number;

};

int main()

{

for_byte_in_int A1;

A1.set(2, 0);

A1.set(10, 2);

A1.set(14, 1);

A1.set(128, 3);

cout << (int)A1.get(0) << endl;

cout << (int)A1.get(1) << endl;

cout << (int)A1.get(2) << endl;

cout << (int)A1.get(3) << endl;

return 0;

}