search

Динамическое выделение памяти для структурной переменной

Выделение и освобождение памяти для структурной переменной. Пусть дана структура Date, которая имеет следующее описание:


// структура, которая описывает день года
struct Date
{
    int day;
    int month;
    int year;
};

Тогда, чтобы выделить и использовать память для переменной типа struct Date нужно написать приблизительно следующий код:


#include "stdafx.h"
#include <iostream>
using namespace std;

// структура, которая описывает день года
struct Date
{
    int day;
    int month;
    int year;
};

int main()
{
    // выделение памяти для структурной переменной оператором new
    struct Date * pd; // указатель на struct Date

    try
    {
        pd = new struct Date; // попытка выделить память
    }
    catch (bad_alloc ba)
    {
        cout << "Память не выделена" << endl;
        return -1;
    }

    // если память выделена, то
    // использование pd в программе,
    // установить дату 11.02.2029
    pd->day = 11;
    pd->month = 2;
    pd->year = 2029;

    cout << pd->month << endl;

    // освободить память использованную под переменную pd
    delete pd;

    return 0;
}

Пример динамического выделения памяти для объекта класса

В примере динамично выделяется память для указателя на объект класса CDayWeek. Пример реализован для приложения типа Console Application.


#include "stdafx.h"
#include <iostream>
using namespace std;

// класс, который описывает день недели
class CDayWeek
{
    int d;

    public:
    // конструктор
    CDayWeek() { d = 1; }

    // методы доступа
    int Get(void) { return d; }
    void Set(int nd)
    {
        if ((1<=nd)&&(nd<=7))
            d = nd;
    }
};

int main()
{
    // выделение памяти для объекта класса оператором new
    CDayWeek *p;

    try
    {
        // попытка выделить память для указателя p
        p = new CDayWeek(); // вызывается конструктор класса CDayWeek
    }
    catch (bad_alloc ba)
    {
        cout << "Память не выделена" << endl;
        cout << ba.what() << endl;
        return -1;
    }

    // если память выделена, то использование класса
    p->Set(3);

    cout << p->Get() << endl;

    // освободить память, на которую указывает указатель p
    delete p;

    return 0;
}

Как выделить память для массива оператором new? Общая форма

Оператор new может быть использован для выделения памяти для массива. Общая форма оператора new в случае выделения памяти для массива:


ptrArray = new type[size];

ptrArray – имя массива, для которого выделяется память; type – тип элементов массива. Тип элементов может быть базовый (int, float, …) или другой структура, класс и т.п.); size – размер массива (количество элементов).

Как освободить память выделенную для массива оператором delete[]? Общая форма

Для выделения памяти, выделенной под массив, оператор delete имеет следующую форму использования:


delete[] ptrArray;

ptrArray – имя массива, для которого выделяется память.

Пример динамического выделения и освобождения памяти для массива указателей на базовый тип

В примере выделяется память для массива указателей на тип float. Затем элементы массива заполняются произвольными значениями. После этого, выделенная память освобождается оператором delete[].


// объявить массив указателей на float
float * ptrArray;

ptrArray = new float[10]; // выделить память для 10 элементов типа float

// использование массива
int d;
for (int i = 0; i < 10; i++)
    ptrArray[i] = i * 2 + 1;

d = ptrArray[3]; // d = 7

delete[] ptrArray; // освободить память, выделенную под массив

Пример выделения памяти для массива структурных переменных и его использование

В примере демонстрируется выделение и освобождение памяти для массива из 3-х структур типа TStudent. Также продемонстрированы способы доступа к полям заданного элемента в массиве структур.


#include "stdafx.h"
#include <iostream>
using namespace std;

// структура, описывающая информацию о студенте
struct TStudent
{
    string name; // Ф.И.О. студента
    string numBook; // номер зачетной книжки
    int course; // курс обучения
    float rank; // рейтинг студента
};

int main()
{
    // выделение памяти для массива структур
    int n_students = 3; // количество студентов
    TStudent * pS; // указатель, массив структур

    try
    {
        // попытка выделить память для массива структур
        pS = new struct TStudent[n_students];
    }
    catch (bad_alloc ba)
    {
        cout << "Память не выделена." << endl;
        cout << ba.what() << endl;
        return -1;
    }

    // если память выделена, то использование массива структур
    // доступ к элементу массива с индексом 0
    pS->name = "Johnson J.";
    pS->numBook = "12389239";
    pS->rank = 3.93;
    pS->course = 4;

    // доступ к элементу массива с индексом 1 - тоже работает
    (pS + 1)->name = "Sullivan";
    (pS + 1)->numBook = "20930032";
    (pS + 1)->rank = 4.98;
    pS[1].course = 3;

    // доступ к элементу массива с индексом 2 - еще один способ
    pS[2].name = "Abram F.D.";
    pS[2].numBook = "l2l28983";
    pS[2].rank = 4.32;
    pS[2].course = 5;

    // вывод некоторых значений
    cout << pS->name.c_str() << endl; // "Johnson J."
    cout << pS[1].rank << endl;       // 4.98
    cout << (pS + 2)->numBook.c_str() << endl;       // "12128983"

    // освободить память, на которую указывает указатель p
    delete[] pS;

    return 0;
}

Пример выделения и освобождения памяти для массива объектов. Инициализация массива объектов

В примере демонстрируется выделение памяти для массива объектов оператором new. После использования массива, происходит уничтожение выделенной памяти оператором delete.


#include "stdafx.h"
#include <iostream>
using namespace std;

// класс, реализующий месяц года
class CMonth
{
    private:
    int month;

    public:
    // конструктор без параметров, если используется массив объектов,
    // то этот конструктор есть обязательным
    CMonth() { month = 1; }

    // конструктор с 1 параметром, для создания массива объектов не подходит,
    // подходит только для одиночних объектов
    CMonth(int nmonth)
    {
        if ((1 <= nmonth) && (nmonth <= 12))
            month = nmonth;
        else
            month = 1;
    }

    // методы доступа
    int Get(void) { return month; }
    void Set(int nmonth)
    {
        if ((1 <= nmonth) && (nmonth <= 12))
            month = nmonth;
    }
};

int main()
{
    // выделение памяти для массива объектов класса
    int nMonths = 12;
    CMonth * pM;

    try
    {
        // попытка выделить память для массива из 12 объектов
        pM = new CMonth[12]; // для каждого элемента вызывается конструктор без параметров
    }
    catch (bad_alloc ba)
    {
        cout << "Память не выделена" << endl;
        cout << ba.what() << endl;
        return -1;
    }

    // если память выделена, то заполнение массива значениями
    for (int i = 1; i <= 12; i++)
        pM[i - 1].Set(i);

    // вывод некоторых значений
    cout << pM[3].Get() << endl; // 4
    cout << pM[5].Get() << endl; // 6

    // освободить память, на которую указывает указатель p
    delete[] pM;

    return 0;
}

CMonth()

В вышеприведенном коде, внутренняя переменная в массиве объектов инициализируется значением 1, так как такое значение задано в конструкторе без параметров CMonth()


...

// конструктор без параметров есть обязательным, так как он выступает как инициализатор массива
CMonth() { month = 1; }

...

Этот конструктор выступает инициализатором массива. Однако, в классе реализован еще один конструктор – конструктор с 1 параметром или параметризованный конструктор. Согласно синтаксису C++, массив объектов не может быть инициализирован параметризованным конструктором. Поэтому, в классе CMonth обязательно должен быть реализован конструктор без параметров.

Если конструктор без параметров CMonth() убрать из кода класса, то невозможно будет выделить память для массива объектов. Можно будет выделять память для одиночных объектов, но не для массива.

Вывод: если нужно выделить память для массива объектов некоторого класса, то этот класс обязательно должен иметь реализацию конструктора без параметров.

Как перераспределить память

Eсли нужно динамически увеличить (уменьшить) размер массива? Перераспределение памяти для структур, инициализация структур. Пример

В примере демонстрируется процесс перераспределения памяти для типа структуры DayWeek. Выделение и перераспределение памяти динамически есть основным преимуществом этого способа по сравнению со статическим выделением памяти. Память в программе можно выделять когда нужно и сколько нужно.

В структуре DayWeek реализован конструктор без параметров (по умолчанию), который инициализирует массив структур значением по умолчанию (d=1).


#include "stdafx.h"
#include <iostream>
using namespace std;

// структура, описывающая день недели
struct DayWeek
{
    int day;

    // структура как и класс тоже может иметь конструктор
    DayWeek() { day = 1; }
};

int main()
{
    // выделение памяти для массива структур и их инициализация
    DayWeek * p;

    try
    {
        // попытка выделить память для массива из 5 структур типа DayWeek
        p = new DayWeek[5];
    }
    catch (bad_alloc ba)
    {
        cout << "Память не выделена" << endl;
        cout << ba.what() << endl;
        return -1;
    }

    // если память выделена, то заполнение массива значениями
    for (int i = 0; i < 5; i++)
        p[i].day = i + 1;

    // использование массива из 5 структур
    int d;
    d = (p + 1)->day; // d = 2

    // Демонстрация увеличения (перераспределения) памяти,
    // нужно увеличить до 7 элементов в массиве (7 дней в неделю)
    // 1. Объявить указатель на новый массив
    DayWeek * p2;

    try
    {
        // 2. Попытка выделить память для нового массива (для 7 элементов)
        p2 = new DayWeek[7];
    }
    catch (bad_alloc ba)
    {
        // если ошибка, то завершение с кодом -1
        cout << "Память не выделена" << endl;
        cout << ba.what() << endl;
        return -1;
    }

    // 3. Сохранить первые 5 элементов старого массива (скопировать p => p2)
    for (int i = 0; i < 5; i++)
        p2[i] = p[i];

    // 4. Освободить память, которая была выделена для старого массива p
    delete[] p;

    // 5. Перенаправить указатель из p на p2.
    p = p2; // Оба указателя указывают на один и тот же участок памяти

    // 6. Использовать массив из 7 элементов, например, дозаполнять 2 последних
    for (int i = 5; i < 7; i++)
        p[i].day = i + 1;

    d = p[5].day; // d = 6
    d = (p + 6)->day; // d = 7

    // 7. После завершения работы, освободить память для указателя p
    delete[] p; // можно было delete[] p2; так как p и p2 указывают на ту же самую область памяти

    return 0;
}

В функции main() сначала выделяется память для массива из 5 структур. Затем эта память перераспределяется для массива из 7 структур. Для этого используется дополнительный указатель p2.

При перераспределении сначала память выделяется для p2 (7 элементов). Затем копируются данные из p в p2. После этого освобождается память, которая была выделена для указателя p (5 элементов).

На следующем шаге значение p устанавливается равным значению p2. Таким образом, оба указателя указывают на одну и ту же область памяти.

После завершения работы с массивом, память для массива p освобождается.

Выделение памяти под двумерный массив, так же delete


int x,y;
//----------------------------------------------------------------------------
std::cout << "Введите кол-во строк в массиве: ";
std::cin >> x;
std::cout << "Введите кол-во столбцов в массиве: ";
std::cin >> y;
//------------------------Выделение памяти------------------------------------
int **mas = new int *[x];
 
for (int i = 0; i < x; i++) {
  mas[i] = new int [y];
}
//----------------------------------------------------------------------------
//                      Выполняйте действия, которые вам необходимы
//--------------------------Освобождение памяти----------------------------
for (int i = 0; i < x; i++) {
  delete []mas[i];
}
delete []mas;

Казалось бы, что может быть прямолинейнее, чем выделение динамически памяти под двумерный массив, а потому должно существовать одно решение.

На самом деле можно по-разному реализовать данную задачу.

Самый простой способ - это когда число колонок в двумерном массиве является константным значением, определенным либо с помощью директивы #define либо с помощью перечислителя. Тогда можно сразу же разместить динамически именно двумерный массив.

Второй подход, это когда число колонок задается во время выполнения программы. В этом случае обычно выделяют несколько одномерных массивов. Сначала выделяют одномерный массив указателей с количеством элементов равных количеству строк в предполагаемом двумерном массиве, А затем для каждого указателя этого одномерного массива выделяют массив объектов базового типа, число элементов которого равно числу колонок в двумерном массиве. В этом варианте важно учитывать, что во время выделения массивов может возникнуть нехватка памяти, а потому желательно правильно удалить те массивы, которые были уже распределены, а иначе программа будет иметь неопределенное состояние.

Третий подход заключается в использовании массивов переменной длины при условии, что C компилятор их поддерживает. Тогда число колонок и строк может быть задано во время выполнения программы, и можно выделить сразу же двумерный массив, а не массив массивов, как было описано в предыдущем абзаце.

Также следует не забывать корректно освобождать выделенную динамически память под массивы, когда массивы уже не нужны.

Ниже приведена демонстрационная программа, в которой показаны перечисленные способы выделения памяти для работы с двумерными массивами или их аналогами.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define COLS    10

char ( * first_allocate( size_t rows ) )[COLS]
{
    char ( *s )[COLS] = malloc( rows * sizeof( char[COLS] ) );

    return s;
}

void first_free( char ( *s )[COLS] )
{
    free( s );
}


char ** second_allocate( size_t rows, size_t cols )
{
    char **s = malloc( rows * sizeof( char * ) );

    if ( s != NULL )
    {
        size_t i = 0;

        while ( i < rows && ( s[i] = malloc( cols * sizeof( char ) ) ) != NULL ) i++;

        if ( i != rows )
        {
            for ( size_t j = i; j != 0; j-- ) free( s[j-1] );
            free( s );
            s = NULL;
        }

    }

    return s;
}

void second_free( char **s, size_t rows )
{
    for ( size_t i = 0; i < rows; i++ ) free( s[i] );
    free( s );
}

void third_allocate( size_t rows, size_t cols, char ( **s )[rows][cols] )
{
    *s = malloc( sizeof( char[rows][cols] ) );
}

void third_free( size_t rows, size_t cols, char ( *s )[rows][cols] )
{
    free( s );
}



int main( void )
{
    size_t rows = 2;
    size_t cols = 10;

    char ( *s1 )[COLS] = first_allocate( rows );
    strcpy( s1[0], "first" );
    strcpy( s1[1], "second" );

    for ( size_t i = 0; i < rows; i++ ) printf( "%s ", s1[i] );
    printf( "\n" );

    first_free( s1 );

    printf( "\n" );

    char **s2 = second_allocate( rows, cols );
    strcpy( s2[0], "first" );
    strcpy( s2[1], "second" );

    for ( size_t i = 0; i < rows; i++ ) printf( "%s ", s2[i] );
    printf( "\n" );

    second_free( s2, rows );

    printf( "\n" );

    char ( *s3 )[rows][cols] = NULL;

    third_allocate( rows, cols, &s3 );
    strcpy( ( *s3 )[0], "first" );
    strcpy( ( *s3 )[1], "second" );

    for ( size_t i = 0; i < rows; i++ ) printf( "%s ", ( *s3 )[i] );
    printf( "\n" );

    third_free( rows, cols, s3 );
}

Вывод программы на консоль будет следующим

first second 

first second 

first second