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

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

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

Следующая программа решает задачу:

/* 09_06.c - проверка палиндромов */ #include <stdio.h>

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

int main ()

{

char line[SIZE]; char * beg, * end; int sizeString = 0;

puts("Print sentence:"); gets(line);

for(;line[sizeString] != '\0'; sizeString++); beg = line;

end = &line[sizeString-1]; while (beg < end)

{

if (*beg == ' ') { beg++; continue; } if (*end == ' ') { end--; continue; } if (*beg != *end) {

puts("Not a palindrome!"); exit(0);

}

beg++; end--;

}

puts("This is a palindrome!"); return 0;

}

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

Print

sentence:

1 22

3

56<ENTER>

Not a

palindrome!

Print

sentence:

level

&

level<ENTER>

This is

a palindrome!

В программе 09_06.с строка функцией gets() вводится в массив line[] из 99 элементов. Реальный размер прочитанной строки оп-

341

ределяется в цикле с параметром sizeString. Цикл прекращается, как только sizeString станет индексом элемента массива со значением '\0'. После цикла указателю end присваивается адрес предыдущего элемента массива. Указатель beg вначале адресует начальный элемент. В теле цикла while сравниваются "непробельные" элементы массива, адресованные указателями beg и end. Первый из них увеличивается, второй уменьшается при каждой итерации. При несовпадении (когда *beg!=*end) выявлен не палиндром, и программа завершается. При естественном завершении цикла обнаружен палиндром. Сообщение об этом выводится на печать.

Примечание. Функция gets() читает данные из входного потока без проверки их количества. Поэтому существует опасность "переполнения" массива line[]. Это нужно учитывать при работе с готовой программой. Контролировать длину вводимой последовательности символов позволяет библиотечная функция fgets(). Ее возможности будут рассмотрены в теме, посвященной работе с файлами.

Керниган Б. и Ритчи Д. [1, с. 27] рассматривают очень простую программу подсчета строк в текстовом файле, использованном в качестве стандартного входного потока исполняемой программы:

#include <stdio.h> int main()

{

int c, nl=0;

while ((c=getchar())!= EOF) if(c=='\n') ++nl;

printf("%d\n", nl); return(0);

}

Читаемые из входного потока символы присваиваются переменной не типа char, а типа int, чтобы обеспечить распознавание признака конца файла, представляемого препроцессорной константой EOF. Указанная константа EOF определена в <stdio.h> и там ей приписано значение, отличное от любых кодов символьных данных, соответствующих типу char.

342

ЗАДАЧА 09-07. На основе текста приведенной программы подсчета строк напишите программу, оформляющую в динамическом символьном массиве очередную строку из входного потока и выводящую ее на печать.

/* 09_07.c - чтение строки в динамический массив. */ #include <stdio.h>

#include <stdlib.h> /* для exit(), malloc(), ... */ #define SIZE 66

int main ()

{

int c, i=0, arLen=SIZE; char * point = NULL;

point = (char *)malloc(arLen); if (point == NULL)

{puts("Error of malloc()!"); exit(0);

}

do

{c=getchar();

if (i >= arLen-1)

{point = (char *)realloc(point,arLen+=SIZE); if (point == NULL)

{puts("Error of realloc()!");

exit(0);

}

}

if (c == '\n' || c == EOF) { point[i] = '\0';

puts(point); i = 0;

}

else

point[i++] = c;

}

while (c != EOF); free(point); return 0;

}

Для "приема" строки из входного потока создается динамический массив, адресуемый указателем point. Начальный размер массива,

343

формируемого функцией malloc(), определяется препроцессорной константой SIZE. Символы входного потока вводятся в цикле функцией getchar() и присваиваются переменной int c. Переменная i служит счетчиком прочитанных символов при формировании очередной строки. Переменная arLen определяет длину динамического массива, элементам которого присваиваются введенные символы. Если количество уже введенных символов (значение i) превышает текущую длину массива (arLen-1), выполняется его увеличение. Функция realloc() выделяет участок памяти размером arLen+SIZE и переносит в его начало содержимое участка памяти, адресованного указателем point. Тем самым принимающий строку динамический массив увеличивается на SIZE байтов (символов). При выполнении условия c=='\n'||c==EOF в i-ю позицию массива заносится признак окончания строки '\0', строка функцией puts() выводится на печать и обнуляется счетчик (переменная i). Все готово к чтению новой строки (при c=='\n') или к окончанию обработки (достигнут конец файла, т.е. c==EOF). После окончания цикла функция free() освобождает динамическую память, и исполнение программы завершено.

Обратите внимание, что после окончания цикла ввода всех строк файла размер динамического массива будет не менее максимальной из длин прочитанных строк. (Длина массива может только возрастать.) При использовании функций динамического выделения памяти рекомендуется проверять успешность их выполнения. Если памяти окажется недостаточно, функции malloc() и realloc() возвращают значение NULL. Вероятность такой ситуации в нашей программе невелика, но при разработке серьезных программ проверку нужно выполнять всегда.

ЭКСПЕРИМЕНТ. Оттранслируйте программу 09_07.с, создайте текстовый файл (например, с именем inputFile) с произвольными данными. Если в результате трансляции создан исполнимый модуль 09_07.exe, запустите программу на исполнение такой командой:

>09_07.exe<inputFile

Убедитесь в правильности результата.

ЭКСПЕРИМЕНТ. Запустите программу на исполнение без переназначения входного потока, т.е. вводите строки текста с клавиатуры.

344

В этом случае программа не сможет завершить цикл ввода, так как во входном потоке, "настроенном" на чтение данных от клавиатуры, отсутствует признак конца файла EOF. Для его моделирования нужно использовать стандартное сочетание двух клавиш: Сtrl+Z.

9.2. Строки и функции

Итак, строковых переменных в языке Си нет, но последовательность символов, ограниченная справа кодом '\0', по соглашениям языка должна восприниматься и обрабатываться как символьная строка. Сочетание слов "восприниматься и обрабатываться" относится в основном к обработке строк функциями. В отличие от обычных массивов строка может быть использована в качестве параметра функции без указания ее длины.

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

Параметры определяемой функции – два указателя на строки. Пусть первый параметр (например, char * string) адресует исследуемую строку, второй (char * sub) – адресует искомую подстроку. Функция должна возвращать искомый адрес либо NULL, если подстрока не встречается в строке.

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

2 С более эффективным алгоритмом поиска подстроки читатель может познакомиться в книге: Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. – М.: МЦНМО, 1999. – 960 с.

345

/* 09_08.c - функция поиска подстроки в строке */ #include <stdio.h>

int sizeStr(char * str)

{

int len;

for (len=0; *(str+len) != '\0'; len++); return len;

}

char * subStr(char * string, char * sub)

{

char * ch;

int lenStr, lenSub, j; lenStr=sizeStr(string); lenSub=sizeStr(sub);

if (lenSub > lenStr) return NULL;

for (ch=string; ch<string+(lenStr-lenSub+1);ch++) { for (j=0; j<lenSub; j++)

if (*(ch+j) != *(sub+j)) break; if (j == lenSub) return ch;

}

return NULL;

}

int main()

{

char longStr[] = "11 22 33 44 33 45 67 33"; char shortStr[] = "33";

char * pointer; char * symbol;

while((pointer=subStr(longStr,shortStr)) != NULL)

{puts(pointer);

symbol=shortStr;

while (*(symbol++) != '\0')

*(pointer++)=' ';

}

printf("\nResult: %s",longStr); return 0;

}

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

33 44 33 45 67 33

33 45 67 33

346

33

Result: 11 22

44

45 67

В тексте программы определена вспомогательная функция sizeStr(), вычисляющая длину строки. С ее помощью в теле функции subStr() вычисляются длины исследуемой строки и подстроки (переменные lenStr, lenSub). Если длина подстроки превышает длину строки, функция subStr() возвращает значение NULL. В противном случае указатель char * ch последовательно адресует символы исследуемой строки. Начиная с каждого из них, во внутреннем цикле выполняется сравнение подстроки с частью исследуемой строки. При несовпадении очередных символов внутренний цикл прерывается оператором break. Если внутренний цикл завершен без прерывания (при этом параметр j равен длине подстроки lenSub), то найдена подстрока и функция возвращает ее адрес (переменная ch), т.е. адрес ее начала в строке-параметре string. При естественном завершении внешнего цикла (по завершении просмотра исследуемой строки) подстрока не найдена – возвращаемое значение

NULL.

В основной программе определены в виде символьных массивов две строки и два указателя pointer и symbol. Массив longStr[] представляет проверяемую строку, в массиве shortStr[] – подстрока. В заголовке цикла while() указателю pointer присваивается найденный адрес искомой подстроки. Если он отличен от NULL, в теле цикла печатается адресуемая указателем pointer часть строки. Затем во вложенном цикле заменяются пробелами символы найденной подстроки. По завершении циклов печатается измененная строка.

ЗАДАЧА 09-09. Напишите функцию перекодировки русских букв из кодов MS Windows в коды MS-DOS. Функция должна изменять строку-параметр.

Для решения задачи обратимся к кодовым таблицам 1251 (MS Windows) и 866 (MS-DOS), точнее, к символам с кодами от 128 до 255 (см., например, Приложение 1 в [3]). Выберем из таблиц последовательности кодов русских букв:

347

MS Windows

А192

Б

193

………...

………...

Я223

а224

б

225

………...

………...

п239

р240

с241

т

242

………...

………...

я255

Ё168

ё184

MS-DOS

А128

Б

129

………...

………...

Я159

а160

б

161

………...

………...

п

175

………...

………...

р224

с

225

………...

………...

я239

Ё240

ё241

Из приведенных фрагментов таблиц видны правила преобразований. Если код W из таблицы MS Windows находится в диапазоне кодов букв от А до п, то нужно заменить его кодом W-64. Для кода W из диапазона кодов букв от р до я требуется код со значением W-16. Для большой и малой букв Ё, ё правило отдельное: код 168 заменить на 240, код 184 – на 241.

Следующая программа решает задачу на основе сказанного:

/* 09_09.c - замена для русских букв кодов Windows на коды DOS */

#include <stdio.h>

/* смена кода одного символа */ char change(char cw)

{

if (cw >= 'А' && cw <= 'п') return (cw - 64); if (cw >= 'р' && cw <= 'я') return (cw -16); if (cw == 'Ё') return char(240);

if (cw == 'ё') return char(241); return cw;

}

/* Замена в строке кодов Windows на коды DOS */ void codeMSDOS(char str[])

{

int i;

348

for (i=0; str[i] != '\0'; i++) str[i] = change(str[i]);

}

int main()

{

char string[] =

"Status Quo - существующее положение (лат.)"; char renyxa[] = "Ёж и селёдка!";

puts("WINDOWS string:"); puts(string); codeMSDOS(string); puts("MS-DOS strings:"); puts(string); codeMSDOS(renyxa); puts(renyxa);

return 0;

}

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

WINDOWS string:

Status Quo - ёє•хёЄтє_•хх яюыюцхэшх(ырЄ.) MS-DOS strings:

Status Quo - существующее положение(лат.) Ёж и селёдка!

В программе функция change() выполняет преобразование кода одного символа в соответствии с приведенным выше правилом. Параметром функции codeMSDOS() является массив с элементами типа char, обязательно содержащий символ-ограничитель строки '\0'. В теле функции элементы массива-параметра последовательно "перебираются" от начала до элемента с символом '\0'. Для каждого элемента выполняется перекодировка функцией change(). В основной программе массивы string[] и renyxa[] инициализированы строками (константами), содержащими русские буквы в кодировке MS Windows. Далее функцией puts() печатается массив string[] до перекодировки и после нее. Массив renyxa[] выводится после перекодировки. Вывод выполняется на экран в кодах MS-DOS. Поэтому в результатах правильно русские буквы воспроизведены только после перекодировки.

349

Примечание. В Windows API имеется функция charToOem, которая работает аналогично codeMSDOS(). (См., например, Саймон Р. Windows 2000 API. Энциклопедия программиста. –Киев: ДиаСофт, 2002. –1088 с.) Эта функция не входит в стандартную библиотеку Си, поэтому в нашем Практикуме не используется.

ЗАДАНИЕ. В приведенной программе функция codeMSDOS() не возвращает значения (указан тип void). Измените функцию, сделав возвращаемым значением адрес массива-параметра.

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

Например, для функции с прототипом

char *codeMSDOS(char[]);

будет возможен такой вызов (в нашей программе):

puts(codeMSDOS(string));

Указанные изменения сделаны в программе 09_09_1.с.

ЗАДАЧА 09-10. Переделайте предыдущую программу таким образом, чтобы функция перекодировки не изменяла исходную строку с Windows-кодировкой.

При такой постановке необходимо, чтобы функция имела доступ к символьному массиву, "принимающему" результат перекодировки. Этот массив должен быть уже определен в точке вызова функции и должен иметь размеры не меньше размера массива с исходной строкой. Например, можно передавать этот массив функции с помощью дополнительного параметра. Определим такую функцию перекодировки из кода Windows в код MS-DOS со следующим прототипом:

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

где win – имя массива-параметра с исходной строкой, dos – массив для результата перекодировки. Алгоритм перекодировки описан выше. Программа, решающая задачу, может быть такой:

/* 09_10.c – перекодировка русских букв из WINDOWS

в MS-DOS */

350