Программирование на языке C: определение массивов Печать
Добавил(а) microsin   

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

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

#define INDEX_A 0
#define INDEX_B 1
#define INDEX_C 2
#define INDEX_D 3
#define INDEX_E 4
 
char myarray[] =
{
  [INDEX_A] = 'Q',
  [INDEX_B] = 'W',
  [INDEX_C] = 'E',
  [INDEX_D] = 'R',
  [INDEX_E] = 'T',
};

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

char myarray[] =
{
  'Q', 'W', 'E', 'R', 'T'
};

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

char myarray[] =
{
  [0] = 'Q',
  [2] = 'E',
  [4] = 'T',
  [3] = 'R'
};

Эта декларация создаст следующий массив, в нем пропущенный элемент с индексом 1 будет заполнен нулем:

char myarray[] =
{
  'Q', 0, 'E', 'R', 'T'
};

В некоторых источниках для массивов придумывают специфичные названия. Например, одномерные массивы называют векторами, двумерные массивы матрицами, и называют массивы просто массивами, когда размерность массива больше 2, или не определена, или не имеет принципиального значения.

[Декларация массива]

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

Примеры:

int i, j, intArray[10], number;
float floatArray[1000];
int tableArray[3][5];      // 3 строки, в каждой 5 столбцов
 
const int NROWS = 100;     // (старый код будет использовать #define NROWS 100)
const int NCOLS = 200;     // (старый код будет использовать #define NCOLS 200)
float matrix[NROWS][NCOLS];

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

Примеры только для C99:

int numElements;
printf("Насколько большим Вы хотите получить массив? ");
scanf( "%d", &numElements; )
 
if( numElements < = 0 )
{
   printf( "Error - Quitting\n" );
   exit( 0 );
}
 
double data[numElements];   // Это работает только для C99, но не в обычном C

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

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

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

Если массив инициализируется полностью, то величину размерности внутри квадратных скобок указывать не обязательно. Компилятор автоматически определит размер массива, чтобы в нем поместились все инициализированные данные (могут быть вариации для многомерных массивов, о чем далее).

Примеры:

int i =  5, intArray[6] = {1, 2, 3, 4, 5, 6}, k;
float sum  = 0.0f, floatArray[100] = {1.0f, 5.0f, 20.0f};
double piFractions[] = {3.141592654, 1.570796327, 0.785398163};

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

int numbers[100] = {1, 2, 3, [10] = 15, 11, 12, [60] = 50, [42] = 420};

В этом примере первые три элемента инициализированы значениями 1, 2 и 3 соответственно, как обычно. Затем элемент 10 (11-й элемент массива) инициализирован значением 15, все промежуточные элементы - от 3 до 9 включительно - при этом автоматически инициализируются нулями. Следующие 2 элемента (12-й и 13-й) инициализируются значениями 11 и 12 соответственно. Элемент 60 (61-й) инициализируется значением 50, и элемент 42 (43-й) значением 420. Обратите внимание, что назначенные инициализаторы не требуют, чтобы они следовали друг за другом по индексам в порядке увеличения).

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

[Использование массивов]

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

#include  
#include < stdio.h>
 
int main (void)
{
   int i, fibonacci[20];
    
   fibonacci[ 0 ] = 0;
   fibonacci[ 1 ] = 1;
    
   for( i = 2; i < 20; i++ )
      fibonacci[i] = fibonacci[i-2] + fibonacci[i-1];
   for( i = 0; i < 20; i++ )
      printf("Fibonacci[%i] = %i\n", i, fibonacci[i]);
}

Результат вывода:

Fibonacci[0] = 0
Fibonacci[1] = 1
Fibonacci[2] = 1
Fibonacci[3] = 2
Fibonacci[4] = 3
Fibonacci[5] = 5
Fibonacci[6] = 8
Fibonacci[7] = 13
Fibonacci[8] = 21
Fibonacci[9] = 34
Fibonacci[10] = 55
Fibonacci[11] = 89
Fibonacci[12] = 144
Fibonacci[13] = 233
Fibonacci[14] = 377
Fibonacci[15] = 610
Fibonacci[16] = 987
Fibonacci[17] = 1597
Fibonacci[18] = 2584
Fibonacci[19] = 4181

Второй пример:

#include < stdlib.h>
#include < stdio.h>
 
int main (void)
{
   int numbers[10];
   int i, index = 2;
   
   for( i = 0; i < 10; i++ ) 
      numbers[ i ] = i * 10;
   
   numbers[8] = 25;
   numbers[5] = numbers[9] / 3;
   numbers[4] += numbers[2] / numbers[1];
   numbers[index] = 5;
   ++numbers[index];
   numbers[numbers[index++]] = 100;
   numbers[index] = numbers[numbers[index + 1] / 7]--;
   
   for( index = 0; index < 10; index++ )
      printf("numbers[%i] = %i\n", index, numbers[index]);
}

Результат вывода:

numbers[0] = 0
numbers[1] = 10
numbers[2] = 6
numbers[3] = 100
numbers[4] = 42
numbers[5] = 30
numbers[6] = 99
numbers[7] = 70
numbers[8] = 25
numbers[9] = 90

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

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

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

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

Двумерные массивы рассматриваются в C/C++ как массив одномерных массивов. Например, int numbers[5][6] обозначает одномерный массив из 5 элементов, где каждый элемент - одномерный массив из 6 целых чисел. Если идти дальше, то int numbers[12][5][6] это массив из 12 элементов, каждый из которых двумерный массив, и так далее.

Другой способ представить себе организацию многомерных массивов - язык C сохраняет в памяти двумерные массивы по строкам, где все элементы одной строки сохраняются вместе как единая информационная единица. Если это четко представлять себе, то это позволит иногда писать более эффективные программы.

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

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

К отдельным элементам данных в многомерных массивах получают доступ путем полного указания всех индексов для элемента. Альтернативно к массиву меньшей размерности может быть доступен путем частичного разрешения имени массива. Например, если "data" был декларирован как трехмерный массив чисел float, то data[1][2][5] означает одно число float, data[1][2] означает одномерный массив чисел float, и data[1] будет относится к двумерному массиву чисел float. Причина такого принципа реализации связана с проблемами управления памятью, которые не входят в область рассмотрения этой статьи.

Пример программы, где используются двумерные (2D) массивы (написана в мае 1995 года, автор George P. Burdell):

#include < stdlib.h>
#include < stdio.h>
 
int main (void)
{
   // Добавление двумерных массивов:
   int a[2][3] = {{5, 6, 7}, {10, 20, 30}};
   int b[2][3] = {{1, 2, 3}, {3,  2,  1 }};
   int sum[2][3], row, column;
   
   // Суммирование:
   for( row = 0; row < 2; row++ )
      for( column = 0; column < 3; column++ )
         sum[row][column] =  
            a[row][column] + b[row][column];
   
   // Печать результатов:   
   printf("The sum is: \n");
   
   for( row = 0; row < 2; row++ )
   {
      for( column = 0; column < 3; column++ )
         printf("\t%d", sum[row][column]);
      printf("\n");
   }
   
   return 0;
}

Результат вывода:

The sum is:
        6       8       10
        13      22      31

Дальнейший материал до последнего времени не соответствует области действия стандарта CS 107.

[Передача массивов в функции (на C++)]

Существует 2 метода передачи обычных данных в функции:

Pass-by-Value (передача по значению). Функция получает копию данных, и все изменения над ними производится в функции локально, без передачи за пределы тела функции.
Pass-by-Reference (передача по ссылке). Функция получает значение переменных по указателю (псевдониму), так что все изменения переменной внутри функции отразятся в данных кода, который вызвал функцию.

Если в функцию передается отдельный элемент массива, то это происходит в соответствии с базовым типом данных. Таким образом, если nums был декларирован как одномерный массив чисел int, то передача nums[i] в функцию произойдет точно так же, как если бы передавалось обычное значение int - либо pass-by-value, либо pass-by-reference, в зависимости от того, как написана функция.

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

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

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

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

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

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

Обратите внимание, как используются массивы в примерах ниже. В вычислении max4, передается двумерный массив, содержащий 2 строки из 3 элементов, как если бы это был одномерный массив из 6 элементов. Это читерство, но однако работает из-за способа организации данных в памяти многомерных массивов.

#include < iostream>
using namespace std;
 
/* Функция ищет максимум в массиве чисел float.
   Обратите внимание, что используется модификатор const для параметра
   numbers, чтобы предотвратить случайную модификацию данных массива. */
static float maxArray (const float numbers[], int arraySize, int & errorCode)
{
   int i;
   float max;
   if( arraySize < = 0 )
   {
      errorCode = -1;
      return 0.0;
   }
   
   errorCode = 0;
   
   max = numbers[0];
   
   for( i = 1; i < arraySize; i++ )
      max = ( numbers[i] > max ) ? numbers[i] : max;
   return max;
}
 
// Программа ищет максимум в массиве.
int main (void)
{
   float array1[] = { 10.0, 20.0, 100.0, 0.001 };
   float array2[2][3] = { { 5.0, 10.0, 20.0 },
                          { 8.0, 15.0, 42.0 } };
   
   int sizes[2] = {4, 3}, err1, err2, err3, err4, err5;
   float max1, max2, max3, max4, max5;
   
   max1 = maxArray( array1, 4, err1 );
   max2 = maxArray( array1, sizes[0], err2 );
   max3 = maxArray( array2[1], 3, err3 );
   max4 = maxArray( array2[0], 6, err4 );
   max5 = maxArray( array1, -4, err5 ); // Генерирует ошибку
   
   cout << "Maximums are " <<  max1 << ", " << max2 << ", " << max3 
        << ", " << max4 << ", " << max5 <<  endl;
   cout << "( Error codes are " <<  err1 << ", " << err2 << ", " << err3 
        << ", " << err4 << ", " << err5 << " )\n" ;      
   return 0;   
}

Результат вывода программы:

Maximums are 100, 100, 42, 42, 0
( Error codes are 0, 0, 0, 0, -1 )

[Строки символов как массивы символов]

Класс "string" это новый тип, добавленный в C++, который не существовал в традиционном C. Традиционный метод обработки строк символов подразумевал использование массива символов. В качестве маркера конца строки использовался нулевой байт (символьная константа '\0'), это называется ASCIIZ-строка, или null-terminated array. Строки, обозначенные двойными кавычками ("Please enter a number > ") сохраняются в памяти null-terminated массивы.

Библиотека cstring содержит несколько функций для взаимодействия с традиционными массивами символов. Объекты C++ string могут быть инициализированы традиционными массивами стиля C:

string prompt = "Please enter your choice > ";

[Ссылки]

1. Introduction to C Programming Arrays site:cs.uic.edu.