Архив рубрики: Массивы и строки в С++

Работа с локализованными строками




Первое, что нужно уметь делать с локализованными строками — это записывать символьные константы широких локализованных символов и отличать их от обычных строк char[]. Для этого строка записывается с предшествующим ей квалификатором L:

Результатом будет:

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

В такой строке рядом с равным успехом могут стоять символы самой разнообразной природы: различных языков, специальные математические символы, общепринятые в технике обозначения из греческого алфавита (α, ε, θ, π, σ, λ, φ, Ω…), музыкальные ноты и др. Как вы, очевидно, понимаете, точно также в составе строк широких символов, с равным успехом, могут встречаться и символы латинского алфавита (основная таблица ASCII), при этом каждый такой символ тоже будет занимать 2 или 4 байт (в зависимости от соглашений принятых в операционной системе), в отличие от привычного 1 байта.

Проделаем ряд операций с русскоязычными строками, но записывая их (пока) в традиционной форме массивов char:

Выполняем:

Казалось бы, что (почти) всё работает в точности как по учебнику и зачем нам какие-то широкие локализованные строки? Но это обманчивая иллюзия! Дело здесь в том, что некоторые традиционные строчные функции (strcat(), strcpy(), strdup(), strstr() и др.) будут возвращать корректный результат. Это потому что они выполняют операции над байтами, байт за байтом, не вникая во внутреннюю структуру копируемых символов.

Но другие операции (и ошибочный результат strlen() на это уже наглядно указывает) будут работать некорректно: strncpy(), strchr(), strsep(), strtok() и др. И они будут создавать вам очень неожиданные результаты, весьма сложные для толкования. Посмотрите как сработает побайтовый реверс строки, и как различается его работа на англоязычной и русскоязычной строке:

Сработает это так, и это определённо не то, что вы рассчитывали получить:

На этом мы закончим рассмотрение возможности представления русскоязычных строк традиционными массивами char[] и обработка их традиционными строчными функциями, и завершим это рассмотрение выводом: работать с русскоязычными строками как массивом char можно только:

а). либо когда мы используем строчные константы в неизменном виде, только как строки для их ввода-вывода в неизменном виде;

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

Во всех остальных случаях корректная работа с кириллицей возможна только как с массивами широких локализованных символов wchar_t (с завершающим строку широким нулевым символом L’\0′). Для работы с таким представлением локализованных строк библиотека C предоставляет широкий набор строчных функций, полностью аналогичный традиционным строчным функциям, но вместо префикса str в их именах используется префикс wcs: wcslen() вместо strlen(), wcsncpy() вместо strncpy() и т.д.

Посмотрим как это работает на примере:

Такой иллюстрации вполне достаточно для того, чтобы увидеть прямые аналогии использования функций работы с символами wchar_t. Тот, кто имеет некоторый опыт работы со строками char без усилий перенесёт его широкие строки. Установка языковой локали (вызов setlocale()) устройства вывода (терминала) обязательна, потому что по умолчанию программа C/C++ устанавливает локаль “C” (так сложилось исторически), которая допускает вывод только 128 символов младшей половины 8-битной таблицы ASCII.

В показанном написании функция устанавливает локаль, используемую в операционной системе по умолчанию — я предполагаю, что мы экспериментируем в русскоязычной установленной системе. Новый стандарт языка (C99) вводит и новый формат для функций форматирования строк (printf(), sprintf()) %ls, это форматирование строк wchar_t[].

Точно так же, как и с массивами char, перешедшими в C++ из C, библиотека C++ вводит полный аналог контейнерного класса string, но содержащий в своём составе широкие локализованные символы, и носит название этот класс wstring:

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

В показанном написании: locale::global( locale( “” ) )это установка локали по умолчанию в ООП манере C++, аналогично тому, как это было показано раньше в манере C.

Вопросы ввода-вывода строк широких символов (на терминал или в файл) отдельный непростой предмет, поэтому его рассмотрение мы отложим до отдельной заметки на этот счёт.

Рассылка новых уроков по программированию:

О локализации символьных строк




локализация символьных строк в с++

Подавляющее число примеров кодов со строками C/C++ (в любых публикуемых источниках) оперирует с нуль-терминальными массивами (ASCIZ) элементов char (в стиле C), или с контейнерным типом string (в стиле C++), построенным как надстройка над такими массивами. Всё это замечательно работает со строками латинских (англоязычных) символов, но может идти вразнос на строках, содержащих символы иноязычных алфавитов (русский, китайский, арабский или иврит). Здесь всё не так просто… и весьма плохо описано в литературе, что и понятно: англоязычных авторов мало занимают вопросы иноязычной локализации, а отечественные авторы, в большинстве, переписывающие и адаптирующие англоязычные публикации, не уделяют внимания этой стороне вопроса.

Язык C — весьма старый язык программирования, а C++ наследует из него форматы и сдерживается требованиями синтаксической совместимости с C. Для того, чтобы не иметь в C/C++ проблем с такими строками (называемыми локализованными) нужно понимать что там с этими локализациями происходит…

Исторически, символы (char) представлялись (1963 год) стандартом ASCII как младшие 7 бит одного байта, при этом старший 8-й бит предназначался для контроля ошибок, возникших при передаче данных. Такая кодировка позволяет кодировать максимально всего 128 различных символов, и этого числа с трудом хватает на символы английского алфавита (большие и малые), цифровые (коды 0x30-0x39), управляющие (код меньше 0x20) и специальные символы. Когда возникала необходимость представления национальных алфавитов, вводилась альтернативная таблица символов, например KOI-7 для русского языка. Переключение в потоке ввода-вывода на альтернативную таблицу символов производилось символом с кодом 0x18 (код называется: Device Control 2) в потоке, а возврат к основную таблицу ASCII — символом с кодом 0x17 (Device Control 1).

Позже, с середины 80-х годов, с времени широкого распространения IBM PC и замены ними компьютеров других семейств, стандарт ASCII был расширен на 8-й бит байта char, байт мог представлять 256 символов: младшие 127 представляли исходную таблицу ASCII (с латинским шрифтом), а старшие — национальный алфавит. Но, поскольку национальные алфавиты могут быть самыми разнообразными, то для поддержки каждого из них потребовалось ввести свою кодовую страницу, например, для русского языка это могут быть страницы CP-866 (в MS-DOS), CP-1251 (в Windows), KOI-8r (в UNIX, Linux) — и каждая из этих страниц предлагает свой, отличающийся от других, порядок символов русского языка. При этом, для корректного отображения (или декодирования) любой локализованной символьной строки нужно обязательно знать кодовую страницу в которой она представлена.

Для того, чтобы положить конец этому вавилонскому столпотворению языковых кодовых страниц, был предложен (1991г.) стандарт представления UNICODE, в системе кодирования которого каждый символ кодируется 32-бит значением (4 байта, но не все 32-бит значения допустимы). Применение данного стандарта позволяет закодировать огромное количество символов разных систем письменности. Документы, закодированные по стандарту UNICODE, могут содержать в едином тексте японские и китайские иероглифы, буквы латиницы, кириллицы, греческого алфавита (α, ε, θ, π, σ, λ, φ, Ω…), математические символы, символы музыкальной нотной нотации, символы вымерших, редких, экзотических народностей. При этом нет необходимости в переключении кодовых страниц. Например, вот как выглядят некоторые символы языка, обозначенного как “сингальский”:

Первый UNICODE стандарт был выпущен в 91-м году. Последний на данный момент — в 2017 и он описывает 136755 разнообразных символов.

Но UNICODE — это ещё только стандарт представления каждого символа. Для представления этого символа в конкретной операционной системе (или языке программирования) нужна ещё система кодирования символов UNICODE.

  • Достаточно широко используются системы кодирования:
    UTF-32 — для представления каждого символа используются 4 байта, непосредственное численное значение кода UNICODE
  • UTF-32 — для представления наиболее часто используемых символов используются 2 байта (первые 65536 позиций), а остальные представляются в виде в виде «суррогатных пар». Такое кодирование используется в операционных системах Windows начиная с Windows NT.
  • UTF-32 — для представления каждого символа используются последовательность байт переменной длины: от 1 байта для символов основной таблицы ASCII, до 6 байт для редко используемых символов (символы русского алфавита кодируются 2-мя байтами). Эта кодировка создавалась позже других для операционных систем Plan 9 и Inferno в 1992г. Кеном Томпсоном и Робертом Пайком с коллегами, и вошла как единая и основная кодировка символьных строк в более поздних языках программирования Python и Go. Такое кодирование используется, на сегодня повсеместно, в POSIX/UNIX операционных системах, Linux.

Возвращаясь к тому, что C/C++ старое семейство языков программирования, для представления в них локализованных символов потребовалось ввести новый тип данных — широкие символы wchar_t вместо char (тип данных появился в стандарте C89, но, в полной мере с API поддержки, только в стандарте C99). Вместо строчных функций библиотеки C вида str*() для широких предлагаются их полные аналоги, но в виде wcs*() (вместо префикса str записываем префикс wcs). В разных системах wchar_t может иметь разную разрядность (в Linux это int32_t, в Windows int16_t) но для программиста это не имеет значения и не создаёт различий.

Для работы и преобразования многобайтовых последовательностей записанных в кодировке UTF-8 в C/C++ вводится семейство функций вида mb*(): mbtowc(), mblen(), mbstowcs(), wcstombs() и др. Это механизм взаимных преобразований между массивами char[] (в которых также выражаются строки UTF-8) и wchar_t[]. Если вы не сталкиваетесь с кодировкой UTF-8 (что с большой вероятностью имеет место в Windows), то эта группа функций вас не должна занимать.

Аналогично, вместо контейнерного класса C++ string вводится аналогичный контейнерный класс широких символов wstring.

Конкретно о технике работы с широкими локализованными строками мы поговорим в следующей статье. А пока 1-й элементарный пример … без комментариев — как повод для размышления (обратите внимание и объясните, что вызов strlen() в каждом случае даёт число байт в строке явно не соответствующее визуально видимое число букв в ней):

P.S. С большой детальностью про локализацию в C/C++ и работе с локализованными строками, кто интересуется с большей подробностью, могут почитать здесь: Языковая локализация C/C++ Языковая локализация C/C++ — там объяснений более 22 страниц формата офисного документа.

Рассылка новых уроков по программированию:

Классы string и wstring. Часть 5




c++ для начинающих, класс string, класс wstring, контейнеры STL C++ , Standard Template Library, контейнер с++Класс string стандартной библиотеки C++ хорошо известен и охотно используем. Но не все и не всегда задумываются над тем, что класс string, при некоторых отличиях в деталях — это и есть контейнер вектор: vector<char> . Правда, он дополнен некоторыми особенностями (но такой код и вы сами могли бы написать):

  • Метод size() задублирован методом length(). Они полностью тождественны, из соображений удобства. Просто для строки естественнее иметь длину, чем размер;

  • Определены перегруженные операции +, += которые возвращают конкатенацию (объединение) строк;

  • Определён конструктор, инициализирующий string при создании начальным значением символьной строки в формате ASCIIZ (char* — указатель на символьный массив в стиле C завершающийся нулём);

  • Определён метод c_str(), который возвращает указатель на внутреннее содержимое строки в формате ASCIIZ. Поскольку это внутреннее значение, его можно использовать, но не стоит пытаться его изменять. Это хорошо не закончится.

Во всём же остальном строки ведут себя точно как вектор, и к ним применимы все операции над векторами. Понимание того, что представляет собой класс string (vector<char>) может позволить создать ряд неожиданных эффектов. Например, поскольку нулевой символ не имеет для vector<char> никакого особого значения (в отличие от строки C), то его тоже вполне можно «заталкивать» в конец string. Тем самым можно поместить в единственную переменную string целый массив C-строк или даже целый текст.

Вот, как подобным образом поместить весь набор переменных окружения (environment) операционной системы в одну переменную string:

Примечание: здесь мы использовали ещё одну из допускаемых форм главной функции программы main() – 3-м параметром которой является массив указателей строк (char*) переменных окружения (environment). Признаком окончания массива строк envp является указатель NULL (тех. документация операционной системы).

Результат:класс string, класс wstrting, контейнеры STL C++ , Standard Template Library, контейнер с++

Что относительно типа wstring? wstring — это эквивалент vector<wchar_t> , вектор «широких», локализованных символов, представляющих интернациональную кодировку символами Unicode. Разбор содержимого (поиск, выделение слов, разбиение на строки и т.д.) русскоязычной или любой другой строки (китайской например) можно делать только в формате wtring (не string). При этом необходимо предварительно установить правильную локаль для программы (локаль по умолчанию “С” предполагает только ASCII символы в 7-битном представлении). А для ввода-вывода wstring предлагаются потоки, соответственно wcin и wcout, вместо cin и cout, предназначенных для string. Это написано для напоминания.

В порядке иллюстрации рассмотрим анализ локализованной строки wstring на предмет того, является ли она палиндромом.

Пробелы и знаки препинания при сравнениях пропускаются:

Буква L перед символьной константой означает, что записанная дальше строка , записана в широких символах wchar_t

Результат:класс string, класс wstrting, контейнеры STL C++ , Standard Template Library, контейнер с++

Локализации — это уже совершенно другая тема, которая далеко уведёт нас от нашей основной темы. Обсуждение вопросов локализации и широких символов в C/C++ можно почитать здесь.

Наверное, в этой части уместно отметить следующие обстоятельства, относящиеся ко всем типам контейнеров STL. При создании контейнера конструктором без параметров, создаётся пустой контейнер, не содержащий ещё ни одного элемента (заготовка для будущего заполнения контейнера). Размер такого контейнера (метод size(), или length() для строк) равен нулю. Но эффективнее, с точки зрения производительности, проверять контейнеры на пустоту методом empty(), который присутствует во всех типах контейнеров.

Рассылка новых уроков по программированию:

Массивы со статической и динамической размерностью. STL Часть 1




Массивы со статической и динамической размерностью, контейнеры STL C++ , Standard Template LibraryПрежде, чем приступать к обзору контейнерных типов библиотеки STL, разумно освежить в памяти информацию об организации массивов C и C++. Потому что контейнеры STL — это некоторые альтернативные формы организации коллекций данных, свободные от ограничений массивов.

Массивы — одна из самых используемых форм организаций данных, и исторически одна из самых первых форм, появившихся в языках программирования (языки конца 50-х годов XX века). Массив — это представление набора последовательных однотипных элементов. Принципиально важным в таком определении являются 2 момента, которые для массива должны выполняться обязательно:

  1. Каждый элемент массива нужно указать номером его местоположения в последовательности подобных элементов.

  2. Все элементы массива должны обязательно быть однотипными. Всем знакомы и понятны простейшие определения, например, целочисленных массивов: int array[100] . Но это вовсе не означает, что в массивы могут организовываться только простейшие встроенные типы языка C++. В массив могут организовываться объекты-переменные любого составного типа (класса, структуры) и степени сложности. Единственным ограничением является то, что все элементы одного массива должны быть одного типа. Например, так может описываться студенческая группа:

Массивы со статической и динамической размерностью, контейнеры STL C++ , Standard Template Library

К этому крайне важному обстоятельству — типы элементов массива — мы ещё вернёмся в дальнейшем.

Ещё со времён самых ранних языков программирования (FORTRAN и др.), на массивы накладывалось сильное ограничение: размер массива должен определяться только целочисленной константой, значение которой должно быть определено на момент компиляции кода. То же самое ограничение сохранилось и в языке C, который стал прародителем C++. Например:

Массивы со статической и динамической размерностью, контейнеры STL C++ , Standard Template Library

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

Массивы со статической и динамической размерностью, контейнеры STL C++ , Standard Template LibraryВо всех таких случаях, после определения массива размер его фиксируется и мы никак не сможем увеличить его размер (если, например, в ходе вычислений окажется, что нам не хватает этого размера). Так определенные массивы называются массивами со статически объявленным (в момент написания кода) размером.

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

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

Массивы со статической и динамической размерностью, контейнеры STL C++ , Standard Template Library

Но это не так! Просто здесь константное значение размера объявляемого массива извлекается из списка значений, и равно, в показанном примере 5.

Единственным способом создать массив, в классических C и C++, размером в N элементов, вычисляемым в момент создания массива (на этапе выполнения) — это был способ динамического размещения массива. Которое в C и C++ делается, соответственно:

Например, так:

Массивы со статической и динамической размерностью, контейнеры STL C++ , Standard Template Library

Самые поздние стандарты (C99, C++11) внесли расширения, которые допускают создание локальных массивов в функциях с размерами, вычисляемыми при входе в функцию. При таких условиях массив будет выделен в стеке функции. Это уже весьма существенно, когда мы не можем знать наперед размер обрабатываемых данных. В качестве примера посмотрим задачу нахождения всех простых чисел, не превосходящих N (решето Эратосфена), где N задаётся при запуске программы.

Этот код мы уже обязаны компилировать с указанием стандарта C++ 2011 года (опциями или компилятора, или свойствами собираемого проекта):Массивы со статической и динамической размерностью, контейнеры STL C++ , Standard Template Library

Но даже после всех расширений, простейший массив, как форма организации набора объектов, оказывается недостаточно гибким. Главные из ограничивающих факторов:

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

  • По правилам C/C++ при вызове функций вместо массива в качестве параметра вместо массива передаётся указатель на его начало (адрес 1-го элемента). Это позволяет сильно повысить эффективность многих вычислительных алгоритмов, но при этом теряется информация о размере массива, и её необходимо передавать отдельным параметром. Например, если бы мы хотели формировать решето Эратосфена не в функции main(), а в отдельной функции, то мы должны были бы формировать её вызов как erastof ( a, n ).

  • Многие интуитивно простейшие операции над массивами вызывают сложности. Например: в 15-ти элементном массиве элемент под номером 10 надо вставить между элементами 2 и 3. При этом а). все элементы с 3 по 9 нужно копировать на одну позицию вправо, б). делать это можно только в нисходящем порядке от 9 к 3 и в). за всеми индексами этих операций необходимо следить в ручном режиме.

Потребности практики требовали большего, что и породило контейнеры STL (Standard Template Library).

Рассылка новых уроков по программированию:

Задача: Палиндром в массиве

Проверить число в массиве на наличие палиндрома (именно через массив).

задача с решением, палиндром, наличие палиндрома в массиве с++, число палиндром, практика программирования
Определение Палиндром – сайт Википедия

Решение:

Единственное место, которое в коде может вызвать затруднение, это логическое присвоение:

Здесь проверяется равенство символов, отстоящих на i позиций от начала и от конца исследуемой строки. Если результат отрицательный (не равно), то это уже не палиндром, и можно выходить из цикла, а дальнейшие сравнения не проводить. Переменная poli здесь не является необходимой, и оставлена для наглядности – можно принимать решение (if) прямо из проверяемого условия.

Проверки делаются до середины строки, потому что дальнейшее продолжение цикла (из-за симметрии условия) только повторит то, что уже сделано ранее.

Выполнение:

задача с решением, палиндром, наличие палиндрома в массиве с++, число палиндром, практика программированияПримечание:

Как легко смогут заметить наблюдательные читатели, этот код проверит на палиндром не только запись числа, но и любую текстовую строку, при выполнении таких условий:

  • строка содержит только латинские (не русские, не китайские, …) литеры (интернациональные литеры в Unicode представляются более чем 1-м байтом);
  • строка не содержит символов пробела и знаков препинания (потому что в определениях палиндромов эти символы, обычно, исключают из сравнений: “Я иду с мечем судия“, “На в лоб, болван“).

Рассылка новых уроков по программированию:

Пузырьковая сортировка (сортировка “пузырьком”)




пузырьковая сортировка с++, сортировка пузырьком с++, bubble sort c++

Алгоритм сортировки массива “пузырьковая сортировка” рассматривается практически во всех учебниках по программированию на С++. Его легко понять начинающим. Он наглядный и очень простой. На практике этот алгоритм почти никто не использует, так как он медлительный и есть более быстрые и усовершенствованные алгоритмы. Мы также разберем его потому, что сортировка “пузырьком” – это основа некоторых более эффективных алгоритмов, которые будут изложены в следующих статьях. Это “быстрая” сортировка, “пирамидальная” сортировка и “шейкерная” сортировка.

Как работает “пузырьковая” сортировка? Допустим у нас есть неотсортированный массив чисел из 5-ти элементов и нам предстоит разместить значения по возрастанию. Для наглядности, на рисунке изобразим этот массив вертикально “Исходный массив”.

пузырьковая сортировка с++, сортировка пузырьком с++, bubble sort c++

Алгоритм сортировки “пузырьком” состоит в повторении проходов по массиву с помощью вложенных циклов. При каждом проходе по массиву сравниваются между собой пары “соседних” элементов. Если числа какой-то из сравниваемых пар расположены в неправильном порядке – происходит обмен (перезапись) значений ячеек массива. Образно говоря в нашем примере 2 “легче” чем 3 – поэтому обмен есть, далее 2 “легче” чем 7 – снова обмен и т.д. При сравнении нулевого и первого элемента на нашем рисунке обмена нет, так как 2 “тяжелее” чем 1. Таким образом более “легкое” значение, как пузырек в воде, поднимается вверх до нужной позиции. Вот почему у этого алгоритма такое название.

Рассмотрим алгоритм сортировки “пузырьком” в работе:

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

пузырьковая сортировка с++ для начинающих, сортировка пузырьком с++, bubble sort c++

Дополнить можно следующим: после того, как внутренний цикл отработает один раз, минимальное значение массива будет занимать нулевую ячейку. Поэтому при повторном проходе, очередное минимальное значение из оставшихся надо будет разместить в следующей ячейке (i + 1). Таким образом нет необходимости сравнивать между собой все элементы массива снова и количество обрабатываемых значений уменьшается на 1. При сортировке “пузырьком” необходимо пройти SIZE – 1 итераций внешнего цикла, так как сравнивать один элемент с самим собой – смысла нет.

Не будем забывать то, о чем мы говорили в самом начале – алгоритм “пузырьковой” сортировки малоэффективен и медлителен. Если у нас есть частично отсортированный массив и необходимо переместить в начало только одно значение, он все равно будет проходить все итерации циклов. То есть производить сравнение уже отсортированных значений массива, хотя этого уже можно и не делать.

Давайте немного улучшим эту ситуацию. Добавим в код еще одну переменную-“флажок”, которая будет давать знать, произошел ли обмен значений на данной внешнего цикла итерации или нет. Перед тем, как войти во внутренний цикл, “флажок” будет сбрасываться в 0. Если обмен значениями в этом цикле случится – “флажку” будет присвоено значение 1, если нет – то он останется равным 0. В последнем случае (если “флажок” равен 0) – обменов не было и массив полностью отсортирован. Поэтому программе можно досрочно выйти из циклов, чтобы не тратить время попусту на последующие ненужные сравнения.

Рассмотрите следующий пример:

Видим такую картину после запуска:

пузырьковая сортировка с++ для начинающих, сортировка пузырьком с++, bubble sort c++

Видно что программа прошла вложенным циклом по массиву 1 раз, переместила значение 2 в нужное место и завершила работу. А вот, что будет на экране, если закомментировать все строки кода, где есть переменная-“флажок”:

пузырьковая сортировка с++ для начинающих, сортировка пузырьком с++, bubble sort c++

Программа вхолостую 4 раза “гоняла” по массиву, хотя значение 2 перемещено в нужную ячейку еще при первом проходе вложенным циклом.

В сети есть прикольное видео (автор: AlgoRythmics ). В нем представлена “пузырьковая” сортировка в усовершенствованном варианте (когда отсортированные элементы массива уже не сравниваются между собой). Единственное – в наших примерах сравнение значений начиналось с последнего элемента. А в предложенном видео – от нулевого к последнему. Посмотрите и увидите, как элементы перемещаются в массиве на нужные места :)

Рассылка новых уроков по программированию:

Двумерные массивы в C++




двумерные массивы c++, двумерные массивы с++, многомерные массивы с++

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

Мы уже рассматривали в предыдущих статьях одномерные массивы и Си-строки (символьные массивы).Там говорилось, что элементы массива размещаются в памяти последовательно – элемент за элементом. Визуально их можно представить в виде одной строки данных в памяти. Чтобы обратиться к какому-либо элементу такого массива, достаточно указать его имя и индекс элемента. Первое отличие двумерного массива от одномерного – его элементы содержат два индекса: int arr [3][4]; Данные такого массива можно представить, как таблицу: 3 х 4.

двумерные массивы c++, многомерные массивы c++

Первый за именем массива индекс – это индекс строки, второй – индекс столбца.

двумерные массивы c++, многомерные массивы c++

Когда вы уже посмотрели на эти рисунки, можно сказать о двумерном массиве так – это массив, в котором каждый элемент также является массивом. int arr [3][4]; – это массив из 3-х элементов, каждый из которых это массив из 4-х элементов.

Данные двумерного массива также располагаются в памяти последовательно, но построчно. Сначала строка с индексом 0 – ячейки от 0-й до 3-й, далее строка с индексом 1 – ячейки от 0-й до 3-й …

Что могут хранить элементы двумерных массивов? Например, можно хранить номера парковочных мест в многоэтажном паркинге (6 этажей и на каждом 15 мест для парковки). Для этого надо объявить двумерный массив int floorsAndParkings[6][15]; и записать в его ячейки номера мест на каждом этаже. Двумерный массив может хранить Си-строки. Например: char someStr [3][256]; Так мы объявили массив, который будет хранить 3 строки по 256 символов каждая.

Инициализация двумерного массива.

Записать данные в двумерный массив можно при его объявлении. Рассмотрим на примере с местами парковки. Допустим в паркинге 2 этажа по 4 места парковки на каждом. Объявим массив и инициализируем его:

int floorsAndParkings[2][4] = { { 1, 2, 3, 4 }, { 1, 2, 3, 4 } };

Чтобы такая инициализация выглядела более читабельно, оформим её так:

Как вы помните, согласно стандарту C++11, знак = можно упустить. Строки инициализируются по тому же принципу:

Как вывести на экран данные двумерного массива? Можно пойти длинным путём и обращаться к каждому элементу вручную:

двумерные массивы c++, многомерные массивы c++

Вывод Си-строк двумерного массива на экран немного легче, так как нам достаточно указать только имя массива и индекс строки. Далее выходной поток cout самостоятельно будет выводить все элементы символьного массива, пока не обнаружит '\0'

двумерные массивы c++, многомерные массивы c++

Хорошо! А если нам надо заполнить и показать данные массива int floorsAndParkings[20][100] или char someStr[50][256]? Эту неблагодарную работу можно в десятки раз облегчить, используя циклы. Точнее вложенные циклы.

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

Мы использовали цикл for ,в строках 15 – 24, для записи данных в массив и одновременно отображения их на экране. Если представлять этот двумерный массив как таблицу – то внешний цикл for проходит по индексам строк – от 0-й до 6-й. Вложенный цикл – по индексам столбцов (по ячейкам строк таблицы) – от 0-й до 9-й. В строках 32 – 82 находится цикл do while. Его роль в том, чтобы снова и снова предлагать забронировать место для автомобиля, пока это необходимо пользователю. В нем находятся два вложенных цикла do while. Они реализовывают выбор этажа и места для парковки с защитой от некорректного ввода значений. Строки 57 – 81 содержат блок if else , который, в случае корректного выбора пользователя выводит сообщение об успешном бронировании. Если же место занято (ячейка содержит значение 0) – сообщает об этом, предлагает повторить выбор этажа и места и отображает обновлённую схему паркинга, где отмечены забронированные места.

Работает это так:

двумерные массивы c++, многомерные массивы c++

продолжение…

двумерные массивы c++, многомерные массивы c++

Рекомендую посмотреть видео. Двумерные массивы с 13-й минуты.

Предлагаю решить несколько задач по теме двумерные массивы.

Делитесь ссылками на статьи с нашего сайта со своими друзьями в социальной сети. Кнопки соц. сетей под статьей. Заранее спасибо!

Рассылка новых уроков по программированию:

Функции для работы со строками в C++




функции strlen (), strcat (), strcpy (), strcmp () в C++

После того, как мы с вами познакомились со строками и символьными массивами в C++, рассмотрим самые распространённые функции для работы с ними. Урок будет полностью построен на практике. Мы будем писать собственные программы-аналоги для обработки строк и параллельно использовать стандартные функции библиотеки cstring (string.h – в старых версиях). Так вы примерно будете себе представлять, как они устроены. К стандартным функциям библиотеки cstring относятся:

  • strlen() – подсчитывает длину строки (количество символов без учета \0);
  • strcat() – объединяет строки;
  • strcpy() – копирует символы одной строки в другую;
  • strcmp() – сравнивает между собой две строки .

Это конечно не все функции, а только те, которые мы разберём в этой статье.

strlen() (от слова length – длина)

Наша программа, которая подсчитает количество символов в строке:

Для подсчёта символов в строке неопределённой длины (так как вводит её пользователь), мы применили цикл while – строки 13 – 17. Он перебирает все ячейки массива (все символы строки) поочередно, начиная с нулевой. Когда на каком-то шаге цикла встретится ячейка ourStr [amountOfSymbol], которая хранит символ \0, цикл приостановит перебор символов и увеличение счётчика amountOfSymbol.

Так будет выглядеть код, с заменой нашего участка кода на функцию strlen():

Как видите, этот код короче. В нем не пришлось объявлять дополнительные переменные и использовать цикл. В выходном потоке cout мы передали в функцию строку – strlen(ourStr). Она посчитала длину этой строки и вернула в программу число. Как и в предыдущем коде-аналоге, символ \0 не включен в общее количество символов.

Результат будет и в первой программе и во второй аналогичен:

функция strlen () в C++

strcat() (от слова concatenation – соединение)

Программа, которая в конец одной строки, дописывает вторую строку. Другими словами – объединяет две строки.

По комментариям в коде должно быть всё понятно. Ниже напишем программу для выполнения таких же действий, но с использованием strcat(). В эту функцию мы передадим два аргумента (две строки) – strcat(someText1, someText2); . Функция добавит строку someText2 к строке someText1. При этом символ '\0' в конце someText1 будет перезаписан первым символом someText2. Так же она добавит завершающий '\0'

Реализация объединения двух строк, используя стандартную функцию, заняла одну строчку кода в программе – 14-я строка.

Результат:

strcat c++, strcat_s c++

На что следует обратить внимание и первом и во втором коде – размер первого символьного массива должен быть достаточным для помещения символов второго массива. Если размер окажется недостаточным – может произойти аварийное завершение программы, так как запись строки выйдет за пределы памяти, которую занимает первый массив. Например:

В этом случае, строковая константа “Учите С++ c нами!” не может быть записана в массив someText1. В нём недостаточно места, для такой операции.

Если вы используете одну из последних версий среды разработки Microsoft Visual Studio, возможно возникновение следующей ошибки: “error C4996: ‘strcat’: This function or variable may be unsafe. Consider using strcat_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.” Так происходит потому, что уже разработана новая более безопасная версия функции strcat – это strcat_s. Она заботится о том, чтобы не произошло переполнение буфера (символьного массива, в который производится запись второй строки). Среда предлагает вам использовать новую функцию, вместо устаревшей. Почитать больше об этом можно на сайте msdn. Подобная ошибка может появиться, если вы будете применять функцию strcpy, о которой речь пойдет ниже.

strcpy() (от слова copy – копирование)

Реализуем копирование одной строки и её вставку на место другой строки.

Применим стандартную функцию библиотеки cstring:

Пробуйте компилировать и первую, и вторую программу. Увидите такой результат:

strcpy c++

strcmp() (от слова compare – сравнение)

Эта функция устроена так: она сравнивает две Си-строки символ за символом. Если строки идентичны (и по символам и по их количеству) – функция возвращает в программу число 0. Если первая строка длиннее второй – возвращает в программу число 1, а если меньше, то -1. Число -1 возвращается и тогда, когда длина строк равна, но символы строк не совпадают.

strcmp c++ Программа с strcmp():

strcmp c++

Делитесь в социальных сетях нашими статьями со своими знакомыми, которые так же изучают основы программирования на языке С++.

На нашем сайте уже есть возможность оформить подписку, чтобы получать уведомления о новых статьях. Чтобы подписаться – впишите свой e-mail ниже.

Рассылка новых уроков по программированию:

Строки в C++ и символьные массивы





строки в С++

Со строками мы с вами работали практически в каждой нашей программе и до этого урока. Точнее со строковыми константами – последовательностью символов в двойных кавычках. Нам часто приходилось выводить на экран ту или иную информацию. Например:

строки в С++

Текст в кавычках и есть строковая константа. Кавычки используются для определения начала и конца строковой константы и её частью не являются.

Достаточно часто необходимо не только печатать какие-то короткие сообщения по ходу программы, а и работать с определённым текстом, хранить его где-то, обращаться к нему и редактировать, по необходимости. К строковой константе, рассмотренной выше, мы не сможем обратиться в программе, например для того, чтобы перезаписать её (мы не знаем ни ее имени ни адреса в памяти). Сейчас вы узнаете об одном из способов работы со строками в C++. Позже мы познакомимся ещё с одним способом – использованием класса string.

Итак о первом: в C++ для хранения строк используют символьные массивы. Это такие же массивы, как мы с вами уже рассматривали в статье о массивах в С++, но хранят они не числовые данные, а символьные. Можно представить символы такого массива расположенными последовательно в соседних ячейках памяти – в каждой ячейке хранится один символ и занимает один байт. Один байт потому, что каждый элемент символьного массива имеет тип char. Последним символом каждой такой строки является символ \0 (нулевой символ). Например:

строки в С++, символьные массивы в С++

Сам текст, включая пробел, состоит из 11-ти символов. Если бы в последней ячейке находилась например . (точка), а не нулевой символ \0 – для компилятора это уже не строка. И работать с таким набором символов надо было бы, как с обычным массивом – записывать данные в каждую ячейку отдельно и выводить на экран посимвольно (при помощи цикла):

строки в С++, символьные массивы в С++

К счастью в C++ есть куда более удобный способ инициализации и обращения к символьным массивам – строкам. Для этого последним символом такого массива обязательно должен быть нулевой символ \0. Именно он делает набор символов строкой, работать с которой, гораздо легче, чем с массивом символов.

Объявляется строка таким образом – создаем массив типа char, размер в квадратных скобках указывать не обязательно (его подсчитает компилятор), оператор = и в двойных кавычках пишем необходимый текст. То есть инициализируем массив строковой константой:

Прописывать нулевой символ не надо. Он присутствует неявно и добавляется в каждую такую строковую константу автоматически. Таким образом, при том что мы видим 11 символов в строке, размер массива будет 12, так как \0 тоже символ и занимает один байт памяти. Займет он последнюю ячейку этого символьного массива. Как видите, для вывода строки на экран, достаточно обратиться к ней по имени: cout << str << endl; cout будет выводить на экран символ за символом, пока не встретит в одной из ячеек массива символ конца строки \0 и вывод прервется. Такое обращение для обычного символьного массива (массива без \0) недопустимо. Так как компилятор выводил бы символы на экран даже выйдя за рамки массива, пока не встретил бы в какой-то ячейке памяти символ \0. Можете попробовать подставить в первый пример вместо цикла оператор cout << str << endl; и увидите, что получится. У меня показало вот так:

строки в С++, символьные массивы в С++

Хочу обратить ваше внимание на отличие символьной константы (в одинарных кавычках – 'f', '@' ) от строковой константы (в двойных кавычках "f", "@" ). Для первой, компилятором C++ выделяется один байт для хранения в памяти. Для символа записанного в двойных кавычках, будет выделено два байта памяти – для самого символа и для нулевого (добавляемого компилятором).

Что если строку должен будет ввести пользователь с клавиатуры? В этом случае необходимо объявить массив типа char с указанием его размера достаточного для хранения вводимых символов, включая \0. Не забывайте об этом нулевом символе. Если вам надо хранить 3 символа в массиве, его размер должен быть на единицу больше – то есть 4.

строки в С++, символьные массивы в С++

Используя пустые кавычки при инициализации, мы присваиваем каждому элементу массива значение \0. Таким образом строка будет очищена от “мусора” других программ. Даже если пользователь введет название содержащее меньшее количество символов, следующий за названием будет символ \0. Это позволит избежать нежелательных ошибок. В памяти эта строка будет выглядеть так:

строки в С++, символьные массивы в С++

Кстати, если допустим перезаписать 11-ю ячейку этого массива – str[11] = '\0';

строки в С++, символьные массивы в С++

и отобразить массив на экране, то увидим только purecodecpp не смотря на то, что в ячейках 12, 13, 14 остались храниться символы. Нулевой символ сыграет свою главную роль при выводе на экран и всё, что находится за ним не будет показано.

Поговорим о вводе строк с клавиатуры подробней. Дело в том, что существуют определённые проблемы о которых надо знать и которые необходимо научиться решать. В следующей программе при вводе строки не всё будет так гладко, как нам хочется:

строки в С++, символьные массивы в С++

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

Решается эта проблема просто. В C++ есть функции get() и getline() , которые мы можем использовать вместе с cin. Они похожи, но чаще используется getline(). В этой статье мы не будем рассматривать их отличие. Допишем в нашу программу ввод с getline ():

строки в С++, символьные массивы в С++

Вы видите, что в скобках мы указали для функции два аргумента – в какой массив считать символы (имя массива) и размер этого массива – строка 11. Результат нам подходит – всё отобразилось правильно. cin.getline() считывает в массив всю строку включая пробелы и табуляции, пока не произойдет нажатие Enter или пока не будет превышен размер массива. Символ новой строки в массиве не сохранится, а заменится на нулевой символ.

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

Дополнительно к прочитанному желательно посмотреть видео-урок. Строки с 7-й минуты:

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

Рассылка новых уроков по программированию:

Массивы в C++ (видео)




массивы в c++, массивы в с++, одномерный массив

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

Итак, зачем же нужны массивы и что они из себя представляют? К настоящему моменту вы уже хорошо знаете, что данные программы хранятся в объявленных нами переменных определённого типа (int, double, char… ). Но бывает так, что программе необходимо хранить сотни (а то и больше) переменных однотипных данных, а также необходимо с ними работать – присваивать значения, изменять их и т.д. К примеру, надо хранить порядковые номера строк. Согласитесь – любому станет страшно от мысли, что надо создать пятьсот переменных типа int, каждой дать уникальное имя и присвоить значение от 1-го до 500-та. (мне уже страшно :) В таком случае, массивы нас просто спасут.

Отметим основное и перейдем к практическому примеру:

  • массив в С++ – это совокупность определенного количества однотипных переменных, имеющих одно имя. К примеру, int array [3];. Эта запись означает, что мы объявили массив с именем array , который содержит в себе 3 переменные типа int;
  • переменные массива называют элементами ;
  • каждый элемент имеет свой уникальный индекс – свой порядковый номер. Используя индекс мы можем обращаться к конкретному элементу. ВАЖНО – индексация элементов массива начинается с 0. Так в массиве int array [3] первый элемент имеет индекс 0, а последний – 2. Чтобы обратиться, например, к нулевому элементу массива и изменить его значение, надо указать имя массива и в квадратных скобках указать индекс элемента – array [0] = 33.

Рассмотрим пример:

В строке 12 мы определяем целочисленную константу SIZE, которая будет хранить размер массива (определённое нами, количество его элементов). В строке 13 объявляем массив: указываем тип данных, которые будут храниться в ячейках массива, даем имя и указываем размер в квадратных скобках . Важно, что в квадратные скобки мы можем записывать только целые константные значения. Надо либо сразу вписать целое число в квадратные скобки при объявлении массива (int firstArray[100];), либо определить целочисленную константу до объявления массива и ввести в квадратные скобки имя этой константы (как в нашем примере). Второй способ использовать предпочтительней, если в ходе программы вам придется несколько раз обращаться к массиву через цикл. Объясняется это тем, что когда мы объявляем цикл, в нём можно указать условие изменения счетчика до значения SIZE. Вот представьте, что нам необходимо изменить размер массива с 10 элементов на 200. В этом случае, нам остаётся всего на всего изменить значение целочисленной константы, и таким образом у нас автоматически подставятся новые значения размера и в массив, и во все циклы программы. Можете попробовать в нашем примере внести любую другую цифру в константу SIZE. И вы увидите, что программа будет прекрасно работать – создаст массив на столько элементов, на сколько вы укажете, внесет данные и отобразит их на экране.

В строках 15 – 19 определяем цикл for. Его счетчик i будет служить индексом элементов массива. В самом начале, он равен 0 и с каждым шагом будет увеличиваться на единицу до тех пор, пока не станет равным SIZE – количеству элементов массива. Обратите внимание, в одном цикле мы и присваиваем различные значения элементам массива и в следующей строке обращаемся к ним, чтобы вывести данные, которые они хранят, на экран.

Запускаем программу и видим результат:

массивы в c++, массивы в с++, одномерный массив

Присвоить значение элементам массива можно разными способами – инициализировать его при создании либо с помощью цикла. Если размер массива большой, есть прекрасная возможность использовать цикл for или while для инициализации его элементов. Так мы сделали в нашем примере. Можно заполнить массив случайными числами – об этом у нас есть отдельная статья.

А если массив совсем небольшой, к примеру на 5 элементов, инициализировать его можно сразу при объявлении:

массивы в c++, массивы в с++, одномерный массив

Так элементу с индексом 0 – firstArray[0] – будет присвоено значение 11, а последнему элементу массива firstArray[4] – значение 15. Есть такая фишка – вы можете не указывать размер массива в квадратных скобках и сделать такую запись:

массивы в c++, массивы в с++, одномерный массив

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

Так же при начальной инициализации элементов массива, когда массив необходимо очистить от «мусора» (остаточных данных других программ в памяти) лучше сразу присвоить всем элементам значение 0. Это выглядит так:

массивы в c++, массивы в с++, одномерный массив

Следует запомнить, что такая инициализация возможна только для заполнения нулями. Если необходимо заполнить элементы массива какими-либо другими числами, лучше применять цикл. В C++11 (стандарт кодирования) при использовании списковой инициализации (инициализации с фигурными скобками) разрешается даже отбросить знак = .

массивы в c++, массивы в с++, одномерный массив

Хочется показать еще один прием инициализации при создании массива. К примеру, для массива из 30-ти элементов нам надо внести значения 33 и 44 только в ячейки с индексом 0 и 1 соответственно, а остальные заполнить нулями. Тогда делаем так:

массивы в c++, массивы в с++, одномерный массив

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

Организовать заполнение массива можно и при помощи оператора cin:

Чтобы присвоить или изменить значение конкретного элемента, надо обратиться к нему, используя его индекс. К примеру, все значения массива из 500-та элементов нас устраивают, но необходимо изменить значение лишь одного. Тогда мы обращаемся к нему по его индексу : firstArray[255] = 7;

С этим разобрались, теперь давайте посмотрим, каким же образом массив располагается в оперативной памяти. Массив типа int из пяти элементов займет 20 байт памяти – 4 байта (int) * 5 (количество элементов) – и эти данные будут располагаться в памяти последовательно, как показано на рисунке:

массивы в c++, массивы в с++, одномерный массив
массив int из пяти элементов в оперативной памяти

Подведем итог и отметим всё самое важное о массивах:

  • синтаксис объявления массива :

тип_Данных_Массива имя_Массива [размер];

  • переменные массива называются элементами, а каждый элемент имеет свой порядковый номер – индекс.
  • нумерация индексов элементов массива начинается с нуля!!!
  • инициализировать массив можно только при его создании – int firstArray[3] = {1, 2, 3}; Выполнять инициализацию позже уже не допускается: firstArray[3] = {1, 2, 3}; Если массив не был инициализирован в начале, можно присвоить значения его элементам, используя циклы или просто обращаясь к необходимому элементу через его индекс.
  • массив может быть одномерным – таким, как рассмотрен в этом примере, и многомерным – двумерным, трехмерным… (их мы рассмотрим в одной из наших следующих статей).

Не забывайте о необходимости практиковаться в решении задач – Задачи: Массивы в C++. Хотите узнать больше о массивах в C++ (в том числе о символьных массивах и строках)? Посмотрите этот видео-урок:

Рассылка новых уроков по программированию: