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

Давыдов В.Г. - Программирование и основы алгоритмизации - 2003

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

i^error (обычно включается между директивами #(/'- if^endif для проверки какого-либо условия на этапе компиляции; при выполнениии такого условия компилятор выводит сообщение, указанное в terror и останавливается);

i^pragma (позволяет настраивать компилятор с учетом специ­ фических особенностей конкретной машины или операционной системы - указанные особенности индивидуальны для каждого компилятора);

import (имеет отношение к включению библиотек типов в cow-технологии).

Их обсуждение выходит за рамки данного пособия.

9.5. Указания по работе с препроцессором

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

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

^define

begin

{

^define

end }

 

i n t main

( void.

)

begin

 

 

rGturn 0;

end

Иногда случается, что текст макроопределения не помещается на одной строке. В подобных случаях признаком продолжения стро­ ки с текстом макроопределения является символ "\", например:

ifdefine

SUM_ZERO_ARRAY( array,

size,

sum

)

 

 

 

 

 

{

int

i^O;

 

 

 

 

 

 

sum

= 0;

i <

size

)

 

 

 

while(

 

 

 

{

sum

+=

array[

I ];

 

 

 

 

 

 

 

 

array[

i J =

0;

 

 

 

 

i + +;

 

 

}

}

180

Большинство компиляторов языков Си/С++ поставляется вме­ сте с набором заголовочных файлов. Одним из примеров такого ро­ да является файл stdio,h. При использовании стандартных заголо­ вочных файлов следует посмотреть их содержимое, чтобы случайно не переопределить стандартное имя. Стандартные заголовочные файлы разработаны квалифицированными программистами и по этой причине их также целесообразно посмотреть.

10. РЕДКО ИСПОЛЬЗУЕМЫЕ СРЕДСТВА ЯЗЫКОВ СИ/С++

10.1.Объявление имени типа typedef

спомощью typedef ыожпо приписать имя существующему ти­ пу данных. Примеры использования объявления имени типа приве­ дены в табл. 28.

Табл. 28. Объявление имени типа

Объявление имени типа

Пример применения

Значение

typedef

int

INTEGER,

INTEGER

 

a, b,

int a, b;

 

typedef

int

SHORT,

SHORT

c,d,

int c, d;

 

typedef

long

LONG,

LONGe,f

 

 

longe,f.

 

typedef

char

* STRPTR;

STRPTR

^,

h,

char *g,

*h,

typedef

struct

 

COMPLEX

k.

struct

 

 

{

 

 

 

 

 

{

 

 

double

r;

 

 

 

double

r;

 

double

i;

 

 

 

double

i.

 

}

 

 

 

 

 

 

 

COMPLEX:

Из приведенной таблицы следует, что объявление имени типа в общем виде записывается следующим образом:

typedef

<type

definition>

<identifier>;

 

Заметим,

что в объявлении имени типа <type definition> и

<identifier> можно поменять местами, хотя делать это не рекоменду­ ется. Чтобы можно было легче обнаружить в программе введенное имя типа лучше использовать в identifier прописные буквы, как это сделано в вышеприведенной таблице.

Из табл. 28 также следует, что простейшая форма typedef по­ хожа на директиву препроцессора ^define. Отличие заключается в том, что typedef обрабатывается компилятором, а директива Udefine

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

Имя, объявленное в typedef можно использовать в том же кон­ тексте, что для спецификации типа, например, как аргумент опера­ ции sizeof.

Основными целями использования (ype<ie/являются:

182

повышение удобства чтения программы;

повышение мобильности программы.

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

10.2. Объекты перечислимого типа

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

определения объекта перечислимого типа представлен на рис. 55 в

виде синтаксической диаграммы.

о

 

Определение объекта

 

Константа

 

перечислимого типа

 

перечисли­

^ }

{ > *

Идентификатор

мого типа

 

 

 

—•fenum V w перечислимого

 

 

типа

 

 

 

Идентификатор объекта

 

перечислимого типа

Константа

< : >

 

 

перечислимого типа

 

 

 

Идентификатор

 

 

 

 

Константное выражение с

 

целочисленным значением

Рис. 55. Определение объектов перечислимого типа

Пример определения объекта перечислимого типа:

епгпа languages { с, разе, ada ^

modula2^ forth

}

master;

или В эквивалентной форме

 

 

 

183

enum languages{

с , разе,

ada, modula2,

forth

} ;

languages

master;

 

 

 

 

Здесь languages

- новый перечислимый тип, a master - объект

типа

languages.

 

 

 

 

 

Значением master

может быть один из идентификаторов:

 

 

 

с, pasc,

adaг modula2,

forth

 

Например, можно написать:

 

master

=

с;

)

 

 

 

±f( master

== с

 

язык См" ) ;

 

printf(

"\п

Я знаю

 

switch(

master

)

 

 

 

{

 

 

 

 

 

 

сазе

с:

 

 

 

 

break;

сазе forth: break;

cie£aul t:

}

Используя идентификатор перечислимого типа можно опреде­ лить дополнительные объекты, например:

languages о1, о2;

Теперь имена ol, о2 обозначают объекты типа

languages.

Внутренним представлением каждой константы

перечислимо­

го типа служит целое значение (типа int).

При объявлении перечис­

лимого типа

 

envaa languages { с , pasc^ ada, modula2,

forth } ;

его константам (слева направо) автоматически присваиваются воз­ растающие целые значения О, 1,2, 3, 4.

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

enum languages { с = -1, разе = 4, ada, modula2,

forth = 4);

Тем константам, значения которых явно не задано, присваива­ ется значение предшествующей константы, увеличенное на единицу.

184

Таким образом, константе ada соответствует значение 5, а константе modula2 - значение 6. Разным константам перечислимого типа мо­ жет соответствовать одно и то же значение {pasc^ forth).

Рассмотрим следующий пример:

#include

 

<stdlo.h>

 

 

 

 

 

 

 

 

 

 

±nt mam

( vaid

)

 

 

 

 

 

 

 

 

 

 

 

{

 

t{

c==-l^

pasc=4^

 

ada,

modula2,

forth=4

} ;

 

enxna

 

 

t

 

 

m,

ml;

 

 

 

 

 

 

 

 

 

m

=

ada;

 

присваивание

не

вполне

корректно

-

//

Следующее

//

 

компилятор

 

формирует

предупреждение,

 

но

программа

//

=

выполняется

 

 

 

 

 

 

 

 

 

 

ml

5 /

"\п

т

=

%d,

ml

=

%d",

т,

ml

) ;

 

 

 

printf

(

корректны -

//

Данные

присваивания

 

также

не

вполне

//

 

компилятор

 

формирует

предупреждение,

 

но

программа

//

 

выполняется

 

 

 

 

 

 

 

 

 

 

т

= О; ml =

6;

=

%d,

ml

=

%d,

ada

=

%d",

m,

ml,

ada ) ;

printf(

 

"\л

т

return

0;

 

 

 

 

 

 

 

 

 

 

 

 

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

т

=^ 5,

ml

=

5

т

= О,

ml

=

б, ada = 5

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

Объектам перечислимого

типа можно присваивать любой

класс хранения, кроме register.

Область действия и время жизни для

них определяются таким же образом, как и для рассмотренных ранее объектов других типов.

Приведем еще несколько примеров перечислимых типов из файла .. \include \graphics. h:

епит COLORS

{

/* dark colors

*/

BLACK,

BLUE,

 

 

GREEN,

 

 

185

 

CYAN,

 

 

 

 

 

 

 

 

 

RED,

 

 

 

 

 

 

 

 

 

MAGENTA,

 

 

 

 

 

 

 

 

BROWN,

 

 

 

 

 

 

 

 

LIGHTGRAY,

 

/* light

colors

*/

 

 

 

 

DARKGRAY,

 

 

 

 

 

LIGHTBLUE,

 

 

 

 

 

 

 

 

LIGHTGREEN,

 

 

 

 

 

 

 

LIGHTCYAN,

 

 

 

 

 

 

 

 

LIGHTRED,

 

 

 

 

 

 

 

 

LIGHTMAGENTA,

 

 

 

 

 

 

 

YELLOW,

 

 

 

 

 

 

 

 

WHITE

 

 

 

 

 

 

 

 

enum

 

graphics_errors

graphresult

error

return

codes

*/

{

 

 

 

 

grOk

t

Graph

= 0,

 

 

 

 

 

 

grNoIni

= - i .

 

 

 

 

 

 

grNotDetected

 

= -2,

 

 

 

 

 

 

grFileNotFound

-^ -3,

 

 

 

 

 

 

grInvalidDriver

= -4,

 

 

 

 

 

 

grNo Loa dMem

= -5,

 

 

 

 

 

 

grNoScanMem

 

= -6,

 

 

 

 

 

 

grNoFlооdMem

= -7,

 

 

 

 

 

 

gr Font

Not

Found

= -8,

 

 

 

 

 

 

grNoFon

tMem

= -9,

 

 

 

 

 

 

grInvalidMode

 

= -10,

error

*/

 

 

 

 

 

 

 

generic

 

 

 

 

grError

 

 

= -11.

 

 

 

 

 

 

grIOerror

 

= -12,

 

 

 

 

 

 

grInvalidFont

 

= -13,

 

 

 

 

 

 

grinvalidFontNum

= -14,

 

 

 

 

 

 

grInvalidVersion

= -18

 

 

 

 

 

10.3. Объединения

Подобно структуре, объединение представляет собой агрегатированный тип данных. Синтаксис объявления объединения иденти­ чен синтаксису объявления структуры, только вместо служебного слова struct используется служебное слово union. Различие между структурой и объединением состоит в том, что каждому элементу

объединения выделяется одна

и та эюе область памяти^ а не раз­

личные области, как в структуре.

Синтаксис объединения поясняется следующей записью:

vLn±oTi [<идентификатор

типа

объединения>]

{

 

 

<тмп>

<идентифмкатор>;

186

} [<список объектов-объединений>]/

Примеры:

union INT__OR_LONG

{

1;

int

long-

1;

} а_питЬег,

b_number;

или в эквивалентной форме

union INT__OR_LONG

// Объявление

типа

объединения

(

1;

 

 

 

 

int

 

 

 

 

long

1;

 

 

 

 

} ;

объектов-объединений

с

типом

INT_OR_LONG

// Определение

INT_OR_LONG

a_number,

Ь_питЬег;

 

 

 

Для объекта-объединения апитЬег или b_number можно легко выполнить преобразование целого значения в длинное целое или наоборот. Для преобразования целого значения в длинное целое достаточно выполнить следующие действия:

а_питЬег. 1 = 7/ / / Теперь а_питЬег. 1 имеет

//значение 11

Наряду с преобразованием типов с помощью объектаобъединения можно получить доступ к отдельным байтам объекта, как это показано в следующем примере.

union

{

lvalue;

 

long

 

dovibl e

dvalue;

 

char

chvalue;

8 ];

char

cvaluel

}

value;

 

Определен объект с именем value, размер которого равен 8 байтам (наибольший из размеров для типов long, double, char, char[ 8]).

/ /

Доступ

к

4

байтам

как

к

объекту

типа

long

value.lvalue

к

8

байтам

как

к

объекту

типа

double

//

Доступ

value.dvalue

 

 

 

 

//

Доступ

к байту как к объекту типа

value.chvalue

 

 

 

 

 

 

 

 

 

 

//

char

 

 

187

к каждому из этих байтов можно осуществить доступ по от­ дельности, используя массив символов value.cvalue:

value, cvalue[ 3 ] // Доступ к 4 байту

Наряду с объектами-объединениями

можно работать и с указа­

телями на эти объекты, как показано ниже:

 

union

 

 

 

 

 

(

lvalue;

 

 

 

long

 

 

 

dovLble

dva

lue;

 

 

 

сЬлг

chvalue;

8 ];

 

 

сЬа.з:

cvalue[

 

 

}

valuef

upvalue = &value;

 

pvalue->lvalue

 

/*

эквивалентно

*/

value.lvalue

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

11. МОДЕЛИ ПАМЯТИ

Материал данного раздела в основном освещает вопросы ис­ пользования оперативной памяти процессора, относящиеся к при­ ложениям для шестнадцатибитной среды DOS и WINDOWS с уче­ том особенностей процессоров INTEL 80x86.

Модель памяти для программ на языках Си/С++, работающих в шестнадцатибитной среде, определяет, как программа использует память компьютера. Модель памяти связана с архитектурой процес­ сора.

Процессоры INTEL 80x86 используют сегментную организа­ цию памяти, позволяющую адресовать 1 Мбайт памяти. Так как все регистры процессора шестнадцатиразрядные, то прямой доступ имеют только 64 Кбайта памяти (диапазон шестнадцатиразрядных беззнаковых адресов О, 1, 2, ..., 2^^-1). Эти 64 Кбайта памяти назы­ ваются сегментом. Для того чтобы адресовать 1 Мбайт памяти, тре­ буется двадцатибитовый адрес. Поэтому для представления двадца­ тибитового адреса используются два регистра (32 бита). Один ре­ гистр содержит адрес сегмента (регистр CS - указатель сегмента ко­ да, DS - указатель сегмента данных, SS - указатель сегмента стека, ES - указатель дополнительного сегмента), а второй регистр содер­ жит смещение в сегменте.

Полный двадцатибитовый адрес, адресующий все адресное пространство процессора, вычисляется следующим образом (рис. 56).

ЮРА : 01С2

16-ричный код смещения

16-ричный код сегмента

Сегментный регистр 0001 0000 1111 1010 (ЮРА)

Сегментный регистр после сдвига 0001 0000 1111 1010 0000 (ЮРАО)

Смещение

0000 0001 1100 0010 (01С2)

0001 0001 0001 0110 0010 (11162) Рис. 56. Получение полного двадцатиразрядного адреса

189