CFA LogoCFA Logo Computer
Новости Статьи Магазин Драйвера Контакты
Новости
RSS канал новостей
В конце марта компания ASRock анонсировала фирменную линейку графических ускорителей Phantom Gaming. ...
Компания Huawei продолжает заниматься расширением фирменной линейки смартфонов Y Series. Очередное ...
Компания Antec в своем очередном пресс-релизе анонсировала поставки фирменной серии блоков питания ...
Компания Thermalright отчиталась о готовности нового высокопроизводительного процессорного кулера ...
Компания Biostar сообщает в официальном пресс-релизе о готовности флагманской материнской платы ...
Самое интересное
Программаторы 25 SPI FLASH Адаптеры Optibay HDD Caddy Драйвера nVidia GeForce Драйвера AMD Radeon HD Игры на DVD Сравнение видеокарт Сравнение процессоров

АРХИВ СТАТЕЙ ЖУРНАЛА «МОЙ КОМПЬЮТЕР» ЗА 2003 ГОД

Язык, на котором говорят везде

Тихон ТАРНАВСКИЙ tarnav@bigmir.net

Продолжение, начало см. в МК № 1-3, 5, 7 (224-226, 228, 230).

5. И входит, и выходит

В прошлый раз у нас все вышло — теперь пора бы и чему-нибудь войти. То бишь займемся теперь функцией scanf, отвечающей за форматированный ввод. Она, как уже говорилось, в качестве единственного аргумента тоже (как и printf) принимает строку, включающую в себя кроме обычных символов еще и «форматы» (вообще-то, «форматы» она может и не включать, но тогда непонятно, зачем она вообще нужна). Каждый из этих «форматов» тоже начинается с процента и отвечает за один из идущих далее по списку аргументов. Только теперь там (в списке) вместо значений для вывода стоят адреса, по которым надо расселять вводимые значения (имеются в виду, конечно, адреса в памяти). Вы скажете: «Какие такие адреса? Нам подавай, чтобы тутась можно было имена переменных задавать, как во всех нормальных языках, а ты про какие-то адреса талдычишь!» А помните, я вам там вначале об адресных операциях говорил? Так вот одна из них —& — и есть получение адреса переменной; таким образом вы можете тут писать просто имена переменных, предваренные змеюками, и это и будут адреса. А так хитро это тут потому, что scanf — это функция, а функции нельзя передать имя переменной: при подстановке в список аргументов имени переменной передается ее значение, которое для этого копируется в памяти. Ибо скомпилированная программа не знает имен, для нее переменная — просто ячейка в памяти, которая имеет свой адрес и хранит какое-то значение. Значение переменной нужно передавать при выводе (поэтому там и стояли просто имена), а при вводе (естественно) передается адрес.

Обычные, не «форматные» символы этой строки воспринимаются как «тута должно быть вот этого» (должен ввестись именно тот символ, который указан), причем это «вот этого» никуда не присваивается, так что это скорее так, для проформы. Исключение составляют все пробельные символы, которые, как пишут в документации, просто игнорируются. На самом же деле игнорируются-то они игнорируются, но не совсем просто: если вы, например, написавши scanf("%d%d%d",&a,&b,&c);, введете 123, то все это «сто двадцать три» уйдет в a, а для b и c уже ничего не останется, и они обнулятся. Если же вы туда же введете 1 2 3, то функция, читая в a, дочитает до пробела и остановится — соответственно, в b она прочитает 2, а в c —3.

«Форматы» понимаются как «возьми здесь нечто вот такое», а адреса в последующих аргументах — как «положи это нечто вот сюда». Если адресов больше, чем форматов в строке, лишние заполняются нулями, если меньше — никто не знает, чем это закончится. Для начала разберемся, какие могут быть форматы и соответствующее им нечто. Формат состоит из:

необязательного флага *;

необязательной максимальной ширины поля для ввода;

необязательных преобразователей l (эль) или L —long, h или H —short;

символа, обозначающего тип вводимого значения.

Флаг * (если он есть) означает, что значение, соответствующее этому формату из входного потока, надо прочитать, но присваивать никому не надо.

Максимальная ширина поля ограничивает количество символов, которые позволительно считать. То есть, если в вышеприведенном примере написать scanf("%1d%1d%1d",&a,&b,&c);, то при вводе 123 каждая буква опять-таки получит по одной цифре.

Преобразователи l (эль) и L действуют аналогично своим родичам из функций вывода — удлиняют заданный тип. Преобразователи h, H действуют только на целые типы, превращая int в short; если он в данном конкретном случае (компилятор/модель памяти) и так ему равен, то, естественно, без этого преобразователя можно обойтись.

Ну и, собственно, сами типы (вернее, типовые символы). Многие из них значат то же самое, что и их выводные аналоги, но отличия (и даже кое-что новенькое) все-таки есть. (Кстати о птичках, старайтесь, чтобы типы, указанные символами, соответствовали типам переменных, адреса которых указаны в аргументах; ибо scanf о типах (и, соответственно, размерах) аргументов ничего не знает (он ведь получает только адреса), а если вы прочтете больше, чем зарезервировали, то при печально сложившихся обстоятельствах система может вас послать на три кнопки.) Итак, вот вам буквочки и то, что scanf, увидев их, захочет прочитать:

d — знаковое десятичное целое;

u — беззнаковое десятичное целое;

o — беззнаковое восьмеричное целое;

x — беззнаковое шестнадцатеричное целое;

i — целое число, понимаемое так же, как и в тексте программы: начинающееся с ненулевой цифры — десятичное, с нуля, после которого цифры — восьмеричное, с 0x (или 0X) — шестнадцатеричное;

e, f, g — число с плавающей точкой;

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

c — символ; единственный случай, когда не пропускаются начальные пробельные символы (если хотите ввести первый непробельный символ, используйте %1s); если задана ширина поля, scanf считает, что аргумент — адрес символьного массива, в который надо натолкать указанное количество символов;

[ — чтение строки по шаблону до первого символа, не входящего в шаблон; шаблон обязан заканчиваться правой квадратной скобкой и может включать в себя в элементарном вариатне просто идущие подряд символы; в менее элементарном варианте можно писать в этой последовательности вместо одного символа два через дефис (это подразумевает все символы от такого-то до такого-то, в порядке возрастания кодов). Еще шаблон может быть предварен крышей (^), которая, как обычно, обозначает отрицание («все, кроме...»). Для того чтобы включить в шаблон сами управляющие символы (-, ^, ]), их надо написать там, где они не смогут управлять: крышу — не первой, а правую скобку или дефис — первыми (обратите внимание, это подразумевает, что из этих двух зюк только одну можно включить в шаблон явно — не смогут же они обе быть первыми). Упомянутая ранее хитрость с буквой s заключается в том, что пробел (табуляцию и т.п.) можно включить в шаблон (либо явно, либо «в промежутке»), и тогда он будет читаться в строку, а не восприниматься как разделитель. Есть еще одна, не упомянутая, но еще более хитрая хитрость: в шаблон можно включить символ \n (перевод строки) — тогда будут читаться все символы (подходящие под шаблон, конечно) не до нажатия на Ентер, а до ввода Ctrl-Z (символ конца файла); таким образом можно организовать многострочный ввод. Пример формата с шаблоном: "%[0-9a-z\t]" — вводить все цифры, латинские буквы и табуляцию.

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

Надо сказать, смешивание в форматной строке «форматов» с «неформатными» символами используют чаще всего для ввода нескольких чисел через нецифренный разделитель, например так:

При вводе же строковых данных в некоторых компиляторах всеми этими наворотами лучше не пользоваться, а писать в каждой функции scanf один «формат» — и все. Ибо иначе она может начать брыкаться. Например, написал я одному досовскому компилятору (а именно Borland C++ 3.1) вот такую вещь (просто так, для проверки):

Если я вводил на запрос scanf’а нечто вроде asd_беспорядочный_набор_букв, printf выдавал мне все как задумано (как введено); если же я пытался ввести один порядочный набор букв, а именно asdfgh, а далее что угодно — программа делала вид, что этого printf’а в ней вообще никакого нет. Человеческому мозгу не дано понять сей знаменательный феномен (если вдруг среди читателей найдется такой экстраординарный мозг, который будет способен это понять, прошу слать объяснения мылом — моя мыльница пришпилена сразу под заголовком). [Правда, позже у меня этот компилятор стал вообще выкидывать такие фортеля (наверное, ему 98-е форточки не понравились с их седьмым Досом), что я немедленно перекинулся в Линукс и в дальнейшем вспоминал СИнтаксис с помощью gcc.]

Теперь у нас уже есть по одной функции для ввода и для вывода. По поводу них надо сказать еще одно: они обе имеют тип int, то есть возвращают некое целое значение. А именно: функция scanf (и ей подобные, о которых позже) в случае удачного прочтения и поселения по заданному адресу хотя бы одного значения возвращает число расселенных значений; в случае прочтения какого-нибудь фуфла (ни одно значение не размещено в памяти) — возвращает ноль; в случае, когда нечего читать — возвращает специальную константу с именем EOF (end of file). Функция printf (и ее родственники) в случае удачного написания возвращает число написанных символов, в случае же, если «ошибочка вышла» — некое мистическое отрицательное число. «Мистическое» потому, что во многих доках о нем только то и пишут, что оно отрицательное, так что создается впечатление, что printf выбирает его среди всех отрицательных чисел методом научного тыка (в доках к некоторым компиляторам об этом числе сказано, что оно все же никакое не мистическое, а просто тот же EOF).

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

6. Возьмемся за руки, друзья...

Браться за руки, правда, будут файлы. Присоединение к текущему исходнику еще каких-либо файлов происходит при помощи одной из тех штук, которые страшно называются «директивами препроцессора» (сразу два мутных слова). На самом деле все не так уж сложно. Вместо «директива», чтобы не пользоваться английскими словечками (как обычно делают почти для всей програмерской терминологии), можно сказать простое русское слово «инструкция» (ну, может, не совсем русское, но все же более простое); а «препроцессор» — это опять-таки калька с английского: приставка pre- у них означает «перед». То есть, «директивы препроцессора» — это некие инструкции, выполняемые с программой перед процессом ее компиляции. Единственное, что есть у них у всех общее — это то, что они должны начинаться с диеза (#), идущего первым символом в строке (без всяких там даже пробелов). А та инструкция, которая нас интересует сейчас, называется include (англ. «включать в себя») и отвечает за добавление (включение) в текущий исходник других сишных исходников. Упомянутый в ней файл (вернее, его содержимое) просто подставляется перед компиляцией вместо самой этой инструкции. Она имеет два варианта. В первом имя файла заключается в угловые скобки — это приказание компилятору искать этот файл в стандартных каталогах с библиотечными файлами (задаются либо через переменные окружения, либо в настройках «среды», либо в опциях командной строки компилятора, либо в конфиг-файле). Если же имя присоединяемого файла заключить в кавычки, тогда компилятор будет искать файл в том же каталоге, в котором лежит текущий исходник. Так как мы сейчас будем присоединять библиотечный файл, то нам нужен первый вариант.

Теперь о самих библиотечных файлах. Точнее, об их именах. Еще точнее, о расширениях. В сях принятое расширение для библиотечных файлов —.h, от слова heading file (заголовочный файл, так это называлось изначально за то, что тела всех функций, входящих в такой файл, обычно уже откомпилированы, а сам файл содержит только «заголовки» — объявления этих функций). Тот файл, который нам сейчас нужен, как я уже говорил, называется stdio.h. Поэтому строчка с его присоединением будет выглядеть так:

Заметьте, директива — это не оператор, точку с запятой после нее ставить не надо.

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

(Продолжение следует)

Рекомендуем ещё прочитать:






Данную страницу никто не комментировал. Вы можете стать первым.

Ваше имя:
Ваша почта:

RSS
Комментарий:
Введите символы или вычислите пример: *
captcha
Обновить





Хостинг на серверах в Украине, США и Германии. © sector.biz.ua 2006-2015 design by Vadim Popov