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

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

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

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

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

Прототип функции может быть таким:

char * trans(const char * word);

Функция в качестве параметра получает указатель на строку с термином (словом) и должна вернуть один из следующих трех "ответов":

!перевод служебного слова;

!нет перевода;

!это не служебное слово.

Вариант "нет перевода" – вспомогательный или временный (мы

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

Для представления служебных слов в теле функции используем массив char * kWord[] из функции wordCheck() программы 09_21.с. Он определяется как массив автоматической памяти.

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

static char * translate[]={….};

В статической памяти определим и строковые константы с сообщениями:

static char * noTrans="Нет перевода!";

static char * notKey="Это не служебное слово!"

Соответствие между служебными словами и переводными эквивалентами установим по их взаимному размещению. Таким образом, термину, адресованному указателем kWord[i], должен соответ-

391

ствовать переводной эквивалент, адресуемый указателем translate[i].

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

/* 09_22.c - перевод служебных слов языка Си */ #include <string.h>

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

char * trans(const char * word)

{

char * kWord[] ={"auto", "break", "case",

"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"};

* translate[] = {"автоматический",

static char

"","вариант", "символьный","",

 

"","","выполнять","","",

 

 

"","","","","переход на",

 

"если","","","","",

 

 

"","","","","",

 

 

"","","","","",

 

 

"","","","","",

 

 

};

 

 

 

int i;

* noTrans =

"Нет перевода!";

static char

static char

* notKey = "Это не служебное слово!";

for (i=0; i

< sizeof(kWord)/sizeof(kWord[0]); i++)

if (strcmp(word,kWord[i]) == 0)

{ if (strlen(translate[i]) == 0) return noTrans;

return translate[i];

}

return notKey;

}

int main()

392

{

char text[99]; char dos[99];

puts("Print the word (<ENTER> - end of run!):"); while(strlen(gets(text)) != 0)

{ cod_DOS(dos, trans(text)); puts(dos);

}

return 0;

}

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

Print the word (<ENTER> - end of run!): case<ENTER>

вариант

if<ENTER>

если

while<ENTER>

Нет перевода! too<ENTER>

Это не служебное слово! <ENTER>

В программе использована функция cod_DOS(), выполняющая перекодировку русских букв из кодов MS Windows в коды MS-DOS (см. программу 09_10.с). Эта перекодировка нужна только для вывода на экран при исполнении программы в режиме MS-DOS. Код MS-DOS формируется в массиве char dos[99] непосредственно перед выводом функцией puts().

Массивы указателей на строки позволяют решать различные задачи сортировки строк. Так как у нас (программы 09_21.с, 09_22.с) уже есть массив kWord[] указателей на строки со служебными словами языка Си, то воспользуемся им для демонстрации особенностей сортировки строк.

ЗАДАЧА 09-23. Выведите на печать служебные слова языка Си в порядке возрастания их длин.

393

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

08_20.с и 09_05.с.

/* lineSort.c - рекурсивная сортировка строк по длинам */

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

/* поменять местами значения v[i] и v[j] */ void swap(char * v[], int i, int j)

{

char * temp; temp = v[i]; v[i] = v[j]; v[j] = temp;

}

/* Сортирует массив указателей, адресующих строки */ void lineSort(char * v[], int n)

{

int i, last; if (n <= 1) return;

swap(v, 0, rand() % n); last = 0;

for (i = 0; i < n; i++)

if (strlen(v[i]) < strlen(v[0])) swap(v, ++last, i);

swap(v, 0, last); lineSort(v, last);

lineSort(v+last+1, n-last-1);

}

В основной программе определим массив указателей на символьные строковые константы kWord[], вычислим количество элементов в нем (sizeArray) и обратимся к функции lineSort(). Затем в цикле выведем (пользуясь сёёёёёёпецификацией %s) адресуемые новыми значениями указателей kWord[i] строки.

Текст основной программы:

394

/* 09_23.c - сортировка строк, адресуемых указателями */

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

{

int i;

char * kWord[] ={"auto", "break", "case", "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 sizeArray = sizeof(kWord)/sizeof(kWord[0]); lineSort(kWord, sizeArray);

printf("Sort strings:\n"); for (i=0; i<sizeArray; i++)

printf("%s%c",kWord[i],(i+1)%5?'\t':'\n'); return 0;

}

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

Sort strings:

for

int

case

do

if

enum

char

goto

auto

void

else

long

const

short

float

union

break

while

double

sizeof

extern

struct

switch

return

static

signed

typedef

default

register

unsigned

volatile

 

continue

 

 

ЗАДАЧА 09-24. Упорядочите непустые строки стандартного входного потока по возрастанию их длин.

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

395

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

09_23.с:

void lineSort(char *v[], int n);

Для чтения в динамический массив очередной строки входного потока можно применить функцию readLine() из программы 09_19.с. Функция возвращает адрес динамического массива, размер которого в большинстве случаев превышает длину той строки, которая находится в нем.

В основной программе, решающей поставленную задачу, необходимо учитывать отмеченную особенность функции readLine(). Так как длина прочитанной функцией readLine() строки заведомо меньше длины массива, в котором она размещена, то в основной программе будем выделять для каждой прочитанной строки динамический массив размером len+1, где len – число символов в конкретной строке. Совокупность таких динамических массивов, содержащих строки, адресуем указателем char * * list;.

Является ли list массивом? Для ответа на этот вопрос вспомним, что в программе 09_23.с массив указателей на строки введен определением

char * kWord[]={

};

Число элементов в этом массиве kWord задано списком инициализации и тем самым фиксировано. В отличие от kWord имя list есть не имя массива указателей, а имя указателя на указатель типа char*. С помощью механизмов динамического выделения памяти list "настраивается" на участок памяти (на массив), заполняемый указателями типа char*. При формировании и заполнении динамического массива с указателями на строки необходимо подсчитывать их количество (переменная int lenList). Чтобы не слишком часто обращаться к функциям выделения памяти, будем выделять ее порциями размером в INCREASE элементов. Для адресации очередной

396

строки, прочитанной из входного потока функцией readString(), введем указатель char * str. Текст программы:

/* 09_24.c - сортировка строк входного потока. */ #include <string.h>

#include <stdio.h> #include "readLine.c" #include "lineSort.c" #define INCREASE 50 int main()

{

char * str;

char ** list = NULL; int lenList = 0, i;

int capacity = INCREASE;

list = (char * *)malloc(capacity); if (list == NULL)

{puts("The malloc() error!"); return 0;

}

while((str = readLine()) != NULL) { if (lenList >= capacity -1)

{list = (char * *) realloc(list,capacity+=INCREASE); if (list == NULL)

{ puts("The realloc() error!"); return 0;

}

}

list[lenList] = (char *)malloc(strlen(str)+1); if (list[lenList] == NULL)

{puts("The malloc() error!"); return 0;

}

strcpy(list[lenList++],str);

}

lineSort(list, lenList); puts("The result:");

for (i=0; i<lenList; i++) puts(list[i]);

printf("Number of lines=%d",lenList); for (i=0; i<lenList; i++)

397

free(list[i]);

free(list); return 0;

}

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

12345<ENTRY>

abc<ENTRY>

qwerty<ENTRY>

12<ENTRY>

^Z<ENTRY> The result: 12

abc 12345 qwerty

Number of lines=4

Обратите внимание, что при вводе с клавиатуры значение EOF (конец файла) моделируется одновременным нажатием клавиш 'Сtrl' и 'z', изображаемое на экране символами ^z.

Командная строка запуска программы с чтением данных из фай-

ла:

C:\practic\progs\09>test < 09_24.c<ENTER>

При таком запуске выводятся строки текста программы 09_24.c, упорядоченные по возрастанию длин.

Чтение строк входного потока выполняется в цикле while. Если количество прочитанных строк (lenList) превысит размер динамического массива указателей на строки (значение переменной capacity), функция realloc() увеличивает его емкость на величину INCREASE. Для очередной строки функцией malloc() выделяется память, достаточная для хранения строки, и выполняется копирование функцией strcpy(). После завершения цикла чтения входного потока массив, адресованный указателем list, сортируется функцией lineSort(). Далее выполняется печать полученного массива и ос-

398

вобождение динамически выделенной памяти. Память, выделенная для строк, освобождается в цикле. Затем free(list) освобождает память, выделенную для массива указателей типа char *.

Коротко о важном

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

!Операция sizeof, применяемая к указателю, адресующему строку, вычисляет размер указателя, а не строки (09_01.с).

!Операция sizeof, применяемая к строковой константе, вычисляет ее длину с учетом терминального символа '\' (09_01.с).

!Функция puts() с аргументом-указателем, адресующим строку, выводит не значение указателя, а содержимое строки (09_01.с).

!Символы строковой константы доступны с помощью разыменования адресующих их указателей типа char * (09_02.с).

!Указателю, адресующему после инициализации начало строковой константы, можно присваивать любое другое значение, например, адрес произвольного символа той же строки-константы

(09_02.с).

!Имя символьного массива является константой! (09_03.с).

!Любая часть символьного массива, не содержащая символов '\0' и ограниченная справа этим символом, воспринимается как символьная строка в стиле Си (09_03.с).

!Операция sizeof, применяемая к имени символьного массива, вычисляет размер массива, независимо от того, где в нем размещены символы-ограничители строк '\0' (09_03.c) и как инициализирован массив (09_04.с).

!Инициализация массива типа char[] строковой константой автоматически добавляет символ-ограничитель '\0' в конец массива, т.е. превращает массив в строку в стиле Си (09_04.с, 09_05.с).

!При поэлементной обработке массива типа char[] элемент со значением '\0' равноправен с остальными элементами массива

(09_05.с, 09_05_1.с).

399

!Символьный массив, инициализированный строковой константой, воспринимается как строка в стиле Си и может быть, например, напечатан функцией puts() (09_05_1.c, 09_04.c).

!К символам строки можно получить доступ в произвольном порядке, адресуя их указателями типа char * (09_02.с, 09_06.c).

!Символы строки как элементы любого массива можно независимо друг от друга адресовать произвольным количеством указате-

лей (09_06.с).

!Строки из входного потока можно читать посимвольно, учитывая, что строку завершает код символа '\n' либо EOF (09_07.с).

!При посимвольном чтении строки из входного потока вместо кода '\n' или EOF строку в программе нужно завершать символом '\0', явно записывая его в конец прочитанных символов

(09_07.с).

!Для стандартного входного потока, настроенного на чтение данных от клавиатуры, нет кода конца файла, т.е. код признака EOF нужно моделировать нажатием пары клавиш 'Сtrl'+'z' (09_07.с,

09_24.c).

!Рекомендуется проверять успешность выполнения каждой операции (каждой функции) динамического выделения памяти

(09_07.c, 09_18.c, функция readLine() в 09_19.с).

!В символьном массиве, представляющем строку, всегда есть код символа-ограничителя '\0'. Поэтому, используя строку в качестве параметра, нет необходимости передавать функции или возвращать из нее длину строки (09_08.с, 09_09.с и др.).

!Строка-аргумент доступна изменениям за счет действия операторов тела функции (09_09.с, 09_10.с).

!Массив, используемый в качестве аргумента для строкирезультата выполнения функции, должен быть определен в вызывающей функцию программе и размер его должен быть достаточен для строки-результата (09_10.с, 09_12.с).

!Чтение символов из стандартного входного потока (от клавиатуры) функцией scanf() с использованием спецификации %s выполняется до первого пробела (до кода обобщенного пробельного символа) либо до символа конца строки '\n', либо до кода EOF.

!Функция gets() читает символы из входного потока до появления кода символа конца строки '\n' либо до кода EOF, т.е. вводятся все пробелы (09_06.с, 09_11.с и др.).

400