Глава 8. Массивы

В этой главе вы узнаете, как создавать массивы данных и работать с ними. В C/C++ темы массивов, указателей и строк взаимосвязаны, во многих книгах они даже рассматриваются в одной главе. С нашей точки зрения, это не лучший подход, поскольку часто для работы с массивами не требуется глубокого знания указателей. Кроме того, с массивами в целом связан достаточно большой объем материала, и параллельное изучение указателей может совершенно запутать дело. В то же время, без уяснения принципов использования указателей вы не сможете полноценно работать с массивами. Поэтому главу "Указатели" можно рассматривать как завершение дискуссии о массивах.

Что такое массивы

Массив можно представить как переменную, содержащую упорядоченный набор данных одного типа. К каждому элементу массива можно получить доступ по его адресу. В языках C/C++ массив не является стандартным типом данных. Напротив, он сам имеет тип: char, int, float, doubleи т.д. Допускается создавать массивы массивов, указателей, структур и др. Принципы построения массивов и работы с ними в основе своей одинаковы в С и C++.

Свойства массивов

Ниже перечислены четыре основных принципа, определяющих свойства массивов: в массиве хранятся отдельные значения, которые называются элементами;

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

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

Объявления массивов

Ниже даны примеры объявления массивов:

intiarray[12];  /* массив из двенадцати целых чисел */
charcarray[20]; /* массив из двадцати символов */

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

Вот как устанавливаются размеры массивов с помощью констант:

#define iARRAY_MAX 20

#define fARRAY_MAX 15

int  iarray[iARRAY_MAX]; char farray[fARRAY_MAX];

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

#include <stdio.h>
#define iARRAY_MAX 20
int iarray[iARRAY_MAX];

main (} {

int i;

for(i= 0; i < iARRAY_MAX; i++) {

}

return(-0); }

Инициализация массивов

Массив можно инициализировать одним из трех способов:

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

Инициализация по умолчанию

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

/*
.*        initar.c
*  Эта программа на языке С демонстрирует инициализацию массивов,
*   выполняемую по умолчанию.
*/

#include <stdio.h>
#define iGLOBAL_ARRAY_SIZE 10
#define iSTATIC_ARRAY_SIZE 20

int iglobal_array[iGLOBAL_ARRAY_SIZE];     /* глобальный массив */

main () {

static int istatic_array[iSTATIC_ARRAY_SIZE]; /* статический массив */

int i;

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

printf ("iglobal_array [%d].:%d\n",i, iglobal_array [i] ) ;

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

printf("istatic_array[%d]: %d\n", i, istatic_array[i]);

return(0); }

После запуска программы на экран будут выведены нулевые значения, присвоенные элементам массива по умолчанию. Данная программа выявляет еще один существенный момент работы с массивами: первый элемент массива всегда имеет нулевой индекс. Это связано с тем, что создатели языка С стремились максимально приблизить его к ассемблерным языкам, где первый элемент таблицы всегда имеет нулевое смещение.

Явная инициализация

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

int  iarray[3]   =       {-1,0, 1};

static float fpercent[4] =             {1.141579,0.75,55E0,-.33E1);

static int  ideoimal[3] =             {0,1, 2, 3, 4, 5, 6, 7, 8, 9};

char cvowels[]   =        {'A','a','E','e','I','i','O','o','U','u'};

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

В третьей строке показан пример задания большего числа элементов массива, чем в нем на самом деле содержится. Многие компиляторы рассматривают подобную ситуацию как ошибку, тогда как другие автоматически увеличивают размер массива, чтобы вместить дополнительные элементы. Компилятор MicrosoftVisualC++ выдаст ошибку вида "too many initializers" (слишком много инициализаторов). В противоположной ситуации, когда при инициализации указано меньше значений, чем элементов массива, оставшиеся элементы по умолчанию примут нулевые значения. С учетом этого можно вообще не задавать размер массива, как в четвертой строке программы. Количество значений, указанных в фигурных скобках, автоматически определит размер массива.

Инициализация безразмерных массивов

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

char   sz!nput_Errdr[41]   = "Введите значения от 0 до 9.\n";

char   szDevice_Error[18]    = "Диск недоступен.\n";

char   szMonitor_Error[49]   = "Для работы программы необходим цветной монитор. \n";

char   szWarning[36]= "Эта операция приведет к удалению файла!\n";

Здесь необходимо предварительно подсчитать число символов в строке, не забыв к полученному числу прибавить 1 для символа \0,обозначающего конец строки. Это нудная работа, к тому же чреватая ошибками. Можно позволить компилятору автоматически вычислить размер массива, как показано ниже:

char    szInput_Error[]     = "Введите значения от 0 до 9.\n";

char    szDevice_Error[]     = "Диск недоступен.\n";

char    szMonitor_Error[]     = "Для работы программы необходим цветной монитор.\n";

char    szWarning[]   = "Эта операция приведет к удалению файла!\n";

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

Доступ к элементам массива

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

int iweekend;

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

int iweek[7] ;

Рассмотрим, как организуется доступ к одиночной ячейке памяти, связанной с переменной iweekend, и к семи ячейкам памяти, связанным с массивом iweek. Чтобы получить значение, хранящееся в переменной iweekend, достаточно обратиться к этой переменной по имени. При доступе к массиву следует дополнительно указать индекс, представляющий собой порядковый номер элемента массива, к которому вы хотите обратиться. В следующем примере последовательно выполняется обращение ко всем элемента созданного массива:

iweek[0];

iweek[1];

iweek[2];

iweek[3];
    .
    .
    .
iweek[6];

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

Начинающие программисты часто допускают ошибку, считая, что первый элемент имеет индекс 1. Для обращения к первому элементу массива следует указывать индекс 0, так как смещение первого элемента относительно самого себя, естественно, является нулевым. А например, к третьему элементу следует обращаться по индексу 2, так как он на две ячейки смещен по отношению к первому элементу.

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

int iweek[7];

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

iweek[3];

Для описанного выше массива iweek следующее выражение присваивания является неправильным:

iweek[7]   =  53219;

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

Рассмотрим следующие объявления массива, переменных и константы:

#define iDAYS_OF_WEEK 7

int   iweek[iDAYS_OF_WEEK];

int iweekend = 1;

int iweekday = 2;

и выражения:

iweek[2];

iweek[iweekday] ;

iweek[iweekend +  iweekday];

iweek[iweekday -  iweekend];

iweek[iweekend -  iweekday];

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

Для того чтобы получить доступ к элементу массива, нет необходимости учитывать размер занимаемой им памяти, так как подобная работа выполняется компилятором автоматически. Предположим, например, что вам нужно получить значение третьего элемента массива iweek, содержащего целые числа. Как было сказано в главе "Работа с данными", в различных системах для представления данных типа int могут использоваться ячейки памяти разных размеров: иногда 2 байта, иногда 4. Но независимо от системы вы в любом случае сможете получить доступ к третьему элементу массива, используя выражение iweek[ 2 ]. Индекс указывает на порядковый номер элемента в массиве вне зависимости от того, сколько байтов занимает каждый элемент.

Вычисление размера массива в байтах

Вам уже известен оператор sizeof, возвращающий размер указанного операнда в байтах. Этот оператор можно использовать с переменными любых типов, за исключением битовых полей. Часто оператор sizeof применяют, чтобы определить размер переменной, тип которой в разных системах может иметь разную размерность. Как говорилось выше, в одном случае для представления целочисленного значения отводится 2 байта, в другом — 4 байта. Поэтому, например, при работе с массивами, размещаемыми в памяти динамически, необходимо точно знать, сколько памяти запрашивать у операционной системы. Следующая программа автоматически вычислит и отобразит на экране число байтов, занимаемых массивом из семи целочисленных элементов:

/*
*       sizeof.с
*   Эта программа на языке С демонстрирует использование
*  оператора sizeofдля определения физического размера массива.
*/

#include <stdio.h>

#define iDAY_OF_WEEK 7

main () {

int iweek[iDAY_OF_WEEK] = (1,2, 3, 4, 5, 6, 7} ;

printf("Массив iweek занимает%d байт.\n",(int) sizeof(iweek));

return(0);.  
}  

Вы можете спросить, почему значение, возвращаемое оператором sizeof, приводится к типу int. Дело в том, что в соответствии со стандартом ANSI данный оператор возвращает значение типа size_t, поскольку в некоторых системах одного лишь типа int оказывается недостаточно для представления размерностей данных некоторых типов. В нашем примере операция приведения необходима также для того, чтобы выводимое число соответствовало спецификации %d функции printf().

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

/*
*       array.с
*   Этa программа на языке С позволяет убедиться
*   в смежном размещении в памяти элементов массива.
*/

#include <stdio.h>

#define iDAYS 7

main ()

{
int index, iarray[iDAYS];

printf("sizeof(int)= %d\n\n",(int)sizeof(int));

for(index = 0; index < iDAYS; index++)

printf("siarray[%d]= %X\n",index, &iarray[index]);

return(0); }

Запустив программу, вы получите примерно следующий результат:

sizeof(int)= 4

&iarray[0]=            64FDDC

Siarrayfl] =           64FDEO

&iarray[2]=            64FDE4

&iarray[3]=            64FDE8

Siarray[4]=            64FDEC

Siarray[5]=            64FDFO

&iarray[6]=            64FDF4

Обратите внимание на то, что оператор взятия адреса & можно применять к любым переменным, в том числе к элементам массива. Над элементом массива можно выполнять те же операции, что и над любой другой переменной, использовать его в выражениях, присваивать значения и передавать в качестве аргумента функциям. В рассматриваемом примере по отображаемым адресам можно убедиться, что на каждый элемент действительно отводится 4 байта памяти.

Ниже показан аналог этой же программы на языке C++:

//
//       array.срр
//  Это версия предыдущей программы на языке C++.
//

#include <iostream.h>

#define iDAYS 7

main () {

int index, iarray[iDAYS];

cout << "sizeof (int) = " << (int)sizeof(int) << "\n\n";
for(index = 0; index < iDAYS; index++)
cout << "siarray["<< index << "] = " << &iarray[index] << "\n";

return(0); }

Выход за пределы массива

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

/*
*       norun.c
*   НЕ запускайте эту программу.
*/

#include <stdio.h>

#define iMAX 10

#define iOOT_OF_RANGE 50

main{)

{

int inot_enough_room[iMAX] , index;
for (index = 0; index < iOUT_OF_RANGE; index++)
inot_enough_room[index] = index;

return(0);

}

Массивы символов

Хотя языки С и C++ поддерживают тип данных char, в переменных этого типа могут храниться только одиночные символы, но не строки текста. Если в программе необходимо работать со строками, их можно создавать как массивы символов. В таком массиве для каждого символа строки отводится своя ячейка, а последний элемент содержит символ конца строки — \0.

В следующем примере создаются три символьных массива. Массив szvehicle1 заполняется символ за символом с помощью оператора присваивания. Данные в массив szvehicle2 заносятся с помощью функции scanf(), а массив szvehicle3 инициализируется константной строкой.

/*
*   string.с
*   Эта программа на языке С демонстрирует работу с массивами символов.
*/

#include <stdio.h>

main() {

char       szvehiclel[7],       /* машина */
szvehicle2[8];            /* самолет */

static char szvehicleS[8] = "корабль"; /* корабль */

szvehiclel[0]        =           'м'

szvehiclel[1]        =           'a'

szvehiclel [2]       =           'ш'

szvehiclel[3]        =           'и'

szvehiclel[4]        =           'н'

szvehiclel[5]        =           'a'

szvehiclel[6]        =           '\0';

printf ("\n\n\tВведите слово -—> самолет ") ;
scanf("%s",szvehicle2);
printf("%s\n",szvehiclel);
printf("%s\n",szvehicle2);
printf("%s\n",szvehicle3);

return(0);

}

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

static char szvehicleS[8]= {'к','о','р','а','б','л','ь','\0'};

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

static char szvehicleS[] = "корабль";

При этом размер массива определяется компилятором автоматически.

Часто содержимое массива запрашивается у пользователя с помощью функции scanf( ) , как в случае с массивом szvehicle2. В нашем примере в функции scanf( ) применяется спецификация %s,означающая ввод строки. В результате функция пропустит ведущие пробельные литеры (символы пробела, табуляции и конца абзаца), после чего; запишет в массив последовательность символов вплоть до следующей пробельной литеры. В завершение к набору символов будет автоматически добавлен символ конца строки. Помните, что при объявлении массива следует указать такой размер, чтобы в массиве поместился весь вводимый текст вместе с символом \0.

Рассмотрим еще раз строку программы, в которой в массив szvehicle2 записывается информация:

scanf ("%s",szvehicle2);

Не удивил ли вас тот факт, что имени szvehicle2 не предшествует оператор взятия адреса &? Это объясняется тем, что имя массива, в отличие от имен других переменных, уже является адресом первого его элемента.

Когда в функции printf ( ) задана спецификация %s, то предполагается наличие аргумента также в виде адреса строки. Строка текста будет выведена на экран полностью, за исключением завершающего нулевого символа.

Ниже показана программа на языке C++, аналогичная рассмотренной выше:

//
//       string. срр
//       Это версия предыдущей программы на языке C++.
//

#include <iostream.h>

main () {

char szvehiclel [7],     // машина

szvehicle2 [8];    // самолет
static char szvehicleS[8] = "корабль"; // корабль

szvehiclel[0]         =          'м'

szvehiclel[1]        =           'a'

szvehiclel[2]        =           'ш'

szvehiclel[3]        =           'и'

szvehiclel[4]        =           'н'

szvehiclel[5]        =           'a'

szvehiclel[6]        =           '\0';

cout<< "\n\n\tВведите слово --> самолет ";

cin >> szvehicle2;

cout << szvehiclel << "\n";

cout << szvehicle2 << "\n";

cout << szvehicleS << "\n";

return(0);

}

При выполнении любой из приведенных программ на экран будет выведено следующее:

машина

самолет

корабль

Многомерные массивы

Под размерностью массива понимают число индексов, которые необходимо указать для получения доступа к отдельному элементу массива. Все массивы, рассмотренные до сих пор, были одномерными и требовали указания только одного индекса. Чтобы определить размерность массива, достаточно посмотреть на его объявление. Если в нем указана только одна пара квадратных скобок ([]), то перед нами одномерный массив, если две ([][]), то двухмерный, и т.д. Массивы с более чем одной размерностью называются многомерными. В реальных программах размерность массива не превышает трех.

В следующей программе создается двухмерный массив:

/*
*       2darray.c
*  Эта программа на языке С демонстрирует работу с двухмерным массивом.
*/

#include <stdio.h>

#define iROWS 4

#define iCOLUMNS 5

main() {

int irow;

int icolumn;

int istatus [iROWS] [iCOLUMNS] ;  

int iadd;

int imultiple;

for (irow = 0; irow < iROWS; irow++)

for (icolumn = 0; icolumn < iCOLUMNS; icolumn++)
{ iadd = iCOLUMNS - icolumn; imultiple = irow; istatus [irow][icolumn] = (irow+1) *icolumn + iadd*imultiple;

}

for(irow = 0; irow < iROWS; irow++)
{ printf("ТЕКУЩАЯ СТРОКА: %d\n", irow);
printf("СМЕЩЕНИЕ ОТ НАЧАЛА МАССИВА:\n");
for(icolumn = 0; icolumn < iCOLUMNS; icolumn++)

printf(" %d ", istatus[irow][icolumn]); printf("\n\n");

}

return(0);}

В программе используются два цикла for, в которых каждый элемент массива инициализируется значением смещения этого элемента относительно первой ячейки массива. Созданный массив имеет 4 строки (константа irows) и 5 столбцов (константа icolumns), что в сумме дает 20 элементов. Многомерные массивы представляются в памяти компьютера в виде линейной последовательности элементов, при этом группировка по индексам осуществляется справа налево, т.е. от самого правого индекса к самому левому.

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

istatus[irow][icolumn]   =   ...

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

ТЕКУЩАЯ СТРОКА: 0 СМЕЩЕНИЕ ОТ НАЧАЛА МАССИВА: 01234

ТЕКУЩАЯ СТРОКА: 1 СМЕЩЕНИЕ ОТ НАЧАЛА МАССИВА: 56789

ТЕКУЩАЯ СТРОКА: 2 СМЕЩЕНИЕ ОТ НАЧАЛА МАССИВА: 10  11  12  13  14

ТЕКУЩАЯ СТРОКА: 3 СМЕЩЕНИЕ ОТ НАЧАЛА МАССИВА: 15  16  17  18  19

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

/*
*     2dinit.c
*  Эта программа на языке С демонстрирует инициализацию двухмерного массива.
*/

#include <stdio.h>

#include <raath.h>

#define iBASES 6

#define iEXPONENTS 3

#define iBASE 0   

#define iRAISED_TO 1

#define iRESULT 2

main () {

double dpowers[iBASES][iEXPONENTS] = {

1,1,1,0,

2,2,2,0, 

3,3,3,0,

4,4,4,0,

5,5,5,0,

6,6,6,0,

};

int irow_index;

for(irow_index  =  0;   irow_index  <   iBASES;   irow_index++)
dpowers [irowjmdex] [iFESOLT]  = pow(dpowers[irow_index] [1BASE], dpowers [irow_index] [iRAISED_TO,] ) ;

for(irow_index = 0; irow_index < iBASES; irow_index++) {
printf(" %d\n", (int) dpowers[irow_index][iRAISEDJTO]);
printf("%2.1f= %.2f\n\n",dpowers[irow_index][iBASE],

dpowers[irow_index][iRESULT]) ,

}

return(0); }

Библиотечная функция pow(x,у) возводит х в степень у. Массив dpowers имеет тип double, поскольку данная функция работает с числами этого типа. В первом столбце массива записаны числа, возводимые в степень, во втором столбце — показатели степени, в третий столбец помещается результат.

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

1

1.1= 1.10

2 2.2= 4.84

3 3.3= 35.94

4 4.4= 374.81

5 5.5= 5032.84

6 6.6= 82653.95

Массивы как аргументы функций

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

Передача массивов функциям в языке С

Предположим, имеется функция isum(), которая подсчитывает сумму элементов массива inumeric_values, содержащего целые числа. Для работы функции необходимы два аргумента: указатель iarray_address_received, принимающий копию адреса массива, и переменная imax_size, содержащая индекс последнего элемента, участвующего в операции суммирования. Тогда заголовок функции isum()будет записан следующим образом:

int isumfint iarray_address_received[], int imax_size)

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

isum(inumeric_values, iactual_index);

,Это простейший способ передачи массивов функциям. Поскольку функция isum() в действительности получает адрес первого элемента массива, она может быть вызвана и так:

itotal = isum(Sinumeric_values[0], iactual_index);

В отличие от переменных, которые могут передаваться в функцию по значению (т.е. внутри функции создается локальная копия переменной), массивы всегда передаются по ссылке, и в вычислениях участвуют реальные элементы массива, а не их копии. Это предупреждает появление сообщений вида "stack overruns heap" (стек перегружен), которые постоянно беспокоят программистов на языке Pascal, если они забывают при описании формальных аргументов указывать перед именами массивов модификатор var. В этом случае массивы передаются по значению, что заставляет компилятор дублировать их содержимое. При работе с большими массивами этот процесс может потребовать много времени и свободной памяти.

В следующем примере создается массив из пяти элементов, а функция iminimum() определяет наименьший из них. С этой целью в функцию iminimum. Oпередаются имя массива и число сравниваемых элементов — в данном случае это весь массив (указана константа 1МАХ).

/*
*       arrayarg.c
*  Эта программа на языке С демонстрирует передачу массива в качестве
*  аргумента функции.
*/

#include <stdio.h>

#define iMAX 10

int iminimum(int iarrayU,int imax);

main()

int iarraytiMAX[] = {3, 7, 2, 1, 5, 6, 8, 9, 0, 4};
int i, ismallest;

printf("Вот элементы массива: ");
for(i = 0; i < iMAX; i++)

printf("%d", iarray[i]);
ismallest = iminimumdarray, iMAX);
printf("\nМинимальное значение: %d\n",ismallest);

return(0); }

int iminimum(int iarrayt[],int imax)
int i, icurrent_minimum;

icurrent_minimum = iarray[0]; for(i= 1; i < imax; i++)

if(iarrayti]< icurrent_minimum) icurrent_minimum = iarray[i];

return(icurrent minimum);

}

Передача массивов функциям в языке C++

Следующая программа на языке C++ также содержит пример передачи массива в функцию.

//
//     arrayarg.cpp
//   Эта программа на языке C++ демонстрирует передачу массива в качестве
//   аргумента функции.
//

#include <iostream.h>

#define iSIZE 5

void vadd_l (int iarray[]);

main() {

int iarray[iSIZE] = {0,1, 2, 3, 4 } ;

int i;

cout <<  "Массив  iarray перед вызовом функции vadd_l:\n\n";
for(i  =  0;   i  <  iSIZE;   i++)

cout <<  "   "  << iarray[i];

vadd_l(iarray);

cout << "\n\nМассив iarray после вызова функции vadd_l:\n\n";
for(i  =0;   i  <   iSIZE;   i++)

cout <<  "  "  << iarray[i];

return(0); }

void vadd_l(int iarray[]) {

int i;

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

iarray [i]++; }

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

Массив iarray перед вызовом функции vadd_l:

0 1 2 3 4

Массив
iarray после вызова функции vadd_l:

1 2 3 4 5

Результаты работы программы дают четкий ответ на вопрос о том, каким способом массив передается в функцию: по значению или по ссылке. Функция vadd_l() добавляет единицу к каждому элементу массива. Так как это действие отражается на массиве iarray в функции main(), можно сделать вывод, что аргумент передается по ссылке.

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

//
//  3darray.cpp
//  Эта программа на языке C++ демонстрирует, как создавать многомерный
//  массив, передавать его в функцию и выбирать отдельный элемент
//  такого массива.
//

#include <iostream.h>

void vdisplay_results(char carray[][3][4]);

char cglobal_cube[5][4][5]=   { {

{'Т','А','В','L','Е'},
{'Z','Е','R','О',' '},
{' ',' ',' ',' ',' '),
{'R','О','W,' ', '3'},

},

{

('Т','А','В','L','Е'},
{'О','N','Е',' ',' '},
{'R','О','W',' ','2'},

},

{

{'Т','А','В','L','Е'},
{'Т','W','О',' ',' '},

},

{

{'Т','А','В','L','Е'),
{'Т','Н','R','Е','Е'),
{'R','О','W',' ','2'},
{'R','О','W',' ','3'),

},

{

('Т','А','В','L','E'},
('F','О','U','R',' '},
('r','о','w',' ','2'},
{'а','b','с','d','е'},

} };

int imatrix[4][3]={ {1},{2},{3},(4} };

main () {

int irow_index, icolumn_index;

char clocal_cube[2] [3][4];

cout<<      "Размер массива clocal_cube         = "

<<  sizeof(clocal_cube) << "\n";

cout <<      "Размер таблицы clocal_cube[0]       = "

<<         sizeof (clocal_cube[0]) << "\n";

cout<<      "Размер строки clocal_cube[0][0]     = "

<<   sizeof(clocal_eube[0][0]) << "\n";

cout <<        "Размер элемента clocal_cube[0][0][0] = "

<<   sizeof(clocal_cube[0][0][0]) << "\n";

vdisplay_results(clocal_cube);

cout << "Элемент cglobal_cube[0][1][2] = " << cglobal_cube[0][1][2] << "\n";

cout << "Элемент cglobal__cube [1][0][2]= " << cglobal_cube[l][0][2] << "\n";

cout <<  "\nВывод фрагмента массива  cglobal_cube   (таблица  0)\n"; for (irow_index = .0; . irow_index < 4;   irow_index++)    (

for(icolumn_index = 0;   icolumn_index <  5;   icolumn_index++) cout << cglobal_cube[0][irow_index][icolumn_index];

cout <<  "\n"; }

cout  <<  "\nВывод фрагмента массива cglobal_cube   (таблица  4)\n"; for(irow_index = 0;   irow_index <  4;   irow_index++)    {

for(icolumn__index = 0;  icolumn_index < 5;  icolumn_index++) cout  <<  cglobal_cube[4][irow_index][icolumn_index];

cout  <<   "\n"; )

cout << "\nВывод всего массива imatrix\n";

for (irow_index = 0; irow_index .< 4; irow_index++) {

for(icolumn_index =0;icolumn_index < 3; icolumn_index++) cout << imatrix[irow_index][icoluran_index];

cout << "\n"; }

return(0); }

void vdisplay_results(char carray[][3][4])

{

cout << "Размер массива carray         = " <<    sizeof(carray) << "\n";
cout << "Размер таблицы carray[0]      = " <<    sizeof(carray[0]) <<  "\n";
cout << "Размер массива cglobal_cube   = " <<    sizeof(cglobal_cube) <<  "\n";
cout << "Размер таблицы cglobal_cube[0]= " <<    sizeof(cglobal_cube[0]) <<  "\n";
}

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

В процессе выполнения программы на экран будут сначала выведены четыре строки с описанием размера всего массива clocal_cube, одной его таблицы, строки и отдельного элемента. Эти значения помогут вам лучше представить, из чего складывается размер массива. К примеру, размер трехмерного массива можно вычислить как произведение размеров трех его составляющих, умноженное на размер одного элемента. В нашем случае размер массива clocal_cubeбудет равен 2*3*4*sizeof(char) = 24.

Обратите внимание на то, что часть массива clocal_cube[0]сама является двухмерным массивом 3x4, то есть ее размер составляет 12. Размер строки clocal_cube[0][0]равен 4, что соответствует числу элементов в ней, так как размер одного элемента равен 1 (sizeof(clocal_cube[0] [0][0])).

Чтобы полностью разобраться с многомерными массивами, следует четко уяснить, что выражение clocal_cube[ 0 ] является константным указателем. В программе не объявлен массив clocal_cube[ 0 ] — на самом деле это последнее измерение многомерного массива clocal_cube. Выражение clocal_cube[0 ] не ссылается ни на один конкретный элемент массива, а лишь указывает на обособленный его фрагмент, поэтому тип данного выражения не char, а константный указатель на char, который не может выступать адресным операндом, то есть стоять слева от оператора присваивания.

Интересные события происходят, когда имя массива clocal_cubeпередается в качестве аргумента функции vdisplay_results(). В теле функции оператор sizeof не сможет правильно определить размер параметра carray, так как функция получает только копию адреса первого элемента массива, поэтому оператор вернет размер этого адреса (4 байта), а не самого массива и даже не его первого элемента. В то же время, размер фрагмента саггау[0]будет вычислен правильно — 3x4=12, поскольку в заголовке функции указано, что первый параметр является массивом, две последние размерности которого равны 3 и 4.

Функция vdisplay_results() выводит также размер глобального массива cglobal_cube, причем вычисляет его правильно. На этом примере мы видим, что из тела функции можно получать непосредственный доступ к глобальному массиву, но если массив передается в качестве аргумента, то функции доступен только его адрес.

Две строки в теле программы, следующие за вызовом функции vdisplay_results() , демонстрируют возможность обращения к отдельным элементам многомерного массива cglobal_cube. Выражение cglobal_cube[0] [1] [2] возвращает значение элемента нулевой (первой по порядку) таблицы, второй строки и третьего столбца — 'R'. Выражение cglobal_cube[l] [0][2]ссылается на элемент второй по порядку таблицы, первой ее строки и третьего столбца — 'В'.

Следующий фрагмент программы представлен тремя парами из двух вложенных циклов for, которые демонстрируют последовательный доступ к элементам трехмерного массива. Первая пара циклов for выводит на экран строки нулевой (первой по порядку) таблицы массива cglobal_cube, причем во внешнем цикле выбирается строка, а во внутреннем цикле последовательно выводятся все элементы этой строки. С помощью второй пары циклов for выводится содержимое пятой таблицы массива cglobal_cube. В конце отображается содержимое массива imatrix в виде таблицы; именно так большинство из нас и представляет себе двухмерный массив.

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

Размер массива clocal_cube            = 24

Размер таблицы clocal_cube[0]        = 12 

Размер строки clocal_cube[0][0]       = 4

Размер элемента clocal_cube[0][0][0]= 1

Размер массива саггау             = 4

Размер таблицы саггау[0]          = 12

Размер массива cglobal_cube            = 100

Размер таблицы cglobal_cube[0]          = 20

Элемент cglobal_cube[0][1][2]          = R 

Элементcglobal_cube[1][0][2]       = В

Вывод фрагмента массива cglobal_cube (таблица 0)

TABLE

ZERO

ROW 3

Вывод фрагмента массива cglobal_cube(таблица 4)

TABLE

FOUR

row 2

abed

Вывод всего массива imatrix

100

200

300

400

Вас удивила последняя часть? Тогда рассмотрим инициализацию массива imatrix. Каждая внутренняя пара фигурных скобок соответствует новой строке массива, но, поскольку внутри скобок указано меньше значений, чем размер строки, отсутствующие элементы массива остаются нулевыми. Вспомните, что в C/C++ все неинициализированные элементы статического массива по умолчанию автоматически становятся равными нулю.

Функции работы со строками и массивы символов

Многие функции работы со строками принимают в качестве аргумента имя массива символов: gets(),putsО, fgetsO,fputsO, sprintfO, strcpyO, strcat(), strncmp() и strlen(). Настало время познакомиться с ними. Сейчас вам будет значительно проще понять принципы их работы, поскольку вы уже изучили основные концепции создания массивов.

Функцииgets( ), puts( ), fgets( ), fputs( ) иsprintf( )

В следующей программе показано, как с помощью функций gets(), puts (), fgets(), fputs() и sprintf() можно управлять процессом ввода/вывода строк:

/*
*       stringio.c
*  Эта программа на языке С демонстрирует применение функций работы со строками.
*/

#include <stdio.h>

#define iSIZE 20

main()

{

char sztest_array[iSIZE];

fputs("Введите первую строку  : ", stdout); gets(sztest_afray);

fputs("Выввели    : ", stdout);
puts(sztest_array) ;

fputs("Введите вторую строку     : ", stdout);
fgets(sztest_array, iSIZE, stdin);
fputs("Выввели    : ", stdout);
fputs(sztest_array, stdout);          .

sprintf(sztest_array, "Это была %s проверка", "только");
fputs("Функция sprintf() создала : ", stdout);
fputs(sztest_array, stdout);

return(0); }

Вот что будет выведено на экран в результате работы программы:

Введите первую строку:             строка один

Вы ввели             :             строка один

Введите вторую строку:             строка два

Вы ввели             :             строка два     

Функция sprintf() создала     :    Это была только проверка

Если введенные строки содержат меньше символов, чем зарезервировано ячеек в массиве sztest_array, программа работает правильно. Тем не менее, если ввести строки большей длины, чем позволяет размер массива sztest_array, на экране может появиться абракадабра.

Функция gets() принимает символы от стандартного устройства ввода (в большинстве случаев это клавиатура) и помещает их в массив, указанный в качестве аргумента. Когда вы нажимаете клавишу [Enter] для завершения ввода, генерируется символ новой строки (\n). Функция gets() преобразует его в нулевой символ (\0), который служит признаком конца строки. Учтите, что при использовании функции gets() нет возможности напрямую определить, превышает ли количество введенных символов размер массива.

Функция puts() выводит на экран то, что было получено с помощью функции gets(). Она выполняет обратную замену — нулевого символа на символ новой строки.

Функция fgets()'аналогична функции gets(), но позволяет контролировать соответствие числа введенных символов установленным размерам массива. Символы читаются из указанного потока, которым может быть файл или, как в данном случае, стандартное устройство ввода (stdin). Введенных символов должно быть на единицу меньше, чем размер массива: в последнюю позицию автоматически добавляется нулевой символ. Если был введен символ новой строки, то он сохранится в массиве непосредственно перед символом \ 0. По аналогии с работающими в паре функциями gets() и puts(), совместно с функцией fgets() используют функцию fputs().

Поскольку первая не удаляет символы новой строки, то и вторая не добавляет их. Функция fputs() направляет символы в указанный поток: файл или устройство стандартного вывода stdout(как правило, это консоль).

Имя функции sprintf() является сокращением от слов "string printf()", т.е. "строковая функция printf()". В этой функции используются те же спецификаторы форматирования, что и в printf().Основное отличие состоит в том, что функция sprintf() помещает результат не на экран, а в указанный массив символов. Это может быть полезно, если результат работы данной функции необходимо вывести несколько раз, скажем, на экран и на принтер.

Функции strcpy( ), strcat( ), strncmp( ) и strlen( )

Все функции, рассматриваемые в этом параграфе, объявлены в файле STRING.H. Для их работы требуется, чтобы передаваемые им наборы символов обязательно заканчивались признаком конца строки — символом \0. В следующей программе демонстрируется использование функции strcpy() :

/*
*   strcpy.с
*   Эта программа на языке С демонстрирует использование функции strcpy().
*/

#include <stdio.h>
#include <string.h>
#define iSIZE 20

main() {

char szsource_string[iSIZE]= "Исходная строка", szdestination_string[iSIZE];

strcpy(szdestination_string, "Постоянная строка");
printf("%s\n",szdestination_string);
strcpy(szdestination_string, szsource_string);
printf("%s\n",szdestination_string);
return (0); 
}

При первом вызове функций strcpy() происходит копирование константной строки "Постоянная строка" в массив szdestination_string, а вторая функция strcpy() копирует туда же массив szsource_string, содержащий строку "Исходная строка". В результате получаем следующие сообщения:

Постоянная строка Исходная строка

Ниже показана аналогичная программа на языке C++:

//
//   strcpy.срр
//  Это версия предыдущей программы, написанная на языке C++.

#include <iostream.h>

#include <string.h>

#define iSIZE 20

main () {

char szsource_string [iSIZE] = "Исходная строка", szdestination_string [iSIZE] ;

strcpy (szdestination_string, "Постоянная строка");
cout << "\n"<< szdestination_string; 
strcpy(szdestination_string, szsource_string) ;
cout << "\n"<< szdestination_string;

return(0); }

Функция strcatf() конкатенирует (объединяет) две самостоятельные строки в один массив. Обе строки должны заканчиваться нулевыми символами. Результирующий массив также завершается символом \0. Применение функции strcat() иллюстрируется следующей программой:

/*
*       strcat.c
*   Эта программа на языке С демонстрирует использование функции strcat() .
*/

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

#define 1STRING_SIZE 35

main()

char szgreeting[]= "Доброе утро,",
szname[] = " Каролина! ",
szmessage[iSTRING_SIZE] ;

strcpy (szmessage, szgreeting) ;  
strcat (szmessage, szname) ;
strcat (szmessage, "Какдела?") ;
printf ("%s\n",szmessage);

return(0); }

В этом примере два массива, szgreetingи szname, инициализируются в момент объявления, тогда как массив szmessage создается пустым. Первое, что делает программа, это копирует с помощью функции strcpyt() содержимое массива szgreetingв массив szmessage. Затем функция strcat() добавляет в конец массива szmessage("Доброе утро,") содержимое массива szname(" Каролина! "). И наконец, последняя функция strcat() добавляет к полученной строке "Доброе утро, Каролина! " текст "Как дела?". В результате программа выведет на экран

Доброе утро, Каролина! Как дела?

Следующая программа демонстрирует возможность сравнения двух строк с помощью функции strncmp():

/*
*        stncmp.c
*   Эта программа на языке С ср'авнивает две строки с помощью функции strncmp() .
*/

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

main () {

char szstringA[] = "Вита", szstringB[]= "Вика";

int istringA_length, iresult = 0;

istringA_length = strlen(szstringA);

if(strlen(szstringB) >= strlen(szstringA))

iresult = strncmp(szstringA, szstringB, istringA_length);
printf ("Строка %s обнаружена",, iresult == 0 ? "была" : "не была");

return(0); }

Используемая в программе функция strlen() возвращает число символов в строке, указанной в качестве аргумента, не считая последнего, нулевого символа. В программе эта функция вызывается дважды с разными целями, что дает возможность получить о ней более полное представление. В первом случае функция записывает в переменную istringA_length длину массива szstringA. Во втором случае значения, возвращаемые двумя функциями strlen(), сравниваются с помощью оператора >=. Если длина массива szstringB больше или равна длине массива szstringA, то происходит вызов функции strncmp().

Функция strncmp() ищет первую строку во второй, начиная с первого символа. Длина первой строки задается в третьем аргументе. Если строки одинаковы, функция возвращает нулевое значение. Когда строки не идентичны, функция возвращает отрицательное значение, если строка szstringA меньше строки szstringB, и положительное значение, если строка szstringA больше строки szstringB.

Результат сравнения заносится в переменную iresult, на основе которой с помощью условного оператора (?:) формируется строка отчета. В нашем примере программа выведет на экран следующее сообщение:

Строка не была обнаружена

В завершение главы подчеркнем, что двумя наиболее частыми причинами сбоев в работе программ являются выход за пределы массива и отсутствие нулевого символа (\0) в конце символьного массива, используемого в качестве строки. Обе эти ошибки могут никак не проявляться в течение многих месяцев, до тех пор пока пользователь не введет, к примеру, слишком длинную строку текста.