Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

информатика_2 / Программирование_С

.pdf
Скачиваний:
95
Добавлен:
16.03.2016
Размер:
2.31 Mб
Скачать

//напечатаем номера и адреса элементов этого массива

//спецификатор формата для вывода адресов %р

int k;

for (ptr = mas, k = 0; ptr < mas+N; ptri++) printf("\nНомер %d, Адрес %p", k++, pti);

}

1.3.4.4. Динамические массивы (массивы динамической памяти)

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

1.3.4.4.1. Использование механизмов С для динамического выделения памяти Для получения массивов с переменными размерами используются указатели и

функции библиотеки динамического выделения памяти <alloc.h>.

Библиотека <alloc.h> содержит функции, которые возвращают значения указателей типа void* (для универсализации):

void *malloc (unsigned N);

Возвращает указатель на начало области памяти длиной N байт. void *calloc (unsighed N, unsigned M);

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

void *realloc(void * Имя_блока, unsighed N);

Изменяет размер памяти, ранее выделенной для блока, и имеющей имя «Имя_блока». Возвращает указатель на начало области памяти длиной N байт. Если адрес блока = NULL, то память ранее не выделялась, и функция выполняется как malloc.

Поскольку тип функций void *, то после выделения памяти необходимо явное преобразование к тому типу, который требуется, с помощью операции приведения типа (тип *). Если память не может быть выделена, любая из функций возвращает значение NULL.

Для освобождения памяти используется функция void * free (void *Имя_блока);

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

При динамическом выделении памяти необходимо знать, сколько надо памяти, и сколько есть в наличии. Этот вопрос решат функции:

coreleft ();

возвращает свободный объем кучи в байтах, sizeof (имя_типа);

или

sizeof (имя_переменной_любого_типа);

81

возвращает размер памяти в байтах, занятый объектом, или требуемый для размещения данного указанного типа.

Пример. Покажем, как можно определить размер свободного динамического пространства, и как операция sizeof применяется к объектам и именам типов.

#define N 4 void main (void)

{

long

l;

// объем пространства может быть большим

int i;

 

 

float

f;

 

doubled;

 

int m1[N];

 

float

m2[N];

 

l = coreleft();

 

printf ("\nСвободно байт= %ld", l);

printf ("\nБайт для int

=%d", sizeof (i));

printf ("\nБайт для float =%d", sizeof (f)); printf ("\nБайт для double =%d", sizeof (d));

printf ("\nБайт для int массива=%d", sizeof (int)*N);

printf ("\nБайт для float массива

=%d", sizeof (float)*N);

printf ("\nБайт для float массива

=%d", sizeof (m2));

}

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

#include <alloc.h>

#include <stdio.h> void main(void)

{

int *mas;

// указатель на массив

int i, N;

 

int *ip;

 

printf("Введите количество элементов "); scanf("%d", &N);

mas = (int *) malloc(N * sizeof(int)); // выделение памяти для массива if (mas == NULL)

{

printf("\nНет памяти"); return;

}

//массив существует. Адресация массива выполняется через имя указателя

//адресация по индексу

82

printf ("Введите %d элементов:\n ", N); for (i = 0; i < N; i++)

scanf("%d", &mas[i]); printf("\n");

// адресация по указателю

for (ip = mas; ip < mas + N; ip ++) printf("%5d", *ip);

// высвобождение памяти free (mas);

}

Важное примечание. При выделении памяти переменной (массиву) через указатель, переменная (массив) не имеет собственного имени. Обращение к переменной (массиву) выполняется только посредством указателя, который играет роль синонима имени.

Объект динамической памяти разрушается при использовании функции free или при завершении работы программы, если не был разрушен ранее.

1.3.4.4.2. Использование механизмов C для динамического выделения памяти Для динамического выделения памяти в C++ введены специальные операции выделения динамической памяти new и высвобождения delete. Операция new выделяет в куче (heap) память требуемого размера для объекта, операция delete

разрушает объект, возвращая память в кучу. Синтаксис:

Указатель = new Имя_объекта [ количество_элементов ]; delete имя_объекта;

Операция new пытается открыть объект с именем Имя_объекта путем выделения sizeof (Имя_объекта) байт в куче. Операция delete удаляет объект с указанным именем, возвращая столько же байт в кучу.

Объект существует с момента создания до момента разрушения или до конца программы.

Пример использования операций new и delete:

name *ptr;

// здесь name любой тип, кроме функции

...

 

if (!(ptr = new name))

// может оказаться равным NULL

{

 

printf("Нет места в памяти.\n"); exit ();

}

// использование объекта по назначению

...

 

delete ptr;

// разрушение объекта

Пример создания динамического массива:

int *mas;

// указатель на массив

 

83

printf ("Введите количество элементов "); scanf ("%d", &N);

mas = new int [N]; // выделение памяти для массива if (mas == NULL)

{

printf("\nНет памяти"); return;

}

//массив существует, адресация массива выполняется через имя указателя

delete mas;

//массив разрушен

Объект динамической памяти разрушается при использовании операции delete или при завершении работы программы, если не был разрушен ранее.

1.3.4.4.3. Динамические двумерные массивы Объявляются как указатель на указатель. Память выделяется для каждой

строки отдельно, как и высвобождается. Адресация не зависит от способа выделения памяти, и может быть как прямой, так и косвенной. Размещение в памяти для каждого массива линейное, примерная схема показана на рис.1.10.

Имя

 

Имя

 

Имя элемента

 

массива

 

строки

 

 

 

 

a[i][j]

 

 

a

 

a[i]

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

A

 

a[0]

a[0][0]

a[0][M]

 

 

 

 

 

 

 

 

 

 

 

 

 

 

a[1]

 

a[1][0]

a[1][M]

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

a[N–1]

 

a[N–1][0]

a[N–1][M]

 

 

 

 

 

 

 

 

 

 

 

 

 

Рис. 1.10. Схема выделения памяти для динамического двумерного массива

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

#include <alloc.h>

void main (void)

{

int **a;

// указатель на массив указателей (на строки матрицы)

84

int N, M;

// размер матрицы

int i, j;

 

int *ptr;

printf ("Введите число строк\n"); scanf ("%d", &N);

printf ("Введите число столбцов\n"); scanf ("%d", &M);

//первый этап: выделение памяти для массива указателей на строки матрицы

//требуется N элементов размером указателя на int

a = (int **) calloc (N, sizeof (int *));

// для массива строк

if (a == NULL)

 

{

 

printf("\nНедостаточно памяти");

 

exit (1);

 

}

 

//второй этап: выделение памяти для каждой строки матрицы

//выполняется N раз, выделяется по M элементов

for (i = 0; i < N; i++)

{

a[i] = (int *) calloc (M, sizeof (int)); //для каждой строки if (a[i] == NULL)

{

printf ("\nНедостаточно памяти"); exit (1);

}

}

//адресация в динамическом двумерном массиве может быть

//прямой или косвенной

//прямая адресация

printf ("Введите матрицу размером %d строк и %d столбцов\n", N, M); for (i = 0; i < N; i++)

for (j = 0; j < M; j++) scanf("%d", &a[i][j]);

printf ("\nРезультат"); for (i = 0; i < N; i++)

{

for (j = 0; j < M; j++) printf ("%d ", a[i][j]);

printf("\n");

}

// высвобождение памяти в два этапа for (i = 0; i < N; i++)

free (a[i]); // разрушаются строки

85

free(a);

// высвобождается указатель на матрицу

}

1.3.4.5. Массивы указателей

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

Синтаксис определения массива указателей: Тип_массива * Имя_массива [количество_элементов]; Или с одновременной инициализацией:

Тип_массива * Имя_массива [] = {инициализатор};

Тип_массива * Имя_массива [количество_элементов] = {инициализатор}; Здесь «Тип_массива» может быть базовым или производным, а

инициализатор — это список значений адресов типа «Тип_массива *».

Например:

 

int Data [6];

// массив целочисленных значений

int *pD [6];

// массив указателей

int *pI [ ] = { &Data[0], &Data[1], &Data[2]}; // с инициализацией

Здесь каждый элемент массивов pD и pI является указателем на объекты типа int, следовательно значением каждого элемента pD[i] и pI[i] может быть адрес объекта типа int. Элементы массива pD не инициализированы, значит, имеют неопределенные адреса. В массиве pI три элемента, значения которых — это адреса конкретных элементов массива Data.

Рис. 1.11 иллюстрирует исходную и результирующую адресации элементов массива A элементами массива указателей *pA.

int A

10

9

8

7

6

5

4

3

2

1

 

 

 

 

 

 

 

 

 

 

 

 

 

int

 

 

 

 

 

 

 

 

 

 

 

*pA

 

 

 

 

 

 

 

 

 

 

 

 

 

 

а) – массивы до сортировки

 

 

 

 

int A

10

9

8

7

6

5

4

3

2

1

 

int *pA

б) – массивы после сортировки

Рис. 1.11. Массивы в задаче сортировки

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

86

порядке, а сортируется массив указателей на элементы массива pA[N]. С его помощью определяется последовательность просмотра элементов массива A[N] в порядке возрастания их значений.

А теперь приведем текст примера:

#define N 10 void main (void)

{

int A [N] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};

int * pA [N] ;

 

int * buf;

// буфер для перемены адресов

int i, j;

 

for (i = 0; i < N; i++)

 

pA[i]=&A[i];

// копирование адресов (рис.1.11,а)

for (i = 0; i < N – 1; i ++)

for (j = i + 1; j < N; j ++)

{

 

if (*pA[i]>*pA[j])

{ // перестановка в массиве адресов

buf = pA [i] ;

pA [i] = pA [j] ;

pA [j] = buf;

}

}

printf ("\n По возрастанию: \n"); for (i = 0; i < N; i ++)

printf ("%d ", *pA [i]); // выведет по возрастанию printf ("\n По возрастанию: \n");

for (i = 0; i < N; i ++)

printf("%d ", A[i]) ; // выведет в исходном порядке

}

1.3.4.6. Указатели и функции

Механизмы указателей при работе с функциями используются для решения многих практических задач.

1.3.4.6.1. Передача параметров по адресу.

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

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

87

При этом используется механизм указателей. Адресная операция (&) применяется к имени параметра. Синтаксис этого описания:

тип_ функции имя_функции (тип_параметра & имя_параметра)

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

В классическом С этот механизм отсутствует, и при передаче параметра по адресу в теле функции необходимо разыменование переменной (операция *). Использование этого механизма отчетливо видно в функции стандартного ввода scanf.

// передача параметра по адресу в стиле C

void change_5 (int * ptr) // формальный параметр — указатель

{

*ptr += 5;

// следует разыменовать

}

 

void main(void)

 

{

 

int x = 5;

 

printf("x = %d\n ", x);

 

change_5 (&x);

// фактический параметр — адрес

printf("А теперь %d\n ",x);

 

}

В C признак передачи параметра по ссылке указывается в списке формальных параметров при описании функции и в прототипе.

// передача параметра по адресу в стиле C++

void change_5 (int &ptr) // формальный параметр — адрес объекта

{

ptr += 5;

}

void main(void)

{

int x = 5;

printf("x = %d\n ",x); change_5 (x);

printf("А теперь %d\n ",x);

}

1.3.4.6.2. Массивы как параметры функций Особенность массива в том, что он является указателем. Имя массива — это

адрес его нулевого байта. Когда параметром функции является массив, то, согласно синтаксису С, в функцию передается его адрес, следовательно, функция

88

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

Способов записи формальных параметров массивов в заголовке функции может быть два:

тип_функции имя_функции (тип_массива имя[])

{

... // обращение к элементам массива

}

тип_функции имя_функции (тип_массива * имя )

{

... // обращение к элементам массива

}

Как видим, длина массива отсутствует в описании формального параметра. Конструкции Имя[ ] и * Имя различны внешне, но одинаковы по выполняемым механизмам, если имя именует массив. Длина массива, как правило, определена его описанием или передается в функцию как параметр.

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

Несомненным достоинством этого механизма является возможность простой и надежной реализации алгоритмов обработки массивов переменной длины. При этом задачи обработки массивов решаются в общем виде. В качестве параметров в функцию следует передать имя массива и его длину. Если содержимое массива будет изменено функцией, это изменение доступно и вызывающей программе. Если функция не изменяет длину массива, ее следует передать как параметрзначение, если изменяет, то как параметр-адрес, например:

int function (int a [], int n);

// а — имя массива, n —длина массива,

int function (int a [], int &n);

// а — имя массива, n — длина массива.

Независимо от способа передачи параметра – массива, фактическим параметром при обращении к функции должно быть имя массива.

Для двумерных массивов задача не может быть решена так просто, потому что двумерный массив при передаче в функцию должен быть разбит на строки. Функция должна знать, каков способ разбиения на строки, поэтому число строк данных можно опустить, а число данных в каждой строке опускать нельзя. Оно должно быть указано обязательно как константное выражение в квадратных скобках при передаче матрицы. Так, прототип функции, получающей матрицу в качестве входного данного, может выглядеть так:

int function (int a [][m], int n, int m); // здесь m — константное выражение Примером использования массивов в качестве параметров функций могут

служить функции ввода и вывода одномерных массивов. Эти функции решают алгоритмические задачи в общем виде.

Пример: #define N 40

// функция вывода массива произвольной длины

89

void print_mas (int mas[], int len)

// параметры — имя и длина массива

{

 

int i;

 

printf ("Массив:\n");

 

for (i = 0; i < len; i++)

 

printf("%5d", mas[i]);

 

printf("\n");

 

}

// функция ввода массива произвольной длины

void input_mas(int *mas, int &len) // параметры — имя и длина массива

// длина передается по ссылке

{

int *ip;

printf ("Введи количество элементов массива < %d\n", N); // наибольшая длина

scanf ("%d", &len);

printf ("Введи элементы массива \n");

for (ip = mas; ip < mas + len; ip ++)

 

scanf("%5d", ip);

// признак & не нужен

}

 

// функция преобразования массива

 

void transform_mas(int *mas, int len)

// параметры — имя и длина массива

{

 

int i;

 

for (i = 0; i < len; i++)

 

mas[i]=mas[i] *2;

 

}

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

void main(void)

{

int a1[N] ,a2[N];

// объявлены два массива,

int n1, n2;

// их длины,

nput_mas (a1, n1);

// каждый массив введен, преобразован, распечатан

transform_mas (a1, n1);

 

print_mas (a1, n1);

 

input_mas (a2, n2);

 

transform_mas (a2, n2);

 

print_mas (a2, n2);

 

}

90

Соседние файлы в папке информатика_2