Указатели. Часть 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


Связанные темы