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

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

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

#include <stdio.h>

/* Перекодировка из Windows в MS-DOS */ void cod_DOS(char dos[], char win[])

{

int i;

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

{

if (win[i] >= 'А' && win[i] <= 'п') dos[i]=win[i] - 64;

else

if (win[i] >= 'р' && win[i] <= 'я') dos[i]=win[i] - 16;

else

if (win[i] == 'Ё') dos[i] = char(240); else

if (win[i] == 'ё') dos[i] = char(241); else

dos[i] = win[i];

}

dos[i] = '\0'; return;

}

int main()

{

char string[] = "Terra incognita - " " неизвестная земля (лат.)";

char result[99]; puts("WINDOWS string:"); puts(string); cod_DOS(result, string); puts("MS-DOS string:"); puts(result);

return 0;

}

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

WINDOWS string:

Terra incognita - эхчэръюьр_ юсырёЄ№(ырЄ.) MS-DOS string:

Terra incognita – неизвестная земля(лат.)

351

В теле функции cod_DOS() обратите внимание на оператор dos[i]='\0', выполняемый после окончания цикла. С его помощью в массив заносится символ окончания строки, т.е. содержимое символьного массива оформляется как строка в стиле Си. В основной программе, кроме строки (массива) с Windows-кодом string[], определен массив фиксированных размеров result[99] с элементами типа char. В этот массив функция cod_DOS() помещает строку результата перекодировки. Остальное очевидно из текста и результатов исполнения программы.

ЗАДАНИЕ. Напишите функцию, создающую на основе строки с символами русских букв в кодировке MS Windows строку в кодировке MS-DOS в динамическом символьном массиве.

Основное отличие новой функции (назовем ее rusDOS()) от функции cod_DOS() – необходимость формирования в теле функции динамического массива для строки-результата. Исходные данные для функции, т.е. строку в Windows-кодировке будем передавать в качестве параметра. Возвращаемый результат – указатель на динамически сформированный массив со строкой в кодировке MS-DOS. После использования результата в точке вызова функции необходимо освобождать память, занятую динамическим массивом. В противном случае при многократном обращении к функции будет происходить "утечка памяти". Однако явным образом освобождать память после каждого обращения к функции rusDOS() обременительно для программиста, применяющего функцию в своей программе. Поэтому защиту от утечек памяти предусмотрим в теле функции rusDOS(). Для этого адрес динамически выделенного символьного массива будем сохранять в статическом указателе, локализованном в теле функции rusDOS(). При инициализации присвоим этому указателю значение NULL. Остальные действия очевидны. Текст функции:

/* rusDOS.c - функция перекодировки русских букв из

Windows в DOS */

#include <stdlib.h> /* для malloc(), free() */ char * rusDOS(char win[])

{

int i, len;

static char * point = NULL;

if (point != NULL) free(point);

352

for (len=0; win[len] != '\0'; len++); point = (char *)malloc(len+1);

if (point == NULL)

{ puts("Malloc() error in rusDOS()"); return NULL;

}

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

{

if (win[i] >= 'А' && win[i] <= 'п') point[i]=win[i] - 64;

else

if (win[i] >= 'р' && win[i] <= 'я') point[i]=win[i] - 16;

else

if (win[i] == 'Ё') point[i] = 240; else

if (win[i] == 'ё') point[i] = 241; else point[i] = win[i];

}

point[i] = '\0'; return point;

}

У функции один параметр – строка в Windows-кодировке. В теле функции определен статический указатель point. При первом обращении к функции он равен NULL, при последующем – сохранит адрес ранее выделенного участка памяти. В этом случае вызов функции free() освобождает память, а затем функция malloc() выделяет новый участок памяти с размерами (переменная len), определенными строкой-параметром.

Пример использования функции:

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

#include <stdio.h> #include "rusDOS.c" int main()

{

char string[] = "Вопросы стиля программирования"; puts(rusDOS(string));

puts(rusDOS("можно обсуждать бесконечно."));

353

puts

(rusDOS("K&R советуют: \"Выберите тот стиль, ")); printf("%s",rusDOS("который вам нравится, и точно\

ему следуйте\".")); return 0;

}

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

Вопросы стиля программирования можно обсуждать бесконечно.

K&R советуют: "Выберите тот стиль,

который вам нравится, и точно ему следуйте".

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

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

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

!В строке-параметре нет информации (строка пуста), т.е. в строке нет значащих символов, отличных от обобщенных пробелов, но есть завершающий символ '\0'. Функция вернет значение NULL.

!В строке-параметре нет пробелов – первое слово является единственным. Нужно возвращать адрес начала строки.

!В предложении после последнего слова имеются пробелы перед концом строки '\0'. В этом случае будем возвращать адрес начала последнего слова и считать, что все последующие пробелы входят в него.

354

/* 09_11.c – функция выбирает последнее слово предложения */

#include <stdio.h> #define SIZE 99

char * word(char string[])

{

char * pk;

for (pk = string;*pk; pk++); /* end of string */ while (*(--pk) == ' ' && pk >= string);

if (pk < string) return NULL;

while (pk != string && *(--pk) != ' '); if (pk == string) return string;

return pk+1;

}

int main ()

{

char sentence[SIZE]; char * endWord; puts("Print sentence:"); gets(sentence);

endWord = word(sentence); if (endWord == NULL)

{printf("\nString is empty!"); return 0;

}

puts("The last word:"); puts(endWord);

return 0;

}

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

Print sentence: <ENTER>

String is empty!

Print sentence:

1 22 333 4444<ENTER> The last word:

4444

355

В теле функции word() определен указатель pk, адресующий при определении начало строки. В цикле for() с телом в виде пустого оператора pk "настраивается" на конец строки (адресует символ '\0'). Затем цикл while() присваивает указателю адрес последнего не равного пробелу символа строки. Если оказывается, что pk<string, то строка пуста и функция возвращает значение NULL. В противном случае новый цикл while() последовательно уменьшает значение pk. Если при этом будет найден пробел, то адресом начала последнего слова является (pk+1). Если pk совпадает с началом строки, то в строке всего одно слово и возвращается адрес начала строки.

Примечание. В программе вводимый пользователем текст (строка) читается из стандартного входного потока в массив фиксированных размеров sentence[]. Чтение выполняется функцией gets(), которая не позволяет контролировать "длину" вводимой последовательности. Тем самым существует возможность выхода за пределы символьного массива sentence[]. Эту потенциальную опасность нужно учитывать при работе с готовой программой.

Описанную "ненадежность" чтения можно устранить, заменив функцию gets() функцией fgets() и введя соответствующую проверку числа прочитанных из входного потока символов. Функция fgets() будет использована в теме, посвященной работе с файлами.

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

Внешне задача аналогична предыдущей, но содержит принципиальное отличие – недостаточно вернуть в точку вызова только адрес начала слова в строке-предложении. Нужно дополнительно указывать его длину либо оформлять и возвращать выбираемое слово в виде завершенной строки в стиле Си. Возможны следующие варианты решения, различающиеся механизмом передачи результатов (данных) из функции в вызывающую ее программу:

1) можно с помощью двух параметров-указателей возвращать адрес начала выбираемого слова и адрес его окончания. В этом случае само слово не копируется, но доступно из исходной строки, представляющей предложение;

356

2)можно возвращать с помощью двух параметров адрес начала слова и его длину. В этом случае в основной программе необходимо, как и в варианте 1, из исходной строки "доставать" найденное слово;

3)можно переписывать искомое слово из исходной строки в символьный массив, используемый в качестве параметра функции. В этом случае в вызывающей программе нужно определить массив подходящих размеров и использовать его имя в качестве аргумента;

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

5)можно определить в теле функции символьный массив, сформировать в нем строку, содержащую искомое слово, и передать адрес массива в точку вызова функции как результат, возвращаемый функцией. В этом варианте массив в теле функции должен

быть массивом динамической памяти.

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

Оформим поиск начала n-го слова в строке-предложении в виде отдельной функции с прототипом

char *begin_N(int n, char string[]);

Функция должна возвращать NULL, если неверно задано значение параметра (n<1), либо в предложении слов меньше, чем n. В противном случае функция возвращает адрес того элемента массива string[], в котором начинается n-е слово предложения.

Следующая программа иллюстрирует применение функции begin_N(). Так как окончание слова еще не определено, то функция main() печатает часть ("хвост") предложения от начала найденного слова:

/* 09_12_1.c - часть предложения от слова с заданным номером */

#include <stdio.h> #define SIZE 99

char * begin_N(int n, char string[])

357

{int count=0; char * pn; char cc;

if (n <= 0) return NULL;

for (cc=' ', pn = string; *pn; pn++)

{if (cc == ' ' && *pn != ' ')

{count++;

if (count == n) break;

}

cc = *pn;

}

if (*pn == '\0') return NULL; return pn;

}

int main ()

{

char sentence[]="De gustibus non est disputandum."; char * begN;

begN = begin_N(-3,sentence); puts("The sentence tail (from -3):"); if (begN == NULL) puts("Error!"); else puts(begN);

begN = begin_N(2,sentence); puts("The sentence tail (from 2):"); if (begN == NULL) puts("Error!"); else puts(begN);

begN = begin_N(8,sentence); puts("The sentence tail (from 8):"); if (begN == NULL) puts("Error!"); else puts(begN);

return 0;

}

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

The sentence tail (from -3): Error!

The sentence tail (from 2): gustibus non est disputandum. The sentence tail (from 8): Error!

358

В теле функции begin_N() указатель char *pn последовательно адресует элементы массива-параметра. Переменная char cc сохраняет символ, предшествующий символу, адресованному в текущий момент указателем pn. Началу очередного слова соответствует ситуация "текущий символ отличен от пробела, предыдущим является пробел". В этом случае счетчик слов int count увеличивается на 1 и сравнивается со значением параметра n. При равенстве count==n цикл прерывается. Условие (*pn=='\0'), проверяемое после цикла, соответствует окончанию "прохода" по строке, без нахождения n-го слова в предложении. В этом случае возвращается нулевой адрес NULL. В "благополучном" случае (когда слово найдено) возвращается значение указателя pn.

Сохраним текст функции begin_N() в файле begin_N.c и перейдем к решению основной задачи. Возьмем для функции выбора из предложения слова с заданным номером (n) такой прототип:

void word_N(char word[], char string[], int n);

У функции word_N три параметра. В массиве string[] функции передается исходное предложение, в массиве word[] размещается выбранное n-е слово предложения. Тем самым определен механизм обмена между функцией и вызывающей программой. В вызывающей программе нужно определить массив с элементами типа char, размеры которого достаточны для представления слова из заданного предложения. Именно этот массив используется в качестве аргумента, заменяющего параметр word при вызове функции. Следующая программа иллюстрирует применение функции:

// 09_12.c - слово с заданным номером в предложении

#include <stdio.h> #include "begin_N.c"

void word_N(char word[], char string[], int n)

{

char * p; p=begin_N(n,string); if (p == NULL)

{*word='\0';

return;

}

359

for (; *p && *p != ' '; p++) *word++ = *p;

*word = '\0';

}

#define SIZE 99 int main ()

{

char sentence[]="De gustibus non est disputandum."; char word3[SIZE];

word_N(word3, sentence, 3); printf("\nThe word 3 is: %s",word3); return 0;

}

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

The word 3 is: non

Из файла begin_N.c на этапе препроцессорной обработки в программу включается текст функции begin_N(). Тем самым эта функция становится доступной для обращений. В теле функции word_N() определен указатель char *p, которому присваивается результат вызова функции begin_N(), т.е. адрес начала искомого (n-го) слова.

Если функция begin_N() вернет нулевой указатель, то в масси- ве-параметре word[] формируется пустая строка. (Оператор *word='\0'; записывает конец строки в элемент word[0].) В противном случае выполняется цикл for() с указателем-параметром p, который последовательно перемещается по символам выбираемого слова. Оператор *word++=*p; присваивает элементам массива word[] значения элементов из строки-параметра string[]. Окончание цикла – достижение конца слова (*p==' ') либо конца предложения (*p=='\0').

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

360