C как вывести символ
Перейти к содержимому

C как вывести символ

  • автор:

C как вывести символ

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

Лст. 1-1. Копирование файла в новый и правка.

Функция read
Рис. 1-1. Функция read.

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

Здесь я предлагаю воспользоваться вот такой полезной вещью:

Лст. 1-2. Очень простой командный файл для компиляции.

Это скрипт для оболочки bash, указание об этом для операционной системы есть в самой первой строке. Я назвал этот файл просто m, чтобы не колотить долго по клавиатуре. Коротко о том, как он работает. Вы уже можете разобрать знакомые строки. Но кое-что для новичков нуждается в пояснении. Не углубляясь в детали программирования для оболочки, запомним следующее:

$1 – это первый параметр, передаваемый в скрипт из командной строки. В нашем случае, ожидается имя главного файла программы на языке C. То есть фактически $1 представляет hello. Командный процессор просто производит подстановку. Остальные объектные модули должны быть уже подготовлены. Компилятор обрабатывает исходный файл на языке C, но перед записью в файл hello.s очищает его от макросов, при помощи потокового редактора sed. Вывод cc подается по конвейеру через фильтр. Программа для sed состоит из набора шаблонов для удаления строк, где они встречаются, поэтому очень важны малозаметные символы апострофов ‘.

Оставшиеся командные строки вам уже знакомы. Всюду, вместо $1, скрипт подставит hello, или другое имя с вашей программой. Только не используйте расширение файла .c – этот скрипт не настолько умен, чтобы его игнорировать.

Немного о ключах в командных строках. cc: Как вы уже знаете, -S это указание компилятору вывести результат в файл с ассемблерным кодом. -fno-builtin – это указание игнорировать имена встроенных функций. Это мы проясним потом, хотя пока ни одной такой функции не используется.

as: -o (output) Это ключ для имени выходного файла, по умолчанию мы получим файл a.out

ld: -o Как и выше, а -s (strip) удаление отладочной информации из исполняемого файла. И в самом деле, нам она только лишняя обуза. Итак, делаем скрипт исполняемым:

Лст. 1-3. Изменение прав доступа к файлу.

и пользуемся им на здоровье.

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

Новая версия hello
Рис. 1-2. Новая версия hello.

Компилируем программу и запускаем:

Новая версия hello
Рис. 1-3. Новая версия. (Еще и работает!)

Вот так просто. Вместо длинных командных строк. По сути дела, мы выполнили операции 1, 2 и 5 из предыдущей главы (см. Рис. 0-8). По аналогии с производством можно сказать так: с помощью скрипта/команды m мы из мастера, который бегает между работниками, превратились в начальника цеха, который не занимается мелочами.

Теперь пришло время обратить внимание на сам код в программе C. Сначала на то, что в языке используется понятие типа данных. Есть несколько элементарных типов, мы не будем нудно перечислять, а затронем только используемые.

char – это числовой тип для представления букв, цифр и знков препинания. Занимает размер в 1 байт. Может представлять беззнаковые числа в диапазоне 0т 0 до 255.

int – это числовой тип для представления целых чисел. Любой элемент данных такого типа занимает 4 байта. Представляет числа со знаком в диапазоне от -2147483647 до 2147483647.
Тип данных записывается перед объявлением имени переменной. Так как char – это всего один байт, то для выделения памяти для строки используют массивы. Квадратные скобки – оператор индекса в массивах. Если в квадратных скобках ничего не указано, то компилятор может узнать о размере массива из его инициализирующей части. Например, ask имеет размер 19 байт, так как такова длина строки, записанная после оператора присваивания – знака равенства.

Кстати, строки в языке C всегда записываются в двойных кавычках, а одиночные символы – в одиночных кавычках.

Переменная-массив answer содержит заготовку ответа пользователю и достаточный запас для остатка – имени пользователя. На этот раз мы не просто инициализировали строку, но еще и предусмотрели 256 байтов.

В квадратных скобках указывается размер массива. Массив name резервирует память в размере 32 байта, но ничем не инициализирован.

Кроме того, в программе есть две переменные целого типа i и j, для перебора символов в строке. Как можно заметить, язык допускает перечисление нескольких переменных после объявления типа.

И еще одна очень важная вещь: каждая инструкция в языке C обязательно заканчивается точкой с запятой. Объявление переменной – тоже инструкция.

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

Лст. 1-4. Память для переменных в программе hello v2.

Данные делятся на инициализированные и нет. В первом случае, они “зашиваются” в код программы. Во втором, – для данных выделяется память необходимого размера, но это происходит в процессе загрузки. Инициализированные данные влияют на размер скомпилированной программы, а неинициализированные нет. Неиспользованная память в сегменте данных заполняется нулями. Как мы можем видеть, это делает компилятор, заполняющий остаток строки нулями. Компилятор, как нетрудно догадаться, сам принимал решение, в каких сегментах размещать объявленные в программе переменные. Что касается выравнивания (alignment), то это способ расположить данные в памяти так, чтобы их выборка производилась как можно быстрее. Выравнивание приводит к небольшой потере памяти: для ask мы использовали всего 20 байт памяти, но из-за выравнивания с границей 16 было израсходовано 16+4 и еще 12 байтов образовали дыру, не занятую ничем. Такова оптимизация по умолчанию, которую предлагает компилятор, – по скорости выполнения.

Теперь к коду самой программы. Начнем с пояснения вызова функции. Первой в программе C всегда вызывается функция main (не считая стартового кода). Все остальное, внутри фигурных скобок, делает пользователь, то есть, тот, кто писал программу. Сначала мы вызываем функцию write, которая должна вывести вопрос на экран. Эта функция принимает три аргумента: дескриптор файла, адрес данных, которые надо вывести, и длинну (размер) данных.

Размер данных в нашем случае выполняет оператор sizeof. Он похож на функцию, но называется оператором. Формально это и есть функция, но sizeof выполняется во время компиляции, а не при работе программы. (Математики обычно называют операторами плохо ведущие себя функции.) Здесь sizeof избавляет нас от подсчета букв в строках.

1 – это дескриптор файла stdout, а 0 – stdin (есть еще 2 – stderr). Для каждой запущенной программы автоматически открываются эти три файловых потока. Правда, они имеют указанные символические имена (STanDard OUTtput, standard input, standard error), но у нас пока все так просто, что мы можем позволить себе роскошь пользоваться совсем небольшими числами.

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

И наконец, третий аргумент – это размер данных. Чтобы знать, когда остановиться. Функция read имеет совершенно аналогичный набор аргументов. Список аргументов этих функций совершенно логичен: куда/откуда, что, и в каком количестве писать или читать.

После того, как функция read запишет в переменную name ввод пользователя, программа должна составить строку из приветствия, имени пользователя, и восклицательного знака. Для этого мы можем просто дописать в строку answer имя пользователя, или то, что он ввел, используя зарезеревированное место. Но что значит “дописать”? Мы должны объяснить это буквально: копируем символы из строки name в строку answer начиная с места после пробела. Копирование должно вовремя остановиться. Для этого мы должны вычислить длину введенного имени. (Можно было бы узнать эту длину другим способом, если вы наблюдательны и можете мыслить по аналогии.)

Цикл for (это тоже оператор, оператор цикла) занимается подсчетом длины. Вообще-то, он может заниматься чем угодно, но в нашем случае он подсчитывает байты. Этот пример цикла не использует инструкции, которые выполняются при его проходе. Бывают и такие циклы. В скобках этого оператора задается начальное значение переменной цикла, i, затем проверяется условие: не равен ли очередной байт нулю?, и затем, переменная цикла увеличивается на единицу. Если условие нарушено – цикл прекращается. Как результат, мы в этом случае используем саму переменную i.
Оператор != – имеет смысл “не равно”. Нулевой байт, как и каждый предстваитель типа char, закавычен апострофами. Причем, это не символ нуля, это именно нулевой байт, поэтому он записывается как \0, а не 0. Дело в том, что ‘0’ – это код символа нуль, он равен 0x30 в шестнадцатеричной системе счисления, а ‘\0’ – это 0x00 (числа записаны по правилам языка C в системе с основанием 16. Десятичный эквивалент 0x30 3*16 = 48).

Следующий цикл for использует переменную j для обращения к name по индексу элемента массива. Здесь проверяется аналогичное условие равенства байта нулю. Таким образом, мы достигаем конца строки name. В языке C признаком конца строки считается нулевой байт, называемый также нуль-терминатором или завершающим нулем, это синонимы. Конструкция answer[i++] означает, что сначала выбирается i-й байт массива answer, а затем i увеличивается на единицу. i++ это пример постинкремента, а у нас есть еще и другой пример, когда переменная сначала инкрементируется, а потом используется. Но об этом чуть ниже.

Последний цикл for имеет инструкцию, именно поэтому в нем нет точки с запятой после закрывающей скобки при for. Инструкция перенесена в следующую строку. Собственно, инструкция как раз и состоит из оператора for и последующего присваивания-копирования.

Странным может показаться последующее, где мы дописываем строку ответа. Когда второй цикл выполняется последний раз, то i увеличивается на единицу, так же, как и во всех предыдущих случаях. Тогда зачем мы обращается к предыдущему элементу answer[i-1]? Все дело в том, что когда данные в файловый поток поступают с клавиатуры, туда же идет и клавиша Enter. И лишь после этого строка завершается нулем. Все логично, вы же ввели строку в файл? Вот строку и получайте. Она интерпретируется как перевод строки, и восклицательный знак переедет на новую строку. Но такая строка, разорванная на две части, нас не устраивает. Поэтому мы подавляем ненужный символ, просто записывая на его место восклицательный знак. А затем, вместо последнего символа записываем перевод строки, символ ‘\n’ или байт 0x0A, что то же самое. И после этого записываем завершающий ноль. Чтобы не затереть \n, мы сначала увеличиваем i на единицу, а затем используем. Это пример преинкремента (предварительного увеличения) ++i. И наконец, записываем на экран строку, которая получилась в результате.

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

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

Лст. 1-5. Код функции getc.

Функция получает в качестве аргумента дескриптор файла и возвращает считанный символ. Если происходит ошибка при операции чтения, то read вернет число -1 в переменную e и установит код (причину) ошибки. Мы эту ситуацию пока не обрабатываем (не пользуемся переменной errno, да и бог с ней). Для нас важно, что если read возвращает не -1 и не 0, а что-то другое, то значит, все в порядке. А именно, read возвращает число реально прочитанных байтов. Если read прочитает символ конца файла, EOF, он же число -1, то она, (функция read), вернет ноль.

Как уже нам известно, read передает считанную из файла информацию через указатель в своем втором аргументе. Сначала мы получаем адрес той переменной, куда надо записать результат, а затем преобразуем его в указатель на целое и (третий аргумент), читаем один элемент данных. Так что, мы можем прочитать из файлового потока и -1 в том числе. Но обратите внимание, что на этот раз -1 попадает не в e, а в r, а это другое дело. В e в этом случае окажется 1.

Следующая инструкция проверяет, чем является e. Если это -1, то мы выходим из функции со значением -1. Если это 0, то мы тоже выходим из функции с тем же значением -1. Таким образом, ситуация ошибки также обрабатывается как конец файла, что в целом логично – файл-то, с точки зрения функции, все равно, накрылся медным тазом, до конца он прочитан, или нет. Если же все прошло нормально, то getc вернет считанный символ как целое число. В том числе, и символ UNICODE, который, в подавляющем числе случаев, занимает не более 2 байтов. Если read вернет 0, то из файла читайть больше нечего, а это равносильно концу файла и getc снова должна вернуть EOF, то есть, -1.

Функция putc действует обратным образом и записывает в поток символ, как целое число. Она использует системную функцию write.

Лст. 1-6. Код функции putc.

Эта функция получает два аргумента: символ для записи c, и идентификатор файла f. В случае успеха, то есть, если write вернет число, отличное от нуля, функция putc возвращает записанный символ, иначе -1.

Поскольку очень часто UNIX программы используются в конвейерах или просто терминале, то файловые потоки известны заранее. Это stdin и stdout (числа 0 и 1, в нашем нестрогом изложении). Поэтому были придуманы еще две функции getchar и putchar:

Лст. 1-7. Код функций getchar и putchar.

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

Имена функций getchar – взять (прочитать, извлечь) символ и putchar – положить (вывести, напечатать) символ просты и понятны всем англоязычным, а теперь и русским, пользователям языка C, С-программистам. К сожалению, этого не скажешь про множество других функций.

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

Была такая машинка шифровальная, широко известная по грешным делам Кригсмарине времен WW2 (Функшлюссель Ц). Вояки выкупили коммерческую машинку, усилили ее добавочными дисками и снабдили ею свой флот, бывший, в основном, подводным. Предполагалось, что расшифровка займет три месяца, а за это время соответствующая информация устареет. Сама суть шифрования заключалась в том, что каждая буква в тексте преобразовывалась в другую по заданному ключу. И если частотный анализ позволял легко восстановить исходную букву для одной операции, то для двух сделать это было уже труднее, трех, четырех – очень сложно и так далее.

Дело в том, что в каждом языке частота букв в словах своя, и зная статистику по буквам в тексте, можно восстановить исходный текст. Чем чаще мы применяем замену букв, тем “ровнее” делается распределение по частоте букв в тексте. Тем большая выборка, время и объем вычислений понадобятся для расшифровки. Знание метода шифрования тут ничем не поможет. Только угаданный ключ. Технически все эти дела могут быть обставлены очень тонко и хитро, но мы упростим задачу, тем более, что я и сам не сильно большой специалист в тайнописи. (Дж. И. Литтлвуд приводит такую шутку: “предположим для простоты”, что означает простоту самого рассуждающего.)

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

Лст. 1-8. Библиотечка для теста и шифровальная машина.

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

Внутри цикла читается очередной символ в переменную c. Сразу же проверяем: не пора ли закругляться? Если нет, запускаем машину, то есть, входим в функцию crypt. Функция получает символ и шифрует его. Зашифрованный символ помещается в ту же переменную, откуда был взят. Затем остается только направить его в выходной поток. Вот и вся работа на этом уровне.

Внутри crypt содержится код, на основе статической переменной i, симулирующий работу колеса настоящей машины. Чтобы «колесо» действительно вращалось, мы проверям значение i, и в случае, если оно больше 3, присваиваем ему значение 0. Таким образом, мы перебираем значение ключа key по кругу, что и означает работу колеса.

Сама же функция шифрования – это просто логическая операция XOR, предложенная еще американским инженером из AT&T Джилбертом Вернамом для шифрования телеграфных кодов. Она обратима, как мы позже увидим.

Посмотрим, как работает машинка:

Работа шифровальной машины и тест функций
Рис. 1-4. Работа шифровальной машины и тест функций.

Мы создали кодированный файл wetterbericht на диске. При попытке напечатать его мы получили абракадабру, содержащую непечатаемые символы. Для бумажной машинки это недопустимо, но для компьютерного файла, — почему бы и нет? В крайнем случае, мы всегда можем напечатать шестнадцатеричный дамп этого файла.

Мы также просто можем расшифровать зашифрованный файл:

Повторное шифрование шифра
Рис. 1-5. Повторное шифрование шифра.

«Весит» наша машинка всего 904 байта.

Если взять не ключ длиной 4 байта, а случайную последовательность символов очень большой длины, например, читаемую из отдельного файла, то можно обеспечить абсолютно стойкое шифрование. Такой прием использовали дипломаты и шпионы в докомпьютерную эру. Случайный файл назывался шифроблокнотом. Если оба корреспондента имеют такой большой файл, размером в несколько гигабайт, то можно шифровать не меньшее количество текста, при условии, что каждый кусок «ключа» используется однократно. Сгенерировать такой файл можно, имея генератор белого шума (например, на основе диода, включенного определенным образом) и АЦП на 8 бит, если направить поток образующихся данных в файл. Это будет истинно случайная последовательность, а не псевдослучайная, которая обычно применяется в компьютерах.

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

Что касается нашей машинки, это всего лишь игрушка, не так ли? Читателю предлагается как-нибудь на досуге выяснить, что означает:

https://amdy.su/wp-admin/options-general.php?page=ad-inserter.php#tab-8

Лст. 1-9. Послание потомкам.

Метод кодирования и длина ключа прежние. Надо найти ключ и закодированный текст.

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

C как вывести символ

Перевод статьи «Secrets of printf» [1], статья посвящена практическому применению оператора вывода printf.

Оператор printf — это просто функция языка C, которая выполняет форматированную печать неких значений, параметров функции. «Форматированную» — означает, что перед выводом параметров на печать параметры функции преобразуются в текст по особым правилам (формату), задаваемым специальной строкой, так называемой строкой форматирования. Точно такая же функция printf есть и на языке PERL. В этой заметке сделана попытка объяснить, как работает printf, и как правильно разработать соответствующую строку форматирования для любого случая.

[1. Основные понятия]

В старые времена программисты должны были писать свои собственные подпрограммы для ввода и вывода чисел. По сути это было не очень сложным делом. Сначала надо было выделить массив текстовых символов для хранения результата, затем просто разделить число на 10, запомнить остаток, добавить 0x30 для получения ASCII кода цифры, и сохранить цифру в конец массива. Далее эту процедуру нужно было повторить, чтобы найти все десятичные цифры числа. Затем нужно было вывести массив на печать. Все просто, не правда ли?

Однако несмотря на то, что просто (для гения типа Эйнштейна), однако все еще требует применения некоторых усилий. И что мы будем делать в более сложных случаях при проверке ошибок, или при выводе отрицательных чисел? Чтобы решить эти проблемы, программисты создали специальные библиотеки с предварительно написанными функциями вывода. И это было неплохо. В конечном счете самые популярные из этих функций были канонизированы для того, чтобы составить «стандартные» библиотеки.

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

Так родился printf.

[2. Простая печать]

В самом простом случае функция printf получает один аргумент: строка символов, которая должна быть напечатана. Как ясно из названия, эта строка состоит из символов, каждый из которых будет выведен именно так, как он появляется в строке. Так, оператор printf(«xyz»); должен просто вывести сначала x, затем y, и наконец z. Это не является по-настоящему «форматированной» печатью (formatted printf), однако это базовая операция, которую может произвести printf.

2.1. «Натуральные» специальные символы

Чтобы идентифицировать начало строки, мы применили двойные («) кавычки в её начале. Чтобы идентифицировать конец строки, мы поместили двойные кавычки также и в конец строки. Но как быть, если нам нужно напечатать также и двойные кавычки? Мы не можем просто поместить двойные кавычки в печатаемую строку, потому что тогда этот символ будет ошибочно задавать маркер конца строки. Таким образом, двойные кавычки стали специальным символом. Для них уже не работает правило печатаю-то-что-вижу. Как все-таки напечатать двойные кавычки?

Различные языки программирования применяют разные способы для решения этой проблемы. Некоторые требуют, чтобы специальный символ был введен дважды. Язык C использует обратный слеш (делительная черта, \) в качестве управляющего символа (escape character), для изменения значения следующего за ним символа. Таким образом, для печати двойных кавычек нужно указать обратный слеш и за ним двойную кавычку (\»). Чтобы напечатать сам обратный слеш, то его нужно ввести дважды. Таким образом, первый обратный слеш означает «следующий символ имеет альтернативное значение», и второй обратный слеш теперь означает «напечатать обратный слеш».

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

\ escape, управляющая последовательность для следующего символа
\\ печатается обратный слеш
« обозначает начало или конец строки
печатаются двойные кавычки
начало или конец символьной константы
\’ печатается одинарная кавычка
% начало спецификации формата
\% печатается символ процента
%% также печатается символ процента

2.2. Альтернативные специальные символы

Однако еще у нас есть символы, которые печатаются без обратного слеша как обычно, но когда слева к ним добавляется обратный слеш, они становятся также спецсимволами. Например, это символ новой строки (new line, или line feed LF, код ASCII 0x0A). Чтобы напечатать букву n, нам нужно просто указать в строке n. Чтобы перевести печать на новую строку, мы должны напечатать \n, что вовлекает альтернативное значение для n (новая строка). В следующей таблице приведен список таких альтернативных спецсимволов.

\a звуковой сигнал, предупреждение (звоночек, bell)
\b backspace, вернуться на 1 символ назад (затереть символ)
\f form feed, переход на новый лист
\n newline, новая строка (linefeed, перевод строки, код ASCII 0x0D, переход печати на новую строку)
\r carriage return, возврат каретки (CR, код ASCII 0x0D, позиция печати возвращается на начало строки)
\t tab, табуляция по горизонтали
\v vertical tab, вертикальная табуляция

[3. Как задавать формат вывода числа (Format Specifications)]

Настоящая сила printf раскрывается при выводе на печать переменных. К примеру, мы можем указать спецификатор формата %d . Если такой спецификатор присутствует в строке, то мы должны предоставить число в качестве второго параметра функции printf. Функция printf сделана так, что может принимать неограниченное количество параметров, если это необходимо (printf относится к функциям с переменным количеством параметров). Пример ниже показывает, как это происходит.

В этом простом примере функция printf имеет 2 аргумента. Первый аргумент — строка «I am %d years old\n» (в ней содержится спецификатор формата %d ). Вторым аргументом является целое число age.

3.1. Список аргументов

Когда printf обрабатывает свои аргументы (список аргументов, отделенных друг от друга запятыми), он начинает печатать символы, которые находит в левом аргументе, символ за символом. Когда в этом процессе попадается символ процента (%), то printf знает, что это спецификатор формата — специальный набор символов, который задает, как надо вывести число. Следующее по порядку с списке аргументов число выводится так, как указано в спецификаторе формата. Затем процесс обработки символов (вывод их на печать) первого аргумента продолжается. Можно указать в строке 1 аргумента функции printf несколько спецификаторов формата. В этом случае 1 спецификатор будет выводить первый дополнительный аргумент, 2 спецификатор второй дополнительный аргумент и так далее, до конца строки. Вот еще один пример (указано 2 спецификатора формата и два дополнительных аргумента функции printf):

3.2 Символ процента (Percent, %)

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

%c выводит на печать одиночный символ (character)
%d выводит на печать десятичное число (представление числа с основанием 10)
%e выводит на печать числа с плавающей запятой (floating-point) в экспоненциальном формате
%f выводит на печать числа с плавающей запятой (floating-point)
%g выводит на печать числа с плавающей запятой (floating-point) в общем формате
%i выводит на печать десятичного целого числа (представление числа с основанием 10)
%o выводит на печать числа в восьмеричном формате (представление числа с основанием 8)
%s выводит на печать строки символов
%u выводит на печать целое десятичное число без знака (представление числа с основанием 10)
%x выводит на печать целого шестнадцатеричного числа (представление числа с основанием 16)
%% выводит на печать символ процента (можно использовать для этого также \%)

Самый простой вывод десятичного числа (целого и с плавающей точкой) требует указания только %d. В таблице приведены некоторые примеры аргументов printf и полученных результатов.

printf-format-specifier-d

Имейте в виду, что в случае использования %d размер получаемой строки заранее не известен. Функция printf сгенерирует строку такого размера, какой нужен.

3.3. Опция ширины формата (Width Option)

Как уже упоминалось, простой печати чисел недостаточно. Есть другие желаемые опции. Возможно, самая важная из них — опция ширины формата. Если указать спецификатор формата %5d, то будет гарантировано, что вывод числа всегда займет 5 символьных позиций (если нужно, то больше, но никак не меньше). Эта возможность очень полезна при печати таблиц, потому что и большие, и маленькие числа займут в строке одинаковое место. Не так давно вся печать была моноширинной (monospaced, все символы по точкам в ширину были одинаковы), т. е. и символ w, и символ i занимали одинаковое место в строке по ширине. Это остается общим правилом в текстовых редакторах, используемых программистами.

Чтобы напечатать десятичное число определенной (как минимум заданной, не меньше) ширины, скажем шириной в 5 пробелов, спецификатор формата должен быть %5d. В таблице приведены простые примеры использования опции ширины (пробелы для наглядности показаны нижней квадратной скобкой).

printf-width-option

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

Чтобы добиться нормального использования, поле опции ширины должно быть указано таким, чтобы удовлетворять максимальному размеру ожидаемого выводимого числа. Например, если Ваши числа могут состоять из 1, 2 или максимум 3 цифр, то формат %3d подойдет. Опция ширины будет работать неправильно, если потребуется напечатать число, которое слишком большое, чтобы уместиться в заданную ширину поля. Функция printf примет решение вывести такие числа полностью, даже если они займут место больше, чем задано в спецификаторе ширины формата. Так сделано потому, что лучше вывести правильный ответ, пусть даже некрасиво, чем напечатать урезанный (неправильный) результат, и потом гадать, где же произошла ошибка.

3.4. Заполнение лишнего места

Когда печатается маленькое по размеру число наподобие 27 в поле формата %5d, встает вопрос — чем и как заполнить 3 другие (пустые) места печати. Цифры 27 можно напечатать по-разному: вывести в первых двух позициях, в последних двух позициях, или где-то посередине. Также пустые места могут быть заполнены не просто пробелами, а звездочками (***27, или 27***, или **27*), или знаками доллара ($$$27), или символами равенства (===27), или начальными нулями (наподобие 00027).

Эти дополнительные символы часто называют символами «проверочной защиты» (check protection), потому что они предназначены помешать плохим парням изменить печатаемую сумму в долларах. Относительно просто поменять заполнение пробелами на что-то другое. Гораздо сложнее подменить символ звездочки, знак доллара и ли символ равенства.

Функция printf предоставляет заполнение пространства пробелами (слева или справа), и заполнение нулями (только слева). Если Вам нужна check protection, или центрирование, то нужно использовать какие-то другие дополнительные методы. Но даже без check protection или центрирования printf
все равно имеет впечатляющую коллекцию опций форматирования.

3.5. Опция выравнивания (Justify Option)

Вывод на печать чисел функцией printf может быть выровнена влево (left-justified, напечатана в поле слева) или вправо (right-justified, напечатано в поле справа). Наиболее естественной выглядит печать чисел выровненными вправо, с дополнением пробелами слева. Так работает спецификатор формата %5d, он означает: напечатать число по основанию 10 в поле шириной 5 символов, и цифры числа выровнять по правому краю, слева дополнив нужным количеством пробелов.

Чтобы сделать число выровненным слева, к спецификатору формата нужно добавить знак минуса (-). Чтобы напечатать число в поле шириной в 5 символов, с выравниванием по левому краю спецификатор формата будет %-5d. В таблице приведены некоторые примеры использования левого выравнивания.

printf-justify-option

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

3.6. Заполнение лидирующими нулями (Zero-Fill Option)

Чтобы печать даты выглядела красиво и качественно, обычно одиночные цифры даты и месяца дополняют слева нулем. Это и есть «лидирующий ноль». Мы можем написать May 5, 2003, или как принято в США 05/05/2003. Можно написать также дату в виде 2003.05.05. Обратите внимание, что лидирующий ноль не изменяет значение дат, а просто добавляет наглядности. Таким способом отформатированная дата хорошо выглядит в списке.

Когда используется zero-filled (заполнение лидирующими нулями), нули всегда добавляются спереди, и результат получается выровненным как по левому, так и по правому краю. В этом случае знак минуса не дает эффекта. Чтобы вывести число в 5 позиций с дополнением нулями слева применяйте спецификатор формата %05d. В таблице показаны примеры использования и полученные результаты.

printf-zero-fill-option

Короткие числа будут дополнены лидирующими нулями. Числа большого размера будут напечатаны как есть, без изменения.

3.7. Забава со знаками «плюс»

Отрицательные числа всегда будут выведены со знаком минуса (-). Положительные числа и нули обычно не печатаются со знаком, однако Вы можете это задать принудительно. Символ плюса (+) в спецификаторе формата делают такой запрос. Чтобы напечатать число со знаком в поле шириной 5 символов, спецификатор формата должен быть %+5d. В таблице показаны примеры использования и полученные результаты.

printf-fun-with-plus-signs

Имейте в виду, что 0 трактуется как положительное число. Короткие числа будут дополнены нужным количеством указанных заполнителей. Слишком большие числа будут выведены без дополнения и не урезанные.

Плюс и минус не связаны друг с другом. Они оба могут появляться в спецификаторе формата.

3.8. Невидимый знак «плюс»

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

printf-invisible-plus-sign

Помните о том, что спецификатор формата %-5d даст нам другой результат, который мы уже рассматривали ранее (он показан здесь снова для наглядности):

printf-justify-option

Имейте в виду, что знак + исчезает, но все еще занимает место слева от числа. Имейте в виду также, что мы можем скомбинировать некоторые опции в одном и том же спецификаторе формата. В этом случае мы имеем скомбинированные опции +, -, 5, или пробел, -, 5, или просто -, 5.

3.9. +, пробел и 0

Здесь приведен другой пример одновременного комбинирования некоторых опций в одном спецификаторе формата. Использование спецификаторов формата % 05d или %0 5d дадут нам следующие результаты:

printf-plus-space-and-zero1

Использование спецификаторов формата %+05d или %0+5d дадут нам следующие результаты:

printf-plus-space-and-zero2

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

3.10. Общие замечания по формату вывода

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

printf-format-specifications-summary

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

[4. Вывод на печать строк]

Опция %s позволяет нам печатать строку внутри строки. Ниже дан пример.

Флаг левого выравнивания может быть применен к строкам, однако конечно же дополнение слева нулями (zero fill), знак +, и невидимый + являются бессмысленными.

printf-strings

[5. Вывод чисел с плавающей точкой (Floating Point)]

Числа с плавающей точкой наподобие 3.1415 содержат внутри себя точку. Обычные целые числа типа 27 не имеют такой точки.

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

В обычной коммерции используются прайсы, где цены часто фигурируют как целые доллары или доллары и центы (precision составляет 0 или 2 цифры). Для цены на бензин цены упоминаются как доллары, центы, и десятая доля от цента (precision составляет 3 цифры). Ниже приведены примеры, как может быть выведено на печать число e=2.718281828.

printf-floating-point1

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

Имейте в виду, что если не указаны точка и precision для %f, то по умолчанию будет приведен формат %.6f (6 цифр после десятичной точки).

Имейте также в виду, что если указана precision 0, то десятичная точка также исчезает. Если Вы хотите её вернуть, то нужно это сделать принудительно в виде простого текста (после спецификатора формата %f).

Мы можем указать оба и ширину (width), и точность (precision) одновременно в одном спецификаторе формата. Имейте в виду, что 5.2 означает общую длину 5, с 2 цифрами после десятичной точки. Самая распространенная ошибка, когда думают, что это означает 5 цифр до точки и 2 цифры после точки, но это неправильно. Будьте внимательны.

printf-floating-point2

Можно также комбинировать precision с флагами, с которыми мы уже познакомились, чтобы указать левое выравнивание, дополнение слева нулями, применение знака +, и т. д.

printf-floating-point3

[6. Как лучше всего разрабатывать формат]

Если Вы придумываете спецификатор формата, то первый шаг, который нужно сделать — решить, что именно Вы печатаете. Если это целое число (unsigned char, short, int, long), число с плавающей точкой (float, double), строка (char []) или одиночный символ (char), то Вы должны выбрать соответствующий спецификатор для базового типа формата.

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

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

[7. Советы для тестирования]

Тест printf включает проверку появления подходящих проблем. Сам по себе алгоритм работы printf непрост для полного понимания — как будет работать вывод в разных ситуациях. Поэтому изучение тестового вывода printf даст более точную картину — что работает не так.

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

7.1. Простые случаи

Можно просто увидеть, есть ли у коротких чисел лидирующие нули. Если так, то в спецификаторе формата здесь должен быть 0. Также просто увидеть, есть ли у положительных чисел знак +. Если этот так, то + должен быть и в спецификации форматирования.

7.2. Перед, между, позади

Следующее, что нужно проверить — что печатается до выводимого числа, в промежутке, и после. К примеру, В спецификации форматирования типа x%5dz, где x стоит перед числом и z за числом. Части x и z не входят в спецификатор формата, но входят как часть в печатаемый результат. Все остальное относится к тому, что печатается «между».

Для того, чтобы определить, что же печатается за числом, посмотрите на вывод отрицательного числа чрезмерно большого размера. Любые пробелы до выведенного числа и после него покажут на пробелы до и после спецификатора формата. Например, если -2035065302 печатается как __-2035065302_ (здесь для наглядности пробелы заменены на подчеркивания), то можете быть уверенными, что строка печати была наподобие __%. _ , с двумя пробелами перед и одним пробелом после спецификатора формата. Это произошло потому, что чрезмерно большое число заняло все позиции, которые были отведены в спецификаторе формата.

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

7.3. Невидимый знак +

Сравните между собой вывод чрезмерно большого отрицательного и такого же положительного числа. Если положительное число имеет дополнительный пробел слева, то в этом месте формат задает невидимый знак +. Если слева от числа нет дополнительного пробела, но невидимый знак не задан.

7.4. Левое выравнивание

Вычтите друг из друга то, что перед, и то что позади. Посмотрите, что осталось слева. Посмотрите на вывод маленького отрицательного числа. Где Вы видите дополнительные печатаемые пробелы? Если они находятся спереди числа, то применено правое выравнивание числа. Если они находятся позади, то число выровнено при печати влево. Если пробелы есть и спереди, и сзади, значит Вы что-то делаете неправильно.

[8. Заключение]

Функция printf является мощным инструментом (в умелых руках) для вывода чисел и чего-нибудь еще, хранимого в переменных. Из-за того, что инструмент мощный и имеет много возможностей, он несколько сложен в освоении. Если попытаться использовать printf наобум, без изучения документации, то его сложность часто делает невозможным понимание принципа вывода. Однако при незначительном изучении сложность может быть развернута в простые возможности, включающие width (ширина поля вывода), precision (точность), signage (управление выводом знака), justification (выравнивание) и fill (заполнение пустых мест поля вывода). Если распознать и понять эти возможности, то printf становится удобным и надежным помощником при выводе значений на печать.

[Ссылки]

1. Secrets of “printf”, Professor Don Colton, Brigham Young University Hawaii .
2. IAR EWB ARM: форматированный вывод printf библиотеки DLIB.

Работа с символами и строками в языке программирования си

ЦЕЛЬ РАБОТЫ: познакомиться с понятиями символьной, строковой переменной, строковой константы, указателя на строки, научиться выполнять операции ввода/вывода строк, определять специфику работы со строковыми функциями.

1. Общие понятия

В данной работе рассматриваются символьные переменные, строковые переменные и константы, операции ввода/вывода строк, основные функции работы со строками.

1.1. Символьные переменные

Любой текст состоит из символов. Для хранения одного символа предназначен тип данных char. Переменную типа char можно рассматривать двояко:

как целое число, занимающее 1 байт и способное принимать значения:

от 0 до 255 (тип unsigned char);

от -128 до 127 (тип signed char);

как один текстовый символ.

Сам же тип char может оказаться как знаковым, так и беззнаковым, в зависимости от операционной системы и компилятора. Поэтому использовать тип char не рекомендуется, лучше явно указывать, будет ли он знаковым (signed) или беззнаковым (unsigned).

Как и целые числа, данные типа char можно складывать, вычитать, умножать, делить, а можно выводить на экран в виде одного символа.

Если нужно вывести числовое значение символа, также называемое ASCII-кодом, то значение символа необходимо преобразовать к типу int. Например:

using namespace std;

unsigned char c=’A’; // Константы char заключаются в одинарные кавычки

c=126; // char можно присвоить и числовое значение

В этом примере переменной с типа char присваивается значение, равное символу ‘A’ (константы типа char записываются как символы в одинарных кавычках), затем на экран выводится значение c как символа и его ASCII-код, потом переменной c присваивается значение 126, то есть символ с ASCII-кодом 126 и снова выводится на экран символ и его ASCII-код.

1.2. Строковые переменные и константы

Текстовую строку можно представить, как массив символов типа char, но в языке СИ для хранения текстовых строк был создан более удобный тип string.

Строковые переменные используются для хранения последовательности символов.

Строковые переменные должны быть объявлены перед тем, как начнется их использование. Объявление строковой переменной выглядит следующим образом: char s[14],

где s рассматривается как массив с элементами символьного типа, который может содержать до 14 элементов. Предположим, что s присваивается строковая константа:

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

Вывод в C++ в консоль символа Unicode, исходя из его code point

Либо взять какую-то библиотеку по конвертации из UTF32 в UTF-8, либо разобраться с форматом UTF-8, он простой. В двух словах: если старший бит байта в строке 0, то имеем обычный ASCII символ. Иначе количество ведущих 1 показывает число байт в кодированном представлении кодовой точки (не меньше двух единиц). Все последующие байты начинаются с 10.

Пример: 0x1f0a0 = 00011111000010100000b, если не путаю. Разбиваем по 6 бит: 011111 000010 100000, итого 3. В 3 байта не влезет: надо 1110xxxx, а тогда xxxx < 11111, поэтому придется использовать 4 байта (на самом деле на википедии фиксированные диапазоны указаны, но думаю, что они по такой же логике разбиты). В 4 байта будет так: 11110000 10011111 10000010 10100000.

Заморачиваться со строками здесь не нужно. Можно просто выводить wchar_t в std::wcout.

Правда под оффтопиком работать не будет, там wchar_t 16-битный и с UTF-8 все плохо.

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

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