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

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

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

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

X = count < slzeof(

la гray ) / 2

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

ли, что будет рассмотрено ниже в разд. 6.

Логические операции (**&&" и "||"Л Приоритет и порядок вы­ полнения логических операций также представлены в табл. 16, а их смысл описан в табл. 19, называемой таблицей истинности.

Табл. 19. Таблица истинности для логических операций

Операнд1

Операнд2

Операнд1 && Операнд2

Операнд1 |j Операнд2

НеО

НеО

1

1

НеО

0

0

1

0

НеО

0

1

0

0

0

0

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

О

&&

(

А >

в

)

2 \\

(

(

А+В

)

< 4 )

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

тинности (табл. 20).

Табл. 20.Задание логической функции таблицей истинности

 

Параметр 1 ( р1 )

 

Параметр 2 ( р2 )

Значение функции

 

 

НеО

 

 

 

НеО

 

0

 

 

НеО

 

 

 

0

 

0

 

 

0

 

 

 

НеО

 

НеО

 

 

0

 

 

 

0

 

0

//

Прототип

 

 

 

 

 

 

±nt

lf(

±nt pi,

±nt

p2

)

 

 

 

//

Определение

функции

//

Возвращает

значение

функции

±nt

If(

 

 

 

 

±nt

 

 

 

//

Параметр

функции

 

(

±nt

 

P2 )

 

//

Параметр

функции

 

±f(

( !pl

) &&

p2

)

 

 

 

 

 

 

 

120

 

retuxm

1,

 

jretux-n

0;

 

//

Пример

вызова

функции

±nt

value

= lf(

4, 0 ) ;

5.4. Тернарная операция

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

выраж__1 ? выраж_2 : выраж__3

Данное выражение интерпретируется следующим образом. Вначале вычисляется "выраж 1". Если его значение отлично от нуля ("истина"), то вычисляется "выраж_2", следующее за знаком "?". Ес­ ли же "выраж 1" имеет нулевое значение ("ложь"), то за значение ус^ловного выражения принимается значение "выражЗ".

Пример.

max

= (

a>b ) ? а : b;

//

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

±f(

a>b

)

a;

else

max

=

max

=

b;

 

5.5. Операции присваивания

Приоритеты и порядок выполнения операций присваивания указаны в табл. 16.

Простая операция присваивания используется следующим образом:

lvalue = выражение/

Приведенная запись получила название выраэюение присваива­ ния. Так как результатом выражения присваивания служит значение "выражения", стоящего справа от операции присваивания, которое присваивается объекту '4value", то выражения присваивания могут быть вложенными:

max = min = 0;

121

в приведенном примере вначале будет выполнено присваива­ ние min = О, в результате которого ''min'' станет равно нулю, а затем это же значение будет присвоено "max",

Операцию присваивания можно записывать в сокращенной форме, что широко используется:

Обычна я

форма

+

2;

Сокращенная

форма

count

=

count

count

+=

2;

offset

= offset

/

2;

offset

/=

2;

s = s * ( f-h2 ) ;

] - 10;

s *= f + 2;

a[ i+2

]

= a[

i+2

a[ i+2

] -= 10;

Операции присваивания "+=", "/=", "*="^ "%=" и подобные им (см. табл. 16) называют составными операциями присваивания.

Операция присваивания выполняется следующим образом:

вначале вычисляется значение выражения, стоящего справа от операции присваивания;

полученное значение автоматически приводится к типу объекта, указанного слева от операции присваивания (! такое преобразо­ вание лучше делать явно с помощью операции приведения типа);

преобразованное значение присваивается объекту, указанному слева от операции присваивания.

5.6. Операция "запятая"

Приоритет и порядок выполнения операций "запятая" указаны в табл. 16. Отметим, что эта операция имеет самый низкий приори­ тет.

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

/ /

Суммирование

первых

десяти

ненулевых

целых

значений

£ог(

±пЬ

1=1,

sum =

1;

1 <

10;

i++,

sum += i

) ;

//

To же

самое,

в

более

длинной

форме

 

 

±nt

±nt

 

sum

=

0;

 

 

 

 

 

for(

i = 1;

1

<= 10;

i + +

)

 

 

 

(

 

 

 

 

 

 

 

 

 

 

6. УКАЗАТЕЛИ

Как известно, оперативная память ЭВМ и, в частности, память IBM PC делится на восьмибитовые байты. Каждый байт пронумеро­ ван, нумерация начинается с нуля. Номер байта называется адресом. Говорят, что адрес указывает на определенный байт. Таким образом, указатель является просто адресом байта памяти.

6.1.Зачем нужны указатели?

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

Зачем эюе нуэюны указатели!

1. Применение указателей во многих случаях позволяет упро­ стить программу и/или повысить ее эффективность.

2. Для управления памятью ЭВМ. Так в состав Си-библиотек входят функции резервирования и освобождения блоков оператив­ ной памяти в любой момент в процессе выполнения программы. В языке же C++ для этой цели предусмотрены операторы new и delete. Эти операторы C++ управляют динамической памятью. Управление динамической памятью позволяет эффективно увеличивать размеры программ при тех же ресурсах компьютера и приспосабливать их к изменяющимся размерам данных. Как программа "узнает", где рас­ положен вновь занятый блок памяти? С помощью указателя!

6.2. Указатели и их связь с массивами и строками

Рассмотрим несколько примеров.

/ /

iarray

- адрес

iarrayf

6

О ]

 

 

int

Напечатаем

Iarrayf

];

 

О ]

//

16-ричный

адрес iarray[

print f (

"iarray

=

\ л " ,

iarray

) ;

 

В Си имеется операция "&" взятия адреса, в результате приме­ нения которой к имени данного получается адрес этого данного. Например, вызов

printf(

"iarray = %х \ л " , &iarray[ О ] ) ;

123

дает точно такой же результат, как и предыдущий вызов.

iarray

=

выражение/

указателем

на

место

//

Ошибка:

имя

массива является

//

 

расположения

массива в памяти^ ему нельзя

присваивать

//новое значение

"Это строка \п"

//

Значением

строки

является

 

//

указатель

на

первый

символ

 

//

строки^

т.е.

адрес

символа "Э"

//в памяти

6.3.Определение и применение указателей

Определение указателей. Синтаксис определения указателя имеет вид:

описатель_класса_хранения спецификация^ типа *идентификатор;

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

Примеры:

±nt

 

*iptr;

/ /

Определяет

iptr

как

указатель

на

ex t em

chctr

 

//

целое

 

 

 

 

*cptr;

//

Объявляет

cptr

как

указатель

на

static

float

*fptr;

//

символ

fptr

как

указатель

на

//

Определяет

 

 

 

//

статическое

данное

с плавающей

 

 

 

//

точкой

 

 

 

 

Операции над указателями. Один из способов получения ад­ реса данного состоит в применении унарной операции взятия адре­ са &, приоритет и порядок выполнения которой указаны выше в табл. 16:

±xit main ( void )

{

int

 

Xr

//

Целое

 

на

целое:

 

пользоваться

 

 

*px;

//

Указатель

 

 

 

 

//

указателем

пока

нельзя^

так как

 

 

 

//

его

значение

еще

не

определено

X =

 

2;

//

Теперь

указателем

рх

пользоваться

рх

=

&х;

*рх

= 3;

//

можно:

*рх

равно

2

3

//

Теперь

*рх

равно

х равно

return

0;

 

 

 

 

 

 

 

}

Единственное ограничение применения операции взятия адре­ са & состоит в том, что ее нельзя применить к объекту с классом хранения register, а также к полям битов (поля битов обсудим позже в разделе "Поля битов и битовые операции").

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

менить операцию

разадресации,

обозначаемую "*". Приоритет и по­

рядок выполнения этих операций также указаны выше в табл. 16.

/ /

Пример

без

 

разадресации

 

 

 

int

main

(

void

 

)

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

 

 

 

 

int

 

 

 

X,

у /

 

 

 

 

 

 

X =

2;

у

=

x;

 

 

 

 

 

 

}

return

0;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

//

Эквивалентный

 

пример

с

разадресацией

 

 

int

main(

 

void

 

)

 

 

 

 

 

 

(

 

 

 

 

 

 

 

 

 

 

 

 

int

 

 

 

X,

у г

*рх;

 

 

 

 

X =

2;

рх

=

&х; у

=

*рх;

 

 

 

 

return

О;

 

 

 

 

 

 

 

 

}

Здесь

*рх

означает

извлечь

значение^

находящееся

по адресу

//

//

рх:

косвенная

 

адресация

 

 

 

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

Инициализация указателей. Указатели, как и объекты других типов, можно определять, совмещая определение с инициализацией. Приведем несколько примеров такого рода.

/ /

Использование

указателей

в инициализирующих

выражениях

//

(рх - адрес

х)

=

&х;

 

 

 

 

 

Lnt

 

X,

*рх

 

 

 

 

 

char

line

[

80

],

"upline

=

line;

0 ]

int

 

*page

=

//

pline

- адрес

line[

 

0xB800;

 

на

начало

видеобуфера

 

 

 

 

 

//

Указатель

 

 

 

 

 

//

цветного

 

дисплея

 

125

Арифметические операции над указателями. В связи с арифметическими операциями над указателями рассмотрим не­ сколько примеров.

Пример 1.

±пЬ

i [ 6 ] г *pi

 

=

i /

 

/

/

i

и pi - адрес i [ О ]

Будем считать, что первый байт массива / имеет адрес 10 000. Тогда для первого элемента массива (его индекс равен нулю):

адрес равен 10 000, или &/[ О ], или /, или/?/;

значение элемента равно *10 000, или /[ О ], или */, или */>/.

 

Для элемента массива с индексом/ (J = О, 1,2,

3, 4, 5):

 

 

адрес равен 10000 + / * sizeofi int ), или &/[/ ], или / + / , или

pi

+

 

У;

*( 10 000 + / * sizeoJ{

int ) ), или

 

 

значение элемента равно

/ [ /

],

 

или *( / + / ), или *(/?/ + /

).

 

 

 

Пример 2.

#define

N

3

//

Строчный размер

массива

^define

М

4

//

Столбцовый размер

массива

int

 

 

а[ N ] [ М ] ,

*ра == а ;

 

Будем считать, что первый байт массива "<я" имеет адрес 20000. Тогда для элемента массива, принадлежащего строке с ин­ дексом О и столбцу с индексом 0:

• адрес равен 20 000, или 8са[ О ][ О ], или а, илира',

значение элемента равно *20 000, или «[ О ][ О ], или *а, или */7а. Для элемента массива, принадлежащего строке с индексом

/(/ = О, 1, ..., iV-1) и столбцу с индексом 0:

адрес равен 20 000 + ( / * М ) * sizeof{ int ), или 8са[ / ][ О ],

или *( а + / ), или а[ / ], или &.ра[ / * М ] ;

значение элемента равно *( 20 000 + ( / * М ) * sizeof{ int ) ),

или а{ / ][ О ], или *( *(flf+ / ) ), или *<з[ / ], или ра{ / * М ] . Для элемента массива, принадлежащего строке с индексом

/ (/ = О, 1, ..., 7V-1)

и столбцу с индексом/ (j — О, 1, ..., М-1):

• адрес равен 20

000 -)-(/* М + / ) * sizeof{ int ), или &а[ / ][/ ],

или *( а + / ) -f/, или а[ / ] +у, или &ра[ / * М + j ];

значение элемента равно *( 20 000 + ( / * М +j ) * sizeoJ{ int ) ),

или а[ / ][/ ], или *(*( а + / ) + / ), или *( а[ / ] +/ ),

или ра[ / * M-^J ].

126

Пример 3.

/ /

Программа

добавляет

 

строку s

к

концу

строки

t

 

 

 

^include

 

<stdlo.h>

 

 

 

//

 

Для

функций

ввода-вывода

 

 

 

int

main

( void

)

 

 

 

//

 

Возвращает

 

О при

успехе

 

 

 

{

char

 

 

t[

24

]

==

"Персональная

 

",

 

 

 

 

 

 

 

 

t

 

 

 

 

 

 

 

 

"^pt

=

t ,

 

/ /

pt

-

адрес

 

начала

 

 

 

 

 

 

 

 

s[

] = "ЭВМ IBM

PC",

 

начала

s

 

 

 

 

 

 

 

 

"^ps

=

s;

 

//

 

ps

-

адрес

 

 

 

 

 

while

(

*pt

!=

'\0'

 

)

pt-h + ;

 

 

символа строки

t,

 

т.е.

 

//

Здесь

pt

-

адрес

 

завершающего

 

 

//

 

адрес

'\0'

 

 

 

 

строку s

в

"хвост"

строки

 

t,

пока

 

//

Посимвольно

копируем

 

 

 

//

 

не

будет

скопирован

 

'\0'

нуль-символ

 

 

 

 

 

while

(

( *pt = *ps

)

!=

 

)

 

 

 

 

 

 

 

{

pt++;

ps++;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

}

 

 

"\n

Сцепление

 

строк

(конкатенация):

%s",

t

)

;

 

printf(

 

 

}

return

 

0;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Хотя на практике это требуется не часто, можно определять объекты, которые будут указателями на указатели и использовать несколько уровней косвенной адресации:

/ /

Указатель

на указатель

на

целое,

т.е.

адрес адреса

целого

/ /

 

(уровень

косвенной

адресации

 

2)

 

 

int

 

<"' *ppi ;

 

 

 

 

 

 

**ppi

. . .

 

/ /

Это

значение

целого

 

В приведенных выше примерах рассматривались только ариф­ метические операции "н-+" и "+", но наряду с ними могут использо­ ваться и такие арифметические операции, как "-" и "—".

Сравнение указателей. Указатели можно сравнивать с помо­ щью операций отношения:

<, >, <=, >=, ==, !=

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

127

Пример.

// Сравнение

указателей:

копирование

одной строки в другую

^include

<stdlo.h>

 

//

Для

функций

ввода-вывода

±nt main

( void

)

 

//

Возвращает

О при

успехе

{

 

al [

7 7 ,

// Строка -приемник

 

cbstr

 

al

 

 

*р1

= al,

//

р1

- адрес

начала

 

 

а2 [

] =

"Пример",

 

 

 

 

*р2

= а2;

//

р2

Строка-источник

а2

 

 

//

- адрес

начала

//Копировать а2 в al

while ( р2 <

(

а2

+ sizeof(

а2 ) ) )

{

= *р2;

р1+ + ; р2+ + ;

*р1

}

"\п

Копия:

%s", al

) ;

pr±ntf(

jretujrn

О;

 

 

 

 

6.4. Указатели на структуры

Другим распространенным применением указателей является манипулирование структурами:

/ / Объявление структуры, содержащей сведения о студенте struct STUDENT_INFO

{

//Факультет

char

fak_name[

30 ];

char

fio[ 30 ];//

ФИО

//Номер группы

char

group_name [

7 ];

поступления

студента

char

date[

9 ]///

Дата

float

stip;

//

Размер

стипендии

 

} ;

//Указатель на структуру

STUDENT_INFO

s_data,

уг

//

Определение

 

структуры

структуры

 

 

sl_data

//

Определение

на

еще одной

 

 

*ps_data;

//

Указатель

структуру

данного

ps_data =

&s_data;

 

//

типа

на

s_data:

 

адрес

 

//

Указатель

 

 

 

 

 

//

первого

байта

s_data

// Доступ

к

элементу

структуры: обе приведенные

ниже формы

128

/ /

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

 

 

 

 

. . .

s__data . stip

. . .

ps_data->stip

. . .

Приоритеты и порядок выполнения операций "." и "->" приведены выше в табл. 16.

/ /

Определение

адреса

элемента структуры:

обе

приведенные

//

ниже

формы

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

. . .

&ps_data->stip

. . .

 

/ /

 

. . .

 

&s__data , stip

должны

Операция

 

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

над

структурами:

оба

операнда

//

быть

объектами

с

одинаковыми

типами

(тегами)

 

sl_data

=

 

s_data;

 

 

 

 

 

 

 

К указателям на структуры можно также применять арифмети­ ческие операции. Например,

ps__data-i- +;

//

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

ps_data += s±zeof(

stxract

STUDENT_INFO ) ;

В языке Си (но не в языке C-I-+) обычно указатель на структуру используется для передачи адреса структуры в функцию. Это позво­ ляет получить из функции в качестве результата модифицированное значение структуры. Заметим, что для этой цели в языке C++ лучше использовать передачу структуры по ссылке.

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

Ранее была рассмотрена программа добавления одной строки в конец другой. Слияние (конкатенация) строк является достаточно распространенной операцией. Поэтому, почему бы не превратить эту программу в функцию? Приведем текст такой функции. Функ­ цию, как типовую, поместим в отдельный файл:

Файл

STRCAT.CPP

 

указателем

ps

к концу

строки

с

указа­

Добавление

 

строки с

телем

pt

 

 

 

 

 

 

 

 

 

 

 

 

V

 

 

 

 

 

 

 

 

 

 

 

 

 

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

функции

 

 

 

 

 

 

 

void

Streat(

*pt,

 

//

Указатель

на

строку-приемник

 

char

 

 

)

 

char

 

 

*ps

//

Указатель

на

строку-источник

 

{

 

(

*pt

)

pt

+ + /

 

 

 

 

 

 

 

while

 

 

 

 

 

 

 

//

Здесь

pt

-

адрес

завершающего

 

символа

строки

с

 

129