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

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

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

ЗАДАНИЕ. Перепишите функцию выбора из строкипредложения слова с заданным номером. Используйте для передачи результата в точку вызова глобальный массив с элементами типа char.

ЗАДАНИЕ. Перепишите функцию выбора из строкипредложения слова с заданным номером. Результат возвращайте в виде указателя на динамический массив, формируемый в теле функции.

9.3. Библиотечные функции для работы со строками

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

ЗАДАЧА 09-13. Введите с клавиатуры в виде одной строки простейшее бинарное арифметическое выражение, включающее в качестве операндов два положительных целых числа и знак одной из четырех арифметических операций (+, -, *, /), размещенный между ними. Вычислите значение выражения.

Задача могла бы решаться достаточно просто в следующих двух случаях: знак операции выделен пробелами или запись каждого операнда имеет известную фиксированную длину. В каждом из этих случаев значения операндов и символ знака операции можно ввести с помощью функции scanf(). Для операндов нужны спецификации преобразования "%d", для знака операции – спецификация "%c".

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

361

труднительно. Будем вводить выражение целиком в виде строки и анализировать его, выделяя из строки компоненты выражения. Последовательность действий (без защиты от неверной записи выражений) можно описать следующим образом:

Ввести строку с выражением Найти начало первого операнда Определить длину первого операнда

Выделить "изображение" первого операнда Получить числовое значение первого операнда Найти позицию размещения знака операции Выделить символ операции Найти начало второго операнда

Определить длину второго операнда Выделить "изображение" второго операнда Получить числовое значение второго операнда

В зависимости от знака операции вычислить значение выражения.

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

В данной задаче будут использованы следующие функции, описанные (кроме atoi()) в заголовочном файле <string.h>: strpbrk(), strspn(), strncpy(), atoi(). Приведем их прототипы и краткие сведения о функциональном назначении.

char *strpbrk(const char *str1,const char *str2);

возвращает адрес первого символа в строке str1, совпадающего с любым из символов строки str2.

int strspn(const char *str1,const char *str2);

определяет длину первого (самого левого) сегмента строки str1, содержащего только символы из строки str2.

char *strncpy(char *str1,const char *str2,

int len);

копирует len символов из строки str2 в строку str1; int atoi(const char * str);

формирует из последовательности символов строки str числовое значение типа int.

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

362

// 09_13.c - арифметическое выражение в виде строки

#include <stdio.h> #include <string.h>

#include <stdlib.h> /* для atoi() */ #define SIZE 9

int main ()

{

char expression[99];

char op1[SIZE]; /* первый операнд */ char op2[SIZE]; /* второй операнд */ char operation; /* знак операции */ char * beg;

int len;

int int_exp, int1, int2; puts("Print the expression:"); gets(expression);

beg = strpbrk(expression,"0123456789"); len = strspn(beg,"0123456789"); strncpy(op1,beg,len);

op1[len] = '\0'; int1 = atoi(op1);

beg = strpbrk(beg,"+/-*"); operation = *beg;

beg = strpbrk(beg,"0123456789"); len = strspn(beg,"0123456789"); strncpy(op2,beg,len);

op2[len] = '\0';

 

int2

= atoi(op2);

 

switch(operation) {

 

case

'+':

 

break;

int_exp = int1 + int2;

case

'*':

 

break;

int_exp = int1 * int2;

case

'-':

 

break;

int_exp = int1 - int2;

case

'/':

 

 

if (int2) {

 

break;

int_exp = int1 / int2;

}

 

 

 

else {puts("Error! Divisor is 0.");

 

return 0; }

 

default:

puts("Error operation!");

363

return 0;

}

printf("\nThe operand 1 is: %s",op1); printf("\noperation=\'%c\'", operation); printf("\nThe operand 2 is: %s",op2); printf("\nExpression value: %d",int_exp); return 0;

}

Результаты выполнения программы.

Первый вариант:

Print the expression: 35 +5 <ENTER>

The operand 1 is: 35 operation='+'

The operand 2 is: 5 Expression value: 40

Второй вариант:

Print the expression: - 66 hh<ENTER>

Error operation!

В тексте программы для представления строки с изображением (с символами) вычисляемого выражения использован массив char expression[99]. В массивы op1 и op2 из него копируются символы операндов, их числовые значения присваиваются переменным int1, int2. В переменную char operation заносится изображение знака операции. Важную роль играет указатель char * beg. Он последовательно адресует начало очередного обрабатываемого участка исходной строки. Остальное очевидно из сопоставления описания алгоритма с текстом программы.

В результатах исполнения программы обратите внимание на наличие пробелов после первого операнда и перед нажатием клавиши ENTER. Алгоритм учитывает и такие особенности ввода исходных данных. Но в целом приведенная программа 09_13.с плохо защищена от ошибок ввода исходных данных. Программа не распознает

364

неверных символов в записи выражения, не анализирует наличия в выражении именно трех частей: операнд, знак операции, операнд. Во втором варианте исходных данных она выдает сообщение "Error operation!", хотя стоило бы сообщить о конкретной ошибке в выражении.

ЗАДАНИЕ. Перепишите рассмотренную программу вычисления арифметического выражения, заменив вызовы библиотечных функций для обработки строк подходящими фрагментами текста на языке Си.

ЗАДАНИЕ. Дополните программу 09_13.с проверками ошибок ввода исходной информации.

Для проверки отсутствия в исходных данных недопустимых символов можно использовать библиотечную функцию:

int strcspn(const char * str1, const char * str2);

Она вычисляет длину первого сегмента строки str1, содержащего символы, не входящие в множество символов строки str2.

Введя строку, представляющую вычисляемое выражение, в массив expression[], выполним такую проверку:

if (strcspn(expression, "+/-* 0123456789"))

{ puts("Invalid symbols!"); return 0;

}

Если выражение не содержит неверных символов, то после поиска в строке очередного операнда или знака операции можно анализировать значение полученного адреса его начала (значение указателя beg):

if (beg==NULL)

{ puts("текст сообщения об ошибке"); return 0;

}

Указанные проверки реализованы в программе 09_13_1.с. Они обеспечивают достаточно полную защиту от неверных исходных

365

данных, хотя и не позволяют однозначно указать характер ошибки. Например, выражению

-55<ENTER>

будет соответствовать сообщение

No operation sign!

хотя при более точном анализе можно было бы характеризовать структуру этого выражения как "Отсутствие первого операнда".

Рассмотрим особенности применения объектов статической и динамической памяти в функциях для обработки строк.

ЗАДАЧА 09-14. Напишите функцию перекодировки очередного слова предложения на русском языке из кода Windows в код MSDOS. Исходное предложение, слова в котором разделены пробелами, передавайте в функцию как параметр. При каждом обращении к функции она должна возвращать MS-DOS код следующего слова. В основной программе определите строку-предложение на русском языке в коде MS Windows. Последовательно обращаясь к функции, перекодируйте в коды MS-DOS и напечатайте все русские слова предложения "в столбик".

Для решения задачи используем функцию из программы 09_10.с (перекодировка из MS Windows в MS-DOS) с прототипом

void cod_DOS (char dos[],char win[]);

Ее текст разместим в файле cod_DOS.c и будем включать в программу на этапе препроцессорной обработки.

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

static char * beg=NULL; static char * res=NULL;

Первый из них при выполнении функции "настраивается" на начало очередного слова предложения. Второй адресует участок дина-

366

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

Если res!=NULL

Освободить динамическую память Если beg==NULL

"Настроить" beg на начало предложения

Найти начало очередного слова (определить значение beg) Если слов больше нет (т.е. beg==NULL)

Вернуть значение NULL Определить длину слова

Выделить память для слова ("настроить" res)

Копировать слово из предложения в память для результата Перекодировка функцией cod_DOS()в памяти для результата Настроить beg на продолжение предложения

Вернуть значение res

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

В результате получится следующая программа:

/* 09_14.c - перекодировка русских слов из предложения */

#include <stdio.h> #include <string.h>

#include <stdlib.h> /* для malloc(), ... */ #include "cod_DOS.c"

char * word(char sentence[])

{

static char * beg = NULL; static char * res = NULL; char codeWIN[]=

"абвгдеёжзийклмнопрстуфхцчшщъыьэюя";

int len;

if (res != NULL) { free(res); res=NULL; } if (beg == NULL) beg = sentence;

beg = strpbrk(beg,codeWIN);

367

if (beg == NULL) return NULL; len = strspn(beg,codeWIN); res = (char *)malloc(len+1); strncpy(res,beg,len); res[len]='\0'; cod_DOS(res,res);

beg += len; return res;

}

#define SIZE 33 int main ()

{

char proverb[] =

"Sentence 1: тише едешь - дальше будешь.";

char * pword = NULL; while(1)

{

pword =

word("Sentence 2: повторение - мать учения."); if (pword == NULL) break;

puts(pword);

}

while(1)

{

pword = word(proverb);

if (pword == NULL) break; puts(pword);

}

return 0;

}

Результаты выполнения программы (в окне MS-DOS):

повторение

мать

учения

тише

едешь

дальше

будешь

368

Cловом в нашей задаче считается последовательность строчных букв русского алфавита, представленных кодами MS Windows. Коды этих букв представлены в массиве char codeWIN[]. Обратите внимание на отсутствие в результатах точки и символа '-'. Они не входят

вчисло букв и поэтому игнорируются.

Вфункции word() динамическая память, адресуемая указателем

res, используется вначале для выделенного из предложения слова. При обращении к функции cod_DOS() в этот же массив записывается результат перекодировки.

Функция word() необычна – она обладает внутренним "состоянием" – сохраняемыми от предыдущего вызова значениями указателей beg и res. Поэтому при обращении к функции word() возможно несколько ситуаций. Если функция до этого не использовалась, то в начале тела функции указатели имеют нулевые начальные значения (NULL). Затем указатель beg получает адрес начала анализируемого предложения (beg=sentence;). А res получает адрес участка памяти для хранения результата. Вторая ситуация возникает в том случае, если функция ранее уже вызывалась и указатели beg и res сохранили предыдущие значения. В этом случае нужно освободить память, адресованную указателем res, и обнулить его (res=NULL). Далее возможны два состояния: если beg==NULL, то предполагается анализ нового предложения. В противном случае beg адресует "хвост" предложения, уже использованного при предыдущем обращении. В обоих состояниях дальнейшие действия одинаковы. Функция strpbrk() "разыскивает" очередную последовательность из русских букв в кодах MS Windows и т.д. по псевдокоду.

В основной программе функция word() вызывается в двух циклах, каждый из которых завершается, когда в предложении не осталось слов.

ЭКСПЕРИМЕНТ. Проверьте "надежность" и предсказуемость исполнения функции word(). Для этого удалите из функции main() программы 09_14.с циклы, т.е. обратитесь к word() дважды с разными аргументами.

Если первое обращение будет таким

pword=word("повторение – мать

учения");

369

то независимо от аргумента второго обращения результатом будет:

повторение

мать

ЗАДАНИЕ. Измените функцию word() из программы 09_14.с так, чтобы при смене аргумента она обрабатывала именно его содержимое независимо от того, чем закончилось предыдущее исполнение функции для другого аргумента.

Решение достаточно простое. В теле функции word() определим дополнительный статический указатель:

static char * arg=NULL;

Присвоим ему адрес начала строки аргумента. При изменении аргумента будем начинать обработку его слов с начала адресуемого им предложения и заменять значение указателя arg. Соответствующие операторы:

if (arg!=sentence) beg=NULL;

if (beg==NULL) arg=beg=sentence;

Соответствующая программа находится в файле 09_14_1.с. Результаты ее исполнения без циклов при двух вызовах функции с разными аргументами:

повторение

тише

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

char * strupr(char * str);

370