CFA LogoCFA Logo Computer
Загрузка поиска
Новости Компьютеры Прайс-лист [Новое] Прайс-лист [Б/У] Для ноутбуков Конфигуратор ПК Заказ, Оплата, Доставка Сервис объявления Драйвера Статьи Как нас найти Контакты
Новости
RSS канал новостей
Список материнских плат компании Biostar пополнился свежими моделями под поколения процессоров Intel ...
Похоже, что компания Gionee в эти дни очень сильно занята. Только недавно мы сообщали об анонсе ...
Компания Enermax в своем коротеньком пресс-релизе рассказала общественности о старте серии недорогих ...
SteelSeries представляет новую игровую клавиатуру APEX 150, которая дает игрокам высочайшую надежность ...
Пока в Сети живо обсуждают информацию о возможном выпуске компанией NVIDIA графического ускорителя ...
Самое интересное
Программаторы 25 SPI FLASH Адаптеры Optibay HDD Caddy Драйвера nVidia GeForce Драйвера AMD Radeon HD Игры на DVD Сравнение видеокарт Сравнение процессоров

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

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

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

Продолжение, начало см. в МК 1-3, 5, 7, 9, 11, 14, 16, 18, 20, 22 (224-226, 228, 230, 232, 234, 237, 239, 241, 243, 245)

17. По указанию свыше...

Сегодня мы продолжаем начатый в прошлый раз разговор об указателях. Закрепим теорию практикой. Пока мы говорили только об одном применении указателей — при написании функции, которая должна «вернуть» несколько значений. Давайте теперь напишем программку с такой функцией. Пусть эта функция занимается вводом с клавиатуры числа в заданной системе счисления. В программировании вообще часто приходится иметь дело с различными системами счисления, в основном с тремя — двоичной, восьмеричной и шестнадцатеричной; нередка и необходимость перевода из них чисел в привычную большинству простых смертных десятичную, и обратно. У некоторых «закоренелых» компьютерщиков бывают иногда и более странные привычки. Вспомните анекдот: «Чем отличается обычный человек от программиста? — Тем, что обычный человек думает, что в килобайте 1000 байт, а программист уверен, что в километре 1024 метра». Так вот, у меня был один знакомый системщик, который, встретив в повседневной жизни два числа, которые надо было сложить или перемножить, переводил их в уме в шестнадцатеричную систему счисления, там производил все необходимые действия, а затем перебрасывал результат обратно.

Ладно, шутки в сторону. Итак, давайте таки напишем эту функцию-переводчик — может, и в жизни когда пригодится. Но уж писать так писать: сделаем-ка эту функцию универсальной, способной понимать числа не только в трех упомянутых, но и вообще в любых системах. Откровенно говоря, если нужен ввод только в этих трех системах, то тогда можно обойтись и стандартной функцией scanf, но ведь наша основная задача сейчас — написать функцию с указателем в качестве аргумента. Цифры будем вводить по аналогии с принятой в шестнадцатеричной системе записью: первые десять — цифрами, а дальше буквами. Таким образом, основание системы счисления у нас ограничится числом 36 — по количеству возможных «цифр». Если кому покажется мало, такие энтузиасты могут в качестве домашнего задания переписать порожденную нами функцию так, чтобы она большие и махонькие буковки отличала друг от друга — получите количество цифр 62 и сможете запрограммировать даже шестидесятеричную систему, которая, если верить преданию, была в ходу у древних египтян.

Вы можете спросить: «А где же упомянутая необходимость возвращать несколько значений? Налицо только одно значение — вводимое число. В чем же дело?» А дело в том, что любая уважающая себя функция, что-нибудь откуда-нибудь вводящая, просто обязана уметь вопить благим матом в случае ошибки ввода. И если она при этом возвращает всего одно значение, то потом нет никакой возможности определить, что лежит в этом одном значении — благой мат или добросовестно введенное число. Ведь какое бы мы ни задали заранее значение этого самого благого мата, нет никакой гарантии, что пользователю не взбредет в голову ввести именно это заранее заданное значение. В данном конкретном случае можно, конечно, предположить, что ноль никто ниоткуда переводить не додумается, но вообразите себе в таком случае сообщение об ошибке: «Возможно, у вас там чего-то не ввелось, а возможно, вы просто ввели ноль. Если так, то непонятно, зачем вы ввели ноль. Вы действительно считаете, что ноль есть смысл куда-то переводить?» Думаю, что такую философски настроенную программу не всякий пользователь оценит. Поэтому давайте пусть все будет по правилам — функция будет возвращать нечто, сигнализирующее, ввелось ли там что-то вообще, а в виде аргумента ей дадим указатель, по которому она расселит введенное значение.

А заодно, для полного боекомплекта, давайте напишем и функцию, выводящую заданное число в заданной системе счисления. Логичнее было бы, конечно, не писать свой собственный ввод-вывод, а сделать функции, которые работали бы со строками — преобразовывали заданное число в строку, представляющую запись этого числа в заданной системе счисления, и обратно. Но со строками мы пока работать не умеем (когда научимся, можно будет при желании легко эти функции переделать), так что будем обходиться вводом-выводом. Правда, этот ввод-вывод мы в сегодняшних функциях реализуем не через уже знакомые нам scanf/printf, а по-другому — во-первых, потому, что этими функциями вводить было бы логичнее строки, а со строками мы, опять-таки, работать пока не умеем; во-вторых, потому, что ввод-вывод у нас будет посимвольный, а символы удобнее вводить-выводить другими функциями, которые я вам сейчас и представлю; ну и в-третьих, просто для разнообразия.

Еще одна оговорка: в функции ввода у нас не будет проверки переполнения, так как Си вообще не предоставляет возможности такой проверки: прибавив, например, единицу к 0xffffffffL (самое большое число, которое помещается в тип unsigned long), мы получим в результате ноль — все, что вылезет за положенные четыре байта, просто сбросится. Единственная возможность реагировать на переполнение в сишной программе — при помощи ассемблерной вставки с переходом по переполнению jo или jno, но мы пока не будем лезть в дебри сисемблера (так в шутку называют написание программ на смеси Си и ассемблера). Так что мы просто скажем пользователю, каков максимум, а если он этот максимум превысит, то сам же и виноват, но об этом мы ему лучше тоже скажем. И вообще, не стоит забывать правило программиста: «Не думай, что пользователь не глупее тебя — пользователи бывают разные». То есть, защищать от дурака все, что только можно.

Новые функции, с которыми я вас обещал познакомить, находятся в уже знакомом нам заголовочном файле stdio.h и называются так: int getchar(void) int putchar(int) Чем они занимаются, можно догадаться из названий: getchar переводится как «брать символ», а putchar — как «класть символ». Работают они со стандартными потоками ввода-вывода: getchar берет из stdin (а если брать оттуда нечего — ждет, пока туда что-нибудь положат), putchar кладет в stdout. При этом getchar, если что-то взял, возвращает взятое, а если ничего взять не получилось, а получилась ошибка — возвращает символ конца файла (EOF). Этот же конец файла можно ввести и вручную (и посмотреть, как программа реагирует на ошибку ввода): в Линуксе при помощи Ctrl+D, в ДОСе —Ctrl+Z или F6. Ну а putchar что положил, то и возвращает; если вдруг случится какая-то ошибка — тоже возвращает EOF.

Обратите внимание, что тут всюду используется тип int, хотя символ — это char. В возвращаемых значениях это сделано для того, чтобы помимо символов поместился еще и EOF (который, напомню, обычно равен –1); а в аргументе putchar'а — просто для совместимости (работает она все равно только с младшим байтом).

Ну вот и познакомились. А теперь — программа:

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

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

К слову, бытует мнение, что указатели сложны для понимания еще и потому, что обозначаются той же звездочкой, что и умножение. Далась, мол, Ричи эта звездочка — что, других значков на клавиатуре мало? Даже анекдоты ходят по этому поводу. Например: «Программистские гадания: определить, что делает си-программа, по расположению звезд в исходнике». Или: «Программизм — это душевная болезнь. И если вы с первого взгляда понимаете, что значит вот это —*a*=*b**c или ++x+++=++y++, — вы неизлечимы!»

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

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

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

18. Функция по определению и функция по объявлению.

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

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

Возникает вопрос, на кой тогда вообще этот цирк, если надо «еще и...». Давайте для примера заглянем в любой заголовочный файл (например, в уже столь нам привычный stdio.h) — там мы увидим только объявления функций. «Как же так?» — спросите вы. А так же так, что их определения уже откомпилированы и лежат отдельно, в так называемых объектных файлах, которые линкуются (подключаются) к программе при ее компиляции.

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

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

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

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

Кстати, именно поэтому я во всех примерах выписывал определения функций перед функцией main(). Впрочем, не только поэтому. Еще дело в том, что, как мне кажется, при разборе примеров логичнее сначала выяснить, как все функции работают, а потом уже их пробовать на практике. В общем, объяснения по этому поводу даны, отныне же я оставляю за собой право описывать функции как в начале, так и в конце — как будет удобнее. А если у вас вдруг что-то из последующих примеров не будет компилироваться — вспомните мои сегодняшние «рассуждения» и «доработайте» исходник.

На сем с вами прощаюсь; в следующий раз мы продолжим разговор об указателях.

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

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






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

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

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





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