Перейти к содержимому

Как создать массив в массиве c

  • автор:

Массивы (C++)

Массив представляет собой последовательность объектов того же типа, которые занимают непрерывную область памяти. Традиционные массивы стилей C являются источником многих ошибок, но по-прежнему распространены, особенно в старых базах кода. В современном C++настоятельно рекомендуется использовать std::vector или std::array вместо массивов стилей C, описанных в этом разделе. Оба этих стандартных типов библиотек хранят их элементы в виде непрерывного блока памяти. Однако они обеспечивают большую безопасность типов и поддерживают итераторы, которые гарантированно указывают на допустимое расположение в последовательности. Дополнительные сведения см. в разделе «Контейнеры».

Объявления стека

В объявлении массива C++ размер массива указывается после имени переменной, а не после имени типа, как в других языках. В следующем примере объявляется массив из 1000 двойных размеров, выделенных в стеке. Число элементов должно быть предоставлено в виде целочисленного литерала или в качестве константного выражения. Это связано с тем, что компилятор должен знать, сколько пространства стека следует выделить; он не может использовать значение, вычисляемое во время выполнения. Каждому элементу в массиве присваивается значение по умолчанию 0. Если вы не назначаете значение по умолчанию, каждый элемент изначально содержит все случайные значения в этом расположении памяти.

 constexpr size_t size = 1000; // Declare an array of doubles to be allocated on the stack double numbers[size] ; // Assign a new value to the first element numbers[0] = 1; // Assign a value to each subsequent element // (numbers[1] is the second element in the array.) for (size_t i = 1; i < size; i++) < numbers[i] = numbers[i-1] * 1.1; >// Access each element for (size_t i = 0; i

Первый элемент в массиве — это нулевой элемент. Последний элемент — это элемент (n-1), где n — это количество элементов, которые может содержать массив. Число элементов в объявлении должно иметь целочисленный тип и должно быть больше 0. Вы несете ответственность за то, чтобы программа никогда не передает значение оператору подстрока, который больше (size — 1) .

Массив нулевого размера является законным только в том случае, если массив является последним полем в списке struct или union когда расширения Майкрософт включены ( /Za или /permissive- не заданы).

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

Объявления кучи

Может потребоваться слишком большой массив для выделения в стеке или размер которого не известен во время компиляции. Этот массив можно выделить в куче с помощью new[] выражения. Оператор возвращает указатель на первый элемент. Оператор подстрока работает с переменной указателя так же, как и в массиве на основе стека. Вы также можете использовать арифметику указателя для перемещения указателя на любые произвольные элементы в массиве. Это ваша ответственность за обеспечение того, чтобы:

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

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

void do_something(size_t size) < // Declare an array of doubles to be allocated on the heap double* numbers = new double[size]< 0 >; // Assign a new value to the first element numbers[0] = 1; // Assign a value to each subsequent element // (numbers[1] is the second element in the array.) for (size_t i = 1; i < size; i++) < numbers[i] = numbers[i - 1] * 1.1; >// Access each element with subscript operator for (size_t i = 0; i < size; i++) < std::cout // Access each element with pointer arithmetic // Use a copy of the pointer for iterating double* p = numbers; for (size_t i = 0; i < size; i++) < // Dereference the pointer, then increment it std::cout // Alternate method: // Reset p to numbers[0]: p = numbers; // Use address of pointer to compute bounds. // The compiler computes size as the number // of elements * (bytes per element). while (p < (numbers + size)) < // Dereference the pointer, then increment it std::cout delete[] numbers; // don't forget to do this! > int main()

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

Вы можете инициализировать массив в цикле, один элемент за раз или в одной инструкции. Содержимое следующих двух массивов идентичны:

 int a[10]; for (int i = 0; i < 10; ++i) < a[i] = i + 1; >int b[10]< 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 >; 

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

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

В следующем примере показана функция, которая принимает массив и длину. Указатель указывает на исходный массив, а не копию. Так как параметр не const является, функция может изменять элементы массива.

void process(double *p, const size_t len) < std::cout > 

Объявите и определите параметр p массива, const чтобы сделать его доступным только для чтения в блоке функций:

void process(const double *p, const size_t len); 

Эта же функция также может быть объявлена таким образом без изменений в поведении. Массив по-прежнему передается в качестве указателя на первый элемент:

// Unsized array void process(const double p[], const size_t len); // Fixed-size array. Length must still be specified explicitly. void process(const double p[1000], const size_t len); 

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

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

int i2[5][7]; 

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

Изображение представляет собой сетку 7 ячеек, широких и 5 ячеек. Каждая ячейка содержит индекс ячейки. Первый индекс ячейки помечен как 0,0. Следующая ячейка в этой строке составляет 0,1 и т. д. до последней ячейки в этой строке, которая составляет 0,6. Следующая строка начинается с индекса 1,0. После этого ячейка имеет индекс 1,1. Последняя ячейка в этой строке — 1,6. Этот шаблон повторяется до последней строки, которая начинается с индекса 4,0. Последняя ячейка в последней строке имеет индекс 4,6. . image-end

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

// arrays2.cpp // compile with: /c const int cMarkets = 4; // Declare a float that represents the transportation costs. double TransportCosts[][cMarkets] = < < 32.19, 47.29, 31.99, 19.11 >, < 11.29, 22.49, 33.47, 17.29 >, < 41.97, 22.09, 9.76, 22.55 >>; 

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

Использование оператора непрямого выражения (*) в типе массива n-1 приводит к получению массива n-1. Если значение n равно 1, возвращается скалярный элемент (или элемент массива).

Массивы C++ размещаются в памяти по срокам. Построчный порядок означает, что быстрее всего изменяется последний индекс.

Пример

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

// multidimensional_arrays.cpp // compile with: /EHsc // arguments: 3 #include // Includes DBL_MAX #include const int cMkts = 4, cFacts = 2; // Declare a float that represents the transportation costs double TransportCosts[][cMkts] = < < 32.19, 47.29, 31.99, 19.11 >, < 11.29, 22.49, 33.47, 17.29 >, < 41.97, 22.09, 9.76, 22.55 >>; // Calculate size of unspecified dimension const int cFactories = sizeof TransportCosts / sizeof( double[cMkts] ); double FindMinToMkt( int Mkt, double myTransportCosts[][cMkts], int mycFacts); using namespace std; int main( int argc, char *argv[] ) < double MinCost; if (argv[1] == 0) < cout MinCost = FindMinToMkt( *argv[1] - '0', TransportCosts, cFacts); cout double FindMinToMkt(int Mkt, double myTransportCosts[][cMkts], int mycFacts)
The minimum cost to Market 3 is: 17.29 

Функция FindMinToMkt записывается таким образом, что добавление новых фабрик не требует изменений кода, просто перекомпиляции.

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

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

Рассмотрим класс Point , определяющий два конструктора:

// initializing_arrays1.cpp class Point < public: Point() // Default constructor. < >Point( int, int ) // Construct from two ints < >>; // An array of Point objects can be declared as follows: Point aPoint[3] = < Point( 3, 3 ) // Use int, int constructor. >; int main()

Первый элемент aPoint создается с помощью конструктора Point( int, int ) , а оставшиеся два элемента — с помощью конструктора по умолчанию.

Массивы статических элементов (независимо от того, можно ли const ) инициализировать в их определениях (за пределами объявления класса). Например:

// initializing_arrays2.cpp class WindowColors < public: static const char *rgszWindowPartList[7]; >; const char *WindowColors::rgszWindowPartList[7] = < "Active Title Bar", "Inactive Title Bar", "Title Bar Text", "Menu Bar", "Menu Bar Text", "Window Background", "Frame" >; int main()

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

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

// using_arrays.cpp int main() < char chArray[10]; char *pch = chArray; // Evaluates to a pointer to the first element. char ch = chArray[0]; // Evaluates to the value of the first element. ch = chArray[3]; // Evaluates to the value of the fourth element. >

Если используются многомерные массивы, в выражениях можно использовать различные сочетания.

// using_arrays_2.cpp // compile with: /EHsc /W1 #include using namespace std; int main() < double multi[4][4][3]; // Declare the array. double (*p2multi)[3]; double (*p1multi); cout 

В приведенном выше коде multi представляет собой трехмерный массив типа double . Указатель p2multi указывает на массив типа double размера три. В этом примере массив используется с одним, двумя и тремя индексами. Хотя в инструкции чаще всего указываются все подстроки, как и в cout инструкции, иногда полезно выбрать определенное подмножество элементов массива, как показано в приведенных ниже cout инструкциях.

Оператор перегрузки подстрока

Как и другие операторы, оператор подстрочного ( [] ) может быть переопределен пользователем. Поведение оператора индекса по умолчанию, если он не перегружен, — совмещать имя массива и индекс с помощью следующего метода.

Как и во всех дополнениях, включающих типы указателей, масштабирование выполняется автоматически, чтобы настроить размер типа. Результирующий значение не является n байтами из источника array_name ; вместо этого это n-йэлемент массива. Дополнительные сведения об этом преобразовании см. в разделе "Адитивные операторы".

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

((array_name) + (subscript1 * max2 * max3 * . * maxn) + (subscript2 * max3 * . * maxn) + . + subscriptn))

Массивы в выражениях

Если идентификатор типа массива отображается в выражении, отличном от sizeof адреса ( & ) или инициализации ссылки, он преобразуется в указатель на первый элемент массива. Например:

char szError1[] = "Error: Disk drive not ready."; char *psz = szError1; 

Указатель psz указывает на первый элемент массива szError1 . Массивы, в отличие от указателей, не изменяются l-значения. Вот почему следующее назначение является незаконным:

szError1 = psz; 

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

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

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

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

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

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

double points[100];

В некотором смысле можно считать, что такое объявление переменной points создает 100 переменных, которые называются points[0], points[1], . points[99]. Плюс к этому, "имена" этих переменных можно вычислять: points[1], points[0+1] или points[k-1] имеют одно значение (если k=2).

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

double points[100]; int students[100];

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

#define NPOINTS 100 #define NSTUDENTS 100 . double points[NPOINTS]; int students[NSTUDENTS];

Объявление массива может быть совмещено с присвоением значений его элементам. Например,

double points[] = ;

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

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

Для доступа к элементу массива достаточно знать его имя и порядковый номер элемента. В языке Си элементы массива индексируются начиная с нуля, то есть в массиве из двух элементов корректными являются индексы 0 и 1. Если массив имеет имя array, то его k -й элемент записывается как array[k] . Это выражение может использоваться как для получения значения элемента массива, так и для его изменения, если оно стоит в левой части оператора присваивания. Рассмотрим для примера следующую программу.

#define NPOINTS 100 int main() < double points[NPOINTS]; int k; points[0] = 0.1; for(k=1; k < NPOINTS; k++) < points[k] = 0.1 + points[k-1]; >return 0; >

Эта программа заполняет массив действительных чисел значениями 0, 0.1, 0.2 и так далее. Отметим, что макропеременная NPOINTS используется как при объявлении массива, так и в качестве верхней границы цикла по всем его элементам. Если размер массива нужно будет изменить, то достаточно исправить одну строчку в программе (#define).

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

int main() < double points[] = ; 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 включительно, то поведение программы, вообще говоря, может быть любым. Наиболее вероятным поведением является вывод на экран какого-то значения, но может возникнуть и критическая ошибка, которая приведет к аварийной остановке программы.

Представление массива в памяти и адресная арифметика

В памяти ЭВМ элементы массива записаны последовательно без пропусков. Имя массива является указателем на его начальный элемент (с индексом 0). Поскольку в массиве все элементы имеют одинаковый тип, то зная адрес начала массива (A), размер одного элемента (size) и индекс k можно вычислить адрес размещения k-ого элемента: A + k*size. Если требуется получить значение k-ого элемента массива, то достаточно выполнить одно умножение (k*size), одно сложение (A + k*size) и загрузить значение из памяти по только что вычисленному адресу. Таким образом, обращение к элементу массива очень эффективно и сложность этой операции не зависит от величины индекса k: получение (или изменение) значения нулевого элемента столь же эффективно, как и миллионного.

Хорошо, адрес начала массива мы знаем — это его имя, индекс нам известен, но как узнать size (размер одного элемента)? Чуть ниже мы узнаем как это сделать, но для работы с указателями на элементы массива это не требуется! В языке Си к указателям можно прибавлять целые числа. Например, если есть указатель double *a; , то значением выражения a+9 будет адрес десятого (еще раз вспомним, что массивы индексируются с нуля!) элемента массива, который начинается с адреса a . Компилятор сам понимает, что a является указателем на double и прибавляет нужное значение.

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

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

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

int print_array(double x[], int len);
int print_array(double *x, int len);

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

Рассмотрим возможную реализацию функции распечатывания массива.

#include 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[] = ; int npoints = sizeof(points)/sizeof(points[0]); print_array(points, npoints); return 0; >

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

Динамические массивы: malloc и free

Статические массивы имеют одно существенное ограничение: размер массива должен быть известен в момент компиляции программы. В большинстве задач размер данных становится известным только в момент выполнения программы. Например, вы написали программу для обработки списка друзей или подписчиков в социальной сети. У одного пользователя друзей мало, а у другого — очень много. Какое значение выбрать для длины массива друзей? 200? 1000? Миллион? Если константа будет очень большой, чтобы "заведомо" (посмотрите как росло число пользователей Интернет) устраивать всех пользователей, то для подавляющего числа пользователей это приведет к излишним затратам памяти. Захотите ли Вы поставить на свой телефон программу, которая при запуске займет всю его память с сообщением: "А вдруг у тебя миллион друзей. Нет? Всего 12?! Неплохо, прямо как у Oушена! А y Трампа миллион. "? [Друзей не должно и не может быть так много, но это к делу не относится.] Чтобы избежать таких ситуаций нужно уметь выделять минимально необходимое количество памяти.

  • выделение памяти под массив;
  • освобождение памяти, когда она больше не требуется.

Стандартная библиотека языка Си содержит несколько функций для работы с динамической памятью. Нам понадобятся две: malloc (memory allocation — выделение памяти) и free (освобождение). Для использования этих удивительных функций нужно в программе подключить заголовочный файл . Пример программы приведен ниже. Сначала посмотрим, что делают эти функции.

malloc: динамическое выделение памяти

Прототип: void *malloc(size_t size); Параметры: size — беззнаковое целое число, размер запрашиваемой памяти в байтах. Возвращает: Адрес начала выделенной памяти или NULL, если не удалось выделить память. Функция malloc возвращает указатель типа void * — это "абстрактный" указатель на память, который может быть приведен к указателю на любой тип. Функция malloc не может сразу возвращать указатель нужного типа, так как она используется для создания разных массивов, а в прототипе нужно указать конкретный тип возвращаемого значения.

Для выделения памяти под массив из n элементов типа T, где в T могут быть стандартные типы int , double и т.п., необходимо знать размер значения T в байтах. Для определения этой величины в языке Си есть специальный оператор sizeof , который в момент компиляции программы вычисляет нужное значение. Например, массив из n целых чисел будет занимать n*sizeof(int) байт памяти.

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

int length; int *points; // . получили значение length (длина массива) points = (int *)malloc(length * sizeof(int));

Если нужен другой тип данных, допустим double , то int заменяется на нужное имя ( double ) в трех местах (кроме первой строки, так как длина массива всегда является целым числом).

free: освобождение памяти

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

Прототип void free(void *ptr); Параметры: ptr — указатель, который был получен в результат вызова malloc.

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

Пример программы с динамическим массивом

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

#include #include int main() < int npoints; double *points; int k; scanf("%d", &npoints); /* npoints получает значение в момент выполнения программы */ points = (double *)malloc(npoints*sizeof(double)); /* Выдели память для хранения npoints элементов, каждый размера sizeof(double) */ if(points == NULL) < printf("Произошла ошибка. Запросили слишком много памяти??\n"); return -1; >/* Считываем данные с использованием адресной арифметики */ k = 0; while(k < npoints && scanf("%lf", points+k) == 1) < k++; >/* Работаем с points как с обычным массивом */ /* Например, вызываем функцию print_array(points, npoints) */ free(points); /* Освободили память */ return 0; >

Функции, которые возвращают массив

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

Во-первых, нужно понять, какой прототип должна иметь такая функция. Она должна вернуть два значения: адрес выделенной памяти и длину массива. Как мы уже знаем, несколько значений можно вернуть используя указатели. Длина массива имеет тип int . Значит параметр функции будет иметь тип int * (адрес, по которому нужно записать значение). Массив — это адрес нулевого элемента, то есть double * . Значит параметр будет иметь тип double ** — "указатель на указатель". Мы должны передать адрес (одна звездочка), по которому нужно записать результат вызова malloc, который имеет тип double * . В результате получаем следующий прототип:

int read_array(FILE *input, double **array, int *length);

Собственно возвращаемое значение функции ( int ) может быть кодом ошибки. Если функция вернет 0, то это означает успешное выполнение. Любое ненулевое значение означает ошибку.

Теперь можно рассмотреть структуру тела функции (для наглядности в приведенном ниже коде отсутствуют проверки успешности считывания и корректности данных).

int read_array(FILE *input, double **array, int *length) < double *arr; int arr_length, k; /* Считываем массив: сначала длину, потом элементы */ fscanf("%d", &arr_length); arr = (double *)malloc(arr_length * sizeof(double)); for(k = 0; k < arr_length; k++) fscanf("%lf", arr + k); /* Копируем результат по заданным адресам */ *length = arr_length; *array = arr; return 0; >

Массивы в C++ — урок 5

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

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

  • Пример инициализации массива
  • Описание синтаксиса
  • Вывод элементов массива через цикл
  • Объявление массива без инициализации
  • Заполнение массива с клавиатуры

Пример инициализации массива

Описание синтаксиса

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

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

Попробуем вывести наш массив на экран с помощью оператора cout .

 #include int main() < std::string students[10] = < "Иванов", "Петров", "Сидоров", "Ахмедов", "Ерошкин", "Выхин", "Андеев", "Вин Дизель", "Картошкин", "Чубайс" >; std::cout 

Скомпилируйте этот код и посмотрите, на результат работы программы. Готово? А теперь запустите программу еще раз и сравните с предыдущим результатом. В моей операционной системе вывод был следующим:

  • Первый вывод: 0x7ffff8b85820
  • Второй вывод: 0x7fff7a335f90
  • Третий вывод: 0x7ffff847eb40

Мы видим, что выводится адрес этого массива в оперативной памяти, а никакие не «Иванов» и «Петров».

Дело в том, что при создании переменной, ей выделяется определенное место в памяти. Если мы объявляем переменную типа int , то на машинном уровне она описывается двумя параметрами — ее адресом и размером хранимых данных.

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

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

Попробуем вывести первый элемент массива — фамилию студента Иванова.

 #include int main() < std::string students[10] = < "Иванов", "Петров", "Сидоров", "Ахмедов", "Ерошкин", "Выхин", "Андеев", "Вин Дизель", "Картошкин", "Чубайс" >; std::cout

Смотрим, компилируем, запускаем. Убедились, что вывелся именно «Иванов». Заметьте, что нумерация элементов массива в C++ начинается с нуля. Следовательно, фамилия первого студента находится в students[0] , а фамилия последнего — в students[9] .

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

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

Конечно же нет! Мы возьмем на вооружение циклы, о которых был написан предыдущий урок.

Вывод элементов массива через цикл

#include #include int main() < std::string students[10] = < "Иванов", "Петров", "Сидоров", "Ахмедов", "Ерошкин", "Выхин", "Андеев", "Вин Дизель", "Картошкин", "Чубайс" >; for (int i = 0; i < 10; i++) < std::cout return 0; > 

Если бы нам пришлось выводить массив из нескольких тысяч фамилий, то мы бы просто увеличили конечное значение счетчика цикла — строку for (. ; i < 10; . ) заменили на for (. ; i < 10000; . ) .

Массив, как и любую переменную можно не заполнять значениями при объявлении.

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

Элементы такого массива обычно содержат в себе «мусор» из выделенной, но еще не инициализированной, памяти. Некоторые компиляторы, такие как GCC, заполняют все элементы массива нулями при его создании.

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

> n; string students[n]; /* Неверно */ 

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

Заполним с клавиатуры пустой массив из 10 элементов.

Заполнение массива с клавиатуры

#include #include using std::cout; using std::cin; using std::endl; int main() < int arr[10]; // Заполняем массив с клавиатуры for (int i = 0; i < 10; i++) < cout > arr[i]; > // И выводим заполненный массив. cout cout

Скомпилируем эту программу и проверим ее работу.

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

Массивы — очень важная вещь в программировании. Автор советует вам хорошо попрактиковаться в работе с ними.

Массивы

П усть нам необходимо работать с большим количеством однотипных данных. Например, у нас есть тысяча измерений координаты маятника с каким-то шагом по времени. Создавать 1000 переменных для хранения всех значений очень. обременительно. Вместо этого множество однотипных данных можно объединить под одним именем и обращаться к каждому конкретному элементу по его порядковому номеру.
Массив в си определяется следующим образом
[];
Например,
int a[100];
Мы получим массив с именем a, который содержит сто элементов типа int. Как и в случае с переменными, массив содержит мусор.
Для получения доступа до первого элемента, в квадратных скобках пишем его номер (индекс). Например

#include #include void main()

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

Массив хранит адрес первого элемента. Индекс i элемента - это сдвиг на i*sizeof(тип) байт от начала

Индекс массива указывает, на сколько байт необходимо сместиться относительно начала массива, чтобы получить доступ до нужно элемента. Например, если массив A имеет тип int, то A[10] означает, что мы сместились на 10*sizeof(int) байт относительно начала. Первый элемент находится в самом начале и у него смещение 0*sizeof(int) .
В си массив не хранит своего размера и не проверяет индекс массива на корректность. Это значит, что можно выйти за пределы массива и обратиться к памяти, находящейся дальше последнего элемента массива (или ближе).

Начальная инициализация массива.

Н апишем простую программу. Создадим массив, после чего найдём его максимальный элемент.

#include #include void main() < int a[10] = ; unsigned i; int max; max = a[0]; for (i = 1; i max) < max = a[i]; >> printf("max element is %d", max); getch(); >

Разберём пример. Сначала мы создаём массив и инициализируем его при создании. После этого присваиваем максимальному найденному элементу значение первого элемента массива.

max = a[0];

После чего проходим по массиву. Так как мы уже просмотрели первый элемент (у него индекс 1), то нет смысла снова его просматривать.
Тот же пример, только теперь пользователь вводит значения

#include #include void main() < int a[10]; unsigned i; int max; printf("Enter 10 numbers\n"); for (i = 0; imax = a[0]; for (i = 1; i max) < max = a[i]; >> printf("max element is %d", max); getch(); >

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

#include #include void main() < int a[10] = ; unsigned i; for (i = 0; i getch(); >

Если необходимо заполнить весь массив нулями, тогда пишем

int a[10] = ;

Можно не задавать размер массива явно, например

int a[] = ;

массив будет иметь размер 3

Размер массива

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

printf("Enter length of array "); scanf("%d", &length);

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

#include #include void main() < int A[57]; //sizeof возвращает размер всего массива в байтах //Для определения количества элементов необходимо //разделить размер массива на размер его элемента int size = sizeof(A) / sizeof(int); printf("Size of array equals to %d", size); getch(); >

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

Переполнение массива

П ускай у вас есть такой код

int A[10]; int i; for (i=0; i

  • 1. Используйте тип size_t для индексирования. Он обезопасит вас от отрицательных значений и его всегда хватит для массива любого размера.
  • 2. Помните, что массив начинается с нуля.
  • 3. Последний элемент массива имеет индекс (размер массива - 1)

Примеры

Т еперь несколько типичных примеров работы с массивами
1. Переворачиваем массив.

#include #include //Это макрос. SIZE в коде будет заменено на 10u #define SIZE 10u void main() < int A[SIZE] = ; unsigned i, j; //счётчики unsigned half; //середина массива unsigned tmp; //временная переменная для обмена значениями half = SIZE / 2; //Один счётчик идёт слева напрво, другой справа налево for (i = 0, j = SIZE - 1; i < half; i++, j--) < tmp = A[i]; A[i] = A[j]; A[j] = tmp; >for (i = 0; i < SIZE; i++) < printf("%d ", A[i]); >getch(); >

Здесь незнакомая для вас конструкция

#define SIZE 10u

макрос. Во всём коде препроцессор автоматически заменит все вхождения SIZE на 10u.
2. Удаление элемента, выбранного пользователем.

#include #include #define SIZE 10u void main() < int A[SIZE] = ; unsigned i; //счётчик int index; //индекс, введённый пользователем //Выводим массив for (i = 0; i < SIZE; i++) < printf("(%d)=%d ", i, A[i]); >//Просим пользователя ввести валидный индекс while (1) < printf("\nEnter index of element to delete "); scanf("%d", &index); if (index >0 && index < SIZE) < break; >> //Копируем следующий элемент массива на место удаляемого //и так до конца for (i = index; i < SIZE-1; i++) < A[i] = A[i+1]; >//Выводим результат for (i = 0; i < SIZE-1; i++) < printf("(%d)=%d ", i, A[i]); >getch(); >

Удаление элемента в данном случае, конечно, не происходит. Массив остаётся того же размера, что и раньше. Мы просто затираем удаляемый элемент следующим за ним и выводим SIZE-1 элементов.
3. Пользователь вводит значения в массив. После этого вывести все разные значения, которые он ввёл.
Пусть пользователь вводит конечное число элементов, допустим 10. Тогда заранее известно, что всего различных значений будет не более 10. Каждый раз, когда пользователь вводит число будем проходить по массиву и проверять, было ли такое число введено.

#include #include #define SIZE 10u void main() < int A[SIZE] = ; unsigned i, j; int counter = 1; //сколько разных чисел введено. Как минимум одно. int input; int wasntFound; //флаг, что введённое число не было найдено //Вводим первое число. Оно ещё не встречалось. printf("0. "); scanf("%d", &A[0]); for (i = 1; i < SIZE; i++) < printf("%d. ", i); scanf("%d", &input); wasntFound = 1; //Проверяем, встречалось ли такое число. Если да, //то выставляем флаг и выходим из цикла for (j = 0; j > //Если флаг был поднят, то заносим число в массив if (wasntFound) < A[counter] = input; counter++; >> for (i = 0; i < counter; i++) < printf("%d ", A[i]); >getch(); >

4. Пользователь вводит число - количество измерений (от 2 до 10). После этого вводит все измерения. Программа выдаёт среднее значение, дисперсию, погрешность.

#include #include #include #define SIZE 20u void main() < //Коэффициенты Стьюдента идут, начиная с двух измерений const float student[9] = ; float A[SIZE]; unsigned i; unsigned limit; float tmp; float sum = .0f; float mean; float disp; float absError; float relError; do < printf("Enter number of measurements "); scanf("%u", &limit); if (limit >1 && limit < 11) < break; >> while(1); for (i = 0; i < limit; i++) < printf("#%d: ", i); scanf("%f", &A[i]); sum += A[i]; >mean = sum / (float)limit; sum = .0f; for (i = 0; i < limit; i++) < tmp = A[i] - mean; sum += tmp * tmp; >disp = sum / (float)limit; absError = student[limit - 2] * sqrt(sum / (float)(limit - 1)); relError = absError / mean * 100; printf("Mean = %.6f\n", mean); printf("Dispertion = %.6f\n", disp); printf("Abs. Error = %.6f\n", absError); printf("Rel. Error = %.4f%", relError); getch(); >

5. Сортировка массива пузырьком

#include #include #define SIZE 10 #define false 0 #define true !false void main() < float a[] = ; float tmp; unsigned i, j; char flag; //Выводи массив for (i = 0; i < SIZE; i++) < printf("%.3f ", a[i]); >printf("\n"); //Пока массив не отсортирован do < flag = false; //Проходим по массиву. Если следующий элемент больше предыдущего, то //меняем их местами и по новой проверяем массив for (i = 1; i < SIZE; i++) < if (a[i] >a[i - 1]) < tmp = a[i]; a[i] = a[i - 1]; a[i - 1] = tmp; flag = true; >> > while(flag == true); //Выводим отсортированный массив for (i = 0; i < SIZE; i++) < printf("%.3f ", a[i]); >getch(); >

6. Перемешаем массив. Воспользуемся для этого алгоритмом Fisher-Yates:
Для i от N-1 до 1 выбираем случайное число j в пределах от 0 до i и меняем местами i-й и j-й элементы.

#include #include #include #include void main() < //Сегодня вместо макроса я решил использовать константу const int SIZE = 20; //Размер массива должен быть задан явно, как "константное выражение" int A[20] = < 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>; int i, rnd; int tmp; //Инициализируем генератор псевдослучайных чисел //В качестве начального числа берём системное время srand(time(NULL)); //Алгоритм Дюрштенфельда, модифицированный алгоритма Фишера-Ятса //Элементарный алгоритм, записать который легче, чем выучить название for (i = SIZE - 1; i > 0; i--) < rnd = rand() % i; //Случайное число в пределе от 0 до i tmp = A[i]; A[i] = A[rnd]; A[rnd] = tmp; >for (i = 0; i < SIZE; i++) < printf("%d ", A[i]); >getch(); >

ru-Cyrl 18- tutorial Sypachev S.S. 1989-04-14 sypachev_s_s@mail.ru Stepan Sypachev students

email

Всё ещё не понятно? – пиши вопросы на ящик

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *