Академический Документы
Профессиональный Документы
Культура Документы
Основы алгоритмизации и
программирования на
языках высокого уровня
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
Каждая переменная в зависимости от ее типа
размещается в одной или нескольких
последовательно расположенных отдельных
ячейках памяти, адрес первой из них является
адресом переменной в памяти.
10
В С++ все переменные должны быть объявлены
до их использования.
В объявлении необходимо указать тип и имя
переменной.
Например,
int theAge;
11
Когда разработчик объявляет
переменную типа int, он сообщает
компилятору о необходимости
зарезервировать для нее в памяти 4 байта.
Компилятор соотносит с именами
переменных определенные адреса в памяти
и, начиная с этих адресов, выделит участки
памяти (в байтах) в соответствии с тем,
какого типа объявлены переменные.
Заботу о том, по какому именно адресу
будет размещена переменная, берет на себя
операционная система.
12
int theAge;
17
Ответы на контрольные вопросы:
1. Указатель (pointer) — это переменная,
значением которой является адрес области в
оперативной памяти.
2. Оперативная память компьютера
разделена на последовательно
пронумерованные ячейки, каждая из которых
занимает 1 байт (8 бит).
3. Когда разработчик объявляет
переменную типа int, он сообщает
компилятору о необходимости
зарезервировать для нее в памяти 4 байта.
18
2. Объявление указателей
Переменные – указатели (или переменные типа
указатель), как и обычные переменные, должны
быть объявлены в программе.
Формат объявления переменной-указателя:
тип *имя_переменной;
При объявлении указателя задают тип
переменной, на которую он должен указывать. Это
позволяет уведомить компилятор о том, как именно
воспринимать область памяти, на которую он
указывает. Сам указатель содержит лишь адрес.
19
Когда создается указатель, компилятор
выделяет для него в памяти объем,
достаточный для хранения адреса, размер
которого соответствует используемым
аппаратным средствам и операционной
системе.
20
Пример. Объявление указателей. Символ «звездочка»
int *ptr1; (*) свидетельствует о
double *ptr2; том, что это указатель
22
Звездочка (*), используемая для объявления
переменной-указателя, не распространяет
свое действие на все имена в объявлении.
Каждый указатель должен объявляться с
собственным префиксом * в имени.
24
Ответы на контрольные вопросы:
1. double *pUir1;
double *pUir2;
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
31
? Что будет выдано на экране консольного окна в
результате выполнения следующей программы?
32
Ответ: myAge: 5
pAge: 0
myAge: 7
pAge: 0
myAge: 9
pAge: 0
33
4. Операторы, используемые при работе с
указателями
34
Операнд оператора взятия адреса
обязательно должен быть переменной. Этот
оператор не может применяться к
константам и выражениям.
35
Различные виды компьютеров используют
различные схемы памяти. Обычно программисту
не нужно знать реальный адрес каждой
переменной, поскольку компилятор способен
сам позаботиться о таких подробностях. Но если
необходимость в этой информации все же
возникнет, то получить ее можно с помощью
оператора обращения к адресу (&).
36
Пример. Получение адреса переменной.
37
Полученный результат может
выглядеть иначе, поскольку каждый
компьютер сохраняет переменные по
разным адресам, в зависимости от того,
что еще находится в памяти и сколько ее
доступно.
38
Пример. Получение адресов нескольких переменных.
39
имя m3 m2 m1
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е.
Оператор косвенного доступа (*) перед переменной рАgе означает, что "само значение
находится по адресу...".
44
Пример. Использование операций & и *.
Изменяем
DEC
Прямой
доступ к DEC
Косвенный
доступ к DEC
45
Имя переменной DEC Ptr
Значение ячейки
памяти
150 0x22ff0с
Адрес ячейки ……… 0x22ff0с …… 0x22ff08
памяти
46
Имя переменной DEC Ptr
2
Значение ячейки
памяти
150 0x22ff0с
3
4
Адрес ячейки ……… 0x22ff0с …… 0x22ff08
памяти
Значение,
1 расположенное
по адресу,
содержащемуся
*Ptr в указателе Ptr
48
Оператор косвенного доступа (*) используется двумя
способами: при объявлении и при косвенном доступе.
1)При объявлении он свидетельствует о том, что это
указатель, а не обычная переменная.
Например:
int *pAge = 0; // объявить указатель и инициализировать
нулем
2)При косвенном доступе звездочка означает, что речь идет о
значении в памяти, находящемся по адресу в данной
переменной, а не о самом адресе.
Например:
*рАgе =5; // разместить значение 5 по адресу, находящемуся в
рАgе
Примечание. Тот же символ (*) используется как оператор
умножения.
Что именно имел в виду программист, поставив звездочку, компилятор
определяет, исходя из контекста.
49
Примечание. Если переменная-указатель pty
содержит адрес переменной х, то о переменной
pty говорят, что она указывает на х.
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.
55
5. Арифметические операции с указателями.
Сравнение указателей
Указатели являются допустимыми операндами в
арифметических выражениях, выражениях присваивания и
сравнения.
С указателями можно использовать только четыре
арифметических оператора:
++ (указатели могут увеличиваться)
− − (указатели могут уменьшаться)
+ и += (к указателям можно прибавлять целые числа)
− и −= (из указателей можно вычитать целые числа,
и один указатель можно вычитать из другого, если
они одного типа
56
Чтобы лучше понять, что происходит при выполнении
арифметических действий с указателями, начнем с
примера.
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, вызовет
синтаксическую ошибку.
73
Указатели могут сравниваться с помощью
операторов сравнения и операторов
отношения, но такое сравнение
бессмысленно, если указатели не ссылаются
на указатели одного массива.
При сравнении указателей сравниваются
адреса в памяти. Сравнение двух указателей,
ссылающихся на элементы одного массива,
может помочь определить, например, какой
из них ссылается на элемент с большим
индексом.
74
6. Ссылки
• понятие ссылки;
• отличие ссылок от указателей;
• создание и использование ссылки;
• использование в ссылках оператора
обращения к адресу (&);
• ограничения ссылок.
75
Понятие ссылки
76
Отличие ссылок от указателей
77
Указатели - это переменные, которые
содержат адрес другого объекта, а ссылки -
это псевдонимы объектов.
Ссылка - это фактически еще одно
название для переменной в программе.
Значение ссылки - это значение
переменной, на которую ссылка выполнена
(в этом смысле она отличается от указателя,
значением которого является адрес ячейки,
на которую ссылается указатель).
78
При объявлении ссылки необходимо
сразу указывать, на какую переменную
она ссылается (в этом случае говорят об
инициализации ссылки).
Ссылка инициализируется только
один раз.
При объявлении (и инициализации)
ссылки перед ее именем указывается
оператор &.
79
Создание и использование ссылок
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
Пример. Обращение к адресу ссылки.
88
Существует различие между символом & в строке 7,
который объявляет ссылку rSomeRef на значение типа int,
и символами & в строках 13 и 14, которые возвращают
адреса целочисленной переменной intOne и ссылки
rSomeRef. Обычно для ссылки оператор взятия адреса не
используется. Просто ссылка используется точно так же,
как и переменная, вместо которой она применяется
(строка 11).
Ссылки нельзя переназначать, т.к. они являются
псевдонимами своих адресатов. Может ввести в
заблуждение явление, происходящее при попытке
переназначить ссылку. То, что кажется переназначением,
оказывается присвоением нового значения адресату. Это
продемонстрировано в следующем примере.
89
Пример. Присвоение значения ссылке.
Объявление
ссылки
Получение
адреса
intOne=intTwo
90
21
22
23
91
Анализ
7-8:объявляются
целочисленная переменная и
ссылка на ее значение.
10: целочисленной переменной
присваивается значение 5.
11-14: значения переменной и
ссылки, а также их адреса
выводятся на экран.
94
95
На что можно ссылаться?
Ссылаться можно на любой объект, включая
определенный пользователем. Обратите внимание, что
ссылка создается на объект, а не на тип. Нельзя
объявить ссылку таким образом:
98
Контрольные вопросы по разделу
«Ссылки»:
99
Ответы на контрольные вопросы:
1. Ссылка — псевдоним или синоним другой
переменной или объекта.
101
Схема стандартной, одноуровневой
адресации:
переменная-указатель через свое
значение-адрес ссылается на обычную
переменную.
102
Схема двухуровневой адресации:
105
Синтаксис объявления переменной-указателя
на указатель:
тип **имя_переменной;
Например, командой
int **q;
объявляется переменная-указатель на указатель
на целочисленное значение.
106
Пример использования двухуровневой адресации.
107
В программе объявляется целочисленная переменная cost и два
указателя:
• указатель рCost на переменную типа int
• указатель fCost на указатель для указателя на переменную типа
int.
108
В данном случае *fCost есть значение, записанное по
адресу fCost (по адресу fCost записан указатель рCost),
a **fCost - это значение, записанное по адресу *fCost
(т.е. значение, записанное по адресу рCost, а это
переменная cost).
109
Хотя система двухуровневой адресации
может показаться слишком замысловатой,
она имеет конкретное практическое
применение, особенно при работе с
двухмерными массивами.
110
РЕЗЮМЕ
111
2. При объявлении указателя указывается тип
значения, хранимого в ячейке, на которую
ссылается указатель, а перед именем
соответствующей переменной-указателя
размещается оператор *. Для получения доступа к
значению, записанному в ячейку, на которую
ссылается указатель, перед именем указателя также
указывают оператор *. Адрес ячейки, в которую
записана переменная, можно получить, указав
перед именем этой переменной оператор &.
3. С переменными-указателями можно
выполнять простые арифметические действия,
результат которых определяется правилами
адресной арифметики.
112
4. Указатель может в качестве значения содержать
адрес переменной-указателя. В этом случае говорят о
многоуровневой адресации. Объявление указателя на
указатель выполняется с использованием двух
операторов * перед именем переменной.
5. Ссылка является альтернативным способом
обращения к переменным. Объявление ссылки
выполняется одновременно с ее инициализацией.
Перед именем ссылки указывается оператор &,
значением ссылки является имя переменной, на
которую выполняется ссылка. После выполнения
ссылки на переменную к переменной можно
обращаться через два имени: непосредственно имя
переменной и имя переменной-ссылки.
113
Для чего нужны указатели?
Мы подробно рассмотрели процедуру присвоения
указателю адреса другой переменной. Но на практике такое
использование указателей встречается достаточно редко. К
тому же зачем задействовать еще и указатель, если
значение уже хранится в переменной? Но, не разобравшись
в синтаксисе применения указателей, нельзя приступить к
их реальному применению.
Чаще всего указатели применяются в таких
случаях:
■ манипулирование данными в динамически
распределяемой памяти;
■ доступ к переменным и функциям класса;
■ передача данных между функциями по
ссылке.
114