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

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
238
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

170

Часть i« Введение в програг^^^ировоние на С^-Ф

 

 

 

 

strcpy(c.address,"72 Main, Anytown, MA");

 

 

 

 

с. acct. number = 800123456L;

/ /

тип

long int

 

c.accnt.balance = 532.841 c.acct.overdue = 0;

/ /

тип

double

Здесь селектор-точка также ассоциируется слева направо, а читается справа налево, поэтому, например, с.acct. balance означает поле balance (типа double) поля accnt (типа Account), которые принадлежат переменной-структуре с (типа Customer).

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

Операции с переменными-структурами

Переменные-структуры одного типа можно присваивать друг другу. Значения полей исходной переменной по битам копируется в поля целевой переменной:

а2 = a1; с.acct = a1;

// один и тот же тип (Account)

Это эквивалентно следующему набору операций присваивания:

а2.number

= a1. number;

 

а2.balance

= а2.balance; а2.overdue = a1.overdue;

a.acct.number

= a1.number; сacct.balance

= a1.balance;

сacct.overdue

= a1.overdue;

 

Здесь вступают в силу свойства C++ как языка с сильным контролем типов. Преобразование между структурами разных типов не допускается. Имена типов должны быть одинаковыми:

с = a1; a1 = с;

/ /

нет, разный тип

a1 = 800123456L;

/ /

даже и не думайте!

Вопрос здесь в имени типа, а не в составе структуры. Предположим, имеется структура с тем же составом, что и Account:

struct

FrozenAcct

 

{ long

number;

/ / та же структура, что и Account

double balance, overdue;

} ;

Структуру FrozenAcct нельзя присваивать структуре Account, и наоборот:

FrozenAcct fa; fa = a1;

/ / нет, имена типов разные

В C++ нет также сравнения структур, так как C++ не знает, какие поля исполь­ зовать для сравнения и как это делать. Если нужно сравнить переменные струк­ турного типа, придется написать для этого программный код в соответствии с требованиями приложения:

i f

(a1. number > а2. number)

/ /

поменять счета с номерами заказов

{

аЗ = a1; a1 = а2; а2 = аЗ; }

/ /

аЗ содержит временные данные

Следующий пример показывает некоторые графические вычисления. Посколь­ ку графические функции не переносимы, в данном примере не используется вывод на экран. Программа просит пользователя ввести координаты конечных точек двух отрезков — АВ и CD. Затем вычисляется длина каждого отрезка и угол между отрезком и осью х.

Введите координаты X и у точки А: 20 20
Введите координаты X и у точки В: 80 80
Введите координаты X и у точки С: 20 20
Введите координаты X и у точки D: 160 80
Длина АВ равна 84, а угол равен 45 градусов Длина СО равна 152, а угол равен 23.1986 градусов
Рис. 5.12. Результат выполнения примера программы из листинга 5.12

Глава 5 • Агрегирование с помощью типов, опредедяе1^1ых nporpoi^iwiHCToivi

| 171 щ

Исходный код этого примера приведен в листинге 5.12. В программе использу­ ются два типа — Point и Line. Она запрашивает ввод данных в пикселях (целые значения), инициализирует с их помощью координаты точки, затем инициализи­ рует линии, вычисляет д/шны и углы. Функции sqrtO и atan2() относятся к заго­ ловочному файлу math. h. Они вычисляют квадратный корень и арктангенс своих параметров типа double. Поскольку их фактические аргументы определены как

int, они неявно преобразуются в double. Длина отрезка выражается в пикселях (целые), поэто­ му результат вычисления квадратного корня не­ явно преобразуется. Чтобы избежать усечения,

| для правильного округления к длине добавляет­ ся 0,5. Переменная coef f позволяет преобразо­ вать углы из радианов в градусы. На рис. 5.12 показаны результаты выполнения рассматрива­ емой программы.

Листинг 5.12. Использование директивы #1пс1ис1е для типов, определяемых программистом

#include

<iostream>

 

 

// для sqrtO и atan2()

#inclucle

<cmath>

 

 

 

#inclucle

"point.h"

 

 

// чтобы тип Point былизвестен компилятору

#inclucle

"line . h"

 

 

// чтобы компилятору был известен тип Line

using

namespace std;

 

 

 

int main ()

 

 

 

 

{

 

 

 

 

 

 

const double coeff = 180/3.1415926536;

// типы, определяемые программистом

Point pi, p2; Line line1, line2;

int diffX, diffY, lengthl, length2;

 

double anglel, angle2;

 

 

cout «

"Введите координаты x и у точки А: '

cin »

р1.х »

р1.у;

 

 

cout «

"Введите координаты х и у точки В: '

cin »

р2.х »

р2.у;

 

 

line1.start = pi; linel.end = p2;

 

cout «

"Введите координаты x и у точки С: '

cin »

pl.x »

pi.у;

 

 

cout «

"Введите координаты х и у точки D: '

cin »

р2.х »

р2.у;

 

 

line2.start = pi; line2.end = p2;

// неприглядная запись

diffX = linel.end.x - linel.start.x;

diffY = linel.end.у - linel.start.у;

 

lengthl = sqrt(diffX*diffX + diffY*diffY) + 0.5;

anglel = atan(diffY,diffX) *coeff;

", a угол равен

cout

«

"Длина AB равна " «

lengthl «

 

«

anglel «

" градусов\п";

 

diffX = line2.end.x - line2.start.x;

 

diffY = line2.end.y - line2.start.y;

 

length2 = sqrt(diffX*diffX + diffY^^diffY) + 0.5;

angle2 = atan(diffY,diffX) *coeff;

", a угол равен

cout «

"Длина CD равна " «

length2 «

 

«

angle2 «

" градусов\п";

 

return 0;

172

Часть I * Введение в орограг^л?

Определение структур в многофайловых программах

Для небольших программ, как в листинге 5.12, вполне разумно включить опре­ деления структур в исходный код. Типы, определяемые программистом, должны быть уникальны в пространстве имен программы. Таким образом, если нужно использовать определяемый программистом тип в разных файлах, программа, состоящая из нескольких файлов, может представлять проблему. Не пытайтесь повторять в файлах определение типа. Если эти определения будут обновляться в процессе сопровождения программы, они легко станут несогласованными.

Решение состоит в том, чтобы поместить каждое определение в отдельный заголовочный файл и включать эти файлы во все исходные файлы программы, где задействован данный тип. Именно это было сделано с типами Point и Line

влистинге 5.12. Обратите внимание на двойные кавычки в именах файлов (вместо угловых скобок). Обычно для заголовочного файла и определяемого в данном файле типа используется одно и то же имя. Поскольку все имена типов в програм­ ме С+4- обязаны быть уникальными, при хранении файлов в одном каталоге не должен возникать конфликт имен. Часто заголовочные файлы хранятся в отдель­ ном каталоге, отличном от того, где находится выполняемый файл. В этом случае

вдирективе #inclucie следует указать полное имя маршрута.

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

влистингах 5.13 и 5.14.

Ли с т и нг 5.13. Заголовочный файл "point, h"

#ifnclef _POINT #ciefine _POINT struct Point

{ int X, y; } ; #endif

Листинг 5.14. Заголовочный файл "line, h'

#ifnclef

_LINE

#clefine

_LINE

#inclucle "point, h"

struct

Line

{ Point

start, end; } ;

#endif

 

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

Обратите внимание, что файл "line.h" должен включать файл "point, h". В противном случае, компилятор может не знать, что означает имя типа Point в файле "line. h".

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

Глава 5 • Агрегирование с пог^ощью типов, опрвАВАяемых профа1^^истог^

| 173 р

не соотносящ,ихся с другими полями. Еще один важный момент: каждая перемен­ ная-структура должна иметь полное определение полей, доступных с помощью записи с точкой и объявленных имен. Наконец, каждое поле (в записи с точкой) представляет собой просто переменную типа, заданного для этого поля в опреде­ лении структуры.

Объединения, перечисления и битовые поля

Данный раздел должен быть относительно короток. В нем обсу>едаются три идеи, связанные с именованием программных элементов для удобства программиста. Первая идея состоит в определении переменной, которую можно использовать для хранения информации нескольких типов, например целого и числа с плавающей точкой. Именно такова идея объединения — union. (В отличие от структуры, его объекты могут иметь различные типы в разные моменты времени.— Прим. пер.) Вторая идея заключается в определении символических имен для родственных констант, не вдаваясь в детали присваивания этим символическим константам числовых значений. Такова идея перечисления — enum. Третья идея состоит в именовании фрагментов данных, чтобы с ними можно было работать отдельно. Это битовые поля.

В C++ данные идеи реализуются аналогично структурам. Программист исполь­ зует в начале определения типа ключевое слово (union, enum или struct). Затем он вводит выбранное для нового типа имя и описывает состав типа (в фигурных скоб­ ках с завершающей точкой с запятой). После этого определенное программистом имя может использоваться в программе как имя типа.

Объединения

Рассмотрим массив структур типа Number с некоторой произвольной числовой информацией.

Number num[6];

Поскольку любое число является здесь допустимым, следует использовать не­ числовое контрольное значение, например строку "end" или что-то еще. Поддер­ живаемый языком C++ строгий контроль типов не позволяет хранить текстовую информацию в числовом поле. Возможный вариант решения данной проблемы — в определении структуры с двумя полями, одно из которых содержит числовое значение, а другое текст. Определение имеет следующий вид:

struct Number

{double value; char text[4]; } ;

Теперь можно использовать элементы этого типа в массиве, сохраняя в поле value числовую информацию и используя поле text для контрольного значения (например "end"), указывающего конец допустимых данных в массиве. Между тем для каждого элемента массива используется только одно поле. Элемент является либо допустимым числовым значением, либо текстовым контрольным. С помощью определения union C++ позволяет избежать нерационального расходования па­ мяти. Ключевое слово union определяет новый тип. При этом используется тот же синтаксис, что и при определении структур. Если полей несколько, они дают альтернативное представление одной и той же области в памяти. Вот как будет выглядеть определение union в данном примере:

union

Number

/ /

еще одно ключевое слово C++

{ double value;

/ /

можно определять любое число полей

char

t e x t [ 4 ] ; } ;

/ /

не забывайте про точку с запятой!

174Часть I • Введение в программирование но С^-Ф

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

итой же области в памяти. Когда определяется переменная union, выделяется столько памяти, чтобы вместить ее самую длинную интерпретацию. Затем про­ грамма может выбирать, какую именно интерпретацию использовать: с плаваю­ щей точкой или массив символов. Если кто-то сделает ошибку и сохранит данные одного типа, а затем прочитает их как другой тип, то результат будет бессмыслен­ ным. Как обычно, компилятор С+4- не проверяет за программиста, правильно ли он работает с памятью.

Вследующем примере числовое значение помещается в переменную-объеди­ нение п1, а строка — в переменную-объединение п2, но затем содержимое п1 отображается на экране как текст, а содержимое п2 — как числовое значение. Нехорошо, конечно, но ведь это свободная страна. Пример показывает также, что можно вполне законно хранить текст в том месте, где используется числовое значение, и наоборот:

Number п1, п2;

 

 

 

 

 

 

 

/ /

принятие обязательств

п1.value = 5.0;

strcpy(n2.text, "по");

 

cout

«

n1.value

«

"

"

«

n2.text

«

endl;

/ /

это нормально

cout

«

nl . text

«

"

"

«

n2.value

«

endl;

/ /

катастрофа

strcpy(n1.text,

"yes"); n2.value

= 25.0;

/ /

старое содержимое исчезает

cout

«

nl . text

«

"

"

«

n2.value

«

endl;

/ /

теперь ОК

11

21

31

end

Text as double: 3.57452e-031

Рис . 5 . 1 3 . Вывод

программы из листинга 5.15

Пример показывает, что при работе с переменными-объедине­ ниями используется запись, аналогичная операциям со структура­ ми. В листинге 5.15 представлена иллюстрируюш^ая это короткая программа. Все выглядит так, будто поля text первых трех компо­ нентов массива пит[ ] не были инициализированы, как и поле value последнего использованного компонента. На самом деле обе ин­ терпретации требуют одинакового объема памяти. (Компилятор выделяет для каждого элемента одну и ту же память — достаточ­ ную Д/1Я последней интерпретации.) Вывод программы показан на рис. 5.13.

Листинг 5.15. Применение union для хранения в переменной значений разных типов

#include

<iostream>

// чтобы тип Number был известен компилятору

#inclucle

"number, h"

#inclucle

<cstring>

 

using namespace std;

 

int main

()

 

{

 

// массив переменных union

Number num[6]; int i = 0;

num[0].value = 11.0; num[1].value = 21.0;

// инициализация

num[2].value = 31.0; strcpy(num[3].text,"end");

while(strcmp(num[i].text, "end") !=0)

// итерация

cout « num[i++].value « endl;

// для иллюстрации

cout «

num[i].text « endl;

cout «

"Текст как double: " « num[i].value « endl;

return 0;

Глава 5 • Агрегирование с помощью типов, определяемых программистом

175 J

Doe, John

15 Oak Street

Anytown, MA02445

King, Amy

P.O.B. 761

Anytown, MA02445

Рис. 5.14.

Вывод программы из листинга 5.16

Пусть вас не пугает обозначение num[0]. value. Массив num[ ] содержит компо­ ненты типа Number, следовательно, num[0] имеет тип Number и содержит поля с именами value и text. Когда используется поле text, это символьный массив, т. е. можно передать num[i].text в качестве аргумента функции strcmp(). Уве­ личение индекса при выводе содержимого поля (как в num[i++].value) вполне законно и целесообразно. Так как индекс i используется здесь только один раз, порядок вычисления нарушен не будет.

Две последние строки программы показывают, как выглядит одно и то же значение при разной интерпретации. Ошибочное использование объединений ведет к получению бессмысленных данных. Заметим, что значение 3.57452е-031' может быть в программе вполне законным значением с плавающей точкой и ин­ терпретироваться как "end" для завершения итерации.

Чтобы избежать ошибок в интерпретации, некоторые программисты приме­ няют в объединениях так называемые поля тегов. Теговое поле не может быть компонентом объединения, поэтому объединение и теговое поле нужно включить в более крупную структуру. Например, пусть адрес содержит три строки, вторая и третья строка — это дом/название улицы и номер почтового отделения. В листинге 5.16 приведена программа, определяюш^ая адрес структуры с полем объединения second и теговым полем kind. Когда теговое поле равно О, вторая строка интерпретируется как дом и назва­ ние улицы, а когда она равна 1 — как номер почтового отделения.

В программе соблюдается данное соглашение при установке значений данных (присваиванием kind значения О и 1) и при использовании данных (проверкой значения поля kind). Вывод программы представлен на рис. 5.14.

Листинг 5.16. Использование объединения с полем тега для обеспечения целостности данных

#include <iostream> #include <cstring> using namespace std;

union StreetOrPOB

 

 

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

{ char

street[30];

 

 

long int РОВ; } ;

 

 

 

 

struct

Address

 

 

 

 

{ char

first[30];

 

 

//О - дом/улица, 1 - почтовое отделение

int kind;

 

 

 

StreetOrPOB second;

 

// тот же или другой

смысл

char third[30]; }

;

 

 

 

int main ()

 

 

 

 

 

Address a1, а2;

 

John");

//дом/улица

 

strcpy(a1. first, "Doe,

 

strcpy(a1. second.street,"15 Oak Street"); al.kind = 0;

 

strcpy(a1.third,"Anytown, MA 02445");

 

 

strcpy(a2.first,"King,

Amy");

// адрес с почтовым

отделением

a2.second.РОВ = 761; a2.kind = 1 ;

strcpy(a2.third,"Anytown,

MA 02445");

 

 

cout «

a1.first «

endl;

// проверка интерпретации данных

if (al.kind

==0)

 

 

cout «

a1.second.street « endl;

 

 

I 176 I Часть I * Введение в nporpar^i^i^

else

cout

«

"P.O. В "

«

a1. second. РОВ «

endl;

cout

«

a1.third

«

endl;

 

cout

«

endl;

 

 

 

 

cout

«

a 2 . f i r s t

«

endl;

 

i f (a2.kind == 0)

 

 

 

/ / проверка интерпретации данных

cout

«

a2.second.street « endl;

 

else

 

 

 

 

 

 

 

cout

«

"P.O.B. " «

a2. second. РОВ «

endl;

cout

«

a2.third

«

endl;

 

return

0;

 

 

 

 

 

Bee хорошо, HO приходится вводить еще один уровень иерархической струк­ туры типов. В результате программисту придется применять такие имена, как a1. second, street, и это не радует. Между тем, единственное назначение типа StreetOrPOB — использование с типом A'ddress. Избавиться от-этого можно с по­ мощью анонимных объединений C+ + . У них нет имени, и переменные данного типа определять нельзя, однако к полям таких объединений можно обращаться без каких-либо уточнений. Например, удобно определить тип Address не с помощью типа StreetOrPOB, а посредством анонимного объединения:

struct

Address

 

 

 

 

 

 

{ char

f i r s t [ 3 0 ] ;

 

 

 

 

 

 

int

kind;

 

 

/ /

0 -

адрес (улица), 1 -

почтовый ящик

union

 

 

 

 

 

 

 

{ char

street[30];

 

 

 

 

long

int РОВ;

}

;

/ /

нет

"второго" (second)

поля типа StreetOrPOB

char

third[30];

}

;

 

 

 

 

Тип объединения исчез, но тип Address имеет теперь два альтернативных поля — street[] и РОВ. Как и на любое другое поле, на них можно ссылаться по имени. Конечно, программист должен знать, что есть что. Мухи отдельно, котлеты отдельно. Данные должны считываться корректно, установленным способом. Однако дополнительный уровень иерархии теперь не нужен:

i f (al.kind == 0)

 

 

/ /

проверка интерпретации данных

cout

«

a1.street

«

endl;

/ /

использование одной интерпретации

else

 

 

 

 

 

 

cout

«

"P.O.B. "

«

al.POB « endl;

/ /

или применение другой

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

Перечисления

Тип "перечисление" позволяет программисту определять переменные, в кото­ рых сохраняются значения только из определенного набора идентификаторов. Обычно вводятся целочисленные символические константы (с помощью #define или определений const) и принимается соглашение по их использованию. Напри­ мер, для эмуляции поведения светофора нужны значения, обозначающие красный, зеленый и желтый свет. Как в примере с днями недели, можно ввести массив

Глава 5 • Агрегирование с помощью типов, определяемых nporpaivi^HCTO^

177

символов "red" (красный), "green" (зеленый) и "yellow" (желтый), а затем /уш

присваивания и сравнения использовать библиотечные функции.

 

char l i g h t [ 7 ] = {

"green"

};

/ /

сначала

- зеленый

 

i f (strcmp(light,

"green")

== О

/ /

далее

-

желтый

 

strcpy(light, "yellow");

 

/ /

и т.

д.

 

 

Хорошо и понятно — при сопровождении программы не будет проблем с ин­ терпретацией намерений разработчика, но эти операции со строками выполня­ ются довольно медленно. Зачем перемещать столько символов (в библиотечной функции в поиске конца строки) только для того, чтобы отследить состояние светофора? Еще одним недостатком такого решения является отсутствие защиты. Если кто-то захочет сделать свет розовым или малиновым, то у программиста нет способа помешать этому.

Другое решение состоит в использовании целых чисел, обозначающих цвета по номерам. Можно присвоить О — зеленому, 1 — красному, 2 — желтому. Обратите внимание, как вводятся эти значения: О, 1 и 2, а не 1, 2 и 3. Это связано

с особенностями массивов

C + +

и индексов. Когда программист, привыкший

писать программы на C + +

или

на С, считает людей в комнате, он говорит:

"Ноль, один, два...".

 

 

 

 

Такой подход позволяет избежать использования функций работы со строками:

int

light

= 0;

/ /

сначала -

зеленый

i f

(light

== 0)

/ /

следующий

- желтый

light = 2;

/ /

и т. д.

 

Преимуществом такого подхода является скорость, но это единственное его преимущество. Такой стиль программирования всегда требует комментариев, особенно в сложных алгоритмах и при большом числе состояний. Если коммен­ тарии слишком загадочные или устаревшие, то сопровождающему приложение программисту придется, мягко говоря, не просто.

Один из способов сделать код более читабельным и при этом сохранить высокую скорость работы программы состоит в использовании символических констант. Можно определить символические константы с подходящими для при­ ложения именами, например RED, GREEN и YELLOW, а затем присвоить им целые значения.

const int RED=0, GREEN=1, YELL0W=2;

/ / константы цветов

Теперь нетрудно переписать приведенный выше пример с помощью данных констант. Такой код выполняется так же быстро, как и предыдущий, но при этом столь же понятен, как пример с символьными строками:

int

light

= GREEN;

/ /

сначала -

зеленый

i f

(light

== GREEN)

/ /

следующий

- желтый

light = YELLOW;

/ /

и т. д.

 

Подобное решение защитит программу от внесения ошибок при сопровождении. Если программист захочет вместо символических констант использовать числа, это будет синтаксическая ошибка. Однако если переменной light будет присвоено значение вне согласованного для набора цветов диапазона (например, light = 42), то синтаксической ошибки не будет. Можно складывать эти значения (RED + GREEN) и выполнять разные другие операции, которые невозможно выполнять с цветами.

Перечисления введены в язык как раз для решения таких проблем. Програм­ мист может определить тип и явно перечислить все допустимые для переменной значения. Для такого определяемого программистом типа (например. Color) используется ключевое слово enum и делается это аналогично применению ключе­ вого слова struct (или union). Перед фигурными скобками (за которыми ставится

tj 178 I

Чость I« Введение в програттироваиив на C++

точка с запятой) указывается имя типа (подобно struct и union). В фигурных скобках программист перечисляет все допустимые для определяемого типа значе­ ния. Часто программисты используют буквы в верхнем регистре (как для констант, определяемых в #clefine или в const). Например, нетрудно определить тип Color как enum:

enum Color { RED, GREEN, YELLOW } ;

/ / цвет как тип

Теперь можно использовать тип Color для определения переменных, для которых допускаются только значения RED, GREEN, YELLOW. Эти значения являются перечислимыми константами. Их можно применять только как неизменяемые г-значения:

Color light = GREEN;

/ /

сначала

- зеленый

i f (light == GREEN)

/ /

далее -

желтый

light = YELLOW;

/ /

и т. д.

 

Такое решение устраняет проблему: для значений перечисления допустимы только операции присваивания и отношения. Их нельзя использовать для вводавывода, но можно проверять на равенство или неравенство, сравнивать (больше, меньше):

i f (light > RED) cout « "True\n";

/ / выводит 'True'

Причина в том, что перечисления реализованы как целые. Первое значение в списке перечисления — О (что для CH--f неудивительно), далее 1 и т. д. Данная программа может обраш,аться к этим значениям путем приведения их типа (как значений перечислимого типа) к целым:

cout « ( i n t ) light « endl; / / выводит О, 1 или 2

Если программист захочет изменить данное значение на другое, то можно сделать это явно в списке перечисления:

enum Color { RED, GREEN=8, Yellow } ; / / YELLOW теперь равно 9

После этого присваивание значений возобновляется (YELLOW равно 9 и т.д.). Если по какой-то причине программист захочет установить GREEN в О, то про­ грамма не сможет различить RED и GREEN (не проблема, если не надо управлять движением на дорогах).

Такая техника полезна, когда значения перечисления планируется использо­ вать как маски для поразрядных операций и, следовательно, они должны пред­ ставляться степенью двойки:

enum Status { CLEAR = 2, FULL = 8, EMPTY = 64 } ;

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

enum {SIZE = 80 } ; / / используйте для определения массивов и пр.

Обратите внимание, что это перечисление анонимно (аналогично анонимному объединению). Оно не имеет имени и, следовательно, нельзя определять пере­ менные данного типа. Однако невелика потеря, поскольку все, что здесь нужно — это символическая константа SIZE. Результат будет тот же, что и при явном опре­ делении константы:

const int SIZE = 80;

/ / то же самое

Глава 5 • Агрегирование с помощью типов, определяемых программистом

| 179 |

Битовые поля

Как и при обсуждении объединений и перечислений, разговор о битовых полях лучше начинать с примеров практических проблем, которые можно решить с помош.ью дополнительных типов C+-f.

Наименьшим объектом, доступным для распределения и адресации в програм­ ме C+ + , является символ. Иногда в программе может потребоваться значение меньшего размера, в этом случае резервирование для него полноразмерного цело­ го было бы напрасной тратой памяти. Кроме того, часто форматы внешних данных и интерфейсы аппаратных устройств вынуждают обрабатывать отдельные элемен­ ты слова.

Например, контроллер дискового массива может манипулировать с адресами памяти и их компонентами: номером страницы (от О до 15) и смещением адреса памяти на странице (от О до 4095). Данный алгоритм может потребовать операций с номерами страниц (4 бита), смеш,ением (12 бит) и полным адресом (16 бит без знака), комбинирования номера страницы и смеш.ения в адрес, извлечения из ад­ реса номера страницы и смещения.

Еще одним примером может быть порт ввода-вывода, где конкретные биты ассоциируются с заданными условиями и операциями. Бит 1 порта может уста­ навливаться, если устройство свободно для передачи состояния, бит 3 — если заполнен буфер приема, а бит 6 — если пуст буфер передачи. Алгоритм может потребовать индивидуальной установки каждого бита в слове состояния и считы­ вания состояния каждого бита. Для каждой из этих вычислительных задач нужно использовать поразрядные логические операции.

Комбинирование номера страницы и смещения в адрес памяти требует сдвига адреса на 12 позиций влево и выполнения с результатом сдвига и адресом пораз­ рядной операции ИЛИ.

unsigned

int address, temp;

/ /

должны быть беззнаковыми

int page,

offset;

/ /

бит знака никогда не устанавливается в 1

temp = page « 1 2 ;

/ /

сделать четыре бита старшими

address = temp | offset;

/ /

предположим, дополнительных битов нет

Получение из адреса номера страницы и смещения — более сложная задача. Чтобы получить номер страницы, нужно сдвинуть адрес на 12 позиций, отбросив тем самым биты смещения, и переместить номер страницы в младшие биты слова. Для получения адреса используется поразрядная операция AND и маска OxOFFF, которая устанавливает ка>вдый из 12 младших бит в 1:

page = address »

12;

/ / отделить биты смещения, получить биты страницы

offset = address

& OxOFFF;

/ / отделить биты страницы от адреса

Чтобы установить отдельные биты в 1, используются три маски: у каждой маски только 1 бит установлен в 1, а все другие равны 0. Применяя к слову состоя­ ния поразрядную операцию ИЛИ, можно установить соответствующий бит в 1, ес­ ли он равен О, или оставить биты в прежнем состоянии, если они уже равны 1. Определенные выше константы CLEAR, FULL и EMPTY представляют собой маски, у которых только 1 бит установлен в 1, а другие биты нулевые. У константы CLEAR бит 1 установлен в 1, у FULL бит 3 установлен в 1, у EMPTY шестой бит — в 1.

unsigned status=0;

/ /

предположим, инициализируется правильно

status

1= CLEAR;

/ /

установить бит 1 в 1

(если он нулевой)

status

1= FULL;

/ /

установить

бит

3

в

1

(если

он нулевой)

status

1= EMPTY;

/ /

установить

бит

6

в

1

(если

он нулевой)

Соседние файлы в предмете Программирование на C++