- •1. Стиль 10
- •3. Проектирование и реализация 63
- •4. Интерфейсы 85
- •5. Отладка 115
- •6. Тестирование 134
- •7. Производительность 157
- •8. Переносимость 180
- •9. Нотация 203
- •Введение
- •Брайан в. Керниган
- •1.1. Имена
- •1.2. Выражения
- •Упражнение 1 -6
- •1.3. Стилевое единство и идиомы
- •1.4. Макрофункции
- •1.5. Загадочные числа
- •1.6. Комментарии
- •1.7. Стоит ли так беспокоиться?
- •Дополнительная литература
- •2.1. Поиск
- •2.2. Сортировка
- •2.3. Библиотеки
- •2.4. Быстрая сортировка на языке Java
- •2.5. "О большое"
- •2.6. Динамически расширяемые массивы
- •2.7. Списки
- •Упражнение 2-8
- •2.8. Деревья
- •Упражнение 2-15
- •2.10. Заключение
- •Дополнительная литература
- •Проектирование и реализация
- •3.1. Алгоритм цепей Маркова
- •3.2. Варианты структуры данных
- •3.3. Создание структуры данных в языке с
- •3.4. Генерация вывода
- •3.5.Java
- •Into the air. When water goes into the air it
- •3.7. Awk и Perl
- •3.8. Производительность
- •3.9. Уроки
- •Дополнительная литература
- •4. Интерфейсы
- •4.1. Значения, разделенные запятой
- •4.2. Прототип библиотеки
- •4.3. Библиотека для распространения
- •Упражнение 4-4
- •4.5 Принципы интерфейса
- •4.6. Управление ресурсами
- •4.7. Abort, Retry, Fail?
- •4.8. Пользовательские интерфейсы
- •Дополнительная литература
- •5. Отладка
- •5.1. Отладчики
- •5.2. Хорошие подсказки, простые ошибки
- •5.3, Трудные ошибки, нет зацепок
- •5.4. Последняя надежда
- •5.5. Невоспроизводимые ошибки
- •5.6. Средства отладки
- •5.7. Чужие ошибки
- •5.8. Заключение
- •Дополнительная литература
- •6. Тестирование
- •6.1. Тестируйте при написании кода
- •6.2. Систематическое тестирование
- •6.3. Автоматизация тестирования
- •6.4. Тестовые оснастки
- •6.5. Стрессовое тестирование
- •6.6. Полезные советы
- •6.7. Кто осуществляет тестирование?
- •6.8. Тестирование программы markov
- •6.9. Заключение
- •Дополнительная литература
- •7.Производительность
- •7.1. Узкое место
- •7.2. Замеры времени и профилирование
- •7.3. Стратегии ускорения
- •7.4. Настройка кода
- •7.5. Эффективное использование памяти
- •7.6. Предварительная оценка
- •7.7. Заключение
- •Дополнительная литература
- •8. Переносимость
- •8.1. Язык
- •8.2. Заголовочные файлы и библиотеки
- •8.3. Организация программы
- •8.4. Изоляция
- •8.5. Обмен данными
- •8.6. Порядок байтов
- •8.7. Переносимость и внесение усовершенствований
- •8.8. Интернационализация
- •8.9. Заключение
- •Дополнительная литература
- •9.1. Форматирование данных
- •9.2. Регулярные выражения
- •Упражнение 9-12
- •9.3. Программируемые инструменты
- •9.4. Интерпретаторы, компиляторы и виртуальные машины
- •9.5. Программы, которые пишут программы
- •9.6. Использование макросов для генерации кода
- •9.7. Компиляция "налету"
- •Дополнительная литература
- •Интерфейсы
- •Отладка
- •Тестирование
- •Производительность
- •Переносимость
Упражнение 9-12
Измените match так, чтобы в ней использовалась версия matchstar, в которой ищется крайне левое максимальное соответствие. Кроме того, следует добиться возвращения позиции символов начала и конца текста, соответствующего шаблону. Теперь создайте новую версию grep, которая вела бы себя как старая, но выполняла замену текста, соответствующего шаблону, заданным новым текстом и выводила полученные строки. Пример вызова:
% grep 'homoiousian' 'homoousian' mission.stmt
Упражнение 9-13
Измените match и grep так, чтобы они работали со строками символов Unicode формата UTF-8. Поскольку UTF-8 и Unicode являются расширением набора 7-битового ASCII, такое изменение будет совместимо с предыдущей версией. Регулярные выражения, так же как и текст, в котором происходит поиск, должны корректно работать с UTF-8. Как в этом случае должны быть реализованы классы символов?
Упражнение 9-14
Напишите программу для автоматического тестирования регулярных выражений: она должна генерировать тестовые выражения и строки, в которых будет происходить поиск. Если можете, используйте уже существующую библиотеку как прототип для ответов, — возможно, вы найдете какие-то ошибки и в ней.
9.3. Программируемые инструменты
Множество инструментов группируется вокруг языков специального назначения. Программа grep — всего лишь одна из целого семейства инструментов, которые используют регулярные выражения или другие языки для разрешения программистских проблем.
Одним из первых подобных инструментов стал командный интерпретатор, или язык управления заданиями. Достаточно быстро стало понятно, что последовательность команд можно поместить в отдельный файл, а потом запустить экземпляр интерпретатора команд, или оболочки (shell), указав этот файл в качестве источника ввода. Следующим шагом стало уже добавление параметров, условных выражений, циклов, переменных и т. п., то есть всего того, из чего в нашем представлении состоит нормальный язык программирования. Единственное отличие заключалось в том, что существовал только один тип данных — строки, а операторами в программах-оболочках являлись различные программы, осуществлявшие порой достаточно интересные вычисления. Несмотря на то что программирование под конкретные оболочки сейчас уже уходит в прошлое, уступая место работе с инструментами типа Perl в командных средах или щелканью по кнопкам в графических средах, этот "старинный" подход по-прежнему остается простым и достаточно эффективным способом создания каких-то сложных операций из простых частей.
Еще один программируемый инструмент, Awk, представляет собой небольшой специализированный язык, который предназначен для отбора и преобразования элементов входного потока; он ищет в этом потоке соответствия заданным шаблонам, а когда находит, то выполняет связанные с шаблонами действия. Как мы уже видели в главе 3, Awk автоматически читает входные файлы и разделяет каждую прочитанную строку на поля, обозначаемые от $1 до $NF, где NF есть количество полей в строке. Его поведение "по умолчанию" удобно для выполнения большого числа типовых действий, поэтому многие полезные программы пишутся на Awk в одну строчку. Так, например, программа
# split.awk: расщепляет текст на отдельные слова
{ for (i = 1; i <= NF; i++) print $i }
выводит "слова" по одному в строке. И напротив, вот программа f mt, которая заполняет каждую выводимую строку словами (до 60 символов); пустая строка означает конец параграфа:
# fmt.awk: форматирует в 60-символьные строки
/. / { for (i = 1; i <= NF; i++) addword($i) } # непустая строка
/ ^ $/ { printlineO; print "" } # пустая строка
END { printlineO }
function addword(w) {
if (length(line) + 1 + length(w) > 60)
printline()
if (length(line) == 0)
line = w
else
line = line " " w
}
function printline() {
if (length(line) > 0) {
print line
line = “”
}
}
Мы часто используем fmt для того, чтобы заново разбить на абзацы почтовые сообщения и другие короткие документы; ее же мы использовали в главе 3 для форматирования вывода программы markov.
Программируемые инструменты часто берут свое начало от малых языков программирования, созданных для более простого выражения решений проблем в какой-то узкой предметной области. Прелестным примером является инструмент Unix под названием eqn, который обрабатывает математические формулы. Язык, применяемый в нем для ввода, очень похож на обычный: например, выражение П/2 мы прочитали бы вслух как "пи на два" (pi over two), и в этом языке оно так и записывается — pi over 2. Тот же подход применяется и в ТЕХ, в нем это выражение было бы записано как \pi \over 2. Если для проблемы, которую вы пытаетесь разрешить, уже существует естественный или привычный способ записи, используйте его или, на худой конец, попробуйте его адаптировать: не пытайтесь писать с нуля.
Awk развился из программы, которая использовала регулярные выражения для выявления аномалий в записях телефонного трафика; теперь же Awk содержит в себе переменные, выражения, циклы и т. п., — и это делает его полноценным языком программирования. Perl и Tcl первоначально проектировались с целью совместить удобство и выразительность малых языков с мощью полноценных языков программирования. В результате получились действительно удобные языки программирования общего назначения, хотя, конечно, чаще всего их используют для обработки текста.
Для подобных инструментов применяют общий термин — языки скриптов (scripting languages). Такое название объясняется их происхождением от ранних командных интерпретаторов, вся "программируемость" которых ограничивалась исполнением заранее записанных "сценариев" (script) программ. Языки скриптов позволяют использовать регулярные выражения более творчески: не только для поиска соответствий шаблону — простого обнаружения соответствия, но и для определения участков текста, которые должны быть изменены. Именно это осуществляется в двух командах regsub (от regular expression substitution — замена с помощью регулярных выражений), реализованных в приводимой ниже программе на языке Tcl. Программа эта является несколько более общей формой программы из главы 4, получающей биржевые котировки; новая версия выполняет это, получая данные из URL, передаваемого ей в качестве первого аргумента. Первая замена удаляет строку http://, если она присутствует; вторая — удаляет символ / и заменяет его пробелом, разбивая, таким образом, аргумент на два поля. Команда lindex получает поля из строки (начиная с индекса 0). Текст, заключенный в квадратные скобки, выполняется как команда Tcl и заменяется результирующим текстом; последовательность $х заменяется значением переменной х.
# geturl.tcl: получает документ из URL
# ввод имеет вид [http://]abc.def.com[/whatever... ]
# если присутствует, удалить http://
regsub "http://" $argv "" argv ;
# в начале строки заменить символ / пробелом
regsub "/" $argv " " argv ;
# выполнить сетевое соединение
set so [socket [lindex $argv 0] 80] ;
set q "/[lindex $argv 1]"
puts $so "GET $q HTTP/1.0\n\n" ; # послать запрос
flush $so
# пропустить заголовок
while {[gets $so line] >= 0 && $line != ""} {} ;
# прочесть и вывести весь ответ
puts [read $so] ;
Этот скрипт, как правило, производит весьма объемистый вывод, большую часть которого составляют тэги HTML, заключенные между < и >. Perl удобен для текстовых подстановок, так что нашим следующим инструментом станет скрипт на Perl, который использует регулярные выражения и подстановки для удаления тэгов:
# unhtml.pl: delete HTML tags
while (<>) { # собирает весь ввод в одну строку
$str .= $_; # накапливая вводимые строки }
$str =~ s/<[^>]*>//g; # удалить <...>
$str =~ s/ / /g; # заменить пробелом
$str =~ s/\s+/\n/g; # сжать свободное место
print $str;
Для тех, кто не знаком с Perl, этот код будет загадкой. Конструкция
$str =~ s/regexp/repl/g
в строке str подставляет строку repl вместо текста, соответствующего регулярному выражению гедехр (берется крайне левое максимальное соответствие); завершающий символ д (от "global") означает, что действия надо произвести глобально, для всех найденных соответствий, а не только для первого. Последовательность метасимволов \s является сокращенным обозначением символа пустого места (пробел, знак табуляции, символ перевода строки и т. п.); \n означает перевод строки. Строка " " — это символ HTML, как и те, о которых мы упоминали в главе 2, он означает non-breakable space — неразрывный пробел.
Собрав все написанное воедино, мы получим совершенно идиотский, но функционирующий web-браузер, реализованный как скрипт командного интерпретатора, состоящий из одной строки:
# web: получает web-страницу и форматирует ее текст,
# игнорируя HTML
geturl.tcl $1 | unhtml.pl | fmt.awk
Такой вызов получит web-страницу, отбросит все управление и форматирование и отформатирует текст по своим собственным правилам. Получился быстрый способ достать страницу текста из web.
Отметьте, что мы неспроста использовали сразу несколько языков (Tel, Perl, Awk) и в каждом из них — регулярные выражения. Собственно говоря, мощь различных нотаций и состоит как раз в подборе наилучшего варианта для каждой проблемы. Tel особенно хорош для получения текста из сети, Perl и Awk прекрасно редактируют и форматируют текст, а регулярные выражения — отличный способ определения фрагментов текста, которые должны быть найдены и изменены. Объединение всех этих языков получается гораздо более мощным, чем любой из них в отдельности. Целесообразно разбить задачу на части, если можно выиграть за счет составления правильной нотации в каждой из них.
