
алгоритмы и програмування / Книги / Шилдт Г. C++ Руководство для начинающих
.pdfС++: РУКОВОДСТВО ДМ) начинающих 183
"Том", "555-3322", "Мария", "555-8976", "Джон" , "555-1037", "Раиса", "555-1400", "Шура", "555-8873"
} ;
cout |
« |
"Введите |
имя: "; |
|
|
|
cin |
» |
str; |
|
|
|
|
for(i=O; i < 10; i += 2) |
|
|
||||
if(!strcmp(str, |
numbers[i])) |
|
|
|||
|
cout « |
"Телефонный номер: " |
« |
numbers[i+1] « "\п"; |
||
|
break; |
|
|
|
|
|
if (i |
|
10) |
cout |
« "Отсутствует |
в |
каталоге.\п"; |
return О;
Вот пример выполнения программы.
Введите имя: Джон
Телефонный номер: 555-1037
Обратите внимание на то, как при каждом проходе цикла for управляющая
переменная i инкрементируется на 2. Такой шаг инкремента объясняется тем,
что имена и телефонные номера чередуются в массиве.
ВАЖНОI
Указатели - один из самых важных и сложных аспектов С++. Вместе с тем
они зачастую являются источниками потенциальных пр06лем. В значительной
степени мощь многих средств С++ определяется использованием указателей. На
пример, благодаря им обеспечивается поддержка связных списков и механизма
динамического вьщеления памяти, и имен~о они позволяют функциям изменять
содержимое своих аргументов. Однако об этом мы поroворим в последующих мо
дулях, а пока (т.е. в этом модуле) мы рассмотрим основы применения указателей
и покажем, как с ними обращаться.
184 Модуль 4. Массивы, СТРОI(И и указатели
~просыДЛ',1текущегоl<ОНТРОЛЯ________
1.Покажите, как инициализировать четырехэлементный. массив list ir~ t-
значениями 1, 2, 3 и 4.
2.как можно переписать эту инициализаuию?
char 5 tr [6] = {'П', 'р', 'и', 'Б', 'е', 'т', '\ О' };
з. Перепишите следующий вариант инициализации в "безразмерном"
формате.
int nums[4] = { 44, 55, 66, 77 };.
При рассмотрении темы указателей нам прИ.':1:СТСЯ ИСПО~ЬЗ0вать такие понятия,
как размер базовых С++-тшюв данных. В этой l'лаве мы предположим, что симво лы занимают в памяти один байт, целочислеНlIые значения - четыре. значения
с плавающей точкой типа floa t - четыре, а значения с плавающей точкой пша double - восемь (эти palMcpbI характерны для ПШИЧflОЙ 32-разрядной среды).
Что представляют собой указатели
Указатель - это объект, который содержит некоторый адрес памяти. Чаще
псего этот адрес обозначает местоположение 11 памяти другого объекта, такого,
как переменная. Например, если х содержит адрес переменной у, то о перемен ной х говорят, что она "указывает" на у.
Переменllьre-указатели (или nеремеuные типа указатель) должны быть соот
ветственно объявлены. Формат объявления переменной-указателя таков:
тип *имя_переменной;
Здесь элемент тип означает базовый тип указателя, причем он должен быть до пустимым С++-типом. Базовый тип определяет, на какой тип данных будет ссы
латься объявляемый указатель. Элемент имя_ переменной представляет собой имя переменноЙ-указателя. Рассмотрим пример. Чтобы объявить переменную ip указателем на iпt-зна'lеиие, используйте следующую инструкцию.
int *ip;
1. |
int |
1ist(] |
= |
{ |
1, 2, |
3, |
4 }; |
2. |
char |
str(] |
|
"Привет"; |
|
||
3. |
int |
nums (] |
= |
( |
44, |
55, |
бб, 77 ); |
С++: РУКОВОДСТВО для начинающих 185
Для объявления указателя на floa t -значение используйте такую инструкцию.
float *fPi
В общем случае использование символа "звездочка" (*) перед именем перемен ной в инструкции объявления превращает эту переменную в указатель.
ВАЖНОI
суказателями
суказателями используются два оператора: "*" и "&". Оператор "&" - унар
ный. Он возвращает адрес памяти, по !<оторому расположен его операнд. (Вспом ните: унарному оператору требуется только один операнд.) Например, при вы
полнении следующего фрагмента кода
ptr = &totali
в переменную ptr помещается адрес переменной total. Этот адрес соответству
ет области во внутренней памяти компьютера, которая принадлежит переменной
Выполнение этой инструкции uuкак не повлияло на значение переменной total. Назначение оператора"&" можно "перевести" на русский язык как "адрес переменной", перед которой он стоит. Следовательно, приведенную выше инструк
цию присваивания можно выразить так: "перемеllная ptr получает адрес пере
менной total". Чтобы лучше понять суть этого присваивания, предположим, что
переменная total расположена в области памяти с адресом 100. Следовательно,
после выполнения этой инструкции переменная ptr получит значение 100.
Второй оператор работы с указателями (*) служит дополнением к первому
(&). Это также унарный оператор, но он обращается к значению переменной, рас положенной по адресу, заданиому его операндом. Другими словами, он ссылается на значение переменной, адресуемой заданным указателем. Если (продолжая ра
боту с предыдущей инструкцией присваивания) перемеНl:lая ptr содержит адрес
переменной total, то при выполнении инструкции
val = *ptri
переменной val будет присвоено значение переменной total, на которую ука
зывает переменная ptr. Например, если переменная total содержит значение
3200, после выполнения последней инструкции переменная val будет содержать
значение 3200, поскольку это как раз то значение, которое хранится по адресу 100. Назначение оператора "*" можно выразить словосочетанием "по адресу".
В данном случае предыдущую инструкцию можно прочитать так: "переменная val получает значение (расположенное) по адресу ptr".
I
186 Модуль 4. Массивы, СТРОI<И и YI<аэатели
Последовательность только что описанных операций выполняется в следую щей программе.
#include <iostrearn> using narnespace std;
int rnain ()
{
int total; int *ptri int val;
total = 3200; // Переменной total присваиваем 3200.
ptr |
&totali |
// |
Получаем |
адрес переменной total. |
val = |
*ptr; |
// |
Получаем |
значение, хранимое |
|
|
// |
по этому |
адресу. |
cout « "Сумма равна: " « val « '\n';
return О;
к сожалению, знак умножения (*) и оператор со значением "по адресу" обо значаются одинаковыми символами "звездочка", что иногда сбивает с то.1КУ
новичков В языке С++. Э111 операции никак не связаны одна с другой. Имейте
в виду, что операторы "*" и "&" имеют более высокий приоритет, чем любой из
арифметических операторов, за исключением унарного минуса, приоритет кото
рого такой же, как у операторов, применяемых для работы с указателями.
Операции, выполняемые с помощью указателей, часто называют оneрацшu.cu
нenрямоzо доступа, поскольку оии позволяют получить доступ к одной перемеи
ной посредством некоторой другой перемениой.
о важности базового типа указателя
На примере предыдущей программы была ноказана возможность присвоения
переменной val значения переменной total посредством операции непрямого доступа, т.е. с использованием указателя. Возможно, при этом у вас возник во
прос: "Как С++-компилятор узнает, сколько необходимо скопировать байтоо в
переменную val из области памяти, адресуемой указателем pt r?". Сформулиру
ем тот же вопрос в более общем виде: как С++-компилятор передает надлежащее
С++: PYI(OBOACTBO для начинающих 187
количество байтов при ВhIЛолнении операции присоаивания с ИСПОЛЬ30ванием
указателя? Ответ звучит так. Тип данных, адресуемый указателем, определяется
базовым типом указателя. В данном случае, поскольку ptr представляет собой
указатель на целочисленный тип, С++-компилятор скопирует впеременную va1
из области памяти, адресуемой указателем ptr, 4 байт информации (что спра
ведливо для 32-разрядной среды), но если бы мы имели дело с dоublе-указате
лем, то в аналогичной ситуации скопировалось бы 8 байт.
~ПРОСЫдl\ЯтекущегоI<ОНТРОЛЯ--------
1. Что такое указатель?
2. Как объявить указатель с именем va1Ptr, который имеет базовый тип
10ng int?
3. Каково назначение операторов",*" и .. &n при использовании с указателями?•
Переменные-указатели должны всегда указывать на соответствующий тип данных. Например, при объявлении указателя типа int КОМШ1JlЯТОР "предпо лагает", что все значения, на которые ссылается этот указатель, имеют тип int. С++-компилятор попросту не позволит выполнить операцию присваивания
с участием указателей (с обеих сторон от оператора присваивания), если тюTh!
этих указателей нссопмсстнмы (по сути, не одинаковы). Например, следующий
фрагмент кода некорректен.
int *р; double f;
11...
р= &f; 11 ОШИБКА!
Некорректность ЭТОl'О фрагмента состоит в недоnyстимости присваивания dоublе-указателя iпt-указателю. Выражение &f генерирует указатель на do- ublе-значение, ар - указатель на целочислеиtlый тип Эти два типа несо
вместимы, поэтому компилятор отметит эту инструкцию как ошибочную и не
скомпилирует программу.
1.Указатель - это объект, который содержитадрес памяти некотороro другого 06ъекта.
2.long int *valPtr;
3.Оператор" * " позволяет получить значение, хранимое по адресу, заданному ero
операндом. Онератор \\ &" возвращает адрес объекта. по которому расположен етО
операнд.
188 'МОДУЛЬ 4. Массивы, СТРОI<И и УI<азатели
Несмотря на то что, как было заявлено выше, при присваивании два указателя должны быть совместимы по типу, это серьезное ограничение можно преодолеть
(правда, на свой страх и риск) с помощью операции приведения типов. Напри мер, следующий фрагмент кода теперь формально корректен.
int *р |
|
|
double f; |
|
|
/ |
/ ... |
|
р |
= (int *) &f; // Теперь |
формально все ОК! |
|
Операция приведения к типу |
(int *) вызовет преобразование dоulйе |
к iпt-указателю. Все же использование операции приведения в таких целях
несколько сомнительно, поскольку именно базовый тип указателя определяет, как компилятор будет обращаться с даННЫМI1, на которые он ссылается. В дан
ном случае, несмотря на то, что р (после выполнения последней инструкции)
в действительности указывает на значение с IIлавающей точкой, компилятор по прежнему "считает", что он указывает на целОЧИСЛСНlIое значение (поскольку р по определению - iпt-указатель).
Чтобы лучше понять, 110<leMY использование операции I1риведения типов при
присва.ивании одного указателя другому не всегда приемлемо, рассмотрим сле
дующую I1рограмму.
//Эта программа не будет выполняться правильно.
#include <iostream> using namespace std;
!i.nt main ()
{
double Х, У; int *р;
Х = 123.23;
Р (int *) &Х; // Используем операцию приведения типов
//для присваивания dоublе-указателя
//iпt-указателю.
/ / CnеД)'DЦИе две инструкции не ДaJD'J.' _enаекых резуm.'2!а'2!ОВ.
У = *р; // Что происходит при выполнении этой инструкции? cout « У; // Что выведет эта инструкция?
return О;
С++: РУI<ОВОДСТВОДЛЯ начинающих 189
Вот какой результат генерирует эта программа. (У вас может ПОЛУЧ~ТI3СЯ дру гое значение.)
1.31439е+ОО9
Как видите, в этой программе переменной р (точнее. указателю на целочис ленное значение) присваивается адрес переменной х (которая имеет тип doubl е). Следовательно, когда переменной у присваивается значение, адресуемое
указателем р, переменная у получает только 4 байт данных (а не все 8, требуемых
ДЛЯ dоublе-значения), поскольку р - указатель на целОЧl1сленный тип iлt. Та ким образом, при выполнении соut-Инструкции на экран будет выведено не чис
ло 123 .23, а, как говорят программисты, "мусор".
Присваивание значений с ПОМОЩЬЮ указателей
При присваивании значения области памяти, адресуемой указателем, сго
(указатель) можно использовать с левой стороны от оператора присваивания.
Например, при выполнении следующей инструкции (если р - указатель на це лочисленный тип)
*р = 101;
число 101 присваивается области памяти, адресуемой указателем р. Таким об
разом, эту инструкцию можно прочитать так: "по адресу р помещаем значение
101". Чтобы инкрементировать или декрементировать значение, расположенное в области памяти, мресуеМШI указателсм, можно ИСfIOЛI>ЗО[]ЗТЬ IШСТРУIЩIfIО, по
добную следующей.
(*р)++;
Круглые скобки здесь обязательны, поскольку оператор "." имеет более низкий
приоритет, чем оператор "++".
Присваивание эначений с использованием указателей демонстрируется D сле дующей программе.
#include <iostream>
иsiлg namespace std;
int rnain ()
{
int *р, пurn;
-
:iS
са
:s: u u
·0
j~
р = &пит;
*р = 1 О О; 11 r Присваиваек перекениой D\DI1 чиcnо 100
190 Модуль 4. Массивы, СТРОI(И и указатели
|
|
// |
|
через ухазатen. р. |
cout |
« |
пит |
« |
' '; |
(*р) ++; |
/ / |
|
t- Ивхреиев'1'ИрУек значение переке_ой пшn |
|
|
|
// |
|
через ухазатenъ р. |
cout |
« |
пит « |
' ': |
|
(*р)--: |
// |
|
t- Дехрекеи'1'ИрУеи значение перекеиной num |
|
|
|
// |
|
через указатеnь р. |
cout |
« |
пит |
« |
'\n': |
return О:
Вот такие результаты генерирует эта программа.
100 101 100
ВАЖНОI
1'111 ИСПОАIИQВQМ'48 ука3Qr8Аей
в выражениях
Указатели можно использовать в большинстве допустимых в С++ выражений,
Но при этом следует применять некоторые снециальные правила и не забывать, что некоторые части таких выражений необходимо заключать в круглые скобки,
чтобы гарантированно получить желаемый [)(':'Iультат.
АрИфметические операции над указателями
с указателями можно использовать только четыре арифметических операто ра: ++, --, + и -. Чтобы лучше понять, что ПРI)ИСХОДИТ при выполнении арифме
тических действий С указателями, начнем с примера. Пусть pl - указатель на int-переменную с текущим значением 2000 (т.е. pl соДсржит адрес 2000). После выполнения (в 32-разрядной среде) выражеНI1Я
pl++:
содержимое переменной-указателя р1 станст равным 2 004, а не 2 0011 Дело в том, что при каждом инкрементировании указатель pl будет указывать на сле
дующее int-значение. Для операции декрементирования справедливо обратное
утверждение, Т.е. при каждом декрсментирооаш/И значение рl будет уменьшать
ся на 4. Например, после выполнения инструкции
рl--:
указатель рl будет иметь значение 1 996, еслн до этого оно было равно 2000.
С++: руководство ДЛfJ начинающих 191
Итак, каждый раз, когда указатель инкрементирустся, он будет указывать на
область памяти, содержащую следующий элемент 6а'ЮВОГО пта этого указателя.
А при каждом декрементировании он будет указывать на область памяти, содержа щую преДЫДУЩИЙ элемеlП базового типа этого указателя. Для Уlса;:штелей на сим
вольные значения результат операций инкрементироваюlЯ и декрементироваJiИЯ
будет таким же. как при "нормальной" арифметике. поскольку символы занимают
только один байт. Но при использовании любого дрyroго типа указателя при ИШЧJe
ментировании или декрементировании значение переменноli-указателя будет уве личиваться или уменьшаться на величину, равную размеру его базового типа.
Арифметические операции над указателями не ограничиваются использова нием операторов инкремента и декремента. Со значениями ука.lателеЙ можно
выполнять операции сложения и вычитания, используя D качестве второго опе
ранда целочислеННhlе значения. Выражение
рl = рl + 9;
заставляет рl ссылаться на девятый элемент 6азового типа указателя рl относи
тельно элемента, на который рl ссылался до выполнения этой ИНСТРУКЦЮI. Несмотря на то что складывать указатели нельзя, один указатель можно вы
честь из другого (если они оба имеют один и тот же 6азовый тип). Разность пока
жет количество элементов базового типа, которые разделяют эти два указателя. Помимо сложения указателя с целочисленным значением и вычитания его из указателя, а тЗI<ЖС вычисления разности двух указателей, над указателями ника кие другие аРИф~fетичеСКllе операции не выl1лняются•. Например, с указателями
нельзя складывать Цоаt - или dоublе-зиачения.
Чтобы понять, как формируется результат выполнения арифметических опе раций нал указателями, выполним следующую короткую программу. Она выводит реальные физические адреса, которые содержат указатель 11а int-значение (i) и
указатель на dоublе-значение (f). Обратите внимание на каждое изменение адре
са (зависящее от базового типа указателя), которое происходит при повторении
цикла. (Для большинства З2~разрядных компиляторов значение i будет увеличи ваться на 4, а значение f - на 8.) Отметьте также, что при использовании указателя в соut-инструкции его адрес автоматически отображается 8 формате адресации,
применяемом для текущего процессора и среды выполнения.
#include <iostream> using namespace std;
int main ()
~
о-
О
м
О
:.:
>-
:s:
:s:
:.:
О
Q....
u
:а
са
s u u
о
~
int Ч, j [10];
192 Модуль 4. Массивы, СТРОI<И и УI(азатели
................................................................................................................................
double *f, ч(10]; int Х;
i j;
f Ч;
for(x=O: х<10: х++)
cout |
« i+x « ' , « |
f+x |
// |
Отобр..аеи адреса, |
|
« |
'\п': |
// |
попучеввые з рез~.та~е |
|
|
|
/ / |
CJIo*elUUl зВ&че_ х |
|
|
|
,,/ / |
с ха*дюс указатеnеи. |
return |
О: |
|
|
|
Вот один из позможных резуJIьтатов ВЫlIолнения этой ПР011>аммы (у вас М,ХУТ
быть другие значения).
0012F724 0012F74C
OO12F'728 0012F754 0О12Р72С 0012F75C
0012F730 0О12Р764
OO12F734 OO12F76C
0012F738 OO12F774
ОО12F7ЗС OO12F77C
0012F740 OO12F784
0012F744 0012F78C
OO12F748 0012F794
Сравнение указателей
Указатели можно сравнивать, использун операторы отношсния ==, < И >.
Однако для :roro, чтuGы результат сравнения указателей поддавался интер
претации, сравниваемые указатели должны быть каким-то образом связаны.
Например, если указатели ссылаются на две отдельные и никак не связанные
переменные, то любое их сравнение в общем случае не имеет смысла. Но если
они указывают на пеrемснtIые, мсжду которыми существует некоторая связь
(как, например, между элементами одного 11 того же массива), то реЗУ:lьтат
сраDнения этих указателей может иметь определенный смысл (пример такого
сравнения вы увидите в проекте 4.2). При этом можно говорить о еще ОДIIОМ типе сравнения: любой указатель можно сравнить с нулевым указателем, ко
торый, по сути, равен нулю.