Работа с массивами в языке Си

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

Статические массивы

Способы объявления статических массивов

Объявление статического массива отличается от объявления обычной переменной только указанием количества элементов массива. Например, следуещее объявление означает, что именем points назвавается массив из 100 действительных чисел.

double points[100];
В некоторм смысле можно считать, что такое объявление переменной points создает 100 переменных, которые называются points[0], points[1], ..., points[99].

В реальных программах следует избегать явного использования числовых констант в объявлениях массива (и других частях программы). Если нам нужно объявить два массива, которые теоретически могут иметь разный размер, например,
double points[100];
int students[100];
то в дальнейшем, если возникнет необходимость увеличить один из массивов, будет сложно отличить одну константу от другой. Особенно это верно при обработке элементов массива (см. ниже). Правильным считается использование директив препроцессора для присвоения константам "говорящих" имен. Например:
#define NPOINTS 100
#define NSTUDENTS 100
...
double points[NPOINTS];
int students[NSTUDENTS];
Объявление массива может быть совмещено с присвоением значений его элементам. Например,
double points[] = {1.0, 3.14, -1.2, 12.65};
создает массив из четырех действительных чисел с указанными значениями. Заметим, что в данном случае число элементов массива в квадратных скобках не указывается. Компилятор самостоятельно вычисляет длину по списку начальных значений. В программе можно вычислить длину такого массива, разделив его размер на размер одного элемента (пример ниже).

Работа с элементами массива

Для доступа к элементу массива достаточно знать его имя и порядковый номер элемента. В языке Си элементы массива индексируются с нуля, то есть в массиве из двух элементов корректными являются индексы 0 и 1. Рассмотрим для примера следующую программу.
#define NPOINTS 100
int main() {
    double points[NPOINTS];
    int k;

    for(k=0; k < NPOINTS; k++) {
        points[k] = 0.1 * k;
    }
    return 0;
}
Эта программа заполняет массив действительных чисел значениями 0, 0.1, 0.2 и так далее. Отметим, что макропеременная NPOINTS используется как при объявлении массива, так и в качестве верхней границы цикла по всем его элементам. Если размер массива нужно будет изменить, то достаточно исправить одну строчку в программе (#define).

Пример работы с массивом, который задан с начальными значениями:

int main() {
        double points[] = {1.0, 3.14, -1.2, 12.65};
        int k;
        int npoints = sizeof(points)/sizeof(points[0]);

        for(k=0; k < npoints; k++) {
            printf("points[%d] = %lf\n", k, points[k]);
        }
        return 0;
} 

Типичная ошибка при работе с массивами состоит в указании неправильного индекса. Если в приведенной выше программе переменная цикла k будет пробегать значения от 0 до npoints включительно, то поведение программы, вообще говоря, может быть любым. В простейшем варианте на экран будет выведено какое-то значение, но может возникнуть и критическая ошибка, которая приведет к остановке программы.

Передача массива в функцию

Пример:
#include <stdio.h>
int print_array(double x[], int len);

int print_array(double x[], int len) {
        int k;

        for(k=0; k < len; k++) {
            printf("x[%d] = %lf\n", k, x[k]);
        }
        return 0;
}

int main() {
        double points[] = {1.0, 3.14, -1.2, 12.65};
        int k;
        int npoints = sizeof(points)/sizeof(points[0]);

        print_array(points, npoints);
        return 0;
} 
Формальными параметрами функции является имя массива (без указания размера) и его длина. При вызове функции в качестве аргументов передаются имя массива и его длина.

Внимание! Если функция print_array изменит значение элемента массива x (например, в цикле будет написано "x[k]=0;"), то изменятся значения и в массиве points функции main. Элементы массива при вызове функций не копируются!

Динамические массивы

#include <stdio.h>
int main() {
        int npoints;
        double *points;

        scanf("%d", &npoints); /* npoints получает значение в момент выполнения программы */

        points = (double *)malloc( npoints*sizeof(double) );
        /* Выдели память для хранения npoints элементов, каждый размера sizeof(double) */

        if(points == NULL) {
           printf("Произошла ошибка. Запросили слишком много памяти??\n");
           return -1;
        }

        /* Работаем с points как с обычным массивом */
        /* Например, вызываем функцию print_array(points, npoints) */
        
        free(points); /* Освободили память */
        return 0;
}
Если нужно работать с массивом другого типа, например с массивом целых чисел, то заменяем слово double на int в двух строчках (но трех местах):
int *points;
points = (int *)malloc( npoints*sizeof(int) );