![](/user_photo/_userpic.png)
книги / Практикум по программированию на языке Си
..pdf#include <string.h> #include <stdlib.h> #define SIZE 66 char * readLine()
{
int c, i=0, arLen=SIZE; static char * point = NULL; if (point == NULL)
{point = (char *)malloc(arLen); if (point == NULL)
{puts("malloc() error in readLine()"); return NULL;
}
}
while(1)
{c=getchar();
if (i >= arLen-1)
{point = (char *)realloc(point,arLen+=SIZE); if (point == NULL)
{puts("realloc() error in readLine()");
return NULL;
}
}
if (c == '\n' || c == EOF) { point[i] = '\0';
if (c == EOF && strlen(point) == 0)
{free(point); return NULL;
}
if (strlen(point) == 0) continue; return point;
}
else
point[i++] = c;
}
}
Функция readLine() написана на основе программы 09_07.c. Основные отличия состоят в следующем.
Указатель point, адресующий динамически выделяемый символьный массив, определен как статический. Проверка его отличия
381
от NULL исключает повторное выделение памяти для следующей строки, формируемой в функции.
Цикл с заголовком while(1) для формирования строки определен как бесконечный. Но после каждого прочтения очередного символа (char c), его значение анализируется. Если это "обыкновенный " символ, – его записывают в очередной элемент массива (point[i++]=c). Если достигнут конец файла (c==EOF) и в строке нет символов (strlen(point)==0), то обработка файла закончена - освобождается динамическая память и возвращается значение NULL. Если достигнут конец строки (c=='\n'), то выполняется анализ ее длины. Если строка пуста – переходим к формированию следующей строки, не выходя из функции (оператор continue;). В случае непустой строки функция возвращает указатель на нее.
/* 09_19.c - обработка строк входного потока */ #include <string.h>
#include <stdio.h> #include "readLine.c"
int main(int k, char * term[])
{
char * str; if (k <= 1)
{puts("No arguments!"); return 0;
}
while((str = readLine()) != NULL)
{ if (strstr(str,term[1]) != NULL) puts(str);
}
free(str); return 0;
}
Пусть программа откомпилирована и создан ее исполнимый модуль в файле test.exe. Для поиска в тексте программы 09_19.с всех строк, содержащих слово "if", можно ввести следующую командную строку запуска программы:
C:\practic\progs\09>test<09_19.c if<ENTER>
382
Результаты выполнения программы:
if (k <= 1)
{if (strstr(str,term[1]) != NULL)
Восновной программе анализируется наличие аргументов в командной строке. Если аргумент присутствует, то в заголовке цикла указатель char * str получает значение, возвращаемое функцией
readLine(). В теле цикла библиотечная функция strstr() сравнивает строку, адресованную указателем str, со строкой, адресованной параметром term[1] функции main(). Если термин из строки, адресованной term[1], обнаружен в строке, сформированной функцией readLine(), то эта строка печатается функцией puts().
Обратите внимание, что электронная версия программы 09_19.с включает в виде комментария результаты исполнения, приведенные в тексте книги. Поэтому обработка текста из файла 09_19.с будет включать дополнительные строки, конечно, содержащие слово "if".
ЗАДАЧА 09-20. Составьте программу для проверки наличия в строке последовательности символов, соответствующей регулярному выражению, заданному в командной строке программы.
Читатель может быть не знаком с языками регулярных выражений, хотя, возможно, использует их при работе с компьютером. Например, при поиске файлов с не полностью заданным именем используется именно регулярное выражение.
Регулярные выражения – это шаблоны, позволяющие сокращенно описывать множества символьных последовательностей. Входящие в регулярные выражения символы изображают либо сами себя, либо являются метасимволами, обозначающими повторение или группировку других символов. Возможны регулярные выражения, построенные на разных наборах метасимволов и соглашений.
Керниган Б. и Пайк Р. [4] представляют набор программ для обработки регулярных выражений, включающих только следующие четыре метасимвола:
. (точка) – любой (но только один символ);
^ – начало строки (например: ^Z соответствует любой строке, в начале которой стоит символ Z);
383
$ – конец строки (например: if$ соответствует любой строке, в конце которой находится if);
* – нуль или несколько повторений предыдущего символа или точки.
Символы ^ и $ внутри регулярного выражения не считаются метасимволами.
Четыре приведенных метасимвола и соглашение о том, что символы ^, $ считаются метасимволами, соответственно, в начале и в конце шаблона, определяют несложный язык регулярных выражений. Вот несколько примеров его применения:
^z – соответствует любой строке, в начале которой есть символ z, например "z00";
do$ – соответствует любой строке, в конце которой находятся символы "do", например "sport do";
a.c – соответствует любой строке из трех символов, "крайними" из которых служат 'a' и 'c', например "abc", "axc";
^g8$ – соответствует только одной строке "g8";
^.$ – соответствует строке из одного (любого) символа, например
"#" или "d";
^*if – соответствует любой строке, первыми значащими (отличными от пробела) символами которой служат "if", например "if(a>0)"
или " if";
ap*… – соответствует любой строке не менее чем из четырех символов, в начале которой размещена буква 'a', например "applet", "argun"," aport", "around";
ww* – соответствует любой строке из букв 'w', например "w", "www";
^$ – соответствует пустой строке.
Используем для решения нашей задачи следующие функции из книги Б.Кернигана и Р.Пайка [4, с. 319–321].
/* KPmatch.c – сравнения строки с шаблоном */ #include <stdio.h>
int KPmatch(char * regexp, char * text)
{
if (regexp[0] == '^')
return matchhere(regexp+1, text); do {
if (matchhere(regexp, text)) return 1;
384
} while(*text++ != '\0'); return 0;
}
Функция KPmatch() ищет в строке, адресуемой параметромуказателем text, последовательность символов, соответствующих регулярному выражению ("шаблону") из строки, адресованной параметром regexp. В теле функции различаются две возможности. Если первый символ регулярного выражения равен '^', то необходимо сравнивать последующие символы шаблона с началом строки (текста). Для этого вызывается функция matchhere(), в которую передается регулярное выражение без первого символа и полный текст анализируемой строки. Если регулярное выражение не начинается с символа '^', то оно последовательно сравнивается сначала со всей строкой, затем со строкой без первого символа и т.д. Для сравнения используется функция matchhere(). Если в функции matchhere() обнаружится соответствие начала строки регулярному выражению, то она вернет значение 1, и исполнение цикла в теле функции KPmatch() прерывается оператором return 1. В противном случае цикл продолжается до конца анализируемой строки. Естественное завершение цикла выполняется, когда в строке не будет обнаружено подходящей последовательности символов. В этом случае выполняется оператор return 0.
Рекурсивная функция matchhere() анализирует начало текста, адресованного ее параметром text, на его соответствие регулярному выражению, адресованному параметром regexp:
// matchhere.c – сравнение начала строки с шаблоном
#include <stdio.h>
int matchhere(char * regexp, char * text)
{
if (regexp[0] == '\0') return 1;
if (regexp[1] == '*')
return matchstar(regexp[0],regexp+2, text); if (regexp[0] == '$' && regexp[1] == '\0')
return *text == '\0'; if (*text != '\0' &&
(regexp[0] == '.' || regexp[0] == *text))
385
return matchhere(regexp+1, text+1); return 0;
}
Если первый символ текста соответствует требованиям первого символа регулярного выражения, то функция matchhere() рекурсивно вызывается для анализа следующей пары символов текста и шаблона:
matchhere(regexp+1,text+1);
Последовательность рекурсивных вызовов может завершиться следующими случаями. Если первый символ регулярного выражения равен '\0', то его анализ закончен и найдена соответствующая ему последовательность символов в исследуемой строке. Выполняется оператор return 1;. Если символ шаблона есть '$', то проверяется наличие '\0' в следующей позиции шаблона. Если это не так, то символ '$' обнаружен не в конце регулярного выражения и не является метасимволом. В противном случае выражение *text=='\0' позволяет оценить, находятся ли допустимые шаблоном символы в конце строки. Если это так, – возвращается значение 1, иначе – 0.
Рекурсивное обращение к matchhere() выполняется, если не достигнут конец строки (*text!=0) и либо символы совпали regexp[0]==*text, либо шаблон допускает любой символ, т.е. значение regexp[0] есть '.' (точка).
Если первый символ шаблона отличен от '\0', а второй есть '*', то вызывается следующая функция анализа повторений в анализируемой последовательности символа шаблона:
/* matchstar.c - обработка шаблона с символом '*' */ #include <stdio.h>
int matchstar(int c, char * regexp, char * text)
{
do
{ if (matchhere(regexp, text)) return 1;
}
while (*text != '\0' && (*text++ ==c || c =='.')); return 0;
}
386
При вызове этой функции ей передаются: повторяемый символ; "хвост" регулярного выражения, следующий за '*', и анализируемая строка. Первое действие в теле функции – обращение к функции matchhere(), которой передается "хвост" регулярного выражения и анализируемая строка. Если функция matchhere() обнаруживает соответствие начала строки шаблону, то оператор return 1; завершает выполнение функции matchstar(). В противном случае цикл продолжается до тех пор, пока очередные символы строки соответсвуют шаблону (*text++==c||c=='.') и не достигнут конец строки (*text!=0). В каждой итерации выполняется "проверка" следующей части строки функцией matchhere(). Естественное завершение цикла будет при несоответствии строки шабло-
ну (return 0;).
На основе приведенных функций решение нашей задачи обеспечит программа:
/* 09_20.c - поиск в строке по шаблону */ #include <string.h>
#include <stdio.h>
int matchhere(char * regexp, char * text); #include "matchstar.c"
#include "matchhere.c" #include "KPmatch.c"
int main(int k, char * reg[])
{
char text[99]; if (k <= 1)
{puts("No arguments!"); return 0;
}
puts("Print the string:"); while(strlen(gets(text)) != 0)
{if (KPmatch(reg[1],text)) puts("\tThe string is valid!");
else
puts("\tThe string is invalid!");
}
return 0;
}
Функции matchhere() и matchstar() "перекрестно" вызывают друг друга, поэтому приходится перед одной из них помещать
387
прототип другой. Так в тексте появился прототип функции matchhere().
Командная строка запуска программы:
C:\practic\progs\09>test if<ENTER>
Результаты выполнения программы:
Print the string:
if (d > 0)<ENTER>
The string is valid! i f f i<ENTER>
The string is invalid!
<ENTER>
Командная строка запуска программы:
C:\ practic\progs\09>test ^if<ENTER>
Результаты выполнения программы:
Print the string: ww if<ENTER>
The string is invalid! if (d<0.0)<ENTER>
The string is invalid! if(d<0)<ENTER>
The string is valid!
<ENTER>
Программа проверяет наличие аргумента в командной строке. Если он присутствует (т.е. k>1), – выполняется цикл, в заголовке которого из входного потока функцией gets() считывается строка. Если ее длина не равна 0, выполняется ее анализ функцией Kpmatch(). Если в строке будет обнаружена последовательность, соответствующая регулярному выражению, заданному в командной строке (reg[1]), то печатается фраза: "The string is valid". В противном случае сообщается о несовпадении: "The string is invalid!". Окончание работы – ввод с клавиатуры пустой строки, т.е. нажатие ENTER.
388
9.5. Массивы указателей на строки
ЗАДАЧА 09-21. Напишите функцию, позволяющую определить, представляет ли строка одно из служебных слов языка Си. В основной программе, используя функцию, проверяйте вводимые с клавиатуры слова. Окончание работы программы – ввод пустой строки.
В теле создаваемой функции определим массив kWord[] указателей на строки, каждая из которых содержит одно служебное слово. Параметр функции – указатель на проверяемую строку.
Текст программы:
/*09_21.c – сравнение строки со служебными словами языка Си */
#include <string.h> |
|
|
|
#include <stdio.h> |
|
|
|
int wordCheck(const char * word) |
|
||
{ |
|
|
"case", |
char * kWord[] ={"auto", "break", |
|||
"char", |
"const", |
"continue", |
"default", "do", |
"double", |
"else", |
"enum", |
"extern", |
"float", |
"for", |
"goto", |
"if", |
"int", |
"long", |
"register", |
"return", |
"short", |
"signed", "sizeof", |
"static", |
|
"struct", |
"switch", |
"typedef", |
"union", |
"unsigned", |
"void", |
"volatile", |
"while"}; |
int i; |
< sizeof(kWord)/sizeof(kWord[0]); i++) |
||
for (i=0; i |
|||
if (strcmp(word,kWord[i]) == 0) |
return i+1; |
||
return 0; |
|
|
|
} |
|
|
|
int main() |
|
|
|
{ |
|
|
|
char text[99]; |
|
|
|
puts("Print |
the word (<ENTER> - end of run!):"); |
while(strlen(gets(text)) != 0) {if (wordCheck(text))
puts("\tThis is keyword!");
else
389
![](/html/65386/197/html_6uGuvU_xKP.t62Q/htmlconvd-WWj79B390x1.jpg)
puts("\tThis is invalid word!"); puts("Print the word:");
}
return 0;
}
Результаты выполнения программы:
Print the word (<ENTER> - end of run!): if<ENTER>
This is keyword! Print the word: too<ENTER>
This is invalid word! Print the word:
do<ENTER>
This is keyword! Print the word: while<ENTER>
This is keyword! Print the word:
<ENTER>
В функции wordCheck() строка, определяемая параметромуказателем const char * word, сравнивается со всеми строками, адресуемыми указателями из массива kWord[]. Для сравнения строк применена библиотечная функция strcmp(), возвращающая нулевое значение при совпадении строк, адресуемых аргументами. Если строки совпали, – из функции wordCheck() возвращается увеличенный на 1 индекс соответствующего элемента массива kWord[]. При несовпадении, т.е. в конце цикла, выполняется оператор return 0;. Напомним, что нуль в логических выражениях соответствует значению ЛОЖЬ, а величина, отличная от нуля, соответствует значению ИСТИНА.
В основной программе информация (проверяемое слово) вводится в массив фиксированной длины char text[99]. Если прочитана строка ненулевой длины, то функция wordCheck() выполняет ее анализ. Остальное очевидно из текста программы и приведенных результатов ее выполнения.
390