Давыдов В.Г. - Программирование и основы алгоритмизации - 2003
.pdfКак указывалось выше, результатом вычисления отношения может быть или ненулевое целое значение ("истина") или нулевое значение ("ложь"). Таким образом, после выполнения присваивания
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