книги / Программирование на языке Си
..pdf312 |
Программирование на языке Си |
3.Если для очередного звена списка вводится нулевое значе ние элемента weight, то звено не подключается к списку, процесс формирования списка заканчивается. Такой набор данных, введенных для элемента очередного звена, счита ется терминальным.
4.Если нулевое значение элемента weight введено для перво го звена списка, то функция input( ) возвращает значение NULL.
5.Список определяется рекурсивно как первое (головное) звено, за которым следует присоединяемый к нему список.
6.Псевдокод рекурсивного алгоритма формирования списка:
Если введены терминальные данные для звена Освободить память, выделенную для звена Вернуть нулевой указатель
Иначе:
Элементу связи звена присвоить результат формирования продолжения списка (использовать тот же алгоритм)
Все-если Вернуть указатель на звено.
Текст функции input( ) на языке Си почти полностью соот ветствует приведенному псевдокоду и учитывает перечислен ные конструктивные решения. Для структуры типа struct cell выделяется память. После того как пользователь введет значе ния данных, выполняется их анализ. В случае терминальных значений библиотечная функция free(p) освобождает память от ненужного звена списка и выполняется оператор return NULL;. В противном случае элементу связи (указатель struct cell * р) присваивается результат рекурсивного вызова функции input(). Далее все повторяется для-нового экземпляра этой функции. Когда при очередном вызове input() пользователь введет тер минальные данные, функция input() вернет значение NULL, которое будет присвоено указателю связи предыдущего звена списка, и выполнение функции будет завершено.
Глава 6. Структуры и объединения |
313 |
Функция рекурсивного просмотра и печати списка имеет та кой прототйп:
void output (struct cell * p);
Исходными данными для ее работы служит адрес начала списка (или адрес любого его звена), передаваемый как значе ние указателя - параметра struct cell * р. Если параметр имеет значение NULL - список исчерпан, исполнение функции пре кращается. В противном случае выводятся на экран значения элементов той структуры, которую адресует параметр, и функ ция output() рекурсивно вызывается с параметром р -> рс. Тем самым выполняется продвижение к следующему элементу спи ска. Конец известен - функция печатает "Список исчерпан!" и больше не вызывает сама себя, а завершает работу.
Текст программы с рекурсивными функциями:
/* Определение структурного типа "Звено списка":*/ struct cell
{
char sign[10]; int weight; struct cell * pc;
} ;
#include <stdlib.h> #include <stdio.h>
/* Функция ввода и формирования списка: */ struct cell * input(void)
{
struct cell * p;
p=(struct cell *)malloc(sizeof(struct cell)); printf("sign=");
scanf("%s",b p->sign); printf("weight="); scanf("%d",b p->weight); . if (p -> weight == 0)
{
free (p); return NULL;
}
314 |
Программирование на языке Си |
р -> pc = input(); /* Рекурсивный вызов */ return р;
}
/* Функция "печати" списка: */ void output(struct cell *p)
{
if (p == NULL}
(
printf("\пСписок исчерпан!"); return;
>
printf("\nsign=%s\tweight=%d",p->sign, p->weight);
output(p -> pc); /* Рекурсивный вызов */
}
void main()
{
struct cell * beg=NULL; /* Начало списка */ printf("\пВведите данные структур:\n"); beg=input(); /* Ввод списка. */ /♦Напечатать список: */ printf("\пСодержимое списка: "); output(beg);
)
Возможный результат выполнения программы:
Введите данные структур: Sign=Zoro
weight=1938
Sign=Zodiac
weight=1812
Sign=0
weight=0
Содержимое списка: Sign=Zoro weight=1938 Sign=Zodiac weight=1812 Список исчерпан!
Глава 6. Структуры и объединения |
315 |
6.5. О бъединения и битовы е поля
Объединения. Со структурами в "близком родстве" находят ся объединения, которые вводятся (описываются, определяют ся) с помощью служебного слова union.
Объединение можно рассматривать как структуру, все эле менты которой имеют нулевое смещение от ее начала. При та ком размещении разные элементы занимают в памяти один и тот же участок. Тем самым объединения обеспечивают возмож ность доступа к одному и тому же участку памяти с помощью переменных (и/или массивов и структур) разного типа. Необхо димость в такой возможности возникает, например, при выде лении из внутреннего представления целого числа его отдельных байтов. Для иллюстрации сказанного введем такое объединение:
union {
char hh[2]; •int ii;
} CC;
Здесь:
•union - служебное слово, вводящее тип данных "объединение" или объединяющий тип данных;
•СС - имя конкретного объединения;
•символьный массив char hh[2] и целая переменная int ii - элементы (компоненты) объединения.
Схема размещения объединения СС в памяти ЭВМ приведе на на рис. 6.4.
Для обращения к элементу объединения используются те же конструкции, что и для обращения к элементу структуры:
имя объединения. имяэлемента ( *указатель на объединение ). имя элемента
указатель на объединение -> имя элемента
Смысловое отличие объединения от структуры состоит в том, что записать информацию в объединение можно с помо щью одного из его элементов, а выбрать данные из того же уча
316 |
Программирование на языке Си |
стка памяти можно с помощью другого элемента того же объе динения. Например, оператор
C C .ii = 1 5 ;
записывает значение 15 в объединение, а с помощью конструк ций CC.hhfO], CC.hhfl] можно получить отдельные байты внут реннего представления целого числа 15.
Определение объединения: union {char hh[2]; int ii; } cc;
(Массив символов)
Участок памяти, выделенный объединению СС:
(Целая переменная)
Рис. 6.4. Схема размещения объединения в памяти
Как и данные других типов, объединение - это конкретный объект, которому выделено место в памяти. Размеры участка памяти, выделяемого для объединения, должны быть достаточ ны для размещения самого большого элемента объединения. В нашем примере элементы int ii и char hh[2] имеют одинаковую длину, но это не обязательно. Основное свойство объединения состоит в том, что все его элементы размещаются от начала од ного и того же участка памяти. А размеры участка памяти, от водимого для объединёния, определяются размером хамого большого из элементов. Например, для объединения
u n io n
{
d o u b le |
d d ; |
|
f l o a t |
|
a a ; |
i n t |
j j |
; |
} u u ;
Глава 6. Структуры и объединения |
317 |
Размеры объекта-объединения uu равны размерам самого большого из элементов, т.е.:
sizeof(uu) == sizeof(double)
Объединяющий тип. В рассмотренных примерах определе ны объединения, но явно не обозначены объединяющие типы. Именованный объединяющий тип вводится с помощью опреде ления такого вида:
union имя объединяющего типа
{
определенияэлементов
};
где union - спецификатор типа;
имя объединяющего типа - выбираемый программистом идентификатор;
определение элементов - совокупность описаний объек тов, каждый из которых служит прототипом одного из элемен тов объединений вводимого объединяющего типа.
Конструкция union имя объединяющего типа играет роль имени типа, т.е. с ее помощью можно вводить объединенияобъекты.
Как и для структурных типов, с помощью typedef можно вводить обозначения объединяющих типов, не требующие при менения union:
typedef union имя объединяющего типа
{
определенияэлементов } обозначение объединяющего типа;
Пример:
typedef union uni
.{
double dou; int in[4]; char ch[8];
} uni name;
318 |
Программирование на языке Си |
На основе такого определения типа можно вводить конкрет ные объединения двумя способами:
union uni snow, ray; uni_name yet, zz4;
Объединения не относятся ни к скалярным данным, ни к данным агрегирующих типов.
Пример. Для демонстрации возможностей, предоставляемых объединениями, рассмотрим функцию bioskey() из стандартной библиотеки (прототип функции в заголовочном файле bios.h) компилятора Turbo С:
int bioskey(int) ;
В MS-DOS принято, что каждое нажатие на любую значи мую клавишу клавиатуры ПЭВМ приводит занесению в буфер клавиатуры двух байтов, младший из которых (по адресу) со держит ASCII-код клавиши, а старший содержит код, называе мый скэн-кодом клавиши. Функция bibskey() имеет один параметр, определяющий режим ее выполнения. При обраще нии bioskey(O) функция при пустом буфере ожидает нажатия любой клавиши, т.е. появления в буфере некоторого кода. Если в буфере уже есть коды, то bioskey(O) выбирает из буфера кла виатуры очередной двухбайтовый код и возвращает его в виде целого числа. Обращение bioskey(l) позволяет проверить нали чие хотя, бы одного кода в буфере. Если буфер не пуст, bioskey(l) возвращает значение очередного кода из буфера, но не удаляет этот код из буфера. '
Следующая программа позволяет получать и печатать ука занные выше коды, поступающие в буфер клавиатуры ПЭВМ, работающей под управлением операционной системы MS-DOS:
/* Печать scan и ASCII кодов клавиатуры IBM PC*/ #include <stdio.h> /* Для функции printf( ) */ #.include <bios.h> /* Для функции bioskey ( )*/
void main( )
{
union '
{
Глава 6. Структуры и объединения |
319 |
|||
char hh[2]; |
|
|
||
int |
ii; |
|
|
|
} cc; |
char |
sen, |
asc; |
|
unsigned |
||||
printf("\n\n Выход |
no Ctrl+Z."); |
|||
printf("\n Нажав клавишу, получите ее" |
||||
" двубайтовый код.\п"); |
||||
printf("\n |
SCAN |
!| ASCII"); |
||
printf("\n |
(10) |
(16) |
(10) (16)"); |
|
do /* Цикл до Ctrl+Z |
*/ |
|||
{ |
|
|
|
|
printf("\n"); |
|
|
||
cc.ii=bioskey(0); |
|
|||
asc=cc.hh[0]; |
|
|
||
scn=cc.hh[l]; |
|
|!%4d %3xH |",scn, sen, |
||
printf |
(" |
%4d %3xH |
||
) |
asc, asc); |
|
||
|
|
|
|
while(asc != 26 || sen != 44);
}
Обратите внимание на спецификацию преобразования %х, она предназначена для шестнадцатеричного представления пе ресылаемого значения. Для каждого байта, получаемого из бу фера клавиатуры, печатается его десятичное и шестнадцате ричное представления. Выход из программы осуществляется при нажатии клавиш Ctrl+Z. В этом случае в программу посту пает двухбайтовый код "44+26". Нажатие любой другой клави ши вызывает чтение очередного кода.
Пример результатов работы с программой:
Выход по Ctrl+Z.
Нажав клавишу, получите ее двубайтовый код.
(10) |
SCAN |
|| |
ASCII |
|
(16) |
11 |
(Ю) |
(16) |
|
16 |
Ю Н |
|I |
113 |
71Н |
17 |
11Н |
|1 |
119 |
77Н |
18 |
12Н |
|I |
101 |
65Н |
19 |
13Н |
I! |
114 |
72Н |
320 |
|
Программирование на языке Си |
||
20 |
14Н |
|1 |
.И 6 |
74Н |
21 |
15Н |
!I |
121 |
79Н |
55 |
37Н |
|1 |
42 |
2ah |
40 |
28Н |
!| |
39 |
27Н |
39 |
27Н |
|1 |
'59 |
ЗЪН |
38 |
26Н |
|! |
76 |
4сН |
44 |
2сН |
|1 |
26 |
1аН |
Вданной программе используется объединение с именем сс,
вкоторое с помощью целочисленного элемента cc.ii помещает результат функция bioskey(O). Затем отдельные байты этого це лого числа (для доступа к которым используется char hh[2] - второй элемент объединения) печатаются как скэн- и ASCII-
коды. Цикл do будет прерван при появлении ASCII-кода 26 и скэн-кода 44 (сочетание клавиш Ctrl и Z), после чего программа заканчивает работу.
В объединения, кроме переменных и массивов, могут вхо дить и структуры. Однако, прежде чем привести содержатель ный пример, остановимся на еще одном важном понятии - на битовых полях.
Битовые поля. Битовое поле может быть только элементом структуры или объединения и вне объектов этих типов не встречается. Битовые поля не имеют адресов и не могут объе диняться в массивы. Назначение битовых полей - обеспечить удобный доступ к отдельным битам данных. Кроме того, с по мощью битовых полей можно формировать объекты с длиной внутреннего представления, не кратной байту. Это позволяет плотно "упаковывать" информацию, что экономит память.
Описание структуры с битовыми полями должно иметь такой формат:
struct { m u n i |
имя_поля_1 |
: |
ширина поля_1; |
тип_2 |
имя_поля_2 |
: |
ширина_поля_2; |
} имяструктуры ;
где mun i - тип поля, который может быть только int, воз можно, со спецификатором unsigned или signed;
Глава 6. Структуры и объединения |
321 |
ширина поля - целое неотрицательное десятичное число, значение которого обычно (в зависимости от реализации ком пилятора) не должно превышать длины слова конкретной ЭВМ.
Разрешается поле без имени (для чего указываются только двоеточие и ширина), с помощью которого в структуру вводятся неиспользуемые биты (промежуток между значимыми полями). Нулевая ширина поля вводится, когда необходимо, чтобы сле дующее в данной структуре поле разместилось с начала очеред ного слова конкретной ЭВМ.
Вместо служебного слова struct может употребляться union.
Вэтом случае определяется объединение с битовыми полями. Для обращения к полям используются те же конструкции,
что и для обращения к обычным элементам структур:
имя структуры. имя_поля_х (* указатель на структуру). имя_поля_1
указатель на структуру -> имя поля г
Например, для структуры хх с битовыми полями:
struct {
int а:10; int b:14;
} хх;
правомочны такие операторы:
хх.а=1; хх.Ь=4 8; или |
хх.а=хх.Ь=0; |
От реализации зависит порядок размещения в памяти полей одной структуры. Поля могут размещаться как слева направо, так и справа налево. Для компиляторов, работающих под управ лением MS-DOS на IBM PC, поля, которые размещены в начале описания структуры, имеют младшие адреса. Таким образом, для примера:
struct {
int х :5;
int у :3; } hh; hh.x=4; hh .y=2;
2 1 - 3 1 2 4