Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Практикум по программированию на языке Си

..pdf
Скачиваний:
25
Добавлен:
12.11.2023
Размер:
3.53 Mб
Скачать

#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

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