Помимо динамического выделения переменных мы также можем динамически выделять и массивы. В отличие от фиксированного массива, где его размер должен быть известен во время компиляции, динамическое выделение массива в языке C++ позволяет нам устанавливать его длину во время выполнения программы.
Динамические массивы
Для выделения динамического массива и работы с ним используются отдельные формы операторов new и delete: new[]
и delete[]
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <iostream> int main() { std::cout << "Enter a positive integer: "; int length; std::cin >> length; int *array = new int[length]; // используем оператор new[] для выделения массива. Обратите внимание, переменная length не обязательно должна быть константой! std::cout << "I just allocated an array of integers of length " << length << '\n'; array[0] = 7; // присваиваем элементу под индексом 0 значение 7 delete[] array; // используем оператор delete[] для освобождения выделенной массиву памяти array = 0; // используйте nullptr вместо 0 в C++11 return 0; } |
Поскольку мы выделяем массив, то C++ понимает, что он должен использовать другую форму оператора new — форму для массива, а не для переменной. По факту вызывается оператор new[]
, даже если мы и не указываем []
сразу после ключевого слова new
.
Обратите внимание, поскольку память для динамических и фиксированных массивов выделяется из разных «резервуаров», то размер динамического массива может быть довольно большим. Вы можете запустить программу, приведенную выше, но уже выделить массив длиной 1 000 000 (или, возможно, даже 100 000 000) без проблем. Попробуйте!
Удаление динамического массива
При удалении динамических массивов также используется форма оператора delete для массивов — delete[]
. Таким образом, мы сообщаем процессору, что ему нужно очистить память от нескольких переменных вместо одной. Самая распространенная ошибка, которую совершают новички при работе с динамическим выделением памяти, является использование delete вместо delete[]
для удаления динамических массивов. Использование формы оператора delete для переменных при удалении массива приведет к таким неожиданным результатам, как повреждение данных, утечка памяти, сбой или другие проблемы.
Инициализация динамических массивов
Если вы хотите инициализировать динамический массив значением 0
, то всё довольно просто:
1 |
int *array = new int[length](); |
До C++11 не было простого способа инициализировать динамический массив ненулевыми значениями (список инициализаторов работал только с фиксированными массивами). А это означает, что нужно перебрать каждый элемент массива и присвоить ему значение явным указанием:
1 2 3 4 5 6 |
int *array = new int[5]; array[0] = 9; array[1] = 7; array[2] = 5; array[3] = 3; array[4] = 1; |
Немного утомляет, не правда ли?
Однако, начиная с C++11, появилась возможность инициализации динамических массивов через списки инициализаторов:
1 2 |
int fixedArray[5] = { 9, 7, 5, 3, 1 }; // инициализируем фиксированный массив int *array = new int[5] { 9, 7, 5, 3, 1 }; // инициализируем динамический массив |
Обратите внимание, в синтаксисе динамического массива между длиной массива и списком инициализаторов оператора присваивания (=
) нет.
В C++11 фиксированные массивы также могут быть инициализированы с использованием uniform-инициализации:
1 2 |
int fixedArray[5] { 9, 7, 5, 3, 1 }; // инициализируем фиксированный массив в C++11 char fixedArray[14] { "Hello, world!" }; // инициализируем фиксированный массив в C++11 |
Однако, будьте осторожны, так как в C++11 вы не можете инициализировать динамический массив символов строкой C-style:
1 |
char *array = new char[14] { "Hello, world!" }; // не работает в C++11 |
Вместо этого вы можете динамически выделить std::string (или выделить динамический массив символов, а затем с помощью функции strcpy_s() скопировать содержимое нужной строки в этот массив).
Также обратите внимание на то, что динамические массивы должны быть объявлены с явным указанием их длины:
1 2 3 4 5 |
int fixedArray[] {1, 2, 3}; // ок: неявное указание длины фиксированного массива int *dynamicArray1 = new int[] {1, 2, 3}; // не ок: неявное указание длины динамического массива int *dynamicArray2 = new int[3] {1, 2, 3}; // ок: явное указание длины динамического массива |
Изменение длины массивов
Динамическое выделение массивов позволяет задавать их длину во время выделения. Однако C++ не предоставляет встроенный способ изменения длины массива, который уже был выделен. Но и это ограничение можно обойти, динамически выделив новый массив, скопировав все элементы из старого массива, а затем удалив старый массив. Однако этот способ подвержен ошибкам (об этом чуть позже).
К счастью, в языке C++ есть массивы, размер которых можно изменять, и называются они векторами (std::vector). О них мы поговорим на соответствующем уроке.
Тест
Напишите программу, которая:
спрашивает у пользователя, сколько имен он хочет ввести;
просит пользователя ввести каждое имя;
вызывает функцию для сортировки имен в алфавитном порядке (измените код сортировки методом выбора из урока №77);
выводит отсортированный список имен.
Подсказки:
Используйте динамическое выделение std::string для хранения имен.
std::string поддерживает сравнение строк с помощью операторов сравнения <
и >
.
Пример результата выполнения вашей программы:
How many names would you like to enter? 5
Enter name #1: Jason
Enter name #2: Mark
Enter name #3: Alex
Enter name #4: Chris
Enter name #5: John
Here is your sorted list:
Name #1: Alex
Name #2: Chris
Name #3: Jason
Name #4: John
Name #5: Mark
Ответ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#include <iostream> #include <string> #include <utility> // для std::swap(). Если у вас не поддерживается C++11, то тогда #include <algorithm> void sortArray(std::string *array, int length) { // Перебираем каждый элемент массива for (int startIndex = 0; startIndex < length; ++startIndex) { // smallestIndex - индекс наименьшего элемента, с которым мы столкнулись int smallestIndex = startIndex; // Ищем наименьший элемент, который остался в массиве (начиная со startIndex+1) for (int currentIndex = startIndex + 1; currentIndex < length; ++currentIndex) { // Если текущий элемент меньше нашего ранее найденного наименьшего элемента, if (array[currentIndex] < array[smallestIndex]) // то тогда это новое наименьшее значение в этой итерации smallestIndex = currentIndex; } // Меняем местами наш начальный элемент с найденным наименьшим элементом массива std::swap(array[startIndex], array[smallestIndex]); } } int main() { std::cout << "How many names would you like to enter? "; int length; std::cin >> length; // Выделяем массив для хранения имен std::string *names = new std::string[length]; // Просим пользователя ввести все имена for (int i = 0; i < length; ++i) { std::cout << "Enter name #" << i + 1 << ": "; std::cin >> names[i]; } // Сортируем массив sortArray(names, length); std::cout << "\nHere is your sorted list:\n"; // Выводим отсортированный массив for (int i = 0; i < length; ++i) std::cout << "Name #" << i + 1 << ": " << names[i] << '\n'; delete[] names; // не забываем использовать оператор delete[] для освобождения памяти names = nullptr; // используйте 0, если не поддерживается C++11 return 0; } |
1.
Не мог никак передать в функцию строку, а потом вспомнил про указатель, заработало как часы, спасибо за уроки. Единственное что — нижний/верхний регистр криво работает.
Чем дальше продвигаешься по урокам, тем сильнее код в ответе отличается от твоего.
Спасибо за терпение в проверке заданий, это стимулирует не бросать.
Красота
Задание:
Уважаемый автор. Поясните пожалуйста как происходит сортировка имён Jason и John. С сортировкой чисел мне все вроде понятно, а вот с std::string не могу понять. Если кто из участников напишет тоже буду очень благодарен. Уроки изучаю с самого начала. Очень интересно, но чем дальше тем сложней. Автор спасибо большое за эти уроки.
Методика алфавитной сортировки текстов возникла задолго до C и даже компьютеров )) : если первая буква совпадает — сравниваем вторую и т д. Если мы дошли до конца одного из слов и совпадают все буквы, то «меньшим» будет считаться более короткое слово. Если совпадает и длинна — то слова равны.
Владимир спасибо за ответ. Про сравнение слов понятно, что известно давно как их сравнивать. Я недопонял как это делает СИ++. Я Правильно понимаю, что при передачи слова, допустим Jason в функцию sortArray, во втором цикле, где сравнивается currentindex и smallestindex, передаётся все слово, т.к. это std:array, а не обычный массив, который распадается в указатель? И СИ++ сравнивает его целиком, начиная с первых букв и если они одинаковые, то сравнивает вторые? Или я затупил?
Как я понял, при сравнении строк "James" и "John" оператором сравнения происходит следующее: в ASCII символы "J" и "J" имеют одинаковый код (целочисленное значение), из-за чего начинают рассматриваться следующие символы: "a" и "о". Мы же знаем, что код символа "a"<"o", т.е получается "James" < "John". Однако при сравнении "James" (или "John") просто с символом "J" — это будет рассмотрено как равенство двух строк, так как после символа "J" никаких других символов не следует, из-за чего "J" = "J".
Адакин спасибо за ответ. Выше для Владимира написал, свое понимание сортировки. Если напишите свое мнение буду признателен.
Массив array как раз-таки и распадается в указатель на этот массив. Используя адресную арифметику указателей, мы можем получить каждый элемент массива из переданного в функцию sortArray указателя. Так это массив типа string, то его каждый элемент содержит строки символов. Как ранее расписал Владимир: при сравнении двух разных строк — сравниваются изначально первые символы строки. При равенстве первых символов — сравниваются вторые и т.д.
Наглядно можно понять при сравнении строк "John" и "Joh": у первой и второй строки одинаковые первые три элемента. Однако количество символов у второй строки больше, чем у первой, из-за чего она и будет больше.
В нашем же примере "James" и "John" равны только первые символы "J". Далее, сравнивая вторые символы, компилятор увидит, что код символа "a"<"o", и примет строку "John">"James", не сравнивая последующие символы и их количество.
По поводу сравнения сравнении "James" (или "John") просто с символом "J" в моём прошлом комментарии — оно неверно, прошу прощения, сам тогда до конца не понял смыл сравнения.
Ну что-то такое можно оформить )
Прикольно. Молодец.
Где так научился?
Код очень качественный, прослеживается грамотное написание от начала до конца, наталкивает на несколько идей относительно подпрограмм и архитектуры в целом, чувак, если ты это читаешь, спасибо, и самое главное — совершенствуй ремесло, ибо умеешь.
вместо:
удобнее использовать:
Странно, но у меня (Visual studio 2019, C++17) работает этот код и без #include <utility> и #include <string>. Почему так?
Iostream автоматически подключает string, насчёт utility не знаю, но скорее всего аналогично
у меня код тоже работает без подключения этих библиотек
Может сгодится:
Спасибо!
А вот почему это работает?
это как arr_name[i]
Хмм я сделал как и в ответе, но добавил элемент тестирования кода. Еще ни все проверки додумал (к примеру у нас тут целое число, а я использую вместо int, double, т.к. стараюсь сделать проверку максимально универсальной).
function.cpp
function.h
ConsoleApplication.cpp
Люди почему-то любят всё усложнять с сортировкой, а достаточно использовать std::sort для этого:
Я просто використав функцію std::sort() для сортування по алфавіту. Чесно, і в готовій відповіді мені не зрозуміло, як порівнюються букви знаками більше менше. Вони якось неявно конвертуються в тип int і звіряються по коду ASCII?
Да всё верно. за каждой буквой стоит значение в цифровом эквиваленте, вот они то и сравниваются))
дякую)
А чем я хуже?
и так сойдёт)
Поначалу хотел сделать через массив char, потом понял что это дикая головная боль и сделал как все
Мое решение!
Всё просто и без усложнений. До последнего думала, что обычное сравнивание std::string ни к чему хорошему не приведет — что не может быть всё так просто. Оказалось, очень даже может.
Запустилось с первого раза 🙂
Получилось!
Почему имя1<имя2 сортирует по алфавиту?
Типа первую букву рассматривают как символ с ASCII числовым кодом?
Тот же вопрос
В данном случае у класса std::string перегружен оператор сравнения.
Подглянул, ибо некоторые вещи подзабыл.
На самом деле понимал как, просто сортировку не помню, а тут все очень просто.
Добавление имен хотел через функцию, количество тоже, маин будет посвободнее.
Не знаю, почему написано что, именно через метод сортировки. Мне, из представленных вами методов, больше всего нравится полный "пузырчатый" метод. Наверно это мой "программисткий" почерк. Теперь, если я взломаю Пентагон, вы знаете как меня определить 🙂
Я долго промучилась. Сначала вообще какая-то ерунда получилась, пришлось вспомнить урок об отладке программ…
Вроде как создание динамического массива выдавало исключение. Я его обработала, как учили в предыдущем уроке, и (о чудо!) оно перестало происходить.
Потом я использовала getline для записи строки из cin, но почему-то первое имя он при этом пропускал. Сделала, как в ответе, заработало нормально. Потом вернула getline, не знаю, что изменилось, но этот косяк исчез.
Вот мой код:
Изменил в своей программе cin на getline, и тоже стало пропускать ввод первого элемента… Почему так, не разобралась?
Всё, понял. Не хватало строки
Честно? Подсмотрел! Но не списывал 🙂 Не знал как string сортировать. Оказалось всё проще, чем я напридумывал с двумерными массивами:)
Добавил проверку ввода имени с большой буквы. А так всё как обычно…
В Вашем листинге решения ТЕСТа 34-й стейтмент выглядит вот так:
т.е., насколько я понял, вводится динамический массив типа string.
А вот уже 40-я строка выглядит вот так:
И вот мне НЕПОНЯТНО: откуда программа знает какой объем в байтах(или в символах) нужно программе "попросить" у ОС для names[i], чтобы "хватило" места на введение конкретного очередного имени ?
А вдруг перед компом сидит какой-нибудь ИНДИЕЦ ?! … У них-то имена ОЧЕННО длинные !? …
🙂 …
Вы можете легко проверить через sizeof, что любая std::string занимает одинаковое количество памяти… Соответственно и выделение памяти под массив строк — не проблема.
Как такое может происходить? std::string состоит из нескольких частей, одна из которых — динамический массив символов (c-string). std::string по сути содержит только указатель на этот массив, соответственно и память выделяется только на этот указатель 🙂
Огромное Вам спасибо за данный ресурс. Вот мой вариант:
Сложнoe решение, учитывая, что мы не проходили инициализацию указателя на массив через std::string *, а также никогда не делали ввод данных в массив через std::cin>>array[i]
Согласен — чем проще, тем лучше. Читаю параллельно с этим курсом Герберта Шилдта, видимо намудрил оттуда.
Здравствуйте, а почему нельзя было решить тест так?
Не получается выполнять сортировку кириллицы.
в main добавлено setlocale(LC_ALL, "rus");
Вывод и ввод кириллицы все ОК. Но после сортировки выводятся непонятные символы и не сортируются судя по адресам в памяти.
Что нужно сделать?
нет, как раз таки с вводом проблемы, и кракозябры сразу же после ввода
Подскажите пожалуйста, если ввести часть имён с заглавной буквы, а часть нет сортировка не пойдёт по алфавиту. Можно ли сделать так, чтобы в любом случае сортировка шла как надо?
Можно написать чекер с "правильной" проверкой и скормить его сортировке
Спасибо!!!
Чем дальше обучение, тем меньше комментариев. Вставлю свои пять копеек:
Неплохо 🙂
Уважаемый автор, не затруднит подсказать, как в тестовом задании изменить код, что-бы можно было вводить в массив имена с пробелом (getline у меня не прокатывает)?
Возможен такой вариант функции main():
Здесь используется getline и cin.ignore.