Покажчики. Частина 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() класу Sytstem.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


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