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

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

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

312

Программирование на языке Си

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

Соседние файлы в папке книги