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, 24 (224-226, 228, 230, 232, 234, 237, 239, 241, 243, 245, 247)

Работа над ошибками

Для начала должен принести свои извинения всем читателям. Дело в том, что в предыдущих своих статьях я допустил несколько неточностей, которые я считаю своим долгом сейчас исправить. Если быть точным, таких неточностей было две. Начну со второй как с более серьезной. В статье в №18 (241) в примере был вот такой кусочек:

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

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

Второе (по важности, и первое по хронологии) упущение я допустил в статье в №9 (232). Когда шла речь о форматах в функциях ввода, а именно о шаблонах, у меня было написано: «Для того чтобы включить в шаблон сами управляющие символы (-, ^, ]), их надо написать там, где они не смогут управлять: крышу — не первой, а правую скобку или дефис — первыми (обратите внимание: это подразумевает, что из этих двоих только один знак можно включить в шаблон явно — не смогут же они оба быть первыми)». Уже когда статья была напечатана, мне пришел в голову один вариант, позволяющий их включить вместе (ни в одной из виденных мною док, кстати, не упомянутый). Проверил — работает. Смысл в чем... Для того чтобы дефис выполнял свое функциональное предназначение (обозначал «промежуток»), он должен стоять промеж двух символов, между которыми этот промежуток можно образовать; то есть левый из них должен быть меньше, а правый, соответственно, больше (конечно, не по виду, а по коду). Таким образом, если дефис стоит либо между двумя «неправильными» символами, либо в конце шаблона, он тоже не будет управлять. Это означает еще, что нельзя задать промежуток «откуда-то до правой квадратной скобки», так как эту самую скобку компилятор примет за конец шаблона, а дефис — за простой, не управляющий символ. Ну вот, теперь все расставлено на свои места — идем дальше.

19. Эти строки я посвящаю...

А дальше пойдет речь опять о массивах и об указателях, но уже в новой ипостаси. Я уже несколько раз говорил о том, что массивы в Сях очень тесно связаны с указателями (можно даже сказать, что массивы и указатели — это одно и то же, только записанное по-разному), а также о том, что не менее тесно с ними обоими связаны строки (по сути, строка — это тоже ни что иное как массив (или указатель) символов). Так вот, этой тройственной связкой теперь и займемся. Для начала поговорим о строках в «массивной» реализации, а потом через связь массивов с указателями плавно перейдем и к указуемым строкам. Хотя, чего о них говорить — давайте лучше их «примерим».

Примерчик будет вводить строки с клавиатуры, считать количество слов и находить самое длинное слово. Если вам лениво вводить много разных слов с клавиатуры, то напомню, что на самом деле клавиатура — это всего лишь умолчательный вариант стандартного ввода, а этот стандартный ввод, как я уже говорил, можно перенаправлять. Вы можете подать вашей программе на стандартный ввод какой-нибудь текстовый файл, набрав в командной строке имя-вашей-программы <имя-какого-нибудь-файла, то есть указав ей этот файл знаком <. Кроме того, вы можете подставить ей в качестве стандартного ввода стандартный вывод любой другой программы, при помощи знака |: имя-другой-программы | имя-вашей-программы. Точно так же вы можете поступать и со стандартным выводом вашей программы — если вам неудобно, что он течет в монитор (например, когда он там не помещается), вы можете либо направить вывод в файл (имя-вашей-программы >имя-файла — тогда, если такой файл уже есть, он перезапишется поверх, или имя-вашей-программы >>имя-файла — тогда вывод допишется в конец существующего файла), только лучше не делайте так с программами, которые у вас чего-нибудь спрашивают, а то и за вопросами придется в этот файл лазить. Либо же, опять-таки, можно передать его какой-нибудь другой программе на стандартный ввод (к примеру, чтобы начало этого вывода не убегало выше монитора, можете передать его программке more, которая будет каждый раз держать его за хвост, пока вы на очередной кусочек не налюбуетесь и не нажмете ей на кнопку имя-вашей-программы |more (в Линуксе удобнее вместо more использовать less). Ну и, конечно, вы можете оба эти варианта комбинировать, например, писать: имя-вашей-программы <отсюда-читать >а-сюда-писать.

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

Сейчас мы определим проверку символа на то, а не буква ли он. Тут можно бы вспомнить уже когда-то упомянутый мною файл ctype.h, и, в частности, функцию (или макрос) isalpha(). Но в вин/досовских компиляторах эта функция определяет исключительно английские буквы. В Линуксе ее результат хотя и зависит от текущей локали (локаль — это, грубо говоря, настройки, говорящие системе, на каком языке с вами разговаривать), но по крайней мере в локали ru_RU я взаимности от isalpha() так и не добился. Посему пишем сами. А дабы не особо пока напрягаться с кодировками, согласимся, не мудрствуя лукаво, на всю нижнюю (не-ascii) половину таблицы, тем паче что в ней ничего кроме русских букв с клавиатуры и не введешь. Правда, если вы дадите на ввод, к примеру, файл с псевдографикой, то ее программа тоже примет за самые настоящие буквы. Но тут уж придется пока смириться с издержками производства, или же писать отдельную версию под каждую систему (а в Линуксе — и под каждую русскую локаль), ибо заводиться с кодировками — это для нас пока слишком сложно.

Последнее условие ((c) & 0x80) означает, что в c установлен (равен единице) старший бит, то есть c лежит в нижней половине таблицы. В данном конкретном случае это равносильно условию (c)>=0x80, но в других случаях (с «нестаршим» битом) «больше-меньший» вариант запишется уже через два условия (как здесь с буквами).

Здесь мы уже сделали несколько предположений, а именно: что вводимые строки будут не длиннее двухсот символов, что длины слов в этих строках не будут превышать сорока, и что всего этих слов в тексте будет не больше, чем 65535. В серьезной программе так, конечно, делать нельзя, и со временем мы будем во всех таких «узких местах» вводить дополнительные проверки.

Сюда будем складывать (в смысле, суммировать) длины всех слов в тексте, чтобы потом, поделив на количество слов, получить «статистику» — среднюю длину слова в тексте. Тип double для хранения целочисленных значений выбран неслучайно: во-первых, в double помещается большее целое число, чем даже в unsigned long. А во-вторых, при переполнении любого целого типа «сбросится» старшая цифра (потому как не влезет); дробный же тип в таком случае будет хранить все старшие разряды и терять «точность», то есть младшие цифры, которые нам для вычисления средней длины слова совсем не важны.

Функция (gets()) принимает указатель на строку и читает в нее одну строчку со stdin (стандартного ввода). Когда мы пишем просто имя массива, без скобочек с индексом, подставляется адрес этого массива, что здесь и требуется. Возвращает функция gets() целое значение: ненулевое, если что-то ввелось, и нулевое, если случилась какая-нибудь ошибка или конец файла. Если пользователь будет вводить текст с клавиатуры, то конец «файла» он может устроить, нажав на соответственную «красную кнопку», о чем мы его заранее и предупреждаем. Вообще-то функцией gets() лучше не пользоваться, так как в ней есть один очень существенный «прокол»: она не делает проверку на переполнение массива, в который читается строка; а в серьезных программах ошибка переполнения буфера — любимая лазейка для хакеров. В линуксовой доке к этой функции написано (и правильно написано) «НИКОГДА не используйте gets()» (надо заметить, в доках к виндовым/досовским компиляторам на этой опасности вообще не акцентируется внимание). Но так как эта программа чисто демонстративная, то в ней я позволил себе такую вольность, заодно обратив ваше внимание на этот минус. В дальнейшем мы будем пользоваться более цивилизованными методами, да и в целом не будем пренебрегать никакими проверками.

Пока «не-буква», идем дальше. Условие str[_] (...не равно нулю) нужно для того, чтобы не проскочить конец строки (который, как вы помните, в Сях обозначается символом с кодом 0), если он вдруг появится раньше, чем буква. Заметьте, тут нельзя было написать isletter(str[_++]), потому как isletter() — макрос, и после того как он развернется этот аргумент вместе с инкрементом будет там стоять аж пять раз, и сам инкремент, таким образом, может выполниться от одного до тех же пяти раз, в зависимости от истинности входящих в макрос условий (если вы помните, у нас уже была подобная ситуация, только там внутри макроса нельзя было писать getchar()).

Все, буквы закончились — значит, теперь у нас есть слово. Соответственно, увеличиваем счетчик слов и суммарную длину всех слов. Насчет (double)len: сколько я ни говорил про автоматическое приведение типов, но вот такое приведение (от целого типа к дробному) многие компиляторы делать не хотят, потому как целые и дробные числа хранятся в памяти совсем по-разному, а если сделать явное приведение, тогда компилятор согласится преобразовывать эти формы хранения.

strncpy() — это как раз и есть та функция для работы со строками, которой я обещал воспользоваться. Она копирует заданное количество символов из начала одной заданной строки в другую заданную строку. Первый ее аргумент — адрес строки-преемника, второй — адрес строки-источника, а третий — количество нужных символов. Но нам надо было скопировать символы не из начала строки, а начиная с len символов тому назад. Так как мы пока работаем со строкой как с массивом, то мы для этого воспользовались адресной операцией — когда мы вернемся к теме указателей, там это можно будет записать по-другому.

Так как strncpy() только копирует заданное количество символов и ничего не добавляет от себя, теперь в ней лежит только нужное слово, а должно лежать и кое-что еще — значит, нам надо добавить к полученной строке завершающий ноль.

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

Вот так это все работает. На мой взгляд, то, что строка не реализована как отдельный тип, а является массивом символов, очень удобно в работе. Тем более, что с этими массивами можно работать еще и как с указателями (мы, кстати, в этом примере уже так с ними работали: ведь примененные нами функции —gets() и strncpy() — принимают в качестве аргументов именно указатели). А те действия со строками, которые в других языках, в которых строка — тип, реализованы как операции, в Сях тоже никуда не делись и представлены в виде библиотечных функций, довольно солидный набор которых лежит в теперь уже знакомом нам string.h. Единственный небольшой минус такой реализации — чуть менее красивый синтаксис «чисто функционального» варианта работы со строками — полностью упразднен в плюсах, за счет введения классов и перегрузки операций: там вы можете задать любому символу операции какое-нибудь действие на свой вкус — например, назначить операции +, примененной к строкам, соединение (которое в книжках по программированию для большей корявости называют «конкатенацией») этих строк. Но пока все-таки вернемся к чистым Сям.

Сейчас я хочу вернуться еще к одной из моих предыдущих статей, а именно к статье в №14 (237). Там в примере на оператор switch был вот такой кусочек:

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

Вместо того, чтобы писать так:

Правда, все вышесказанное (насчет gcc) относится к gcc 2.95, именно на нем я проверял эти программы. Возможно, в gcc 3.x ситуация изменилась; сейчас у меня нет возможности это проверить. Но даже если это так, gcc 2.x все еще довольно-таки распространен, а кроме того никто ведь не гарантирует, что точно так же себя не поведет еще какой-нибудь, совсем другой компилятор.

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

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

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






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

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

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





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