Работа с одномерными динамическими массивами Лабораторная работа №3

advertisement
Лабораторная работа №3
Работа с одномерными динамическими массивами
1.
Цель
работы: приобретение основных
динамическими массивами
навыков работы с
2. Основные сведения
Динамическими называются массивы, размер которых неизвестен на этапе
написания программы и определяется во время её выполнения. В таком
случае программист сам управляет ресурсами памяти – выделяет
необходимую память и освобождает память, если она больше не будет нужна.
Для работы с динамической памятью вводится специальный тип переменной
– указатель. Объявление указателя отличается от объявления обычной
переменной только тем, что перед его именем ставится знак * ( <тип>
*<имя переменной-указателя> ). В переменных типа указатель хранятся
адреса данных, а не сами данные. Для того, чтобы работать с динамическим
массивом, надо также объявить указатель на соответствующий тип (тип
элементов массива, например, int *р;), в нем будет храниться адрес первого
элемента массива, содержащего целые числа.
Существуют способы выделения памяти как в стандартном языке Си (с
помощью функций malloc() и calloc()), так и в его расширении С++ (с
помощью оператора new). Рассмотрим и тот, и другой вариант.
Чтобы использовать функции malloc() и calloc() , нужно включить
заголовочный файл <stdlib.h>. Функции malloc() и calloc() динамически
выделяют память в соответствии со значениями их параметров и возвращают
адрес начала выделенного участка памяти. Тип возвращаемого значения
указателя void *. Его можно преобразовать к указателю любого типа с
помощью явного приведения типа, например:
int *p; // Указатель на целое р.
p=(int *) malloc(size); //Указателю на целое p присваивается адрес начала
выделенной области памяти размером size байт.
p=(int *) calloc(n,size); //Указателю на целое p присваивается адрес начала
выделенной области памяти размером n*size байт.
free(p); //Функция, которая освобождает выделенную по адресу p память.
Преобразование указателя любого типа к типу void осуществляется
автоматически, так что в качестве фактического параметра можно подставить
указатель любого типа без явного приведения типов.
После выделения памяти надо проверить, успешно ли прошло
выделение; если память выделить не удалось, то значение указателя будет
равно NULL, и нужно завершить программу. После освобождения памяти
следует обнулить соответствующий указатель (p=NULL).
Рассмотрим пример формирования
массива средствами стандартного языка Си.
одномерного
динамического
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
main()
{ float *p; // указатель на вещественные числа
int i,n; // размер массива n заранее неизвестен, т.е. n – не константа
printf("\n Размер массива: ");
scanf("%d",&n);
p=(float *) malloc(n*sizeof(float));// Выделяем
память под n
вещественных чисел; функция sizeof(<тип>) определяет размер типа в
байтах.
if ( p == NULL ) // если не удалось выделить память
{
printf(" Не удалось выделить память ");
return 1; // выход по ошибке, код ошибки 1
}
for (i=0;i<n;i++)
{ printf("p[%d]=",i);
scanf("%f",&p[i]);
}
for (i=0;i<n;i++)
printf("\t p[%d]=%8.2f",i,p[i]);
free(p);
p=NULL;
getch();
return 0;
}
Следующий приведённый прием выделения памяти относится уже не к
стандартному языку Си, а к его расширению Си ++. Оператор выделения
памяти new возвращает адрес нового выделенного блока и для работы с
массивом нам надо также его запомнить. Если требуется выделить память
под простую переменную, например типа int, можно использовать оператор
new следующим образом: p=new int; а если память выделяется под массив,
надо указать количество его элементов: p = new int[n]; Если память
выделена успешно, значение указателя будет адресом начала выделенного
блока памяти.
Далее работа с динамическим массивом, на который указывает
указатель p, идет так же, как и с обычным массивом размера n. После
использования массива надо освободить выделенную память, вызвав
оператор delete p; После освобождения памяти значение указателя не
изменяется, но использовать его уже нельзя, потому что память считается
свободной и может быть использована операционной системой. Чтобы всётаки не возникала такая ситуация, следует обнулить указатель после
освобождения памяти: p=NULL;
Следующая программа демонстрирует приемы выделения и
освобождения памяти, принятые в языке С++.
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
main()
{
int i,n; // размер массива n заранее неизвестен, т.е. n – не константа
int *p; // указатель на целые числа
printf (" Размер массива -> ");
scanf ("%d", &n);
p = new int [n]; // выделение памяти под n целых чисел
if ( p == NULL ) // если не удалось выделить память
{
printf(" Не удалось выделить память ");
return 1; // выход по ошибке, код ошибки 1
}
for (i = 0; i < n; i ++ )
{
printf ("\n p[%d] = ", i);
scanf ("%d", &p[i]);
}
for (i = 0; i < n; i ++ )
printf ("\n p[%d] = %d", i,p[i]);
puts("\n");
delete p; // освобождение памяти
p=NULL;
getch();
return 0;
}
Итак, мы вводим размер массива в переменную n (это число обязательно
должно быть больше нуля) и уже во время работы программы выделяем
память. Далее можем работать с динамическим массивом, как с обычной
индексированной переменной.
Но можно работать и непосредственно с указателями, т.е. с адресами.
Имя массива можно использовать как адрес его начального элемента, т.е. p
совпадает с &p[0]. При изменении значения указателя p на p+i он на самом
деле сдвигается к i-му следующему числу данного типа, то есть, для
указателей на целые числа, на n*sizeof(int) байт. Учитывая, что при
добавлении числа к указателю он сдвигается на заданное число ячеек
данного типа, следующие записи равносильны и вычисляют адрес i-ого
элемента массива: &p[i] и p+i. Таким образом, возможно использовать
вместо индексированного элемента p[i] выражения *(p+i), т. к. имя массива
без индексов есть адрес его первого элемента. Следующий пример
иллюстрирует инвертирование массива при работе непосредственно с
указателями.
#include <stdlib.h>
#include <stdio.h>
main()
{ int i,n,s;
int *p;
printf (" Размер массива -> ");
scanf ("%d", &n);
p = new int [n];
if ( p == NULL )
{
printf(" Не выделена память ");
return 1;
}
for (i = 0; i < n; i ++ )
{
*(p+i)=rand()%20;
}
for (i = 0; i < n; i ++ )
printf ("\n p[%d] = %d", i,*(p+i));
puts("\n");
for (i=0; i<n/2; i++)
{ s=*(p+i);
*(p+i)=*(p+(n-i-1));
*(p+(n-i-1))=s;
}
for (i = 0; i < n; i ++ )
printf ("\n p[%d] = %d", i,*(p+i));
puts("\n");
delete p;
p=NULL;
system("PAUSE");
return 0;
}
3. Выполнение работы
1.Используя функцию malloc(), выделить память под одномерный
динамический массив b[n] (n вводить с клавиатуры). Заполнить его целыми
случайными числами в диапазоне -50 … 50. Получить динамический
массив c[m], содержащий положительные числа массива b, и
динамический массив d[k], содержащий отрицательные числа. m и k
должны быть равны количеству положительных и отрицательных чисел.
Вывести исходный массив и полученные массивы. Освободить память.
2. Используя оператор new, выделить память под одномерный
динамический массив a[n] (n вводить с клавиатуры) и массив указателей
b[n]. Заполнить его вещественными случайными числами. Отсортировать
массив по возрастанию, используя массив указателей . Вывести исходный
и отсортированный массив. Освободить память.
3. Контрольные вопросы
1.
2.
3.
4.
5.
Где хранятся адреса динамических переменных?
Как выделить память под простую динамическую переменную?
Как выделить память под одномерный динамический массив?
Как освободить динамическую память?
Какое значение будет иметь указатель после освобождения памяти?
Download