Вы находитесь на странице: 1из 111

Кафедра менеджмента технологий

Основы алгоритмизации и
программирования на
языках высокого уровня

Холодова Елена Петровна


+375 29 681 63 09
alena-kholadava@yandex.ru
Минск, 2017
УКАЗАТЕЛИ
Основные понятия

2
Вопросы:
1. Понятие переменных-указателей.
2. Объявление и использование указателей.
3. Инициализация указателей.
4. Операторы, используемые при работе с
указателями.
5. Арифметические операции с указателями.
Сравнение указателей.
6. Ссылки.
7. Указатели на указатели.

3
1. Понятие переменных-указателей

Одной из наиболее мощных возможностей языка


С++ является непосредственный доступ к памяти
при помощи указателей. Этим язык С++ превосходит
некоторые другие языки.
В то же время, использование указателей — одна
из наиболее подверженных ошибкам областей
программирования.
Правильное применение требует отличного
понимания того, как компилятор управляет
распределением памяти.

4
Указатель (pointer) — это
переменная, значением которой
является адрес другой переменной,
то есть адрес области в памяти.

5
 Как хранятся переменные?
 Что такое адрес области в
памяти (memory address)?

Ответ требует рассмотрения


устройства компьютерной памяти.

6
Переменная (variable) – это именованная
область памяти, в которой могут храниться
различные значения.
С точки зрения программиста
переменная ‒ это место в памяти
компьютера, где можно размещать
хранимое значение, а затем извлекать его
оттуда.
Обычные переменные содержат
непосредственные значения. Указатель,
напротив, содержит адрес переменной,
хранящей непосредственное значение.
7
Оперативная память компьютера -
это хранилище значений переменных.
Она разделена на последовательно
пронумерованные ячейки, каждая из
которых занимает 1 байт. Номера ячеек
называют адресами памяти.
Адрес часто представлен в
шестнадцатеричном виде. В 32-разрядном
процессоре адрес будет 32-битным числом,
например 0x0001ЕА40.

8
Переменные имеют не только
адреса, но и имена.
Имя переменной можно представить
себе как надпись на ячейке, по которой
ее можно найти, даже не зная
настоящего номера (адреса в памяти).

9
Каждая переменная в зависимости от ее типа
размещается в одной или нескольких
последовательно расположенных отдельных
ячейках памяти, адрес первой из них является
адресом переменной в памяти.

На рисунке схематически представлена эта идея. Согласно рисунку,


переменная MyVariable начинается с ячейки с адресом 103. В
зависимости от своего размера, переменная MyVariable может
занимать одну или несколько ячеек памяти.

10
В С++ все переменные должны быть объявлены
до их использования.
В объявлении необходимо указать тип и имя
переменной.
Например,
int theAge;

В примере тип переменной theAge, заданный


при объявлении, — int (от англ. integer— целое
число). Это означает, что переменная имеет вид
"целое число со знаком" и что под значение числа,
которое будет записано на участке theAge, отведено
32 бита (4 байта).

11
Когда разработчик объявляет
переменную типа int, он сообщает
компилятору о необходимости
зарезервировать для нее в памяти 4 байта.
Компилятор соотносит с именами
переменных определенные адреса в памяти
и, начиная с этих адресов, выделит участки
памяти (в байтах) в соответствии с тем,
какого типа объявлены переменные.
Заботу о том, по какому именно адресу
будет размещена переменная, берет на себя
операционная система.
12
int theAge;

Адресом переменной theAge будет 102.

Адресом переменной является адрес ее первого


байта.
13
Таким образом, имена
переменных — это названия тех
участков в памяти компьютера
(каждый участок имеет свой
адрес), где будут находиться
данные (это могут быть не только
числа), с которыми программа
будет работать при реализации
алгоритма.
14
Контрольные вопросы по вопросу №1
«Понятие переменных-указателей»:

1. Что такое указатель?


2. Какой объем занимает одна
ячейка оперативной памяти?
3. Какой объем занимает в
оперативной памяти переменная
типа int?

17
Ответы на контрольные вопросы:
1. Указатель (pointer) — это переменная,
значением которой является адрес области в
оперативной памяти.
2. Оперативная память компьютера
разделена на последовательно
пронумерованные ячейки, каждая из которых
занимает 1 байт (8 бит).
3. Когда разработчик объявляет
переменную типа int, он сообщает
компилятору о необходимости
зарезервировать для нее в памяти 4 байта.
18
2. Объявление указателей
Переменные – указатели (или переменные типа
указатель), как и обычные переменные, должны
быть объявлены в программе.
Формат объявления переменной-указателя:
тип *имя_переменной;
При объявлении указателя задают тип
переменной, на которую он должен указывать. Это
позволяет уведомить компилятор о том, как именно
воспринимать область памяти, на которую он
указывает. Сам указатель содержит лишь адрес.

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

20
Пример. Объявление указателей.  Символ «звездочка»
int *ptr1; (*) свидетельствует о
double *ptr2; том, что это указатель

Переменная ptr1 объявляется указателем на


значение типа int. В результате будет получена
переменная ptr1, предназначенная для хранения
адреса значения типа int.
Переменная ptr2 объявляется указателем на
значение типа double. В результате будет получена
переменная ptr2, предназначенная для хранения
адреса значения типа double .

Когда переменная объявляется указателем на какой-


либо тип, это значит, что она будет хранить адрес переменной
определенного типа.
21
Поскольку указатели – это не более чем
обычные переменные, им можно присваивать
любые имена, допустимые для других
переменных.
Но есть негласное правило соглашения об
именовании – имена всех указателей пишут с
маленькой буквы p (от pointer - указатель),
например, pAge, pNumber.

22
Звездочка (*), используемая для объявления
переменной-указателя, не распространяет
свое действие на все имена в объявлении.
Каждый указатель должен объявляться с
собственным префиксом * в имени.

Пример. Объявление нескольких указателей и


обычных переменных.
int *ptr1, *ptr2;
double *ptz1 , *ptz2;
int tr, kr, *ptr1, *ptr2; // объявляются две
обычные переменные и два указателя
23
Контрольные вопросы по вопросу №2
«Объявление указателей»:

1. Как объявить два указателя на


переменные типа double?
2. Какие имена должны иметь
переменные-указатели?

24
Ответы на контрольные вопросы:
1. double *pUir1;
double *pUir2;

2. Имена указателей должны


удовлетворять тем же требованиям, что и
обычные переменные.

25
3. Инициализация указателей
Указатели могут инициализироваться в
момент объявления, а также им можно
присваивать значения уже в ходе
выполнения.
Указатель может быть
инициализирован:
либо значением NULL, 0 (т.е. указатель не
содержит адреса и не указывает ни на какой объект).
либо адресом другой переменной.
26
Указатель со значением NULL
указывает в никуда. NULL - это
символическая константа.
Инициализация указателя значением 0
эквивалентна NULL, но
предпочтительнее использовать NULL.
Когда указателю присваивается 0, оно
сначала преобразуется в указатель
соответствующего типа.

27
Если предназначенный для хранения в
указателе адрес заранее не известен,
ему следует присвоить значение NULL.
Указатели, значения которых равны
NULL, называют пустыми (null pointer).
Неинициализированные указатели
называются дикими (wild pointer). Они
опасны.

28
Пример. Инициализация указателя при
объявлении значениями NULL и 0.

int *ptx = 0;
//или второй вариант
int *ptx = NULL;

Если указатель инициализируется нулем, то


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

29
Пример. Объявление указателей с инициализацией и без
инициализации.

Результат:

30
Полученный результат может выглядеть иначе, поскольку каждый компьютер
сохраняет переменные по разным адресам, в зависимости от того, что еще
находится в памяти и сколько ее доступно.
Имя переменных ptx pty ptz
Содержимое (в памяти) 0 0 0x401a54

Адрес памяти 0x28ff0c 0x28ff08 0x28ff04

31
? Что будет выдано на экране консольного окна в
результате выполнения следующей программы?

32
Ответ: myAge: 5
pAge: 0
myAge: 7
pAge: 0
myAge: 9
pAge: 0
33
4. Операторы, используемые при работе с
указателями

 & (амперсанд), или оператор взятия адреса, - это


унарный оператор, возвращающий адрес своего
операнда.
 * (оператор косвенного доступа). Унарный
оператор, также называемый оператором
взятия значения или разыменовывания,
возвращает значение объекта, на который
указывает его операнд (т.е. указатель).

34
Операнд оператора взятия адреса
обязательно должен быть переменной. Этот
оператор не может применяться к
константам и выражениям.

Например, если Dec – переменная, то


операция &Dec позволяет получить
адрес этой переменной.

35
 Различные виды компьютеров используют
различные схемы памяти. Обычно программисту
не нужно знать реальный адрес каждой
переменной, поскольку компилятор способен
сам позаботиться о таких подробностях. Но если
необходимость в этой информации все же
возникнет, то получить ее можно с помощью
оператора обращения к адресу (&).

36
Пример. Получение адреса переменной.

37
Полученный результат может
выглядеть иначе, поскольку каждый
компьютер сохраняет переменные по
разным адресам, в зависимости от того,
что еще находится в памяти и сколько ее
доступно.

38
Пример. Получение адресов нескольких переменных.

39
имя m3 m2 m1

Значение не Значение не Значение не


память определено определено определено

адрес 0x22ff04 0x22ff08 0x22ff0c

40
Как уже говорилось ранее, указатели принято
инициализировать при объявлении либо нулем, либо адресом
другой переменной.
Например,
int *ptx = 0; // указатель ptx инициализируется нулем
int y=7;
int *pty = &y; /* инструкция присвоит указателю pty адрес
переменной y. После этого можно сказать, что указатель
«указывает» на переменную y */
Имя переменной Значением
ptx y pty pty является
адрес y

Значение ячейки
памяти
0 7 0x22ff1с
Адрес ячейки памяти 0x22ff0с 0x22ff1с 0x22ff2с …….
Адрес переменной y можно получить с помощью инструкции &y

41
* (оператор взятия значения или
разыменовывания) – предназначен для
обращения к значению, которое хранится в
памяти по адресу, хранящемуся в переменной-
указателе.
Иначе говоря, при извлечении значения из
указателя будет возвращено то значение, которое
хранится по адресу, содержащемуся в указателе.
Например, если Ptr – указатель, то операция
*Ptr – получение значения, которое расположено
по адресу памяти, который содержится в
указателе Ptr.

42
Обычные переменные обладают
прямым доступом к собственным
значениям.
Чтобы создать новую переменную
yourAge типа int и присвоить ей значение
другой переменной, howOld, можно
применить следующий код:
int howOld = 50;
int yourAge;
yourAge = howOld;

43
Указатель обеспечивает лишь косвенный доступ к
значению переменной, адрес которой он хранит.
Пример. Присвоить переменной yourAge значение
переменной howOld с помощью указателя рАgе.

int SchowOld =50; // объявление переменной


int *pAge = &SchowOld; /* объявление указателя и
присваивание адреса переменной SchowOld */
int yourAge; // объявление переменной
yourAge= *pAge; /* берется значение, хранимое по
адресу, который находится в переменной рАgе, и
присваивается переменной yourAge */

Оператор косвенного доступа (*) перед переменной рАgе означает, что "само значение
находится по адресу...".

44
Пример. Использование операций & и *.

Изменяем
DEC
Прямой
доступ к DEC
Косвенный
доступ к DEC

45
Имя переменной DEC Ptr

Значение ячейки
памяти
150 0x22ff0с
Адрес ячейки ……… 0x22ff0с …… 0x22ff08
памяти

&DEC &Ptr Ptr


DEC
*Ptr

46
Имя переменной DEC Ptr
2
Значение ячейки
памяти
150 0x22ff0с
3
4
Адрес ячейки ……… 0x22ff0с …… 0x22ff08
памяти

Значение,
1 расположенное
по адресу,
содержащемуся
*Ptr в указателе Ptr

При извлечении значения из указателя будет возвращено то значение,


которое хранится по адресу, содержащемуся в указателе.
47
 Доступ к значению переменной по
указателю на нее называется косвенным,
поскольку осуществляется не совсем по
правилам.

Косвенное обращение подразумевает


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

48
Оператор косвенного доступа (*) используется двумя
способами: при объявлении и при косвенном доступе.
1)При объявлении он свидетельствует о том, что это
указатель, а не обычная переменная.
Например:
int *pAge = 0; // объявить указатель и инициализировать
нулем
2)При косвенном доступе звездочка означает, что речь идет о
значении в памяти, находящемся по адресу в данной
переменной, а не о самом адресе.
Например:
*рАgе =5; // разместить значение 5 по адресу, находящемуся в
рАgе
Примечание. Тот же символ (*) используется как оператор
умножения.
Что именно имел в виду программист, поставив звездочку, компилятор
определяет, исходя из контекста.

49
Примечание. Если переменная-указатель pty
содержит адрес переменной х, то о переменной
pty говорят, что она указывает на х.

Если указатель pty указывает на целую


переменную x, тогда выражение *pty может
фигурировать в любом контексте, в котором
допускается x.
Например,
Y=(*pty) * (*pty) ;
равносильно выражению
Y=x*x;

50
Очень важно понимать разницу между указателем, хранимым в нем адресом
и значением, расположенным по адресу, который содержится в этом указателе.
Обычно
понимание этого и составляет основную сложность при изучении указателей.
Пример. Указатели, адреса и переменные.
int theVariable = 5; Выполнение программы
int *pPointer = 0;
приводит к выводу числа 10.
pPointer = &theVariable;
* pPointer = * pPointer + 5; В программе описаны
cout << theVariable; переменная theVariable типа int и
указатель *pPointer типа int.
или
Переменной theVariable присвоено
int theVariable = 5; начальное значение 5 (двоичное
int *pPointer=&theVariable; представление 0000 0000 0000 0101).
* pPointer = * pPointer + 5; Переменной *pPointer – адрес
cout << theVariable;
переменной theVariable.

51
ДРУГИЕ ПРИМЕРЫ

53
Пример. Присвоение указателям адресов переменных,
косвенное обращение к переменным через указатели.

54
Любой указатель может указывать только на
объекты одного конкретного типа данных, заданного
при его объявлении.
Т.е. указатель типа double не может указывать на
тип int.

Исключением является только указатель на тип


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

55
5. Арифметические операции с указателями.
Сравнение указателей
Указатели являются допустимыми операндами в
арифметических выражениях, выражениях присваивания и
сравнения.
С указателями можно использовать только четыре
арифметических оператора:
 ++ (указатели могут увеличиваться)
 − − (указатели могут уменьшаться)
 + и += (к указателям можно прибавлять целые числа)
 − и −= (из указателей можно вычитать целые числа,
и один указатель можно вычитать из другого, если
они одного типа

56
Чтобы лучше понять, что происходит при выполнении
арифметических действий с указателями, начнем с
примера.

Пусть р1 — указатель на int-переменную с


текущим значением 2000 (т.е. р1 содержит адрес
2000).

После выполнения выражения p1++


содержимое переменной-указателя р1 станет
равным 2004, а не 2001.
Дело в том, что при каждом
инкрементировании указатель р1 будет указывать
на следующее int-значение.

57
Для операции декрементирования
справедливо обратное утверждение, т.е. при
каждом декрементировании значение р1
будет уменьшаться на 4.
После выполнения инструкции p1--
указатель р1 будет иметь значение 1996,
если до этого оно было равно 2000.

58
А каждый раз, когда
Итак, каждый раз, когда
указатель
указатель декрементируется,
инкрементируется, он
он будет указывать па область
будет указывать на область
памяти, содержащую
памяти, содержащую
предыдущий элемент базового
следующий элемент
типа этого указателя.
базового типа этого
Значение
указателя.
int a; Р1 равно
int a;
2004
int *P1=&a; int *P1=&a;
P1--; P1++;
P1
a
1996 2000 2004 2005 2006 2007 2008 2012

P1-- P1++
59
60
Для указателей на символьные значения
результат операций инкрементирования и
декрементирования будет таким же, как при
"нормальной" арифметике, поскольку
символы занимают только один байт.
Но при использовании любого другого
типа указателя при инкрементировании или
декрементировании значение переменной-
указателя будет увеличиваться или
уменьшаться на величину, равную размеру
(количеству байт) его базового типа.

61
Значение указателя
необходимо
привести к типу int

62
Если не
привести
к типу int

63
Выражение p1=p1+9 заставляет р1 ссылаться на
девятый элемент базового типа указателя р1
относительно элемента, на который р1 ссылался до
выполнения этой инструкции.

64
65
Несмотря на то, что складывать указатели
нельзя, один указатель можно вычесть из другого
(если они оба имеют один и тот же базовый тип).
Разность покажет количество элементов
базового типа, которые разделяют эти два
указателя.

Помимо сложения указателя с целочисленным


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

66
 Арифметика указателей не определена,
если указатели не ссылаются на массив.
Нельзя предполагать, что две обычные
переменные одного типа хранятся в
памяти по соседству.

67
Пример.

Число 1 – это
смещение
относительно
указателя.

68
Значение переменной-указателя может быть
присвоено другой переменной-указателю,
если обе одного типа. Исключением является
указатель на значение типа void, который
считается универсальным указателем,
способным представлять указатель любого
типа. Указателю типа void можно присвоить
указатель любого типа, и указатель типа void
можно присвоить указателю любого типа. Ни
в одно из этих случаев не требуется
выполнять операцию приведения типа.

69
Пример. Присваивание указателю значения другого указателя.

В результате указатель pb
указывает на такую же
ячейку, что и pa, но
значения переменных a и b
не изменились!
Инструкция pb = pa
кардинально отличается от
инструкции *pb = *pa,
когда меняется значение
переменной b, но значение
самого указателя остается
неизменным.

70
Пример. Присваивание указателю значения другого указателя.

71
Указатель типа void не может быть
разыменован. Компилятор знает, что
указатель типа int ссылается на 4-байтный
объект в системе, где значение типа int
занимает 4 байта, а указатель типа void
просто содержит адрес неизвестного типа –
точное количество байтов, занимаемое
объектом, на который ссылается указатель,
компилятору неизвестно. Однако, чтобы
разыменовывать указатель, компилятор
должен знать, сколько байтов занимает
объект, адресуемый указателем.
72
 Попытка присвоить значение указателя одного
типа указателю другого типа, если ни один из них
не является указателем типа void, вызовет
синтаксическую ошибку.

 Попытка разыменовать указатель типа void


вызовет синтаксическую ошибку.

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

74
6. Ссылки

• понятие ссылки;
• отличие ссылок от указателей;
• создание и использование ссылки;
• использование в ссылках оператора
обращения к адресу (&);
• ограничения ссылок.

75
Понятие ссылки

В С++ существует неявная форма


указателя – ссылка. Ссылки обладают почти
теми же возможностями, что и указатели, но
синтаксис их несколько проще.
Ссылка (reference) — это псевдоним,
который при создании инициализируется
именем другого объекта, адресата (target).

76
Отличие ссылок от указателей

В некоторых первоисточниках можно


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

77
Указатели - это переменные, которые
содержат адрес другого объекта, а ссылки -
это псевдонимы объектов.
Ссылка - это фактически еще одно
название для переменной в программе.
Значение ссылки - это значение
переменной, на которую ссылка выполнена
(в этом смысле она отличается от указателя,
значением которого является адрес ячейки,
на которую ссылается указатель).

78
При объявлении ссылки необходимо
сразу указывать, на какую переменную
она ссылается (в этом случае говорят об
инициализации ссылки).
Ссылка инициализируется только
один раз.
При объявлении (и инициализации)
ссылки перед ее именем указывается
оператор &.

79
Создание и использование ссылок

При объявлении ссылки вначале указывают


тип объекта адресата, за которым следуют
оператор ссылки (&) и имя ссылки.
Синтаксис объявления ссылки:

тип &имя_ссылки = имя_переменной;

Для ссылок можно использовать любое


допустимое имя переменной, но многие
программисты используют перед именами ссылок
префикс «r».
80
Таким образом, для целочисленной
переменной SomeInt ссылку можно
создать так:

int &rSomeRef = SomeInt;

Это читается следующим образом: "rSomeRef


является ссылкой на целочисленное значение,
инициализированное адресом переменной SomeInt".
81
ПРИМЕЧАНИЕ
Оператор ссылки (&) выглядит так же, как и
оператор возвращения адреса, который
используется при работе с указателями. Но здесь
он используется в объявлении.

Повторение: звездочка (*) в объявлении


означает, что эта переменная — указатель. Когда
звездочка используется при операциях с
указателями, она является оператором
косвенного доступа, а в математическом
выражении — оператором умножения.

82
Пример. Создание и использование ссылок.
Если объявить, но не
инициализировать ссылку,
произойдет ошибка компиляции.
Ссылки, в отличие от указателя,
необходимо инициализировать при
объявлении

83
Анализ
В строке 6 объявлена локальная целочисленная переменная intOne, а в
строке 7 — ссылка rSomeRef на целочисленное значение, инициализируемая
адресом переменной intOne. Если объявить, но не инициализировать ссылку,
произойдет ошибка компиляции. Ссылки, в отличие от указателя,
необходимо инициализировать при объявлении.
В строке 9 переменной intone присваивается значение 5, а в строках 10-
11 на экран выводятся значения переменной intOne и ссылки rSomeRef. Они
оказываются одинаковыми, поскольку rSomeRef — это не более чем
псевдоним intOne.
В строке 13 ссылке rSomeRef присваивается значение 7. Поскольку это
ссылка, и она является псевдонимом переменной intOne, число 7 в
действительности присваивается переменной intOne, что и подтверждается
выводом значения на экран в строках 14-15.
84
Пример. Создание и использование ссылок.

85
86
Использование в ссылках оператора
обращения к адресу (&)

Если запросить ссылку об адресе, она вернет


адрес своего адресата. В этом и состоит природа
ссылок. Они являются псевдонимами для своих
адресатов.

87
Пример. Обращение к адресу ссылки.

Ссылка rSomeRef инициализируется


адресом переменной intOne. Выводятся
адреса двух переменных, которые
оказываются идентичными.

В языке С++ не предусмотрено предоставление доступа к адресу самой ссылки,


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

88
Существует различие между символом & в строке 7,
который объявляет ссылку rSomeRef на значение типа int,
и символами & в строках 13 и 14, которые возвращают
адреса целочисленной переменной intOne и ссылки
rSomeRef. Обычно для ссылки оператор взятия адреса не
используется. Просто ссылка используется точно так же,
как и переменная, вместо которой она применяется
(строка 11).
Ссылки нельзя переназначать, т.к. они являются
псевдонимами своих адресатов. Может ввести в
заблуждение явление, происходящее при попытке
переназначить ссылку. То, что кажется переназначением,
оказывается присвоением нового значения адресату. Это
продемонстрировано в следующем примере.

89
Пример. Присвоение значения ссылке.

Объявление
ссылки

Получение
адреса

intOne=intTwo

90
21
22
23
91
Анализ
7-8:объявляются
целочисленная переменная и
ссылка на ее значение.
10: целочисленной переменной
присваивается значение 5.
11-14: значения переменной и
ссылки, а также их адреса
выводятся на экран.

16: создается новая переменная intTwo, которая тут же


инициализируется значением 8.
17: предпринимается попытка переназначить ссылку
rSomeRef так, чтобы она стала псевдонимом переменной
intTwo, но этого не происходит. На самом же деле ссылка
rSomeRef остается псевдонимом переменной intOne, поэтому
такое присвоение эквивалентно следующей операции:
intОne = intTwo;
92
Это кажется достаточно убедительным,
особенно при выводе на экран значений
переменной intOne и ссылки rSomeRef
(строки 18-20): их значения совпадают со
значением переменной intTwo. На самом
деле при выводе на экран адресов в
строках 21-23становится очевидным, что
ссылка rSomeRef ссылается на
переменную intOne, а не на переменную
intTwo.
93
Использование ссылок и указателей

Убедиться в том, что ссылка на практике является


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

94
95
На что можно ссылаться?
Ссылаться можно на любой объект, включая
определенный пользователем. Обратите внимание, что
ссылка создается на объект, а не на тип. Нельзя
объявить ссылку таким образом:

int &rIntRef = int; // неверно


Ссылку rIntRef необходимо инициализировать,
используя конкретную целочисленную переменную,
например:

int howBig = 200;


int &rIntRef = howBig;
96
Нулевые указатели и ссылки
Когда указатели освобождены или не
инициализированы, им следует присваивать
нулевое значение (0). Ссылок это не касается.
Фактически ссылка не может быть нулевой, и
программа, содержащая ссылку на нулевой объект,
считается некорректной. Во время ее работы может
случиться все, что угодно.
Компиляторы могут поддерживать нулевой объект,
ничего не сообщая об этом до тех пор, пока объект не
попытаются каким-то образом использовать. Но
пользоваться поблажками компилятора опасно,
поскольку они могут дорого обойтись во время
выполнения программы.
97
Указатели и ссылки исключительно
важны при работе с функциями и
составляют основу одного механизма
передачи функциям аргументов.

98
Контрольные вопросы по разделу
«Ссылки»:

1. Что такое ссылка?


2. Какой оператор используется для
создания ссылки?
3. Каков адрес ссылки?

99
Ответы на контрольные вопросы:
1. Ссылка — псевдоним или синоним другой
переменной или объекта.

2. При объявлении ссылки используется


символ амперсанда (&). Ссылки следует
инициализировать при объявлении. В отличие
от указателя, получить "нулевую" ссылку
нельзя.

3. Адрес ссылки совпадает с адресом


переменной или объекта, псевдонимом которого
она является.
100
7. Указатели на указатели

Чтобы создать указатель, который


будет ссылаться на переменную-
указатель, которая ссылается на
обычную переменную, необходимо
создать указатель на указатель.

101
Схема стандартной, одноуровневой
адресации:
переменная-указатель через свое
значение-адрес ссылается на обычную
переменную.

102
Схема двухуровневой адресации:

на переменную ссылается указатель, а на этот


указатель, в свою очередь, ссылается еще один
указатель. Значением второго указателя
является адрес ячейки, в которую записан адрес
исходной переменной, т.е. адрес первого
указателя.
103
104
Двухуровневая адресация не является
верхом совершенства, и по аналогам может
быть трехуровневая, четырехуровневая и т.д.
адресация. Однако адресация уровня больше
двух на практике используется редко.
Для объявления переменной-указателя на
указатель перед именем этой переменной
указывают два оператора *. Каждый
дополнительный уровень адресации означает
наличие дополнительного оператора * в
объявлении переменной-указателя.

105
Синтаксис объявления переменной-указателя
на указатель:

тип **имя_переменной;

Например, командой
int **q;
объявляется переменная-указатель на указатель
на целочисленное значение.

106
Пример использования двухуровневой адресации.

107
В программе объявляется целочисленная переменная cost и два
указателя:
• указатель рCost на переменную типа int
• указатель fCost на указатель для указателя на переменную типа
int.

В качестве значения указателю рCost командой рCost=&cost


присваивается адрес ячейки cost, а указатель fCost получает в
качестве значения адрес ячейки, в которую записано значение
переменной-указателя pCost (fCost=&pCost;)
После выполнения команд cost=100 и (*рCost)+=5 значение
переменной cost становится равным 105. После этого данное
значение уменьшается на единицу - вследствие выполнения
команды (**fCost) --.

108
В данном случае *fCost есть значение, записанное по
адресу fCost (по адресу fCost записан указатель рCost),
a **fCost - это значение, записанное по адресу *fCost
(т.е. значение, записанное по адресу рCost, а это
переменная cost).

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

110
РЕЗЮМЕ

1. Указателем называется переменная,


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

111
2. При объявлении указателя указывается тип
значения, хранимого в ячейке, на которую
ссылается указатель, а перед именем
соответствующей переменной-указателя
размещается оператор *. Для получения доступа к
значению, записанному в ячейку, на которую
ссылается указатель, перед именем указателя также
указывают оператор *. Адрес ячейки, в которую
записана переменная, можно получить, указав
перед именем этой переменной оператор &.
3. С переменными-указателями можно
выполнять простые арифметические действия,
результат которых определяется правилами
адресной арифметики.
112
4. Указатель может в качестве значения содержать
адрес переменной-указателя. В этом случае говорят о
многоуровневой адресации. Объявление указателя на
указатель выполняется с использованием двух
операторов * перед именем переменной.
5. Ссылка является альтернативным способом
обращения к переменным. Объявление ссылки
выполняется одновременно с ее инициализацией.
Перед именем ссылки указывается оператор &,
значением ссылки является имя переменной, на
которую выполняется ссылка. После выполнения
ссылки на переменную к переменной можно
обращаться через два имени: непосредственно имя
переменной и имя переменной-ссылки.

113
Для чего нужны указатели?
Мы подробно рассмотрели процедуру присвоения
указателю адреса другой переменной. Но на практике такое
использование указателей встречается достаточно редко. К
тому же зачем задействовать еще и указатель, если
значение уже хранится в переменной? Но, не разобравшись
в синтаксисе применения указателей, нельзя приступить к
их реальному применению.
Чаще всего указатели применяются в таких
случаях:
■ манипулирование данными в динамически
распределяемой памяти;
■ доступ к переменным и функциям класса;
■ передача данных между функциями по
ссылке.
114