хранит глобальные переменные и константы;
размер определяется при компиляции.
Стек (stack)
хранит локальные переменные, аргументы функций и промежуточные значения вычислений;
размер определяется при запуске программы (обычно выделяется 4 Мб).
Куча (heap)
динамически распределяемая память;
ОС выделяет память по частям (по мере необходимости).
Динамически распределяемую память следует использовать в случае если мы заранее (на момент написания программы) не знаем сколько памяти нам понадобится (например, размер массива зависит от того, что введет пользователь во время работы программы) и при работе с большими объемами данных.
Динамическая память, называемая также "кучей", выделяется явно по запросу программы из ресурсов операционной системы и контролируется указателем. Она не инициализируется автоматически и должна быть явно освобождена. В отличие от статической и автоматической памяти динамическая память практически не ограничена (ограничена лишь размером оперативной памяти) и может меняться в процессе работы программы
Работа с динамической памятью в с
Для работы с динамической памятью в языке С используются следующие функции: malloc, calloc, free, realloc . Рассмотрим их подробнее.
Выделение (захват памяти) : void *malloc(size_t size);
В качестве входного параметра функция принимает размер памяти, которую требуется выделить. Возвращаемым значением является указатель на выделенный в куче участок памяти. Если ОС не смогла выделить память (например, памяти не хватило), то malloc возвращает 0.
После окончания работы с выделенной динамически памятью нужно освободить ее. Для этой цели используется функция free, которая возвращает память под управление ОС: void free(void *ptr);
Еслидинамическая памятьне освобождена до окончания программы, то она освобождается автоматически при завершении программы. Тем не менее, явное освобождение ставшей ненужной памяти является признаком хорошего стиля программирования.
Пример: // выделения памяти под 1 000 элементов типа int
int * p = (int *) malloc(1000*sizeof(int));
if (p==NULL) cout<< "\n память не выделена";
free (p); // возврат памяти в кучу
2. Выделение (захват памяти) : void *calloc(size_t nmemb, size_t size);
Функция работает аналогично malloc, но отличается синтаксисом (вместо размера выделяемой памяти нужно задать количество элементов и размер одного элемента) и тем, что выделенная память будет обнулена. Например, после выполнения int * p = (int *) calloc(1000, sizeof(int)) p будет указывать на начало массива типа int из 1000 элементов, инициализированных нулями.
3. Изменение размера памяти:void *realloc(void *ptr, size_t size);
Функция изменяет размер выделенной памяти (на которую указывает ptr, полученный из вызова malloc, calloc или realloc ). Если размер, указанный в параметре size больше, чем тот, который был выделен под указатель ptr, то проверяется, есть ли возможность выделить недостающие ячейки памяти подряд с уже выделенными. Если места недостаточно, то выделяется новый участок памяти размером size и данные по указателю ptr копируются в начало нового участка.
В процессе выполнения программы участок динамической памяти доступен везде, где доступен указатель, адресующий этот участок. Таким образом, возможны следующие три варианта работы с динамической памятью, выделяемой в некотором блоке (например, в теле неглавной функции).
Указатель (на участок динамической памяти) определен как локальный объект автоматической памяти. В этом случае выделенная память будет недоступна при выходе за пределы блока локализации указателя, и ее нужно освободить перед выходом из блока.
{ int* p= (int *) calloc(n, sizeof(int))
free (p); // освобождение дин. памяти
Указатель определен как локальный объект статической памяти. Динамическая память, выделенная однократно в блоке, доступна через указатель при каждом повторном входе в блок. Память нужно освободить только по окончании ее использования.
{static int* p = (int *) calloc(n, sizeof(int));
p= (int *) calloc(n, sizeof(int));
f(50); //выделение дин. памяти с последующим освобождением
f1(100); //выделение дин. памяти (первое обращение)
f1(100); //работа с дин. памятью
f1 (0); // освобождение дин. памяти
Указатель является глобальным объектом по отношению к блоку. Динамическая память доступна во всех блоках, где "виден" указатель. Память нужно освободить только по окончании ее использования
int* pG; //рабочий указатель для дин. памяти (глобальная переменная)
void init (int size)
for (i=0; i< size; i++) //цикл ввода чисел
{ printf("x[%d]=",i);
scanf("%d", &pG[i]);
int sum (int size)
for (i=0; i< size; i++) //цикл суммирования
// выделение памяти
pG= (int *) calloc(n, sizeof(int));
//работа с дин.памятью
printf(\ns=%d\n”,sum(n));
free (pG); pG=NULL; // освобождение памяти
Работа с динамической памятью в С++
В С++ есть свой механизм выделения и освобождения памяти - это функции new и delete. Пример использования new : int * p = new int; // выделение памяти под 1000 эл-тов Т.е. при использовании функции new не нужно приводить указатель и не нужно использовать sizeof(). Освобождение выделенной при помощи new памяти осуществляется посредством следующего вызова: delete p; Если требуется выделить память под один элемент, то можно использовать int * q = new int; или int * q = new int(10); // выделенный int проинициализируется значением 10 в этом случае удаление будет выглядеть следующим образом: delete q;
В С++, как и во многих других языках, память можно выделять статически (память выделяется до начала выполнения программы и освобождается после завершения программы) или динамически (память выделяется и освобождается в процессе выполнения программы).
Статическое выделение памяти выполняется для всех глобальных и локальных переменных, имеющих явные описания в программе (без использования указателей). В этом случае механизм выделения памяти определяется расположением описания переменной в программе и спецификатором класса памяти в описании. Тип переменной определяет размер выделяемой области памяти, но механизм выделения памяти от типа не зависит. Имеется два основных механизма статического выделения памяти.
· Память под каждую из глобальных и статических (объявленных со спецификатором static) переменных выделяется до начала выполнения программы в соответствии с описанием типа. От начала до конца выполнения программы данные переменные связаны с выделенной для них областью памяти. Таким образом, они имеют глобальное время жизни, при этом область видимости у них различная.
· Для локальных переменных, объявленных внутри какого-либо блока и не имеющих спецификатора static, память выделяется другим способом. До начала выполнения программы (при её загрузке) выделяется довольно объёмная область памяти, называемая стеком (иногда используют термины стек программы или стек вызовов , чтобы сделать различие между стеком как абстрактным типом данных). Размер стека зависит от среды разработки, например, в MS Visual C++ по умолчанию под стек выделяется 1 мегабайт (это значение поддаётся настройке). В процессе выполнения программы при входе в определённый блок выделяется память в стеке для локализованных в блоке переменных (в соответствии с описанием их типа), при выходе из блока эта память освобождается. Данные процессы выполняются автоматически, поэтому локальные переменные в С++ часто называют автоматическими .
При вызове функции в стеке выделяется память для её локальных переменных, параметров (в стек помещается значение или адрес параметра), результата функции и сохранения точки возврата – адреса в программе, куда нужно вернуться при завершении работы функции. При завершении работы функции все связанные с ней данные удаляются из стека.
Использование термина "стек" объяснить легко – при принятом подходе к выделению и освобождению памяти переменные, которые помещаются в стек последними (это переменные, локализованные в самом глубоко вложенном блоке), удаляются из него первыми. То есть, выделение и освобождение памяти происходит по принципу LIFO (LAST IN – FIRST OUT, последним пришёл – первым вышел). Это и есть принцип работы стека. Стек как динамическую структуру данных и его возможную реализацию мы рассмотрим в следующем разделе.
Во многих случаях статическое выделение памяти ведет к ее неэффективному использованию (особенно это характерно для массивов больших размеров), т. к. не всегда выделенная статически область памяти реально заполняется данными. Поэтому в С++, как и во многих языках, есть удобные средства динамического формирования переменных. Суть динамического выделения памяти заключается в том, что память выделяется (захватывается) по запросу из программы и освобождается также по запросу. При этом размер памяти может определяться типом переменной или явно указываться в запросе. Такие переменные называются динамическими . Возможности создания и использования динамических переменных тесно связаны с механизмом указателей.
Суммируя всё сказанное выше, можно представить следующую схему распределения памяти в процессе исполнения программы (рисунок 2.1). Расположение областей друг относительно друга на рисунке довольно условное, т.к. детали выделения памяти берёт на себя операционная система.
Рисунок 2.1 – схема распределения памяти
В заключение этого раздела коснёмся одной болезненной проблемы в процессе работы со стеком – возможности его переполнения (эта аварийная ситуация обычно называется Stack Overflow ). Причина, породившая проблему, понятна – ограниченный объём памяти, которая выделяется под стек при загрузке программы. Наиболее вероятные ситуации для переполнения стека – локальные массивы больших размеров и глубокая вложенность рекурсивных вызовов функций (обычно возникает при неаккуратном программировании рекурсивных функций, допустим, забыта какая-либо терминальная ветвь).
Для того, чтобы лучше понять проблему переполнения стека, советуем провести такой нехитрый эксперимент. В функции main объявите массив целых чисел размером, допустим, на миллион элементов. Программа скомпилируется, но при её запуске возникнет ошибка переполнения стека. Теперь добавьте в начало описания массива спецификатор static (или вынесите описание массива из функции main ) – программа заработает!
Ничего чудесного в этом нет – просто теперь массив помещается не в стек, а в область глобальных и статических переменных. Размер памяти для этой области определяет компилятор – если программа скомпилировалась, значит, она будет работать.
Тем не менее, объявлять в программе статически формируемые массивы огромных размеров, как правило, нет необходимости. В большинстве случаев более эффективным и гибким способом будет динамическое выделение памяти для таких данных.
Динамическое выделение памяти необходимо для эффективного использования памяти компьютера. Например, мы написали какую-то программку, которая обрабатывает массив. При написании данной программы необходимо было объявить массив, то есть задать ему фиксированный размер (к примеру, от 0 до 100 элементов). Тогда данная программа будет не универсальной, ведь может обрабатывать массив размером не более 100 элементов. А если нам понадобятся всего 20 элементов, но в памяти выделится место под 100 элементов, ведь объявление массива было статическим, а такое использование памяти крайне не эффективно.
В С++ операции new и delete предназначены для динамического распределения памяти компьютера. Операция new выделяет память из области свободной памяти, а операция delete высвобождает выделенную память. Выделяемая память, после её использования должна высвобождаться, поэтому операции new и delete используются парами. Даже если не высвобождать память явно, то она освободится ресурсами ОС по завершению работы программы. Рекомендую все-таки не забывать про операцию delete .
// пример использования операции new int *ptrvalue = new int; //где ptrvalue – указатель на выделенный участок памяти типа int //new – операция выделения свободной памяти под создаваемый объект.
Операция new создает объект заданного типа, выделяет ему память и возвращает указатель правильного типа на данный участок памяти. Если память невозможно выделить, например, в случае отсутствия свободных участков, то возвращается нулевой указатель, то есть указатель вернет значение 0. Выделение памяти возможно под любой тип данных: int , float ,double , char и т. д.
// пример использования операции delete: delete ptrvalue; // где ptrvalue – указатель на выделенный участок памяти типа int // delete – операция высвобождения памяти
Разработаем программу, в которой будет создаваться динамическая переменная.
// new_delete.cpp: определяет точку входа для консольного приложения.
#include "stdafx.h"
#include
// код Code::Blocks
// код Dev-C++
// new_delete.cpp: определяет точку входа для консольного приложения.
#include
В строке 10 показан способ объявления и инициализации девяткой динамического объекта, все, что нужно так это указать значение в круглых скобочках после типа данных. Результат работы программы показан на рисунке 1.
Ptrvalue = 9 Для продолжения нажмите любую клавишу. . .
Рисунок 1 — Динамическая переменная
Создание динамических массивов
Как было сказано раньше, массивы также могут быть динамическими. Чаще всего операции new и delete применяются для создания динамических массивов, а не для создания динамических переменных. Рассмотрим фрагмент кода создания одномерного динамического массива.
// объявление одномерного динамического массива на 10 элементов: float *ptrarray = new float ; // где ptrarray – указатель на выделенный участок памяти под массив вещественных чисел типа float // в квадратных скобочках указываем размер массива
После того как динамический массив стал ненужным, нужно освободить участок памяти, который под него выделялся.
// высвобождение памяти отводимой под одномерный динамический массив: delete ptrarray;
После оператора delete ставятся квадратные скобочки, которые говорят о том, что высвобождается участок памяти, отводимый под одномерный массив. Разработаем программу, в которой создадим одномерный динамический массив, заполненный случайными числами.
// new_delete_array.cpp: определяет точку входа для консольного приложения.
#include "stdafx.h"
#include
// код Code::Blocks
// код Dev-C++
// new_delete_array.cpp: определяет точку входа для консольного приложения.
#include
Созданный одномерный динамический массив заполняется случайными вещественными числами, полученными c помощью функций генерации случайных чисел, причём числа генерируются в интервале от 1 до 10, интервал задается так — rand() % 10 + 1 .
Чтобы получить случайные вещественные числа, выполняется операция деления, с использованием явного приведения к вещественному типу знаменателя — float((rand() % 10 + 1)) . Чтобы показать только два знака после запятой используем функцию setprecision(2) ,
прототип данной функции находится в заголовочном файле
Array = 0.8 0.25 0.86 0.5 2.2 10 1.2 0.33 0.89 3.5 Для продолжения нажмите любую клавишу. . .
Рисунок 2 — Динамический массив в С++
По завершению работы с массивом, он удаляется, таким образом, высвобождается память, отводимая под его хранение.
Как создавать и работать с одномерными динамическими массивами мы научились. Теперь рассмотрим фрагмент кода, в котором показано, как объявляется двумерный динамический массив.
// объявление двумерного динамического массива на 10 элементов: float **ptrarray = new float* ; // две строки в массиве for (int count = 0; count < 2; count++) ptrarray = new float ; // и пять столбцов // где ptrarray – массив указателей на выделенный участок памяти под массив вещественных чисел типа float
Сначала объявляется указатель второго порядка float **ptrarray , который ссылается на массив указателей float* ,где размер массива равен двум. После чего в цикле for каждой строке массива объявленного в строке 2 выделяется память под пять элементов. В результате получается двумерный динамический массив ptrarray .Рассмотрим пример высвобождения памяти отводимой под двумерный динамический массив.
// высвобождение памяти отводимой под двумерный динамический массив: for (int count = 0; count < 2; count++) delete ptrarray; // где 2 – количество строк в массиве
Объявление и удаление двумерного динамического массива выполняется с помощью цикла, так как показано выше, необходимо понять и запомнить то, как это делается. Разработаем программу, в которой создадим двумерный динамический массив.
// new_delete_array2.cpp: определяет точку входа для консольного приложения.
#include "stdafx.h"
#include // код Code::Blocks // код Dev-C++ // new_delete_array2.cpp: определяет точку входа для консольного приложения.
#include При выводе массива была использована функция setw() , если вы не забыли, то она отводит место заданного размера под выводимые данные. В нашем случае, под каждый элемент массива по четыре позиции, это позволяет выровнять, по столбцам, числа разной длинны (см. Рисунок 3). 2.7 10 0.33 3 1.4
6 0.67 0.86 1.2 0.44
Для продолжения нажмите любую клавишу. . . Рисунок 3 — Динамический массив в С++ Очень часто возникают задачи обработки массивов данных, размерность которых заранее неизвестна. В этом случае возможно использование одного из двух подходов: Для использования функций динамического выделения памяти необходимо описать указатель , представляющий собой начальный адрес хранения элементов массива. int
*p; // указатель на тип int
Начальный адрес статического массива определяется компилятором в момент его объявления и не может быть изменен. Для динамического массива начальный адрес присваивается объявленному указателю на массив в процессе выполнения программы. Функции динамического выделения памяти находят в оперативной памяти непрерывный участок требуемой длины и возвращают начальный адрес этого участка. Функции динамического распределения памяти: void
* malloc(РазмерМассиваВБайтах); Для использования функций динамического распределения памяти необходимо подключение библиотеки #include
Поскольку обе представленные функции в качестве возвращаемого значения имеют указатель на пустой тип void
, требуется явное приведение типа возвращаемого значения. Для определения размера массива в байтах, используемого в качестве аргумента функции malloc()
требуется количество элементов умножить на размер одного элемента. Поскольку элементами массива могут быть как данные простых типов, так и составных типов (например, структуры), для точного определения размера элемента в общем случае рекомендуется использование функции int
sizeof
(тип); Память, динамически выделенная с использованием функций calloc(), malloc()
, может быть освобождена с использованием функции free(указатель);
«Правилом хорошего тона» в программировании является освобождение динамически выделенной памяти в случае отсутствия ее дальнейшего использования. Однако если динамически выделенная память не освобождается явным образом, она будет освобождена по завершении выполнения программы. Форма обращения к элементам массива с помощью указателей имеет следующий вид: int
a, *p; // описываем статический массив и указатель
Пример на Си
: Организация динамического одномерного массива и ввод его элементов. 1 Пусть требуется разместить в динамической памяти матрицу, содержащую n
строк и m
столбцов. Двумерная матрица будет располагаться в оперативной памяти в форме ленты, состоящей из элементов строк. При этом индекс любого элемента двумерной матрицы можно получить по формуле index = i*m+j;
где i
- номер текущей строки; j
- номер текущего столбца. Рассмотрим матрицу 3x4 (см. рис.) index = 1*4+2=6
Объем памяти, требуемый для размещения двумерного массива, определится как n·m·(размер элемента)
Однако поскольку при таком объявлении компилятору явно не указывается количество элементов в строке и столбце двумерного массива, традиционное обращение к элементу путем указания индекса строки и индекса столбца является некорректным: a[i][j]
- некорректно. Правильное обращение к элементу с использованием указателя будет выглядеть как *(p+i*m+j)
, Пример на Си
1 #define
_CRT_SECURE_NO_WARNINGS
Результат выполнения Возможен также другой способ динамического выделения памяти под двумерный массив - с использованием массива указателей. Для этого необходимо: Графически такой способ выделения памяти можно представить следующим образом. 1 #define
_CRT_SECURE_NO_WARNINGS
Результат выполнения программы аналогичен предыдущему случаю. С помощью динамического выделения памяти под указатели строк можно размещать свободные массивы. Свободным
называется двухмерный массив (матрица), размер строк которого может быть различным. Преимущество использования свободного массива заключается в том, что не требуется отводить память компьютера с запасом для размещения строки максимально возможной длины. Фактически свободный массив представляет собой одномерный массив указателей на одномерные массивы данных. Для размещения в оперативной памяти матрицы со строками разной длины необходимо ввести дополнительный массив m
, в котором будут храниться размеры строк. Пример на Си
1 #define
_CRT_SECURE_NO_WARNINGS
Если размер выделяемой памяти нельзя задать заранее, например при вводе последовательности значений до определенной команды, то для увеличения размера массива при вводе следующего значения необходимо выполнить следующие действия: Все перечисленные выше действия (кроме последнего) выполняет функция void
* realloc (void
* ptr, size_t size); Размер блока памяти, на который ссылается параметр ptr
изменяется на size
байтов. Блок памяти может уменьшаться или увеличиваться в размере. Содержимое блока памяти сохраняется даже если новый блок имеет меньший размер, чем старый. Но отбрасываются те данные, которые выходят за рамки нового блока. Если новый блок памяти больше старого, то содержимое вновь выделенной памяти будет неопределенным. Прежде чем углубиться в объектно-ориентированную разработку, нам придется сделать
небольшое отступление о работе с памятью в программе на С++. Мы не сможем написать
сколько-нибудь сложную программу, не умея выделять память во время выполнения
и обращаться к ней. Int ival = 1024;
заставляет компилятор выделить в памяти область, достаточную для хранения переменной
типа int, связать с этой областью имя ival и поместить туда значение 1024. Все
это делается на этапе компиляции, до выполнения программы. Int ival2 = ival + 1;
то обращаемся к значению, содержащемуся в переменной ival: прибавляем к нему
1 и инициализируем переменную ival2 этим новым значением, 1025. Каким же образом
обратиться к адресу, по которому размещена переменная? Int *pint; // указатель на объект типа int
Существует также специальная операция взятия адреса, обозначаемая символом
&. Ее результатом является адрес объекта. Следующий оператор присваивает
указателю pint адрес переменной ival: Int *pint;
pint = &ival; // pint получает значение адреса ival
Мы можем обратиться к тому объекту, адрес которого содержит pint (ival в нашем
случае), используя операцию разыменования
, называемую также косвенной
адресацией
. Эта операция обозначается символом *. Вот как можно косвенно
прибавить единицу к ival, используя ее адрес:
*pint = *pint + 1; // неявно увеличивает ival
Это выражение производит в точности те же действия, что и Ival = ival + 1; // явно увеличивает ival
В этом примере нет никакого реального смысла: использование указателя для косвенной
манипуляции переменной ival менее эффективно и менее наглядно. Мы привели этот
пример только для того, чтобы дать самое начальное представление об указателях.
В реальности указатели используют чаще всего для манипуляций с динамически размещенными
объектами. Оператор new имеет две формы. Первая форма выделяет память под единичный объект
определенного типа: Int *pint = new int(1024);
Здесь оператор new выделяет память под безымянный объект типа int, инициализирует
его значением 1024 и возвращает адрес созданного объекта. Этот адрес используется
для инициализации указателя pint. Все действия над таким безымянным объектом
производятся путем разыменовывания данного указателя, т.к. явно манипулировать
динамическим объектом невозможно. Int *pia = new int;
В этом примере память выделяется под массив из четырех элементов типа int.
К сожалению, данная форма оператора new не позволяет инициализировать элементы
массива.
// освобождение единичного объекта
delete pint;
// освобождение массива
delete pia;
Что случится, если мы забудем освободить выделенную память? Память будет расходоваться
впустую, она окажется неиспользуемой, однако возвратить ее системе нельзя, поскольку
у нас нет указателя на нее. Такое явление получило специальное название утечка
памяти
. В конце концов программа аварийно завершится из-за нехватки памяти
(если, конечно, она будет работать достаточно долго). Небольшая утечка трудно
поддается обнаружению, но существуют утилиты, помогающие это сделать. Объясните разницу между четырьмя объектами:
(a) int ival = 1024;
(b) int *pi = &ival;
(c) int *pi2 = new int(1024);
(d) int *pi3 = new int;
Что делает следующий фрагмент кода? В чем состоит логическая ошибка? (Отметим,
что операция взятия индекса () правильно применена к указателю pia. Объяснение
этому факту можно найти в разделе 3.9.2.) Int *pi = new int(10);
int *pia = new int;
Стандартные функции динамического выделения памяти
void
* calloc(ЧислоЭлементов, РазмерЭлементаВБайтах);
которая определяет количество байт, занимаемое элементом указанного типа.
Динамическое выделение памяти для одномерных массивов
int
b;
p = a; // присваиваем указателю начальный адрес массива
... // ввод элементов массива
b = *p; // b = a;
b = *(p+i) // b = a[i];
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include
#include
#include
int
main()
{
int
*a; // указатель на массив
int
i, n;
system("chcp 1251"
);
system("cls"
);
printf("Введите размер массива: "
);
scanf("%d"
, &n);
// Выделение памяти
a = (int
*)malloc(n * sizeof
(int
));
// Ввод элементов массива
for
(i = 0; i
printf("a[%d] = "
, i);
scanf("%d"
, &a[i]);
}
// Вывод элементов массива
for
(i = 0; i
free(a);
getchar(); getchar();
return
0;
}
Результат выполнения программы:Динамическое выделение памяти для двумерных массивов
Индекс выделенного элемента определится как
где
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include
#include
#include
int
main()
{
int
*a; // указатель на массив
int
i, j, n, m;
system("chcp 1251"
);
system("cls"
);
printf("Введите количество строк: "
);
scanf("%d"
, &n);
printf();
scanf("%d"
, &m);
// Выделение памяти
a = (int
*)malloc(n*m * sizeof
(int
));
// Ввод элементов массива
for
(i = 0; i
{
for
(j = 0; j
{
scanf("%d"
, (a + i*m + j));
}
}
// Вывод элементов массива
for
(i = 0; i
{
for
(j = 0; j
{
printf("%5d "
, *(a + i*m + j));
}
printf("\n"
);
}
free(a);
getchar(); getchar();
return
0;
}
При таком способе выделения памяти компилятору явно указано количество строк и количество столбцов в массиве.
Пример на Си
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include
#include
#include
int
main()
{
int
**a; // указатель на указатель на строку элементов
int
i, j, n, m;
system("chcp 1251"
);
system("cls"
);
printf("Введите количество строк: "
);
scanf("%d"
, &n);
printf("Введите количество столбцов: "
);
scanf("%d"
, &m);
// Выделение памяти под указатели на строки
// Ввод элементов массива
for
(i = 0; i
{
// Выделение памяти под хранение строк
a[i] = (int
*)malloc(m * sizeof
(int
));
for
(j = 0; j
{
printf("a[%d][%d] = "
, i, j);
scanf("%d"
, &a[i][j]);
}
}
// Вывод элементов массива
for
(i = 0; i < n; i++) // цикл по строкам
{
for
(j = 0; j < m; j++) // цикл по столбцам
{
printf("%5d "
, a[i][j]); // 5 знакомест под элемент массива
}
printf("\n"
);
}
// Очистка памяти
for
(i = 0; i < n; i++) // цикл по строкам
free(a[i]); // освобождение памяти под строку
free(a);
getchar(); getchar();
return
0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include
#include
int
main()
{
int
**a;
int
i, j, n, *m;
system("chcp 1251"
);
system("cls"
);
printf("Введите количество строк: "
);
scanf("%d"
, &n);
a = (int
**)malloc(n * sizeof
(int
*));
m = (int
*)malloc(n * sizeof
(int
)); // массив кол-ва элеменов в строках массива a
// Ввод элементов массива
for
(i = 0; i
printf("Введите количество столбцов строки %d: "
, i);
scanf("%d"
, &m[i]);
a[i] = (int
*)malloc(m[i] * sizeof
(int
));
for
(j = 0; j
scanf("%d"
, &a[i][j]);
}
}
// Вывод элементов массива
for
(i = 0; i
for
(j = 0; j
printf("%3d "
, a[i][j]);
}
printf("\n"
);
}
// Освобождение памяти
for
(i = 0; i < n; i++)
{
free(a[i]);
}
free(a);
free(m);
getchar(); getchar();
return
0;
}
Результат выполненияПерераспределение памяти
if
(i>2) i -= 2;
printf("\n"
);
a = (int
*)realloc(a, i * sizeof
(int
)); // уменьшение размера массива на 2
for
(int
j = 0; j < i; j++)
printf("%d "
, a[j]);
getchar(); getchar();
return
0;
}
В С++ объекты могут быть размещены либо статически – во время компиляции, либо
динамически – во время выполнения программы, путем вызова функций из стандартной
библиотеки. Основная разница в использовании этих методов – в их эффективности
и гибкости. Статическое размещение более эффективно, так как выделение памяти
происходит до выполнения программы, однако оно гораздо менее гибко, потому что
мы должны заранее знать тип и размер размещаемого объекта. К примеру, совсем
не просто разместить содержимое некоторого текстового файла в статическом массиве
строк: нам нужно заранее знать его размер. Задачи, в которых нужно хранить и
обрабатывать заранее неизвестное число элементов, обычно требуют динамического
выделения памяти.
До сих пор во всех наших примерах использовалось статическое выделение памяти.
Скажем, определение переменной ival
С объектом ival ассоциируются две величины: собственно значение переменной,
1024 в данном случае, и адрес той области памяти, где хранится это значение.
Мы можем обращаться к любой из этих двух величин. Когда мы пишем:
С++ имеет встроенный тип “указатель”, который используется для хранения адресов
объектов. Чтобы объявить указатель, содержащий адрес переменной ival, мы должны
написать:
Основные отличия между статическим и динамическим выделением памяти таковы:
Вторая форма оператора new выделяет память под массив заданного размера, состоящий
из элементов определенного типа:
Некоторую путаницу вносит то, что обе формы оператора new возвращают одинаковый
указатель, в нашем примере это указатель на целое. И pint, и pia объявлены совершенно
одинаково, однако pint указывает на единственный объект типа int, а pia – на
первый элемент массива из четырех объектов типа int.
Когда динамический объект больше не нужен, мы должны явным образом освободить
отведенную под него память. Это делается с помощью оператора delete, имеющего,
как и new, две формы – для единичного объекта и для массива:
Наш сжатый обзор динамического выделения памяти и использования указателей,
наверное, больше породил вопросов, чем дал ответов. В разделе 8.4
затронутые проблемы будут освещены во всех подробностях. Однако мы не могли
обойтись без этого отступления, так как класс Array, который мы собираемся спроектировать
в последующих разделах, основан на использовании динамически выделяемой памяти.Упражнение 2.3
Упражнение 2.4
while (*pi < 10) {
pia[*pi] = *pi;
*pi = *pi + 1;
}
delete pi;
delete pia;