Изучение правил и принципов использования указателей и ссылок
Одними из базовых элементов исходного кода являются переменные и объекты, которые отвечают за хранение данных в процессе выполнения программы. Для операций с ними мы используем идентификаторы - цифробуквенные обозначения, которые удобны для восприятия человеком, разрабатывающим код.
Однако, эти элементы имеют также и другую сторону - адрес ячейки памяти, в которой располагается значение переменной или объекта. Можно представить переменную как некий ящик, в котором располагается значение, в то время как сам ящик находится в определённом месте в памяти, это место определяется адресом.
Размер ящика, в котором располагается некоторое значение переменной определяется её типом: так для типа int в общем случае 4 байта, для типа char - 1 байт и т.д. Человеку удобней работать с именем переменной, а вычислительной машине - с её адресом.
В ряде случаев и разработчику требуется обратиться к адресу переменной, в частности такими случаями могут быть:
- операции с массивами;
- выделение и освобождение памяти в определённые моменты выполнения программы;
- передача значений в функцию с целью их изменения;
- разработка сложных структур, таких как связанный список.
Для работы с адресами в языке C++ используется достаточно широкий набор инструментов, к которым относятся указатели и ссылки. Рассмотрим их далее.
Поскольку переменная обладает адресом, по которой располагается её значение в памяти, то для его получения есть специальная операция, которая называется операция взятия адреса. Она обозначается знаком &, который ставится перед именем переменной. Приведём пример программы, в которой осуществляется взятие адреса
Адрес, который выведется в консоль будет представлен числом в шестнадцатеричном формате (признак такого числа - начинается с 0x). Программа, обращаясь по этому адресу, может получить значение переменной. Как видно, для человека такой формат не очень удобен, поскольку запоминать такие значения и впоследствии ими оперировать достаточно неудобно.
Получение адреса существующей переменной может быть использовано для такого механизма языка C++ как ссылки. Ссылка - это псевдоним существующей переменной, которая используется для доступа к значению этой переменной. Чтобы понять принцип работы ссылки, рассмотрим пример программы
В рассмотренной программе y - это ссылка на переменную х. В момент объявления у ей присваивается адрес переменной х, таким образом эти два имени переменных относятся к одной ячейке памяти. При объявлении перед переменной у указывается знак & - это признак того, что у является ссылкой. Инициализировать ссылку (то есть определить её адрес как адрес другой переменной) можно только при объявлении, в последующем изменение элемента, на который ссылается ссылка невозможно.
Одним из ключевых применений ссылок является передача аргументов в функцию с целью дальнейшего изменения их значения. При изучении вопроса функций было отмечено, что возвратить из функции можно лишь одно значение. Использование механизма ссылок позволяет возвратить из функции более одного значения. Ранее рассматривался пример, который позволял определять, существует ли треугольник по заданным значениям длин сторон треугольника
В качестве аргументов функции triangleExistance() передаются длины трёх сторон треугольника, а результат в виде значения типа bool является возвращаемым значением. Рассмотрим вариант этой программы, где для возврата значения используется механизм ссылок.
В приведённом примере есть ряд изменений. Во-первых, функция triangleExistance() больше не возвращает значения. Об этом свидетельствует ключевое слово void в указании типа возвращаемого значения, а также отсутствие оператора return в функции. Во-вторых, функция triangleExistance() стала иметь четыре аргумента, причём четвёртый - это ссылка. Здесь как раз и включается механизм передачи по ссылке. Первые три аргумента передаются по значению - то есть значения переменных a_tr_1, b_tr_1, c_tr 1 копируются и становятся значениями переменных a, b, c соответственно.
Изменение значений переменных a, b, c в функции triangleExistance() (если бы такое потребовалось) никак не отразится на значении переменных a_tr_1, b_tr_1, c_tr_1, поскольку все они хранятся в разных ячейках памяти. Иная ситуация обстоит с четвёртым аргументом - здесь происходит передача по ссылке. У переменной existence, которая была объявлена в функции main() при вызове функции берётся не значение, а адрес, который принимает переменная ex, являющаяся ссылкой.
Таким образом, переменные existence и ex обращаются к одной и той же ячейке памяти и всякое изменение значения одной приведёт к изменению значения другой и наоборот. Это позволяет записать необходимое значение в переменную ex и быть уверенным, что оно будет и у переменной existence.
Механизм возврата одного значения с использованием передачи аргумента по ссылке не является предпочтительным и используется только в том случае, когда объём данных слишком велик для потенциального копирования. Однако в случаях, когда из функции необходимо вернуть два и более значений, без этого механизма не обойтись. Рассмотрим ещё один пример с функцией, которая обменивает значения двух переменных.
В рассмотренном примере оба аргумента передаются по ссылке, поскольку они оба изменяются в функции. Как видно из результатов работы программы, обмен осуществляется успешно.
Ещё одним механизмом для работы с адресным пространством являются указатели. Указатель - это переменная, которая хранит в качестве значения адрес ячейки памяти. При объявлении указателя записывается тот тип данных, адрес которой будет хранить указатель. То есть, если имеется переменная типа int, то указатель, который будет хранить её адрес должен быть указателем на тип int. Отличительным признаком указателя при объявлении является знак * перед именем указателя. Рассмотрим пример программы, в которой используется указатель.
Результат работы программы должен показать, что адрес переменной x был успешно помещён в качестве значения указателя ptr. Существует возможность не только присваивать значение адреса указателю, но и получать значение, которое хранится по адресу, размещённому в указателе. Такая операция называется операцией разыменования. Рассмотрим пример программы
Операция разыменования обозначается как * перед именем указателя. В приведённом примере в указатель записывается значение адреса переменной х, а затем осуществляется доступ к значению, которое хранится по этому адресу с использованием операции разыменования. В результате в консоль должны быть выведены два одинаковых значения.
Указатели достаточно часто используются при работе с массивами. Особенностью массивов в языке С/С++ является то, что имя массива без прямоугольных скобок - это константный указатель на массив, точнее на его начало. Чтобы убедиться в этом, рассмотрим пример программы
Программа выведет адрес первого элемента массива, а затем его значение. Одной из особенностей массива является то, что его элементы располагаются в памяти последовательно. Этот факт позволяет обеспечивать доступ к элементам массива за счёт изменения указателя на первый элемент массива. Рассмотрим ещё один пример, в котором выведем адреса всех элементов массива
Из результатов работы программы видно, что значение переменной i меняется с каждой новой итерацией на 1, значение адреса меняется на 4. Это происходит потому, что увеличение значения адреса в указателе происходит в соответствии с размером типа, на который указывает указатель. Имея адрес элементов массива, можно получить их значение с использованием операции разыменования. Рассмотрим это в следующем примере
Поскольку имя массива является константным указателем, то изменить его значение нельзя, однако его можно присвоить другому указателю и изменять его значение для доступа к различным элементам массива. Рассмотрим это на примере
Такая тесная взаимосвязь между механизмами массивов и указателей позволяет использовать указатели при передаче массивов в функцию. Ранее была рассмотрена программа, которая имеет функцию для вывода значений элементов массива в консоль
Реализуем решение этой же задачи с использованием указателя. Пример программы представлен далее
Return 0;
}
Функция display() претерпела небольшие изменения. Первый аргумент стал указателем. Таким образом, в процессе вызова функции в указатель копируется значение адреса начала массива. Далее, в цикле, получение значения элемента массива осуществляется посредством операции разыменования. После получения очередного значения адрес в указателе инкрементируется.
Таким образом осуществляется доступ ко всем элементам массива. Стоит отметить, что в этом случае изменение значений элементов массива в функции отразится и на основном массиве, объявленном в функции main(). Механизм передачи массива в функцию с использованием указателя схож с механизмом передачи аргументов в функцию по ссылке.
Дата добавления: 2023-09-28; просмотров: 307;