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

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

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

букв, выделенная из входного потока), сравнивается в цикле со значениями, адресованными указателями array[i].word. При совпадении строк возвращается адрес &array[i].

Восновной программе, кроме массива структур stWord[], определен указатель pword на структуру того же типа. Массив stWord[] инициализирован, как в программе 11_06.с.

Вцикле перебора строк входного потока каждая строка вначале адресуется указателем char * string. Затем этому указателю

библиотечной функцией strbrk() присваивается адрес начала алфавитной последовательности. Если она не обнаружена (т.е. string==NULL), обработка входной строки завершается. В противном случае переменной len библиотечная функция strspn() присваивает длину найденной последовательности латинских букв. Библиотечная функция strncpy() копирует len символов из участка памяти, адресованного указателем string, в символьный массив char temp[]. Присваивание temp[len]='\0' оформляет строку, которую затем анализирует функция analyse(). Если слово не является служебным, т.е. отсутствует в массиве stWord[], продолжается анализ строки (указатель string перед этим "перемещается" в конец просмотренной последовательности букв). В противном случае увеличивается счетчик найденного служебного слова (pword– >counter++) в массиве структур.

При достижении конца входного потока (EOF) функция readLine() возвращает значение NULL, и цикл перебора входных строк завершается. Затем в отдельном цикле "просматриваются" все элементы массива структур и печатаются значения компонентов тех из них, для которых найдены служебные слова во входном потоке.

ЗАДАНИЕ. Удалите из программы указатель struct keyword * pword. Вместо него примените значение, возвращаемое функцией analyse(), точнее, выражение, в которое входит обращение к этой функции. (Об эффективности не беспокойтесь, конечно, быстродействие программы уменьшится.)

Значение, присвоенное указателю pWord в 11_11.с, использовано дважды – проверяется его отличие от NULL и осуществляется обращение к элементу counter из найденной функцией analyse() структуры из массива stWord[]. В первом случае замена тривиаль-

481

на – можно использовать явное обращение к функции analyse(). Для доступа к компоненту структуры из массива структур можно заменить оператор pword–>counter++; одним из следующих:

(*analyse(temp, stWord, AR_SIZE)).counter++; analyse(temp, stWord, AR_SIZE) –>counter++;

Обратите внимание на расстановку скобок в первом из приведенных операторов.

Можно написать и еще более сложное выражение для обращения к компоненту counter, применив имя массива stWord[], обращение к функции analyse() и индексирование:

stWord[analyse(temp,stWord,AR_SIZE)- stWord].counter++;

Программа без указателя pword приведена в файле 11_11_1.с.

11.4.Битовые поля структур и объединения

ЗАДАЧА 11-12. Введите в виде трех целых чисел дату (число, месяц, год) и "упакуйте" ее в структуру с битовыми полями. Напечатайте дату, взяв данные из структуры. Определите и напечатайте размеры структуры.

Для представления числовых значений дня, месяца и года используем битовые поля длиной 5, 4 и 11 разрядов соответственно. Для ввода значений функцией scanf() введем вспомогательную переменную. Для вычисления и печати размеров структуры используем макрос PRINTZ() из программы 11_01.с. Остальное очевидно из следующего текста программы:

/* 11_12.c – Поразрядная "упаковка" даты */ #include <stdio.h>

#define PRINTZ(TERM) \ printf("sizeof("#TERM")=%d\n",sizeof(TERM))

int main (void)

482

{

struct { unsigned

dd :5;

unsigned

mm :4;

unsigned

yy :11;

} date;

 

int rr;

 

puts("Input date:");

printf("day=");

date.dd = rr;

scanf("%d",&rr);

printf("month=");

date.mm = rr;

scanf("%d",&rr);

printf("year=");

date.yy = rr;

scanf("%d",&rr);

printf("date: %02d-%02d-%4d\n", date.dd, date.mm, date.yy);

PRINTZ(date); return 0;

}

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

Input date: day=2<ENTER> month=2<ENTER> year=2002<ENTER> date: 02-02-2002 sizeof(date)=4

Обратите внимание на спецификацию преобразования %02d, использованную для вывода значений числа и месяца. Нуль перед значением ширины поля указывает, что левые позиции, не занятые значащими цифрами выводимого значения, дополняются нулями. Размер структуры оказался равен четырем байтам (32 бита), что больше, чем суммарный размер битовых полей (5+4+11). Как подчеркивают Б.Керниган и Д.Ритчи [1], все технические детали структур с битовыми полями существенно зависят от реализации. Именно в особенностях конкретного транслятора нужно искать ответ на вопрос, почему структуре "date" выделено 4 байта.

ЭКСПЕРИМЕНТ. Попытайтесь обойтись без вспомогательной переменной int rr при вводе значений date.dd, date.mm, date.yy.

483

Попытка использовать обращение scanf("%d", &date.dd) приведет к ошибке на этапе компиляции (см. программу 11_12_1.с). Соответствующее сообщение:

…cannot take address of bit-field "dd"

ЭКСПЕРИМЕНТ. Попробуйте определить размеры битовых полей с помощью операции sizeof.

Выражение sizeof(date.dd) приведет к такому сообщению компилятора:

… 'sizeof' applied to bit-field

Оба эксперимента иллюстрируются неверной программой

11_12_1.с.

ЗАДАЧА 11-13. Введите с клавиатуры символ и напечатайте (выведите на экран) битовое представление его кода.

Определим объединение code с двумя компонентами – переменная unsigned char ss и структура byte с восьмью битовыми полями единичной длины каждое.

/* 11_13.c - битовое

представление кода

символа */

#include <stdio.h>

 

 

 

int main ()

 

 

 

{

 

 

 

union { unsigned char ss;

:1;

struct { unsigned

b0

:1; unsigned b1

unsigned

b2

:1; unsigned b3

:1;

unsigned

b4

:1; unsigned b5

:1;

unsigned

b6

:1; unsigned b7

:1;

}byte;

}code; printf("\n symbol="); scanf("%c",&code.ss);

printf("\n Bit numbers: 7 6 5 4 3 2 1 0"); printf("\n Bit values : %d %d %d %d %d %d %d %d",

code.byte.b7,code.byte.b6,code.byte.b5,code.byte.b4,

484

code.byte.b3,code.byte.b2,code.byte.b1,code.byte.b0

); return 0;

}

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

symbol=j<ENTER>

Bit numbers: 7 6 5 4 3 2 1 0

Bit values : 0 1 1 0 1 0 1 0

В тексте программы обратите внимание на обращение к функции scanf("%c",&code.ss). Здесь аргумент – адрес переменной, входящей в объединение code. При выводе в функции printf() используются уточненные имена битовых полей структуры byte того же объединения (code.byte.b1,…)

ЗАДАНИЕ. Оформите в виде отдельной функции процедуру печати битового представления кода отдельного символа, реализованную в приведенной программе 11_13.c.

Вариант функции:

/* byteprint.c – функция печати битового кода символа */

#include <stdio.h>

void byteprint(unsigned char ch)

{

union { unsigned char ss;

struct { unsigned b0 :1; unsigned b1 :1; unsigned b2 :1; unsigned b3 :1; unsigned b4 :1; unsigned b5 :1; unsigned b6 :1; unsigned b7 :1;

}byte;

}bind;

bind.ss=ch;

printf("%u%u%u%u%u%u%u%u",

bind.byte.b7,bind.byte.b6,bind.byte.b5,bind.byte.b4,

bind.byte.b3,bind.byte.b2,bind.byte.b1,bind.byte.b0

);

}

485

ЗАДАНИЕ. Проиллюстрируйте применение функции byteprint().

/* 11_13_1.c - ввести символ и вывести его битовый код */

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

{

unsigned char symbol; printf("\n symbol="); scanf("%c",&symbol);

printf(" Bit code of symbol="); byteprint(symbol);

}

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

symbol=t<ENTER>

Bit code of symbol=01110100

ЗАДАЧА 11-14. Введите целое число и выведите на экран битовое представление его кода.

В начале Практикума (§4.6, задачи 04-19 – 04-22) мы уже использовали функцию, текст которой следует изучить именно в этой теме в связи с решением предлагаемой задачи:

/* intBitPint.c – функция печати битового кода значения типа int */

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

void intBitPrint(int integer)

{

union {

int ii;

ar[4];

}

unsigned char

bind;

 

bind.ii

= integer;

printf(" ");

byteprint(bind.ar[3]);

byteprint(bind.ar[2]);

printf(" ");

486

byteprint(bind.ar[1]); printf(" "); byteprint(bind.ar[0]); printf("\n");

}

Как видно из текста, функция intBitPrint() использует функцию byteprint(), предварительно включая текст ее определения препроцессорной директивой. В теле функции intBitPrint() определено объединение bind с двумя компонентами – переменной int ii и символьным массивом из четырех элементов. Предполагается, что размер объектов типа int – 4 байта. Значение аргумента присваивается компоненту объединения bind.ii. Затем с помощью обращений к функции byteprint() выводятся биты отдельных байтов этого компонента, доступных с помощью элементов массива из того же объединения. Порядок вывода соответствует представлению целых величин в процессорах фирмы Intel. Код выводится в удобном для восприятия виде – младшие разряды чисел размещаются справа.

В программу, решающую поставленную задачу, включим проверку размера данных типа int. Это защитит от получения неверных результатов, например, при использовании 16-разрядных компиляторов.

/* 11_14.c – вывести битовое представление целого числа */

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

{

int integer;

if (sizeof(int) != 4)

{printf("\nsizeof(int) != 4..."); return 0;

}

printf("\ninteger=");

scanf("%d",&integer);

printf("Bit code of integer:\n"); intBitPrint(integer);

return 0;

}

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

487

integer=16<ENTER> Bit code of integer:

00000000 00000000 00000000 00010000

integer=-16<ENTER> Bit code of integer:

11111111 11111111 11111111 11110000

ЗАДАНИЕ. Модифицируйте программу 11_14.с, чтобы она выполнялась как на 16-, так и на 32-разрядных компиляторах.

Низкая мобильность программ с битовыми полями привела к следующей рекомендации Кернигана Б. и Пайка Р. [4]: "Битовые поля настолько зависят от конкретных машин, что никому не следует их использовать".

11.5.Динамические информационные конструкции

ЗАДАЧА 11-15. Упорядочите по длине строки, читаемые из стандартного входного потока, и выведите их на печать в порядке увеличения длин.

Используем для решения задачи динамический односвязный список из структур-звеньев такого типа:

struct cell { int len;

struct cell * next; char * pLine;

};

Здесь pLine – указатель на строку; len – длина строки (без учета оконечного символа-ограничителя '\0'); next – указатель на следующее звено в связном списке структур типа struct cell.

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

488

дет к снижению быстродействия. Именно поэтому будем использовать связный список, лишенный указанного недостатка.

Алгоритм решения задачи можно описать таким псевдокодом:

Цикл до конца входного потока (EOF) Прочитать строку из входного потока

Создать структуру с очередной строкой из входного потока Включить структуру в упорядоченный список

Конец - цикла Печать сформированного списка

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

char *getline(void); – функция читает из входного потока очередную строку и сохраняет ее в динамически созданном массиве. struct cell *cellCreate(void); – функция для создания

структуры, содержащей очередную строку из входного потока. struct cell *inList(struct cell *list, struct

cell *new); – функция, включающая в список, адресованный указателем list, структуру (новое звено списка), адресованную указателем new. Функция возвращает указатель на начало списка. Алгоритм включения должен быть таким, чтобы список формировался как упорядоченная последовательность структур.

void printList(struct cell * list); – функция последовательной печати строк из списка. Параметр list – адрес начала списка. Печать (вывод на экран) выполняется в том порядке, в каком структуры помещены в список (по возрастанию длин строк).

Прежде чем переходить к программированию, необходимо принять решение о том, как (каким способом) будут представлены в программе строки, считываемые из входного потока, и структуры-звенья списка. Так как в данной теме мы не затрагиваем работу с файлами, то будем считать, что количество строк во входном потоке не особенно велико, т.е. все исходные данные и создаваемый список помещаются в основной памяти. Каждую строку из входного потока будем сохранять в отдельном динамически создаваемом символьном массиве. Длину такого массива будем брать равной длине прочитанной строки (с учетом символа-ограничителя '\0'). Структуры-звенья списка также будем формировать динамически, т.е. наш список бу-

489

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

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

char * getLine(void);

Ее назначение – прочитать строку из входного потока, создать динамический символьный массив для ее хранения и вернуть указатель на этот массив. При достижении конца файла (EOF) функция будет возвращать значение NULL. Основное отличие предлагаемой функции от определенной ранее функции readLine() состоит в том, что при каждом новом обращении к readLine() выполняется освобождение участка памяти, выделенного при предыдущем обращении. В readLine() это было сделано для предотвращения "утечки памяти" при многократных обращениях. Функция getLine(), наоборот, при каждом обращении выделяет для строки новый участок памяти, сохраняя все предыдущие. Для нашей задачи именно это и нужно.

/* getLine.c - функция чтения строки в динамический массив */

#include <stdlib.h> #include <stdio.h> #include <string.h> #define S_AR 4000 char * getLine(void)

{

int len;

char strAr[S_AR]; char * pstr;

if(gets(strAr) == NULL) return NULL;

len = strlen(strAr); if (len > S_AR-1)

{puts("\nERROR! String is too long!"); exit(1);

}

pstr = (char *) malloc(len+1); if (pstr == NULL)

{ puts("\nERROR! No memory!");

490