![](/user_photo/_userpic.png)
книги / Практикум по программированию на языке Си
..pdf#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
![](/html/65386/197/html_6uGuvU_xKP.t62Q/htmlconvd-WWj79B354x1.jpg)
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