Покажчики. Частина 5. Виділення пам‘яті для покажчика. Масиви покажчиків на базові типи, функції, структури, класи
Зміст
- 1. Які помилки програмування можуть виникати при виділенні пам‘яті для покажчика?
- 2. Які є способи виділення пам‘яті для некерованого покажчика (*)? Приклад
- 3. В яких випадках доцільно використовувати масив покажчиків?
- 4. Яка загальна форма опису одновимірного масиву некерованих покажчиків (*)?
- 5. Масив покажчиків на тип int. Приклад
- 6. Масив покажчиків на тип double. Приклад опису. Приклад динамічного виділення пам’яті для елементів масиву
- 7. Масив покажчиків на тип char. Приклад опису та використання
- 8. Приклад сортування масиву рядків з використанням некерованого покажчика
- 9. Як описати масив некерованих покажчиків (*) на функцію? Приклад опису та використання
- 10. Масив некерованих покажчиків (*) на структуру
- 11. Масив некерованих покажчиків (*) на клас. Приклад опису та використання
- 12. Створення динамічного масиву екземплярів класу ClassName**. Кількість екземплярів формується динамічно. Приклад покажчика типу Point**
- Зв’язані теми
Пошук на інших ресурсах:
1. Які помилки програмування можуть виникати при виділенні пам‘яті для покажчика?
Під час опису покажчика на деякий тип обов‘язково треба подбати, щоб покажчик вказував на змінну (об‘єкт), для якого попередньо виділена пам‘ять. Якщо цього не зробити, виникне виключна ситуація. У прикладі продемонстровано спробу використання двох покажчиків, для одного з яких пам‘ять не виділена.
Приклад. Демонстрація виділення пам‘яті для покажчика.
// покажчик на int int * p; // значення покажчика p поки що невизначене int *p2; // значення p2 невизначене int x; // змінна, під яку виділена пам'ять x = 240; p = &x; // покажчик p вказує на елемент, для якого виділена пам'ять *p = 100; // працює, бо p вказує на x, тому x = 100 //*p2 = 100; // помилка, тому що p2 вказує невідомо куди
Пояснимо деякі фрагменти вищенаведеного прикладу. У прикладі оголошується два покажчики на тип int
int * p; int *p2;
Після такого оголошення значення покажчиків невизначене. А це означає, що покажчики вказують на довільні ділянки пам‘яті. Якщо спробувати записати в ці покажчики деяке константне значення, наприклад
*p = 25; *p2 = 100;
тоді виникне виключна ситуація і система поведе себе непередбачувано. Це пояснюється тим, що здійснюється спроба зміни значення за покажчиком, який вказує на довільну ділянку пам‘яті (наприклад, на програмний код а не на дані).
Після присвоєння
p = &x;
покажчику p вказує на змінну x, для якої вже виділена пам‘ять. Тому рядок
*p = 100;
виконується коректно. А рядок коду
*p2 = 100;
є помилкою, тому що покажчик p2 невизначений.
⇑
2. Які є способи виділення пам‘яті для покажчика? Приклад
Існує декілька способів виділення пам’яті для покажчика:
- присвоїти покажчику адресу змінної, для якої вже статично виділена пам‘ять;
- виділити пам‘ять динамічно з використанням оператора new;
- виділити пам‘ять динамічно з використанням спеціальної функції з бібліотеки C++, наприклад, malloc().
Приклад. Способи виділення пам‘яті для покажчика на double.
double x; double *p; // Спосіб 1. Присвоєння адреси змінної, для якої вже виділена пам'ять x = 2.86; p = &x; // p вказує на x *p = -0.85; // x = -0.85 // Спосіб 2. Використання оператора new p = new double; *p = 9.36; // Спосіб 3. Використання функції malloc() p = (double *)malloc(sizeof(double)); *p = -90.3;
У вищенаведеному прикладі в способі 3, щоб використати функцію malloc() потрібно на початку тексту програми підключити модуль <malloc.h>:
#include <malloc.h>
⇑
3. В яких випадках доцільно використовувати масив покажчиків?
Використання масивів покажчиків є ефективним у випадку, коли потрібно компактно розмістити в пам‘яті протяжні об‘єкти даних, якими можуть бути:
- рядки тексту;
- структурні змінні;
- об‘єкти класу.
Масив покажчиків дозволяє зекономити пам‘ять та підвищити швидкодію у випадку оперування рядками змінної довжини.
⇑
4. Яка загальна форма опису одновимірного масиву некерованих покажчиків (*)?
Загальна форма опису одновимірного масиву некерованих покажчиків (*):
тип * ім’я_масиву[розмір]
де
- ім’я_масиву – безпосередньо ім’я одновимірного масиву покажчиків;
- тип – тип, на який вказують покажчики в масиві;
- розмір – розмір масиву покажчиків. У цьому випадку компілятор резервує місце для покажчиків, кількість яких задана в значенні розмір. Розмір резервується для самих покажчиків а не для об‘єктів (даних), на які вони вказують.
⇑
5. Масив покажчиків на тип int. Приклад
Для кожного покажчика в масиві покажчиків на тип int попередньо має бути виділена пам‘ять (дивіться п. 1).
Приклад. Масив з 10 покажчиків на int.
// масив покажчиків на int int * p[10]; // масив з 10 покажчиків на int int x; // змінна, під яку виділена пам'ять x = 240; p[0] = &x; // це добре, покажчик вказує на елемент, для якого виділена пам'ять *p[0] = 100; // працює, бо p[0] вказує на x, тому x = 100 //*p[1] = 100; // помилка, тому що p[1] вказує невідомо куди
⇑
6. Масив покажчиків на тип double. Приклад опису. Приклад динамічного виділення пам’яті для елементів масиву
Масив покажчиків на дійсні типи оголошується так само як і на цілочисельні типи (див. п.2).
Приклад. Масив з 20 покажчиків на тип double.
// масив покажчиків на double double *p[20]; // значення покажчиків невизначене // пам'ять виділяється динамічно в циклі for (int i=0; i<20; i++) { p[i] = new double; // динамічне виділення пам'яті для покажчиків *p[i] = i+0.5; }
⇑
7. Масив покажчиків на тип char. Приклад опису та використання
Приклад. Опис масиву на тип char* з одночасною ініціалізацією.
// масив покажчиків на char char * ap[] = { "C/C++", "Java", "C#", "Object Pascal", "Python" }; // відображення масиву покажчиків на char в компоненті listBox1 String ^str; // допоміжна змінна listBox1->Items->Clear(); for (int i=0; i<5; i++) { str = gcnew String(ap[i]); listBox1->Items->Add(str); }
У вищенаведеному прикладі покажчики отримують початкове значення, яке рівне адресі початку в пам‘яті відповідних рядкових літералів.
⇑
8. Приклад сортування масиву рядків з використанням некерованого покажчика
Використання покажчиків дає свої переваги, коли потрібно сортувати масиви рядків. Сортування з використанням покажчиків відбувається швидше, оскільки не потрібно копіювати рядки при зміні їх позицій в масиві рядків. Фактично, змінюють позиції тільки самі покажчики. А це відбувається швидше ніж перестановка рядків.
Приклад. Сортування рядків методом вставки. Запропоновано метод без використання функції strcmp() з модуля <string.h>. Замість цього використовується функція Compare() класу System.String. Також продемонстровано відображення рядків, на які вказує масив покажчиків, в елементі управління listBox1 (тип ListBox).
// масив покажчиків на char char * ap[] = { "C/C++", "Java", "C#", "Object Pascal", "Python" }; int n = 5; // кількість рядків в ap[] // сортування рядків методом вставки for (int i=0; i<n-1; i++) for (int j=i; j>=0; j--) { // допоміжні змінні String ^s1 = gcnew String(ap[j]); // перетворення з char* в String String ^s2 = gcnew String(ap[j+1]); // використання методу Compare з класу System.String int res; // допоміжна змінна res = s1->Compare(s1,s2); if (res==1) // s1>s2 { // поміняти покажчики місцями char * tp; tp = ap[j]; ap[j] = ap[j+1]; ap[j+1] = tp; } } // відображення масиву покажчиків на char в компоненті listBox1 String ^str; // допоміжна змінна listBox1->Items->Clear(); for (int i=0; i<5; i++) { str = gcnew String(ap[i]); listBox1->Items->Add(str); }
У вищенаведеному прикладі, фактично, відбувається сортування покажчиків за значеннями на які вони вказують.
⇑
9. Як описати масив некерованих покажчиків (*) на функцію? Приклад опису та використання
Масив некерованих покажчиків (*) на функцію можна описувати тільки для консольних додатків. Якщо програма розробляється за шаблоном Windows Forms у середовищі CLR, то використовувати масив некерованих покажчиків (*) на функції не вдасться.
Нехай дано 4 функції, які отримують два операнди типу int і здійснюють такі операції над ними:
- додавання, функція Add();
- віднімання, функція Sub();
- множення, функція Mul();
- ділення, функція Div().
Реалізація функцій має наступний вигляд:
// Функція Add, повертає x+y int Add(int x, int y) { return x+y; } // Функція Sub, повертає x-y int Sub(int x, int y) { return x-y; } // Функція Mul, повертає x*y int Mul(int x, int y) { return x*y; } // Функція Div, повертає x/y int Div(int x, int y) { return (int)(x/y); }
Демонстрація виклику функцій через масив покажчиків:
// тільки для консольного додатку // масив покажчиків на функцію int (*p[4])(int, int); int res_add, res_sub, res_mul, res_div; p[0] = Add; // p[0] вказує на Add res_add = (*p[0])(7, 5); // res = 7+5 = 12 p[1] = Sub; // p[1] вказує на Sub res_sub = (*p[1])(7, 5); // res = 7-5 = 2 p[2] = Mul; // p[2] вказує на Mul res_mul = (*p[2])(7, 5); // res = 7*5 = 35 p[3] = Div; // p[3] вказує на Div res_div = (*p[3])(7, 5); // res = 7/5 = 1
⇑
10. Масив некерованих покажчиків (*) на структуру
У Visual C++ масив покажчиків на структуру можна описати як для некерованих покажчиків (*) так і для керованих покажчиків (^). У даному прикладі розглядається масив некерованих покажчиків (*).
Більш детально про види покажчиків у C++ описується тут.
Приклад. Нехай задано структуру, яка описує точку (піксель) на екрані монітору. У середовищі MS Visual Studio якщо проект створений за шаблоном Windows Forms Application, то структура має бути описана за межами класу головної форми програми Form1.
// структура, що описує піксель на екрані монітора struct MyPoint { int x; int y; int color; // колір };
Якщо структура описана в іншому модулі, наприклад “MyStruct.h”, тоді на початку файлу модуля основної форми потрібно додати рядок
#include "MyStruct.h"
Використання масиву покажчиків на структуру MyPoint:
// масив некерованих покажчиків на структуру MyPoint *p[3]; // виділення пам'яті для масиву структурних змінних for (int i=0; i<3; i++) { p[i] = new MyPoint; } // заповнення значеннями полів структур for (int i=0; i<3; i++) { p[i]->x = i+5; // довільні значення x, y p[i]->y = 2*i+14; p[i]->color = i; // довільне значення кольору }
⇑
11. Масив некерованих покажчиків (*) на клас. Приклад оголошення та використання
Масив покажчиків на клас визначається так само як і масив покажчиків на структуру.
Приклад. Нехай задано клас, що описує точку на екрані монітора (піксель).
Клас описаний у двох модулях:
- модуль “MyPixelClass.h” визначає оголошення класу, його методів (функцій) та полів (внутрішніх змінних);
- модуль “MyPixelClass.cpp” визначає реалізацію методів (функцій), оголошених в класі (в модулі “MyPixelClass.h”).
Текст модуля “MyPixelClass.h” (оголошення класу):
#pragma once // клас оголошено без ключового слова ref class MyPixelClass { int x; int y; int color; public: MyPixelClass(void); int GetX(void); int GetY(void); int GetColor(void); void SetXYColor(int nx, int ny, int nc); };
Текст модуля “MyPixelClass.cpp” (реалізація класу):
#include "StdAfx.h" #include "MyPixelClass.h" // Реалізація методів класу MyPixelClass MyPixelClass::MyPixelClass(void) { x = y = color = 0; } int MyPixelClass::GetX(void) { return x; } int MyPixelClass::GetY(void) { return y; } int MyPixelClass::GetColor(void) { return color; } void MyPixelClass::SetXYColor(int nx, int ny, int nc) { x = nx; y = ny; color = nc; }
Демонстрація використання масиву покажчиків на клас MyPixelClass з іншого програмного коду (наприклад, обробник події кліку на кнопці):
// масив некерованих покажчиків на клас MyPixelClass *mp[5]; // виділення пам'яті для об'єктів класу, // на які будуть вказувати покажчики for (int i=0; i<5; i++) mp[i] = new MyPixelClass; // заповнення об'єктів класу довільними значеннями // демонстрація виклику методу SetXYColor() for (int i=0; i<5; i++) mp[i]->SetXYColor(i+5, 2*i+8, i); // демонстрація інших методів класу int d; d = mp[1]->GetX(); // d = 6 d = mp[2]->GetColor(); // d = 2 d = mp[3]->GetY(); // d = 2*3+8 = 14
⇑
12. Створення динамічного масиву екземплярів класу типу ClassName**. Кількість екземплярів формується динамічно. Приклад покажчика типу Point**
У попередніх прикладах (пп. 10, 11) кількість покажчиків на клас була наперед відома і оголошення масиву екземплярів класу мало приблизно такий вигляд
ClassName* AP[n];
тут
- ClassName – ім’я деякого класу (типу);
- AP – назва масиву покажчиків;
- n – константа, що визначає кількість елементів масиву.
Однак, бувають випадки, коли потрібно створити масив екземплярів класу, кількість яких у програмі наперед невідома і формується динамічно. У цьому випадку оголошується покажчик на покажчик класу за зразком нижче
ClassName** AP;
Потім, для цього покажчика пам’ять виділяється приблизно наступним чином
... AP = new ClassName* [n]; for (int i = 0; i < n; i++) AP[i] = new ClassName(parameters_list); ...
тут
- AP – масив покажчиків на тип ClassName;
- parameters_list – список параметрів конструктора, на основі якого створюється екземпляр класу;
- n – кількість покажчиків, для яких потрібно виділити пам’ять.
Приклад.
У нижченаведеному прикладі демонструється створення та використання повністю динамічного масиву покажчиків на прикладі класу Point. Для цього у функцїі main() виконуються такі операції:
- оголошується масив AP покажчиків на тип Point;
- виділяється пам’ять для масиву AP та його елементів;
- масив заповнюється даними;
- виводиться вміст масиву на екран;
- звільняється пам’ять, що була виділена для елементів масиву.
#include <iostream> using namespace std; // Клас, що описує точку на координатній площині class Point { private: int x, y; // внутрішні поля класу public: // Конструктор без параметрів Point() :Point(0, 0) // делегування конструктору з двома параметрами { } // Конструктор з двома параметрами Point(int x, int y) { this->x = x; this->y = y; } // Аксесори int GetX() { return x; } int GetY() { return y; } void SetXY(int _x, int _y) { x = _x; y = _y; } // Відстань від точки до початку координат double Length() { return sqrt(x * x + y * y); } }; void main() { // Динамічне виділення пам'яті для масиву покажчиків Point** // 1. Оголосити масив покажчиків типу Point Point** AP; // 2. Задати кількість покажчиків int n; cout << "n = "; cin >> n; // 3. Виділити пам'ять для масиву з n покажчиків типу Point // Блок try-cath необхідний для коректного завершення програми, // у випадку, якщо при виділенні пам'яті виникла помилка. try { // 3.1. Спроба виділення пам'яті для масиву з n покажчиків в цілому AP = new Point * [n]; // 3.1. Спроба виділення пам'яті для кожного елементу масиву типу Point // та заповнення його значеннями for (int i = 0; i < n; i++) AP[i] = new Point(i, i + i); // 4. Якщо пам'ять виділено і масив сформовано, то вивести його for (int i = 0; i < n; i++) cout << AP[i]->GetX() << " - " << AP[i]->GetY() << endl; } catch (bad_alloc ba) { // Виконання дій, якщо виникла помилка cout << ba.what() << endl; return; } // 5. Звільнити пам'ять, що була виділена під масив AP // 5.1. Звільнити пам'ять, що була виділена для кожного елементу масиву for (int i = 0; i < n; i++) delete AP[i]; // 5.2. Звільнити пам'ять, що була виділена для масиву в цілому delete[] AP; }
⇑
Зв’язані теми
- Покажчики. Частина 1. Загальні поняття. Типи покажчиків. Керовані та некеровані покажчики. Покажчик на функцію. Приклади використання
- Покажчики. Частина 2. Некеровані покажчики. Операції над покажчиками. Покажчик на тип void. Виділення пам’яті. Нульовий покажчик. Операція взяття адреси &
- Покажчики. Частина 3. Некеровані покажчики і масиви. Покажчик на структуру. Покажчик на клас
- Покажчики. Частина 4. Покажчики і рядки символів. Використання покажчиків при перетворенні і конвертуванні рядків
- Покажчики. Частина 6. Складені native та managed типи даних. Керовані покажчики (^) в середовищі CLR. Виділення пам’яті. Кваліфікатори ref та value. Керовані покажчики (^) на структури, класи
- Масиви. Визначення масиву. Одновимірні масиви. Ініціалізація масиву
- Структури. Складені типи даних. Шаблон структури. Структурна змінна. Структури в середовищі CLR. Оголошення та ініціалізація структурної змінної