- 1. Объявление массива
- 2. Инициализация массивов
- 3. Работа с массивами
- 3.1. Доступ к элементу массива
- 3.2. Обработка массивов
- 3.3. Ввод/вывод массивов
- 3.4. Пример 1. Обработка одномерного массива
- 3.5. Пример 2. Обработка двумерного массива
- Первый способ
- Второй способ
- 3.6. Пример 3. Суммирование элементов матрицы
- Способ инициализации массива c
- Содержание
- Объявление массива в C/C++
- Доступ к элементам массива
- Инициализация массива при объявлении
- Обход элементов массива в цикле
- Неопределённое поведение: выход за границы (out of bounds)
- Передача массива как параметра функции
- Динамически изменяемый массив
- Класс std::vector
- Добавление элементов в конец массива
- Перемещение элементов в памяти при изменении массива
- Метод erase для удаления элементов из середины
- Последствия перемещения элементов: ошибка в простом цикле с erase
1. Объявление массива
Объявление массива имеет следующий синтаксис:
[ ]; [ ];
Здесь квадратные скобки являются элементом синтаксиса, а не признаком необязательности конструкции.
Объявление массива может иметь одну из двух синтаксических форм, указанных выше. Квадратные скобки, следующие за именем, – признак того, что переменная является массивом. Константное выражение, заключенное в квадратные скобки определяет число элементов в массиве. Индексация элементов массива в языке C++ начинается с нуля. Таким образом, последний элемент массива имеет индекс на единицу меньше, чем число элементов массива.
Во второй синтаксической форме константное выражение в квадратных скобках опущено. Эта форма может быть использована, если в объявлении массива присутствует инициализатор, либо массив объявляется как формальный параметр функции, либо данное объявление является ссылкой на объявление массива где-то в другом месте программы. Однако для многомерного массива может быть опущена только первая размерность.
Многомерный массив, или массив массивов, объявляется путем задания последовательности константных выражений в квадратных скобках, следующей за именем:
[ ][ ] . ;
Каждое константное выражение определяет количество элементов в данном измерении массива, поэтому объявление двумерного массива содержит два константных выражение, трехмерного – три и т.д.
Массив может состоять из элементов любого типа, кроме типа void и функций, т.е. элементы массива могут иметь базовый, перечислимый, структурный тип, быть объединением, указателем или массивом.
Примеры объявлений массивов
int x[10]; | // Одномерный массив из 10 целых чисел. Индексы меняются от 0 до 9. |
double y[2][10]; | // Двумерный массив вещественных чисел из 2 строк и 10 столбцов. |
2. Инициализация массивов
Как и простые переменные, массивы могут быть инициализированы при объявлении. Инициализатор для объектов составных типов (каким является массив) состоит из списка инициализаторов, разделенных запятыми и заключенных в фигурные скобки. Каждый инициализатор в списке представляет собой либо константу соответствующего типа, либо, в свою очередь, список инициализаторов. Эта конструкция используется для инициализации многомерных массивов.
Наличие списка инициализаторов в объявлении массива позволяет не указывать число элементов по его первой размерности. В этом случае количество элементов в списке инициализаторов и определяет число элементов по первой размерности массива. Тем самым определяется размер памяти, необходимой для хранения массива. Число элементов по остальным размерностям массива, кроме первой, указывать обязательно.
Если в списке инициализаторов меньше элементов, чем в массиве, то оставшиеся элементы неявно инициализируются нулевыми значениями. Если же число инициализаторов больше, чем требуется, то выдается сообщение об ошибке.
Примеры инициализации массивов
int a[3] = <0, 1, 2>; | // Число инициализаторов равно числу элементов |
double b[5] = <0.1, 0.2, 0.3>; | // Число инициализаторов меньше числа элементов |
int c[ ] = <1, 2, 4, 8, 16>; | // Число элементов массива определяется по числу инициализаторов |
int d[2][3] = <<0, 1, 2>, <3, 4, 5>>; | // Инициализация двумерного массива. Массив состоит из двух строк, // в каждой из которых по 3 элемента. Элементы первой строки // получают значения 0, 1 и 2, а второй – значения 3, 4 и 5. |
int e[3] = <0, 1, 2, 3>; | // Ошибка – число инициализаторов больше числа элементов |
Обратите внимание, что не существует присваивания массиву, соответствующего описанному выше способу инициализации.
int a[3] = <0, 1, 2>; | // Объявление и инициализация |
a = <0, 1, 2>; | // Ошибка |
3. Работа с массивами
3.1. Доступ к элементу массива
Для доступа к конкретному элементу массива используются так называемые индексные выражения:
[ ]
Здесь квадратные скобки являются требованием синтаксисам языка, а не признаком необязательности конструкции.
Индекс массива может быть не только константой, но и выражением, которое имеет целочисленный тип, например, a [ i + 1] (здесь a должно быть именем ранее объявленного массива, а i – переменной целого типа).
Объявление массива и индексное выражение, используемое для доступа к элементу массива, имеют схожий синтаксис. Различаются они по месту в программе. Это особенно важно, когда мы определяем индекс последнего элемента массива. Как было сказано ранее, индексы элементов массива в языке C начинаются с 0, и номер последнего элемента на 1 меньше количества элементов массива. Поэтому если Вы объявили массив x из 10 элементов, Вы не можете написать индексное выражение x [10], т.к. в этом случае Вы пытаетесь обратиться к элементу с индексом 10, которого нет в Вашем массиве. Компилятор не выдаст сообщения об ошибке, но результаты работы такой программы будут непредсказуемы.
Имя массива является адресом его начала! Оно имеет тип константный указатель на . Конструкция a [ i ] эквивалентна *( a + i ) (см. лекцию 5).
Для многомерного массива надо указать соответствующее количество индексов в квадратных скобках.
3.2. Обработка массивов
Для обработки элементов массива обычно используется оператор пошагового цикла for .
for (i = 0; | // Присваиваем счетчику цикла значение индекса первого элемента |
i | // В теле цикла происходит обработка одного элемента массива |
Для обработки многомерного массива используется соответствующее количество циклов.
Массивы не самодостаточны в том смысле, что не гарантируется хранение информации о количестве элементов вместе с самим массивом. В большинстве реализаций С++ отсутствует проверка диапазона индексов для массивов. Таков традиционный низкоуровневый подход к массивам. Более совершенное понятие массива можно реализовать при помощи классов.
В С++ массивы тесно связаны с указателями. Имя массива можно использовать в качестве указателя на его первый элемент. Гарантируется осмысленность значения указателя на элемент, следующий за последним элементом массива. Это важно для многих алгоритмов. Но ввиду того, что такой указатель на самом деле не указывает ни на какой элемент массива, его нельзя использовать ни для чтения, ни для записи. Результат получения адреса элемента массива, предшествующего первому, не определён, и такой операции следует избегать.
Неявное преобразование имени массива в указатель на его первый элемент широко используется в вызовах функций.
int f(. int x[], . ) < . >int f(. int *x, . ) < . >void main() < int a[10]; . | // Можно использовать любой из двух вариантов |
f(. a, . ); . > | // Передаём в функцию f указатель на массив a |
Неявное преобразование массива в указатель при вызове функции приводит к потере информации о размере массива. Вызываемая функция должна каким-либо образом определить этот размер, чтобы выполнять осмысленные действия.
При объявлении многомерного массива как параметра функции можно опустить только первую размерность.
int g(. int x[][10], . ) | // Вторая и последующие размерности обязательны |
Это ограничение при желании можно обойти. Правда, при этом возникают другие проблемы (см. пример 3 в конце лекции).
3.3. Ввод/вывод массивов
В языке C нет возможности вводить и выводить весь массив одним оператором ввода/вывода. Можно вводить и выводить только один элемент массива. Следовательно, для того чтобы ввести весь массив, надо использовать цикл.
int a[10], n; printf(«Введите количество элементов массива (от 0 до 9): «); | // Объявляем массив и переменную для количества элементов массива |
scanf(«%d», &n); | // Ввод количества элементов массива |
if (n 9) | // Если входные данные неверны, // то печатаем соответствующее сообщение и выходим из программы |
for (i = 0; i for (i = 0; i a[1] = 4 a[2] = 15 a[3] = -2 . 3.4. Пример 1. Обработка одномерного массиваДаны три массива разной размерности. Определить в каком массиве больше сумма элементов. #include #include const int nmax = 100; int ArrayInput( int *n, double x[], char *fname); // Функция ввода массива из файла double Sum( double x[], int n); // Функция поиска суммы элементов массива void main( int argc, char *argv[]) < double a[nmax], b[nmax], c[nmax]; double sa, sb, sc, max; int na, nb, nc; setlocale(LC_ALL, "rus"); // Меняем кодировку для консольного приложения if (argc return ; >if (!ArrayInput(&na, a, argv[1])) return ; if (!ArrayInput(&nb, b, argv[2])) return ; if (!ArrayInput(&nc, c, argv[3])) return ; sa = Sum(a, na); sb = Sum(b, nb); sc = Sum(c, nc); max = sa; if (sb > max) max = sb; if (sc > max) max = sc; if (sa == max) printf(«Массив А имеет максимальную сумму элементов: %9.3lf\n», max); if (sb == max) printf(«Массив B имеет максимальную сумму элементов: %9.3lf\n», max); if (sc == max) printf(«Массив C имеет максимальную сумму элементов: %9.3lf\n», max); > double Sum( double x[], int n) < double s = 0; for ( int i = 0; i return s; >int ArrayInput( int *n, double x[], char *fname) < FILE *file; if ((file = fopen(fname, "r")) == NULL) < printf("Невозможно открыть файл '%s'\n", fname); return 0; >if (fscanf(file, «%d», n) return 0; > if (*n nmax) < printf("Кол-во эл-тов массива должно быть от 1 до %d! (файл '%s')\n", nmax, fname); return 0; >for ( int i = 0; i if (fscanf(file, «%lf», &x[i]) return 0; > fclose(file); return 1; > 3.5. Пример 2. Обработка двумерного массиваДля каждой строки матрицы проверить наличие нулевых элементов. Первый способВторой способ3.6. Пример 3. Суммирование элементов матрицыДаны две матрицы разного размера. Функция Sum находит сумму элементов матрицы, не зависимо от того, что матрицы имеют разное количество столбцов. Обратите внимание, что функция будет выдавать корректный результат, только если используются все объявленные элементы матриц. #include #include double Sum( double *x, int m, int n); void main( int argc, char *argv[]) < const int na = 4, mb = 3, nb = 5; double a[na][na], b[mb][nb]; double sa, sb; FILE *file; setlocale(LC_ALL, "rus"); if (argc return ; >if ((file = fopen(argv[1], «r»)) == NULL) < printf("Невозможно открыть файл '%s'\n", argv[1]); return ; >for ( int i = 0; i for ( int j = 0; j if (fscanf(file, «%lf», &a[i][j]) return ; > fclose(file); if ((file = fopen(argv[2], «r»)) == NULL) < printf("Невозможно открыть файл '%s'\n", argv[2]); return ; >for ( int i = 0; i for ( int j = 0; j if (fscanf(file, «%lf», &b[i][j]) return ; > fclose(file); sa = Sum(a[0], na, na); // a[0] — указатель на первую строку матрицы // (и, соответственно, на начало всей матрицы). sb = Sum( reinterpret_cast double *>(b), mb, nb); // Преобразование без проверки с помощью reinterpret_cast. // Просто b нельзя написать — это вызовет сообщение // о невозможности преобразовать матрицу в указатель. printf(«SumA = %6.2lf\nSumB = %6.2lf\n», sa, sb); > double Sum( double *x, int m, int n) < double s = 0; for ( int i = 0; i for ( int j = 0; j return s; > Содержание Источник Способ инициализации массива cВ этой статье вы научитесь работать с массивами: объявлять, инициализировать и получать доступ к элементам СодержаниеОбъявление массива в C/C++В программировании часто встречается задача обработки множества экземпляров однотипных данных. Представьте себе ситуацию: мы провели опрос 100 человек и узнали их возраст. Чтобы сохранить собранные данные, вы можете создать целочисленный массив, содержащий 100 элементов: В C++ массивы статичны: вы не сможете изменить размер или тип элементов после объявления. Доступ к элементам массиваВы можете получать доступ к элементам массива, используя индексы и оператор [] . Допустим, вы объявили массив marks , как показано ниже. К первому элементу можно обратиться выражением marks[0] , ко второму — выражением marks[1] , и так далее. Доступ всегда начинается с единицы, а индекс последнего элемента на единицу меньше размера массива. Инициализация массива при объявленииМожно инициализировать массив при объявлении. Для этого надо указать в списке столько значений, сколько вмещает массив, либо одно значение 0, чтобы заполнить массив нулями: Обход элементов массива в циклеУзнать число элементов в массиве можно функцией std::size. Обойти можно, используя цикл по индексам либо range-based for: Неопределённое поведение: выход за границы (out of bounds)Выход за пределы массива является неопределённым поведением (англ. undefined behavior). Нет гарантий, как поведёт себя программа в этом случае. Высока вероятность, что вы испортите память других переменных, но эффект может различаться в разных режимах компиляции: Передача массива как параметра функцииМассив в стиле языка C хранит только указатель на начало и не хранит свой размер, что и создаёт сложность в передаче в функцию. Размер массива известен во время компиляции, но не известен во время выполнения. Поэтому передать размер можно несколькими не очень очевидными путями: Динамически изменяемый массивОбычные массивы имеют неизменный размер. Вы можете ввести вспомогательную переменную, которая бы хранила число реально используемых ячеек массива. Но и в этом случае вы не сможете использовать элементов больше, чем задано при компиляции в виде размера массива. Так мог бы выглядеть имитация динамического массива: Класс std::vectorСтандартная библиотека C++ содержит шаблонный класс vector, который работает как динамический массив произвольного размера. Размер может расти до тех пор, пока у операционной системы есть область памяти подходящего размера (вплоть до нескольких гигабайт). Класс является шаблонным, то есть при объявлении переменной потребуется параметризовать шаблон класса vector типом элемента: Использование вектора похоже на использование массива:
Добавление элементов в конец массиваДля добавления существует два метода: push_back и emplace_back
Вы можете практически всегда использовать push_back. Метод pop_back можно использовать для удаления элемента: В документации std::vector можно прочитать о других методах. Перемещение элементов в памяти при изменении массиваДинамический массив использует для хранения элементов динамическую память (так же известную как “куча”, англ. heap). При добавлении большого числа элементов динамический массив несколько раз перераспределяет память, поскольку выделенной ранее линейной области памяти уже не хватает для хранения всех элементов. Обычно при нехватке памяти под очередной элемент vector запрашивает новую область памяти в 1,5-2 раза больше предыдущей, перемещает в неё уже существующие элементы и добавляет в конец новый, а затем освобождает старую область памяти. Если не сообразили, как это происходит, взгляните на картинку: Новая область находится уже другом месте, потому что менеджер динамической памяти не мог просто взять и расширить старую область (ведь сразу за ней находилась чужая память). Поэтому все итераторы, ссылки и указатели на элементы могут стать некорректными после любого изменения массива! Метод erase для удаления элементов из серединыМетод erase класса vector получает итератор и уничтожает элемент, на который итератор указывает: Последствия перемещения элементов: ошибка в простом цикле с eraseИспользование итератора, ссылки или указателя на элемент после перераспределения памяти в массиве является неопределённым поведением: скорее всего произойдёт падение программы либо будет пропущено несколько элементов коллекции. Это показано в примере ниже: Если вы запустите этот код, вы можете увидеть что угодно. Скорее всего программа выведет 10 38 99 , хотя должна вывести 10 23 7 38 99 по замыслу автора. Для решения этой проблемы метод erase возвращает новый, валидный итератор на элемент, следующий после удалённого. Если элемент был последним, erase вернёт итератор end. Учитывая это, мы можем исправить код, чтобы новое значение it либо получалось из erase, либо получалось путём инкремента: Программа корректно напечатает 10 23 7 38 99 . Источник |