Лекция 6
Содержание
Введение
Указатель
Указатель
Описание различных видов указателей
Инициализация указателей
Способы инициализации указателей
Способы инициализации указателей
Способы инициализации указателей
Освобождение памяти
Указатели на составные типы
Интерпретации сложных описаний
Операции с указателями
Пример использования указателей при работе с массивами. Необходимо сформировать массив строк. Удалить из него строку с заданным
Пример использования указателей при работе с массивами. Необходимо сформировать массив строк. Удалить из него строку с заданным
Результат работы программы
Контрольные вопросы
Список литературы
Список литературы
254.15K
Категория: ПрограммированиеПрограммирование

Указатели. Работа с указателями. Лекция 6

1. Лекция 6

Указатели. Работа с указателями

2. Содержание

Введение
Работа с массивами
Указатель
Работа с динамическими
Виды указателей
Инициализация
2
указателей
Способы инициализации
Освобождение памяти
Указатели на составные
типы
Интерпретация сложных
типов
Операции с указателями
массивами
Пример использования
указателей
Контрольные вопросы
Список литературы

3. Введение

Любую переменную перед использованием в
программе необходимо описать, то есть
выделить для неё некоторый участок памяти.
Причем для символьных переменных
выделяется 1 байт памяти, для целочисленных
– 2 байта, для представления вещественных
переменных требуется 4 байта памяти.
Адреса имеют целочисленные беззнаковые
значения, и их можно обрабатывать как
целочисленные величины. Для этих целей в языке
С++ введены переменные типа «указатель».
3

4. Указатель

Таким образом, когда компилятор обрабатывает
оператор определения переменной, например, int
i=10; он выделяет память в соответствии с типом
(int) и инициализирует ее указанным значением
(10). Все обращения в программе к переменной по
ее имени (i) заменяются компилятором на адрес
области памяти, в которой хранится значение
переменной.
Указатель – это переменная, значением которой
служит адрес объекта конкретного типа. Значением
указателя может быть заведомо не равное
никакому адресу значение, принимаемое за нулевой
адрес (константа NULL).
4

5. Указатель

5
Указатели необходимо определять и описывать с
помощью разделителя «*». При этом нужно сообщать, на
объект какого типа ссылается описываемый указатель.
Например:
char *z;
/*z – указатель на объект символьного типа
*/
int *k, *i;
/* k, i – указатели на объекты целого типа
*/
float *f;
/* f – указатель на объект вещественного
типа */
Разделитель «*» – это унарная операция обращения по
адресу. Операндом операции разыменования всегда
является указатель. Результат этой операции – тот
объект, который адресует указатель-операнд.

6.

Итак, указатели предназначены для хранения адресов
областей памяти. В С++ различают три вида
указателей - указатели на объект, на функцию и на
void,
отличающиеся
свойствами
и
набором
допустимых операций. Указатель не является
самостоятельным типом, он всегда связан с какимлибо другим конкретным типом.
Указатель на функцию содержит адрес в сегменте
кода, по которому располагается исполняемый код
функции, то есть адрес, по которому передается
управление при вызове функции. Указатели на
функции используются для косвенного вызова
функции (не через ее имя, а через обращение к
переменной, хранящей ее адрес), а также для
передачи имени функции в другую функцию в
качестве параметра.
6

7. Описание различных видов указателей

1) Указатель функции имеет тип «указатель функции, возвращающей значение
заданного типа и имеющей аргументы заданного типа»:
тип (*имя) (список_типов_аргументов);
Например, объявление:
int (*fun) (double.double);
задает указатель с именем fun на функцию, возвращающую значение типа int и
имеющую два аргумента типа double.
2) Указатель на объект содержит адрес области памяти, в которой хранятся
данные определенного типа (основного или составного). Простейшее
объявление указателя на объект (в дальнейшем называемого просто
указателем) имеет вид:
тип *имя;
где тип может быть любым, кроме ссылки битового поля, причем тип может
быть к этому моменту только объявлен, но еще не определен. Звездочка
относится непосредственно к имени, поэтому для того, чтобы объявить
несколько указателей, требуется ставить ее перед именем каждого из них *.
Например, в операторе:
int *a, b, *c;
описываются два указателя на целое с именами a и c, а также целая переменная
b. Размер указателя зависит от модели памяти. Можно определить указатель на
указатель и т. д.
7

8.

3) Указатель на void применяется в тех случаях, когда
конкретный тип объекта, адрес которого требуется хранить, не
определен (например, если в одной и той же переменной в
разные моменты времени требуется хранить адреса объектов
различных типов).
Указатель может быть константой или переменной, а также
указывать на константу или переменную. Рассмотрим примеры:
int i;
// целая переменная
const int ci = 1;
// целая константа
int *pi;
// указатель на целую переменную
const int *pci;
// указатель на целую константу
int *const cp = &i;
// указатель-константа на целую
переменную
const int *const cpc = &ci;
// указатель-константа на
целую константу
Как видно из примеров, модификатор const, находящийся между
именем указателя и звездочкой, относится к самому указателю и
запрещает его изменение, а const слева от звездочки задает
постоянство значения, на которое он указывает. Для
инициализации указателей использована операция получения
адреса &.
8

9. Инициализация указателей

Указатели используют при работе с динамической
памятью, называемой кучей (перевод с английского языка
слова “Heap”). Это свободная память, в которой можно во
время выполнения программы выделять место в
соответствии с потребностями. Доступ к выделенным
участкам
динамической
памяти,
называемым
динамическими переменными, производится только через
указатели.
В языке С++ используется два способа работы с
динамической памятью. Первый использует семейство
функций malloc и достался в наследство от С, второй
использует операции new и delete.
При определении указателя надо стремиться выполнить
его инициализацию, Инициализатор записывается после
имени указателя либо в круглых скобках, либо после знака
равенства.
9

10. Способы инициализации указателей

1) Присваивание указателю адреса существующего объекта:
- с помощью операции получения адреса:
int a = 5;
// целая переменная
int *p = &a;
// в указатель записывается адрес a
int *p (&a);
// то же самое другим способом
- с помощью значения другого инициализированного указателя:
int *r = p;
- с помощью имени массива или функции, которые трактуются как
адрес:
int b[10];
// массив
int *t = b;
// присваивание адреса
начала массива

void f(int a){ /* … */ }
// определение функции
void (*pf)(int);
// указатель на функцию
pf = f;
// присваивание адреса функции
10

11. Способы инициализации указателей

2) Присваивание указателю адреса области памяти в
явном виде:
char *vp = (char *)0xB8000000;
Здесь 0xB8000000 - шестнадцатеричная константа, (char *)
- операция приведения типа: константа преобразуется к
типу «указатель на char».
3) Присваивание пустого значения:
int *suxx = NULL;
int *rulez = 0;
В первой строке используется константа NULL,
определенная в некоторых заголовочных файлах как
указатель, равный нулю. Поскольку гарантируется, что
объектов с нулевым адресом нет, пустой указатель можно
использовать для проверки, ссылается указатель на
конкретный объект или нет.
11

12. Способы инициализации указателей

12
4) Выделение участка динамической памяти и присваивание ее адреса
указателю.
- с помощью операции new:
int *n = new int;
// 1
В данном случае операция new выполняет выделение достаточного для
размещения величины типа int участка динамической памяти и
записывает адрес начала этого участка в переменную n.
int *m = new int (10);
// 2
В этом операторе, кроме описанных выше действий, производится
инициализация выделенной динамической памяти значением 10.
int *q = new int [10];
// 3
Операция new выполняет выделение памяти под 10 величин типа int
(массива из 10 элементов)
- с помощью функции malloc:
int *u = (int *)malloc(sizeof(int));
// 4
В функцию передается один параметр - количество выделяемой памяти
в байтах. Конструкция (int*) используется для приведения типа
указателя, возвращаемого функцией, к требуемому типу. Операцию new
использовать предпочтительнее, чем функцию malloc, особенно при
работе с объектами.

13. Освобождение памяти

Освобождение памяти, выделенной с помощью операции
new, должно выполняться с помощью delete, а памяти,
выделенной функцией malloc - посредством функции free.
При этом переменная-указатель сохраняется и может
инициализироваться повторно. Приведенные выше
динамические переменные уничтожаются следующим
образом:
delete n; delete m; delete [] q; free (u);
Если память выделялась с помощью new[], для
освобождения памяти необходимо применять delete[].
Размерность массива при этом не указывается. Если
квадратных скобок нет, то никакого сообщения об ошибке
не выдается, но помечен как свободный будет только
первый элемент массива, а остальные окажутся
недоступны для дальнейших операций. Такие ячейки
памяти называются мусором.
13

14. Указатели на составные типы

С помощью комбинаций звездочек, круглых и
квадратных скобок можно описывать составные
типы и указатели на составные типы, например, в
операторе
int *(*p[10])();
объявляется массив из 10 указателей на функции
без параметров, возвращающих указатели на int.
По умолчанию квадратные и круглые скобки
имеют одинаковый приоритет, больший, чем
звездочка, и рассматриваются слева направо. Для
изменения порядка рассмотрения используются
круглые скобки.
14

15. Интерпретации сложных описаний

При интерпретации сложных описаний необходимо
придерживаться правила
«изнутри наружу»:
1) если справа от имени имеются квадратные скобки,
это массив, если скобки круглые - это функция;
2) если слева есть звездочка, это указатель на
проинтерпретированную ранее конструкцию;
3) если справа встречается закрывающая круглая
скобка, необходимо применить приведенные выше
правила внутри скобок, а затем переходить наружу;
4)
в
последнюю
очередь
интерпретируется
спецификатор типа.
15

16. Операции с указателями

В языке С++ допустимы следующие основные
операции над указателями:
1) Присваивание.
2) Получение значения того объекта, на который
ссылается указатель.
3) Получение адреса самого указателя.
4) Бинарные операции (+).
5) Операция инкремента и декремента.
6) Операция разадресации.
7) Работа с массивами.
16

17.

Для того, чтобы получить значение, лежащее в
ячейке памяти, на которую ссылается указатель,
применяется операция *.
float *ukaz , pp=54.678;
ukaz=&pp;
cout<<“Значение, содержащееся по указателю
ukaz=”<< *ukaz ;
Переменная типа указатель имеет имя, собственный
адрес в памяти и значение. Выражение &имя
указателя определяет, где в памяти размещен
указатель. Содержимое этого участка памяти
является значением указателя.
float *u1, *u2;
cout<<“\n Адреса указателей:\n * u1=”<<&u1<<”\n
* u2=”<< &u2 ;
17

18.

В операции присваивания слева от знака операции
должно находиться имя указателя, а справа – адрес
переменной, указатель, уже имеющий значение, или
константа NULL.
char *z ;
int *i, *k, date=2008;
i=&date ;
k=i ;
z=NULL;
Для того, чтобы присвоить указателю одного типа
значение указателя другого типа, используют
«приведение типов».
char *z;
int *k;
z=(char *)k;
18

19.

Две переменные типа указатель нельзя суммировать, однако к
указателю можно прибавить целую величину. При этом
значение указателя зависит от прибавляемой величины и от
типа объекта, с которым связан указатель.
char *t;
int *a;
float *b;
/* t=1B2, a=AA5, b=C11 */
t=t+3; /* t=1B5 */
a=a+3; /* a= AAB */
b=b+3; /* b=C1D */
Операция вычитания применима не только к указателю и
целой величине, но и к двум указателям одного типа. При этом
определяется расстояние между размещением в памяти двух
объектов. Например:
int x[5], *i, *j, k;
i=&x[4]; j=&x[0] ; k=ij; /* k=4, а не 8 */
19

20.

Операция инкремента и декремента. Инкремент
перемещает указатель к следующему элементу
массива, декремент к предыдущему. Фактически
значение указателя изменяется на величину
sizeof (тип). Если указатель на определенный тип
увеличивается или уменьшается на константу,
его значение изменяется на величину этой
константы, умноженную на размер объекта
данного типа, например:
short *р = new short [5];
p++; // значение р увеличивается на 2
long *q = new long [5];
q++; // значение q увеличивается на 4
20

21.

Операция
разадресации,
или
разыменования,
предназначена для доступа к величине, адрес которой
хранится в указателе. Эту операцию можно использовать
как для получения, так и для изменения значения
величины (если она не объявлена как константа):
char a; // переменная типа char
char *p = new char; /* выделение памяти под указатель
и под динамическую переменную типа char */
*p = 'Ю'; a = *p; // присваивание значения обеим
переменным
Операции
разадресации
и
инкремента
имеют
одинаковый приоритет и выполняются справа налево, но,
поскольку инкремент постфиксный, он выполняется после
выполнения операции присваивания. Таким образом,
сначала по адресу, записанному в указателе р, будет
записано значение 10, а затем указатель будет увеличен на
количество байт, соответствующее его типу.
21

22.

Работа с массивами. В соответствии с синтаксисом языка
С++ имя массива без индексов является указателемконстантой, то есть адресом его первого элемента (адрес
начала массива). Прибавив к имени массива целую
величину, можно получить адрес соответствующего
элемента. Таким образом, &z[i] и z+i – это две формы
определения адреса одного и того же элемента массива,
отстоящего на i позиций от его начала. При этом адрес
следующего элемента в массиве будет зависеть от типа
элементов массива.
Массив указателей фиксированных размеров вводится
одним из следующих определений:
тип *имя массива[размер];
тип *имя массива [ ]=инициализатор;
тип *имя массива [размер]=инициализатор;
int data[6] ; /* обычный массив */ int *pd[6] ; /* массив
указателей */
int *pi[ ]={ &data[0], &data[4], &data[2] } ;
22

23.

Для работы с динамическим массивом необходимо выделить под него
память заданного размера из динамической области. Для этого можно
воспользоваться функцией calloc.
Синтаксис:
void * malloc(num, size );
Функция malloc выделяет блок памяти для массива размером - num
элементов, каждый из которых занимает size байт, и инициализирует все
свои биты нулями.
результате выделяется блок памяти размером num * size байт, причём весь
блок заполнен нулями.
Например, в примере выделена память под динамический целочисленный
массив из 100 элементов:
int * a = (int*) malloc(100, sizeof(int));
Здесь используется явное приведение типа (int*) к типу данного массива.
Затем с таким массивом можно работать традиционным способом. Таким
образом, можно работать с динамическими массивами, размер которых
заранее не известен, а определяется во время работы программного
приложения. Например, при формировании массива из файла произвольного
размера.
23

24. Пример использования указателей при работе с массивами. Необходимо сформировать массив строк. Удалить из него строку с заданным

номером.
#include "pch.h"
#include <iostream>
#include <string>
using namespace std;
int main()
{
int n,p;
cin >> n;
string *s = new string[n];
for (int i = 0; i < n; i++) { cin >> *(s + i); }
cout << "write number " << endl;
cin >> p;
p -= 1;
cout << endl;
for (int i = p; i < n - 1; i++) { *(s + i) = *(s +
i + 1); }
for (int i = 0; i < n-1; i++) {
cout << *(s + i)<<" "; }
delete[] s;
return 0;
}
24
В данном примере объявляются две
переменные:
n,p.
Переменная
n
используется для задания количества
элементов массива, p для номера
элемента, который надо удалить.
Создаётся указатель s на массив строк
размером
n,
инициализируются
ссылкой на 0 элемент.
В цикле for считываются элементы
строкового массива. После выводится
предложение пользователю ввести
номер элемента массива, который
необходимо
удалить.
Значение
введённого числа уменьшается на 1
т.к. нумерация массива начинается с 0.
Начиная с введённого числа -1 до n-1
реализуется проход по элементам
массива с помощью цикла for, при этом
текущему элементу присваивается
значение следующего. В данном цикле
необходим проход до n-1 p, иначе будет
выход за границы массива.

25. Пример использования указателей при работе с массивами. Необходимо сформировать массив строк. Удалить из него строку с заданным

номером.
#include "pch.h"
#include <iostream>
#include <string>
using namespace std;
int main()
{
int n,p;
cin >> n;
string *s = new string[n];
for (int i = 0; i < n; i++) { cin >> *(s + i); }
cout << "write number " << endl;
cin >> p;
p -= 1;
cout << endl;
for (int i = p; i < n - 1; i++) { *(s + i) = *(s +
i + 1); }
for (int i = 0; i < n-1; i++) {
cout << *(s + i)<<" "; }
delete[] s;
return 0;
}
25
Далее выводится исправленный
массив до n-1, так как был удален 1
элемент и стоящие после него
сместились влево на 1 позицию.
В данной программе массив
задается с помощью выделения
для него памяти через оператор
new[],
следовательно
удалять
массив, во избежание утечки
памяти необходимо через delete[]
имя_массива.
Так же в данной программе
используется
навигация
по
элементам массива с помощью
указателей. В начале s ссылается
на 0 элемент, и с помощью
конструкции *(s+i) осуществляется
проход по элементам массива.
Данная
конструкция
(*(s+i))
эквивалентна конструкции s[i].

26. Результат работы программы

26

27. Контрольные вопросы

Для чего предназначены указатели?
2. Перечислите виды указателей.
3. Способы инициализации указателей.
4. Что такое ссылка?
1.
27

28. Список литературы

Павловская Т.А. С/С++. Программирование на языке высокого уровня
28
/ Т. А. Павловская. - СПб.: Питер, 2004. - 461 с.: ил.
Павловская Т.А. С/С ++. Структурное программирование: Практикум /
Т.А. Павловская, Ю.А. Щупак. СПб.: Питер, 2007. - 239 с.: ил.
Павловская Т. А., Щупак Ю. А. C++. Объектно-ориентированное
программирование: Практикум. - СПб.: Питер, 2006. - 265 с: ил.
Кольцов Д.М. 100 примеров на Си. - СПб.: “Наука и техника”, 2017 - 256
с.
5 Доусон М. Изучаем С++ через программирование игр. - СПб.: “Питер”,
2016. - 352.
Седжвик Р. Фундаментальные алгоритмы на С++. Анализ/Структуры
данных/Сортировка/Поиск: Пер. с англ. Роберт Седжвик. - К.:
Издательство “Диасофт”, 2001. - 688с.
Сиддкхартха Р. Освой самостоятельно С++ за 21 день. - М.: SAMS, 2013.
- 651 с.
Стивен, П. Язык программирования С++. Лекции и упражнения, 6-е
изд. Пер. с англ. - М.: ООО "И.Д. Вильямс", 2012. - 1248 с.
Черносвитов, А. Visual C++: руководство по практическому изучению
/ А. Черносвитов . - CПб. : Питер, 2002. - 528 с. : ил.

29. Список литературы

29
Страуструп Б. Дизайн и эволюция языка С++. - М.: ДМК, 2000. - 448 с.
Мейерс С. Эффективное использование С++. - М.: ДМК, 2000. - 240 с.
Бадд Т. Объектно-ориентированное программирование в действии. - СПб:
Питер, 1997. - 464 с.
Лаптев В.В. С ++. Объектно-ориентированное программирование: Учебное
пособие.- СПб.: Питер, 2008. - 464 с.: ил.
Страуструп
Б.
Язык
программирования
С++.
Режим
доступа:
http://8361.ru/6sem/books/Straustrup-Yazyk_programmirovaniya_c.pdf.
Керниган Б., Ритчи Д. Язык программирования Си. Режим доступа:
http://cpp.com.ru/kr_cbook/index.html.
Герберт
Шилдт:
С++
базовый
курс.
Режим
доступа:
https://www.bsuir.by/m/12_100229_1_98220.pdf,
Богуславский А.А., Соколов С.М. Основы программирования на языке Си++.
Режим доступа: http://www.ict.edu.ru/ft/004246/cpp_p1.pdf.
Линский,
Е.
Основы
C++.
Режим
доступа:
https://www.lektorium.tv/lecture/13373.
Конова Е. А., Поллак Г. А. Алгоритмы и программы. Язык С++: Учебное пособие.
Режим
доступа:
https://vk.com/
doc7608079_489807856?hash=e279524206b2efd567&dl=f85cf2703018eeaa2
English     Русский Правила