1. Введение в символьные массивы
Введение в символьные массивы: C-строки
Компьютеры на самом низком уровне не понимают текст. Процессоры оперируют исключительно числами. Чтобы научить машину работать с буквами, программисты придумали таблицы кодировок, где каждому символу присвоен уникальный числовой код. В языке C++ базовым типом данных для хранения одного символа является char (от английского character). Он занимает в памяти ровно один байт, что позволяет хранить различных значений.
Однако в реальных задачах нам редко нужен только один символ. Мы работаем со словами, предложениями и целыми текстами. Чтобы сохранить слово, нам нужно расположить несколько символов char в памяти компьютера друг за другом. Такая структура данных называется символьным массивом.
Исторически в языке C (предшественнике C++) не было специального удобного типа данных для строк. Программисты использовали обычные массивы символов. Этот подход перекочевал и в C++, получив название C-строки (C-strings). Понимание того, как они работают, критически важно, так как это фундамент, на котором строятся более современные и удобные инструменты.
Анатомия C-строки и нуль-терминатор
Представьте себе обычный массив чисел. Если мы передаем его в функцию, мы всегда должны передавать и его размер, иначе программа не узнает, где заканчиваются полезные данные и начинается «мусор» в памяти.
С текстом разработчики языка пошли другим путем. Чтобы не таскать за каждой строкой переменную с ее длиной, было принято элегантное решение: строка заканчивается там, где встречается специальный символ-маркер. Этот маркер называется нуль-терминатором и записывается как \0.
Нуль-терминатор — это символ, числовой код которого в таблице ASCII равен . Не путайте его с символом цифры '0' (ее код равен ). Когда программа читает символьный массив, она перебирает букву за буквой, пока не наткнется на \0. Как только маркер найден, чтение прекращается.
!Визуализация C-строки в памяти
Из этого правила вытекает важнейший закон работы с C-строками: размер массива всегда должен быть как минимум на элемент больше, чем количество видимых символов в строке. Если вы хотите сохранить слово из букв, вам потребуется массив размером .
Объявление и инициализация
В C++ существует несколько способов создать и заполнить символьный массив. Рассмотрим их от самых низкоуровневых к более удобным.
Посимвольная инициализация
Мы можем создать массив и перечислить каждый символ в фигурных скобках, как при работе с любым другим массивом.
Если в этом примере забыть написать \0, команда std::cout выведет слово «Hello», а затем продолжит выводить случайные символы из соседних ячеек памяти, пока случайно не наткнется на нулевой байт. Это классическая ошибка, приводящая к чтению чужой памяти.
Инициализация строковым литералом
Посимвольный ввод крайне неудобен. Поэтому компилятор C++ позволяет использовать строковые литералы — текст, заключенный в двойные кавычки.
Обратите внимание на разницу: одинарные кавычки ('A') используются для одного символа типа char, а двойные ("A") — для строки, даже если она состоит из одной буквы. Строка "A" занимает в памяти 2 байта: саму букву 'A' и скрытый \0.
Массивы с запасом памяти
Часто мы не знаем заранее, какое слово введет пользователь. В таких случаях мы создаем массив фиксированного размера «с запасом».
> Практическое задание для самостоятельной работы: > Напишите программу, в которой объявлен символьный массив размером 20 элементов. Инициализируйте его названием вашего любимого фрукта с помощью строкового литерала. Выведите результат на экран.
Ввод и вывод C-строк
Вывод символьных массивов на экран с помощью объекта std::cout работает интуитивно понятно. Объект cout спроектирован так, что при получении указателя на char (каковым является имя массива), он печатает символы до тех пор, пока не встретит \0.
С вводом данных через std::cin ситуация немного сложнее. Рассмотрим типичный пример:
Если вы запустите эту программу и введете Иван, она отработает идеально. Но попробуйте ввести Иван Иванов. Программа выведет только Привет, Иван!.
Почему так происходит? Стандартный оператор ввода >> читает данные до первого пробельного символа (пробела, знака табуляции или переноса строки). Как только cin видит пробел после слова «Иван», он останавливает запись в массив name и автоматически добавляет \0. Фамилия «Иванов» остается в буфере клавиатуры и будет прочитана при следующем вызове cin.
Чтение строк с пробелами: функция getline
Чтобы прочитать целую строку, включая пробелы, необходимо использовать метод getline, который встроен в объект cin.
Метод cin.getline() принимает два обязательных аргумента:
Использование cin.getline() не только решает проблему с пробелами, но и защищает вашу программу от переполнения буфера (buffer overflow). Если пользователь попытается ввести 150 символов в массив размером 100, cin.getline() прочитает ровно 99 символов, поставит \0 и остановится. Обычный cin >> попытался бы записать все 150 символов, разрушив данные в соседних ячейках памяти, что привело бы к аварийному завершению программы.
> Практическое задание для самостоятельной работы:
> Создайте программу, которая запрашивает у пользователя его любимую цитату из книги или фильма (она точно будет содержать пробелы). Сохраните ее в массив размером 200 символов с помощью cin.getline() и выведите на экран в кавычках.
Разница между размером массива и длиной строки
При работе с C-строками важно четко разделять два понятия: физический размер выделенной памяти и логическую длину самой строки.
Допустим, мы объявили массив: char city[50] = "Moscow";.
Физический размер массива — это количество байт, которое он занимает в оперативной памяти. В нашем случае это байт. Мы можем узнать это значение с помощью оператора sizeof().
Логическая длина строки — это количество полезных символов до нуль-терминатора. В слове "Moscow" букв.
В этом примере цикл while проверяет каждый элемент массива. Если элемент не равен \0, счетчик length увеличивается на . Как только цикл натыкается на нуль-терминатор, он останавливается. Это базовый алгоритм, который лежит в основе стандартной функции вычисления длины строки, с которой мы познакомимся в следующих уроках.
Понимание того, что строка — это просто массив символов с маркером конца, дает вам полный контроль над текстом. Вы можете обращаться к отдельным буквам по их индексу, например buffer[0] вернет 'D', а buffer[3] вернет 'a'. Вы можете изменять эти буквы, переводить их в верхний регистр или заменять одни символы на другие, просто перезаписывая значения в ячейках массива.