c++ — Чем отличаются ссылки от указателей в С++
Ссылки проще понять в сравнении с указателями. По сути, это те же указатели, но немного измененные.
Напишу только про обычные ссылки («lvalue-ссылки» &
). Про «rvalue-ссылки» &&
— читать отдельно.
Ссылки всегда разыменовываются автоматически (после того, как были созданы):
int x = 0; int x = 0; int *y = &x; int &y = x; *y = 42; y = 42; // Не нужно разыменование.
Следствие: Ссылку нельзя «перенацелить» на другой объект, после того, как она создана.
Если для указателя можно написать
y = &z;
, то для ссылки присваивание меняет объект, на который она ссылается, а не ее саму.Следствие: Ссылку обязательно инициализировать сразу при создании.
int *z; /* ок */ int &z; // Ошибка компиляции.
Следствие: Ссылки не бывают константными.
Поскольку их и так нельзя менять, дополнительный
const
не имеет смысла и запрещен.int *const x
— «x
— константный указатель на неконстантныйint
«. Ноint &const x
— ошибка компиляции.Не путать с
const int *x
иconst int &x
— неконстантный указатель/ссылка на константныйint
. Тут оба варианта легальны.
Ссылки инициализируются не адресом, а самим объектом (см. пример выше).
Разница только в способе записи, эффект на самом деле одинаковый.
Следствие: Ссылки не бывают нулевыми.
Указатель можно инициализировать
nullptr
— нулевым адресом. А поскольку ссылка инициализируется не адресом, туда указатель не подойдет.Попытка создать нулевую ссылку (пример:
int *x = nullptr; int &y = *x;
) вызывает неопределенное поведение. Но компиляторы обычно никак не защищают от этого (вероятнее всего, крашнет при использовании ссылки, а не при создании).Но поскольку это UB (т.е. «undefined behavior», т.е. «неопределенное поведение»), проверки а-ля
&ссылка == nullptr
не имеют никакого смысла, поскольку могут быть выброшены компилятором как всегда ложные.
И указатели, и ссылки часто используют как параметры функций — когда передавать по значению не хочется (либо потому, что нужно изменить параметр и видеть изменения снаружи функции, либо потому, что тип параметра дорого копировать). Соответственно, чаще всего указатели — для опциональных параметров (вместо которых можно передать ноль), а ссылки — для обязательных.
Чтобы передать в функцию массив, традиционно используют указатель на первый элемент, хотя чисто с точки зрения языка, ссылка на первый элемент ничем не хуже.
Некоторые не любят использовать неконстантные ссылки в параметрах. Дескать, нагляднее писать
при вызове, чтобы сразу видеть, что объект меняется.
И указатели и ссылки нельзя инициализировать rvalue (грубо говоря, временными объектами, у которых компилятор не дает взять адрес):
int *x = &42; // ошибка компиляции, сразу на `&`. int &y = 42; // ошибка компиляции
- Исключение: Ссылки на константные типы — можно.
const int &z = 42; // ок
Дальше идут тонкости.
Ссылки продляют время жизни временных объектов.
const int &x = 42; int y = x;
Этот пример легален. Временный объект, на который указывает ссылка, продляет свое время жизни до времени жизни ссылки.
Но в этом правиле много исключений, например при возврате ссылки из функции это не действует. Никаких принципально новых возможностей эта фича не дает, поэтому проще вообще не пользоваться.
Ссылки не являются объектами.
Это больше формальность в стандарте, и тут нужно объяснение.
Объект — это просто экземпляр какого-то типа, с именем или без. Например,
int x
— объект типаint
, и42
— тоже. (Исключение: функции — не объекты.)Переменная — это, грубо говоря, объект с именем.
int x
— переменная, а42
— нет. (Хотя исключения есть. Например, безымянные параметры функций — тоже переменные.)И второе исключение: ссылки — это переменные, но не объекты. Т.е.
int &x
— переменная но не объект, аint x
— одновременно и переменная и объект.Поэтому, более точное определение переменной — это «то, что было объявлено (если это не функция)». Т.е.
— переменная, если где-то выше есть объявлениеint x;
. А42
— нет, потому что для нее ничего такого нет.Следствие: Формально, у ссылок нет адресов.
&ссылка
выдает адрес не самой ссылки, а того, на что она указывает (это следует из того, что ссылки разыменовываются автоматически).Можно положить ссылку в структуру, и взять адрес структуры. Технически, по этому адресу как раз будет лежать ссылка (в виде указателя), но формально — по этому адресу непонятно что.
- Следствие: Менять какие-то байтики по «адресу» ссылки — UB.
Следствие: Не бывает указателей на ссылки, массивов ссылок.
Выражения не могут иметь типы-ссылки.
Это тоже больше формальность.
У внимательного читателя мог возникнуть вопрос: Если не бывает объектов-ссылок, то чем будет являться возвращенная из функции ссылка? Ведь не переменной, имени-то у нее нет, и объявлена она не была.
Сначала поясняю, что такое выражение. Классическое определение — это «операнды, соединенные операторами». Т.е.
1 + 1
,(2 * 3) + 4
, и т.п. Отдельные операнды — это тоже выражения, например42
илиx
(если выше естьint x;
).Выражение — это часть исходного кода программы (грамматический термин), а не то, что существует в памяти (то — объекты).
Выражение ссылаются на объекты. Например,
1 + 1
ссылается на временный объект int со значением 2. Аx
ссылается на объект и переменнуюint x;
(если такая объявлена выше).Rvalue и lvalue — это свойства выражений (т. н. «категория выражения»), а «временный» и «не временный» — свойства объектов.
Так вот, выражения не могут иметь типы-ссылки.
Т.е. если имеем
int &x
, то выражениеx
— это lvalue типаint
(ссылкой оно быть не может). Аналогично, если естьint y
, то выражениеy
— тоже lvalue типаint
.Аналогично, если имеем
int &x()
, тоx()
— lvalue типа int. А дляint y()
,y()
— rvalue типа int.
Ссылки | Python: Списки
Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером
Пока мы работали с неизменяемыми значениями, эти значения передавались в функции и сохранялись в переменных.
С изменяемыми объектами все по-другому. Настало время узнать, что в Python все передается по ссылке. Что же такое ссылка? Разберемся в этом уроке.
Но начнем знакомство со старших братьев ссылки — это адреса и указатели.
Ссылки и управление памятью
Все данные, с которыми работает программа, находятся в оперативной памяти компьютера. Чтобы иметь доступ к некоторому участку памяти, нужно знать адрес этого участка.
В языках с ручным управлением памятью необходимо постоянно следить за тем, что память по адресу выделена и еще не освобождена. В таких языках программист явно запрашивает у операционной системы нужное ему количество памяти.
Операционная система в ответ на запрос выделяет участок в общей оперативной памяти, закрепляет этот блок за попросившим доступ и возвращает указатель, по сути представляющий собой тот самый адрес. Получив указатель, программист может сохранить что-то в выделенную память.
По окончании работы выделенные участки нужно освобождать — сообщать ОС, что память свободна и может быть использована для чего-то другого. Если обратиться по указателю к участку памяти, который еще не выделен или уже освобожден, программа завершится с ошибкой!
Python же является языком с автоматическим управлением памятью. Как только программисту требуется создать некое значение, требуемое количество памяти выделяется средой исполнения автоматически. Значение сохраняется в эту память и программисту возвращается ссылка на сохраненное значение.
Как только данные перестают использоваться, память будет освобождена — также автоматически. Таким образом ссылки выполняют ту же роль, что и указатели в упомянутых выше языках. Но пользоваться ссылками всегда безопасно: ссылка не может указывать на память, не готовую к использованию.
Более того, программисту на Python не нужно отдельно получать память и отдельно заполнять ее — данные размещаются в памяти все той же средой исполнения.
Как работают ссылки
Когда мы создаем некое значение, мы получаем от среды исполнения именно ссылку на него. Ссылок на одно и то же значение в любой момент времени может быть сколько угодно. Python экономит усилия и всегда и везде передает любые значения по ссылкам — создает новые ссылки на существующие данные. Даже переменные — это всего лишь имена, привязанные к ссылкам.
И при вызове функции с передачей ей аргументов, передаются не сами значения, а только ссылки на них — по одной новой ссылке на каждое значение. Когда же выполнение функции завершится, ненужные ссылки уничтожаются.
Как только исчезает последняя ссылка на некое значение, среда исполнения понимает, что и само значение больше никому не нужно и его можно удалить из памяти, освободив таким образом место. Этим занимается специальный механизм среды исполнения, так называемый счетчик ссылок.
Указатели в C++: Полное пошаговое руководство
Указатели — одна из лучших возможностей C++. Следовательно, важно понимать указатели в C++, чтобы вы могли использовать эти возможности. Указатель работает с адресами памяти, а не с переменными, что делает его более интересным. Этот учебник поможет вам узнать все, что вам нужно знать об указателях C++.
Зачем нужны указатели в C++?
Указатели предоставляют множество возможностей языку C++. Он предлагает такие функции, как обращение к одному и тому же месту в памяти из одного места в памяти, то есть вы можете изменить одно место в программе, и оно может отразить эти изменения в других частях программы.
Благодаря указателям возможно более гибкое выделение и освобождение памяти. И вы также используете его в полиморфизме и указателях на функции.Теперь поймите, что такое указатели в C++.
Что такое указатель в C++?
В C++ каждая переменная имеет свой уникальный адрес или местоположение в памяти компьютера, и этот специальный адрес называется адресом памяти. Вы можете определить указатель как переменную, которая содержит адрес памяти какой-либо другой переменной. Это позволяет разработчику иметь дело с памятью.
Здесь 2000 — адрес переменной, хранимой указателем, а 22 — значение переменной.
Объявление и инициализация указателя
Основной синтаксис указателя в C++:
Синтаксис:
Здесь тип данных может быть int, char, double и т. д. Имя указателя может быть любым со знаком *. Оператор * объявляет, что переменная является указателем.
Пример:
Инициализация
Пример:
Здесь «a» — это переменная типа данных int, а 30 — это значение, которое присваивается этой переменной a. Указатель ptr ссылается непосредственно на значение a, поэтому здесь ptr будет хранить адрес переменной a. & (оператор адреса) используется для получения адреса данных, хранящихся в переменной a.
Таким образом, концепция указателей в C++ и ее синтаксис ясны. Итак, теперь вы узнаете об операторе разыменования.
Оператор разыменования в указателе
Как вы поняли, указатель хранит адрес, а не данные, но как тогда получить доступ к данным? Вы можете сделать это с помощью оператора разыменования *. Он предоставляет вам доступ к данным, которые хранятся по адресу памяти.
Когда вы используете оператор разыменования с ptr, он будет указывать на адрес памяти переменной a, т.е. 2000, который также является его собственным (ptr) значением. Затем он укажет на значение, хранящееся по адресу памяти, т.е. 22.
Пример:
В этом примере указатель ptr хранит адрес переменной a. С помощью оператора разыменования вы печатаете значение a, то есть 50. Используя *ptr, вы получаете доступ к значению, которое находится по адресу, доступному в указателе.
Ниже прилагается вывод приведенного выше примера.
Арифметика указателя
Существуют некоторые арифметические операции, которые можно выполнять с указателем в C++, поскольку указатель хранит адрес, который является числовым значением. И арифметические операторы:
- Оператор приращения (++)
- Дополнение (+)
- Вычитание (-)
Оператор увеличения: Когда вы увеличиваете указатель, размер его типа увеличивает его адрес. Например, для увеличения целочисленного указателя с адресом 450 после увеличения будет 454, поскольку размер типа int равен 4 байтам.
Оператор уменьшения: при уменьшении указателя его адрес будет уменьшен на размер его типа.
Операции увеличения и уменьшения указателя используются для обхода массива, поскольку при увеличении указатель будет указывать на следующий адрес памяти следующего элемента. При уменьшении он будет указывать на предыдущий адрес памяти.
Добавление: когда вы выполняете операцию добавления к указателю ptr на 1, т. е. ptr+1, он будет указывать на следующий адрес памяти. Точно так же, если вы добавите 3 к ptr, то он будет указывать на адрес, который в три раза превышает размер типа указателя, типа 3* (размер).
Вычитание: при вычитании указателя адрес будет уменьшаться на n* (размер).
Теперь вы узнаете, как использовать указатель C++ для доступа к элементам массива.
Использование указателей для доступа к элементам массива
В массиве имя называется адресом массива внутри памяти и поэтому при присвоении адреса массива указателю мы не используем знак амперсанда &, так как имя массива обозначает адрес массива первый элемент массива.
Итак, для доступа к элементам массива можно использовать оператор разыменования с именем массива.
Например:
Здесь имя массива — «records», внутри которого пять элементов. С помощью имени массива вы печатаете каждый элемент массива, потому что, как вы знаете, ptr+1 будет указывать на следующий адрес памяти. Аналогично, записи +1 будут указывать на следующий адрес, но вы используете вместе с ним оператор разыменования, поэтому значение печатается для этого адреса. То же самое касается записей+2, записей+3 и так далее.
Ниже приведен вывод приведенного выше примера.
Вы также можете перемещаться по элементам массива с помощью оператора приращения.
В этом примере вы назначите записи массива указателю ptr, а затем с помощью цикла for пройдете элементы от 0 до 4. Внутри цикла for значения, хранящиеся по адресу указателя ptr будут печататься один за другим после каждой итерации. Ptr++ поможет переместить указатель на следующую позицию после каждой итерации.
Ниже приведен вывод упомянутого примера.
Передача указателей на функции
В C++ вы также можете передавать указатели в качестве аргументов для функций. Когда вы передаете указатели на функцию, адрес фактического аргумента копируется в формальный аргумент вызываемой функции. И именно поэтому он отражает изменения исходных переменных.
Взгляните на пример.
В этом примере вы передаете адрес двух переменных n1 и n2 функции подкачки. Таким образом, эта функция принимает два аргумента в качестве указателей, то есть ptr1 и ptr2. Он взял внутри этой функции целочисленную переменную temp и присвоил ей значение *ptr1, затем вы присвоили значение *ptr2 переменной *ptr1. Теперь temp имеет значение *ptr1, которое вы передаете *ptr2. Так вот как значения теперь меняются местами.
Так как теперь он поменял местами значения с указателями, он также должен отразить это замененное значение вне функции.
Как видно из приведенного выше вывода, они меняют местами значения.
Продвиньтесь по карьерной лестнице в качестве разработчика стека MEAN с помощью программы Full Stack Web Developer — MEAN Stack Master’s Program. Зарегистрируйтесь сейчас!
Заключение
Прочитав этот учебник по указателям C++, вы бы поняли, почему вы используете указатели в C++, что такое указатель в C++, его объявление и инициализация, а также об операторе разыменования в C++. Вы также узнали об арифметике указателей и о том, как вы можете использовать указатели для доступа к элементам массива с помощью некоторых примеров.
Если вы, возможно, хотите построить карьеру в области разработки программного обеспечения, обязательно ознакомьтесь с программой последипломного образования в области разработки полного стека от Simplilearn. Это может оказаться идеальным решением, которое поможет вам правильно построить свою карьеру.
У вас есть вопросы относительно этого руководства по указателям в C++? Если вы это сделаете, пожалуйста, оставьте их в разделе комментариев. Мы поможем вам решить ваши вопросы. Чтобы узнать больше об указателях в C++, щелкните следующую ссылку: Указатели в C++
Приятного обучения!
Понимание указателей — Видеоруководство по C
Из курса: C: Структуры данных, указатели и файловые системы
Понимание указателей
“
— [Инструктор] Чтобы понять указатели, вы должны изучить переменные. В этом коде вы видите переменную Integer alpha. Сразу вы знаете три вещи об этом. Его тип данных, целое число. Его имя, альфа. И его значение, 27, назначено в строке 7. Вы также можете получить два других информативных факта о переменных в вашем коде. Количество байтов, которые он занимает в памяти, определяется размером оператора, который появляется в конце строки 9. И расположением переменной в памяти, которое получается с помощью оператора & в конце строки 10. Соберите и запустите. И здесь вы видите, что целочисленная переменная alpha содержит значение 27, что она занимает четыре байта памяти в этой системе и находится по адресу 0060FEFC. И этот адрес будет отличаться от компьютера к компьютеру, а также на одном и том же компьютере, когда вы запускаете код в разное время. Это не гарантия. Эти два элемента, размер переменной и ее расположение в памяти, связаны с концепцией указателей в языке C. Указатель — это переменная, которая содержит ячейку памяти. Это мое определение. Он избегает использования описания, указатель указывает на адрес, что верно, но лучше сказать, что указатель — это переменная, содержащая ячейку памяти. Место памяти чего? Что ж, указатель содержит ячейку памяти или адрес другой переменной в вашем коде. Поскольку указатель сам по себе является переменной, его можно изменить, каким-то образом изменив адрес. Указатель также может манипулировать данными, хранящимися по адресу, на который он ссылается. Указатели объявляются так же, как и любые другие переменные. У них есть тип данных и имя переменной. При объявлении переменной перед именем ставится звездочка. Теперь это единственный оператор указателя звездочки. Это не оператор умножения. Как и все переменные, указатель должен быть инициализирован перед использованием. Неинициализированный указатель приводит к проблемам. Чтобы инициализировать указатель, вы присваиваете ему адрес другой переменной в вашем коде, которая соответствует типу данных указателя. Итак, int для целых чисел, char для символов и так далее. Оператор амперсанд используется для получения адреса переменной. Возможно, вы видели, как этот оператор используется в функции сканирования F, а также в файле упражнений этого фильма.