C++. Покажчики. Частина 5. Виділення пам‘яті для покажчика. Масиви покажчиків на базові типи, функції, структури, класи




Покажчики. Частина 5. Виділення пам‘яті для покажчика. Масиви покажчиків на базові типи, функції, структури, класи


Зміст


Пошук на інших ресурсах:

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;
}

 


Зв’язані теми