Press "Enter" to skip to content

Массивы (C )

Класс является шаблонным, то есть при объявлении переменной потребуется параметризовать шаблон класса vector типом элемента:

Урок №74. Массивы

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

Оглавление:

  1. Что такое массив?
  2. Элементы массива
  3. Пример программы с использованием массива
  4. Типы данных и массивы
  5. Индексы массивов
  6. Объявление массивов фиксированного размера
  7. Чуть-чуть о динамических массивах
  8. Заключение

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

К счастью, структуры не являются единственным агрегированным типом данных в языке C++. Есть еще массив — совокупный тип данных, который позволяет получить доступ ко всем переменным одного и того же типа данных через использование одного идентификатора.

Рассмотрим случай, когда нужно записать результаты тестов 30 студентов в классе. Без использования массива нам придется выделить 30 почти одинаковых переменных!

// Выделяем 30 целочисленных переменных (каждая с разным именем)
int testResultStudent1 ;
int testResultStudent2 ;
int testResultStudent3 ;
int testResultStudent30 ;

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

int testResult [ 30 ] ; // выделяем 30 целочисленных переменных, используя фиксированный массив

В объявлении переменной массива мы используем квадратные скобки [] , чтобы сообщить компилятору, что это переменная массива (а не обычная переменная), а в скобках — количество выделяемых элементов (это называется длиной или размером массива).

В примере, приведенном выше, мы объявили фиксированный массив с именем testResult и длиной 30. Фиксированный массив (или «массив фиксированной длины») представляет собой массив, размер которого известен во время компиляции. При создании testResult , компилятор выделит 30 целочисленных переменных.

Элементы массива

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

В вышеприведенном примере первым элементом в нашем массиве является testResult[0] , второй — testResult[1] , десятый — testResult[9] , последний — testResult[29] . Хорошо, что уже не нужно отслеживать и помнить кучу разных (хоть и похожих) имен переменных — для доступа к разным элементам нужно изменять только индекс.

Важно: В отличие от повседневной жизни, отсчет в программировании и в языке С++ всегда начинается с 0, а не с 1!

В массиве длиной N элементы массива будут пронумерованы от 0 до N-1 ! Это называется диапазоном массива.

Пример программы с использованием массива

Здесь мы можем наблюдать как определение, так и индексирование массива:

Массивы (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[] выражения. Оператор возвращает указатель на первый элемент. Оператор subscript работает с переменной указателя так же, как и в массиве на основе стека. Вы также можете использовать арифметику указателя для перемещения указателя на любые произвольные элементы в массиве. Вы несете ответственность за обеспечение того, чтобы:

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

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

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-мерного типа массива приводит к получению массива 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; 

C++ dərs massivler

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

Объявление массива в C/C++

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

// массив из 100 целых чисел int ages[100]; // массив из 100 целых неотрицательных чисел unsigned ages[100]; // массив из 20 чисел с плавающей точкой // (вы можете использовать константу, известную при компиляции) constexpr unsigned ARRAY_SIZE = 20; float rotations[ARRAY_SIZE]; // общий синтаксис DataType variableName[ARRAY_SIZE]; 

В C++ массивы статичны: вы не сможете изменить размер или тип элементов после объявления.

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

Вы можете получать доступ к элементам массива, используя индексы и оператор [] . Допустим, вы объявили массив marks , как показано ниже. К первому элементу можно обратиться выражением marks[0] , ко второму - выражением marks[1] , и так далее. Доступ всегда начинается с единицы, а индекс последнего элемента на единицу меньше размера массива.

void example()  // Объявляем массив оценок int marks[5]; // Заполняем массив по элементам. marks[0] = 19; marks[1] = 10; marks[2] = 8; marks[3] = 17; marks[4] = 9; > 

Инициализация массива при объявлении

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

// Объявляем массив размера 5 и инициализируем. int marks[5] =  19, 10, 8, 17, 9 >; // Объявляем массив без указания размера, // размер будет определён из списка инициализациии. int marks[] =  19, 10, 8, 17, 9 >; // Объявляем массив размера 10 и заполняем нулями. int ages[10] =  0 >; 

Обход элементов массива в цикле

Узнать число элементов в массиве можно функцией std::size. Обойти можно, используя цикл по индексам либо range-based for:

#include int main()  int ages[] =  17, 18, 29, 30, 16, 27, 22 >; // цикл по индексам массива, // специальный тип size_t - это беззнаковое целое, // разрядность которого совпадает с разрядностью платформы // (4 байта на 32-битных машинах и 8 байт на 64 битных) for (size_t i = 0; i  std::size(ages); ++i)  const int age = ages[i]; std::cout  <"age #"  <i  <" is "  <age  <std::endl; > // цикл по всем элементам массива, // эта конструкция известна как range-based for. int agesSum = 0; for (int age : ages)  agesSum += age; > std::cout  <"ages sum is "  <agesSum  <std::endl; // ещё одна фишка: цикл в стиле итераторов auto end = std::end(ages); int minAge = ages[0]; for (auto it = std::begin(ages); it != end; ++it)  // если этот элемент меньше минимального, обновляем минимальный возраст. const int age = *it; if (age  minAge)  minAge = age; > > std::cout  <"smallest age is "  <minAge  <std::endl; > 

Неопределённое поведение: выход за границы (out of bounds)

Выход за пределы массива является неопределённым поведением (англ. undefined behavior). Нет гарантий, как поведёт себя программа в этом случае. Высока вероятность, что вы испортите память других переменных, но эффект может различаться в разных режимах компиляции:

#include int main()  // Индексы элементов: 0, 1, 2 int ages[] = 1, 2, 3>; // Неопределённое поведение! Запрос элемента с индексом 3 в массиве, // где такого индекса нет. std::cout  <ages[3]  <std::cout; > 

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

Массив в стиле языка C хранит только указатель на начало и не хранит свой размер, что и создаёт сложность в передаче в функцию. Размер массива известен во время компиляции, но не известен во время выполнения. Поэтому передать размер можно несколькими не очень очевидными путями:

#include // Передаём указатель на начало массива и размер массива // Тип size_t - это целочисленный тип, число байтов которого равно числу байт в указателях, // то есть 4 байта на 32-битных платформах и 8 байт на 64-битных. void printArrayV1(int* values, size_t size)  for (size_t i = 0; i  size; ++i)  std::cout  <values[i]  <std::endl; > > // Передаём ссылку на массив известного размера constexpr size_t AGES_COUNT = 3; void printArrayV2(int (&values)[AGES_COUNT])  for (size_t i = 0; i  std::size(values); ++i)  std::cout  <values[i]  <std::endl; > > // Третий способ - использовать gsl::span, // но ввиду сложности этого пути мы не станем его описывать. int main()  // Индексы элементов: 0, 1, 2 int ages[] = 1, 2, 3>; printArrayV1(ages, std::size(ages)); printArrayV2(ages); > 

Динамически изменяемый массив

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

Так мог бы выглядеть имитация динамического массива:

#include #include #include // Псевдо-динамический массив, вмещает не больше 100 элементов struct DynamicArray  static constexpr size_t MAX_SIZE = 100; size_t size = 0; int data[MAX_SIZE] =  0 >; >; // Добавляет элемент в конец массива void array_push(DynamicArray& array, int value)  // Если условие не соблюдатся, assert вызовет аварийное // завершение программы (но только в отладочной сборке). assert(array.size  DynamicArray::MAX_SIZE); // Поместим значение по индексу [size], после последнего элемента array.data[array.size] = value; // Увеличим размер на единицу ++array.size; > void array_print(const DynamicArray& array)  std::cout  <"; for (size_t i = 0; i  array.size; ++i)  std::cout  <array.data[i]; // Если следующий элемент существует, выводим запятую if (i + 1  array.size)  std::cout  <", "; > > std::cout  <">"  <std::endl; > // Программа создаст массив, заполнит его и выведет содержимое: [10, 3, 7] int main()  DynamicArray ages; array_push(ages, 10); array_push(ages, 3); array_push(ages, 7); array_print(ages); > 

Класс std::vector

Стандартная библиотека C++ содержит шаблонный класс vector, который работает как динамический массив произвольного размера. Размер может расти до тех пор, пока у операционной системы есть область памяти подходящего размера (вплоть до нескольких гигабайт).

Класс является шаблонным, то есть при объявлении переменной потребуется параметризовать шаблон класса vector типом элемента:

#include int main()  std::vectorint> ages =  10, 3, 7 >; > 

Использование вектора похоже на использование массива:

  • работает запрос элемента ages[index] , причём индексация так же начинается с нуля
  • при выходе за границы динамического массива так же возникает неопределённое поведение (англ. undefined behavior)
  • работает перебор элементов с помощью индексов, range-based for или итераторов
  • есть метод size для получения размера: ages.size()
#include #include // Печатает содержимое динамического массива чисел // В отличии от статичного массива, объект класса vector легко передать как параметр. void print(const std::vectorint>& values)  std::cout  <"; for (size_t i = 0; i  values.size(); ++i)  std::cout  <values[i]; // Если следующий элемент существует, выводим запятую if (i + 1  values.size())  std::cout  <", "; > > std::cout  <">"  <std::endl; > int main()  std::vectorint> ages =  10, 3, 7 >; print(ages); > 

Добавление элементов в конец массива

Для добавления существует два метода: push_back и emplace_back

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

Вы можете практически всегда использовать push_back. Метод pop_back можно использовать для удаления элемента:

#include #include int main()  // эквивалентно инициализации ages = < 10, 3, 7 >. std::vectorint> ages; ages.push_back(10); ages.push_back(3); ages.push_back(7); // убираем последний элемент ages.pop_back(); > 

В документации std::vector можно прочитать о других методах.

Перемещение элементов в памяти при изменении массива

Динамический массив использует для хранения элементов динамическую память (так же известную как “куча”, англ. heap). При добавлении большого числа элементов динамический массив несколько раз перераспределяет память, поскольку выделенной ранее линейной области памяти уже не хватает для хранения всех элементов. Обычно при нехватке памяти под очередной элемент vector запрашивает новую область памяти в 1,5-2 раза больше предыдущей, перемещает в неё уже существующие элементы и добавляет в конец новый, а затем освобождает старую область памяти.

Если не сообразили, как это происходит, взгляните на картинку:

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

Метод erase для удаления элементов из середины

Метод erase класса vector получает итератор и уничтожает элемент, на который итератор указывает:

#include #include // Печатает содержимое динамического массива чисел void print(const std::vectorint>& values)  std::cout  <"; for (size_t i = 0; i  values.size(); ++i)  std::cout  <values[i]; // Если следующий элемент существует, выводим запятую if (i + 1  values.size())  std::cout  <", "; > > std::cout  <">"  <std::endl; > int main()  // эквивалентно инициализации ages = < 10, 3, 7 >. std::vectorint> ages =  10, 3, 7 >; // удаляем элемент с индексом 0, остаётся ages.erase(ages.begin()); // удаляем элемент с индексом 1, остаётся ages.erase(ages.begin() + 1); print(ages); > 

Последствия перемещения элементов: ошибка в простом цикле с erase

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

#include #include // ! КОД С НЕОПРЕДЕЛЁННЫМ ПОВЕДЕНИЕМ ! // После вызова erase итератор it невалиден и не должен использоваться. void eraseNegativeAndPrint(std::vectorint> &ages)  for (auto it = ages.begin(); it != ages.end(); ++it)  if (*it  0)  ages.erase(it); > else  std::cout  <*it  <" "; > > std::cout  <std::endl; > int main()  // эквивалентно инициализации ages = < 10, 3, 7 >. std::vectorint> ages =  10, -33, 23, -18, 7, 38, 99 >; eraseNegativeAndPrint(ages); > 

Если вы запустите этот код, вы можете увидеть что угодно. Скорее всего программа выведет 10 38 99 , хотя должна вывести 10 23 7 38 99 по замыслу автора.

Для решения этой проблемы метод erase возвращает новый, валидный итератор на элемент, следующий после удалённого. Если элемент был последним, erase вернёт итератор end. Учитывая это, мы можем исправить код, чтобы новое значение it либо получалось из erase, либо получалось путём инкремента:

#include #include void eraseNegativeAndPrint(std::vectorint> &ages)  for (auto it = ages.begin(); it != ages.end();)  if (*it  0)  it = ages.erase(it); > else  std::cout  <*it  <" "; ++it; > > std::cout  <std::endl; > int main()  // эквивалентно инициализации ages = < 10, 3, 7 >. std::vectorint> ages =  10, -33, 23, -18, 7, 38, 99 >; eraseNegativeAndPrint(ages); > 

Программа корректно напечатает 10 23 7 38 99 .

Идиома remove_if + erase

В C++ есть замечательная библиотека алгоритмов . В данном случае алгоритмом называют шаблон функции, способный заменить цикл в какой-то одной узкой задаче. Например, remove_if перемещает элементы, соответствующие условию, в конец массива (в “удалённую” зону), и возвращает итератор на начала “удалённой” зоны. Затем вызовом erase можно уничтожить элементы из этой зоны.

#include #include #include bool isNegative(int value)  return (value  0); > void eraseNegativeAndPrint(std::vectorint> &ages)  // Алгоритм remove_if принимает два итератора и функцию, которая решает, // надо ли удалять элемент. auto newEnd = std::remove_if(ages.begin(), ages.end(), isNegative); // У метода erase есть версия, принимающая два итератора: // она удаляет все элементы от первого до второго, // включая первый и не включая второй. ages.erase(newEnd, ages.end()); for (const int age : ages)  std::cout  <age  <" "; > > int main()  // эквивалентно инициализации ages = < 10, 3, 7 >. std::vectorint> ages =  10, -33, 23, -18, 7, 38, 99 >; eraseNegativeAndPrint(ages); > 

Функция корректно напечатает 10 23 7 38 99 .

Для дополнительного чтения

  • О выборе структур данных для начинающих
  • Алгоритмы STL

Comments are closed, but trackbacks and pingbacks are open.