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.
c++ — Зачем нужны ссылки на указатели?
Вопрос задан
Изменён 7 месяцев назад
Просмотрен 229 раз
Наткнулся на вот такой код, который, как я понимаю, создает указатель и ссылку на этот указатель:
int* int_ptr = &x; int*& strange_ref = int_ptr;
Отсюда вопрос, зачем в С++ вообще существует подобный синтаксис, ведь strange_ref, несмотря на то, что является типом int*&, все равно будет иметь такой же функционал, как и int*. Все также можно будет разыменовать эту ссылку и тд. Почему этот синтаксис вообще существует и где он применяется?
- c++
- указатели
- ссылки
- дизайн-языка
4
Ну вот конкретный практический пример: типичная функция навроде swap без проблем работает с указателями, принимая их по ссылке, как и другие объекты:
template<typename T> void swap(T & left, T & right) { T tmp{left}; left = right; right = tmp; }
Или несколько операций с предварительно выбранным одним указателем:
int * p1{}; int * p2{}; int * & pcur{cond ? p1 : p2}; ... // много операций, изменяющих pcur;
Собственно указатели являются такими же объектами, как и все остальные, и для них можно использовать все те же сценарии работы со ссылками.
2
несмотря на то, что является типом int*&, все равно будет иметь такой же функционал, как и int*
А вот и нет.
int x = 1, y = 2; int *a = &x; int *b = a; int *&c = a; a = &y; std::cout << *b << '\n'; // 1 std::cout << *c << '\n'; // 2
4
Зарегистрируйтесь или войдите
Регистрация через GoogleРегистрация через Facebook
Регистрация через почту
Отправить без регистрации
Почта
Необходима, но никому не показывается
Отправить без регистрации
Почта
Необходима, но никому не показывается
Нажимая на кнопку «Отправить ответ», вы соглашаетесь с нашими пользовательским соглашением, политикой конфиденциальности и политикой о куки
Указатели в 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++
Приятного обучения!
1. Разместите два указателя x и y . Выделение указателей не назначьте любые указатели. | |
2. Выделите указатель и установите x , чтобы он указывал на него. Каждый язык имеет для этого свой синтаксис. Важно то, что память динамически выделяется для одного указателя, и 90 159 x 90 160 указывают на этот указатель. | |
3. Разыменование x для сохранения 42 в его указателе. Это базовый пример операции разыменования. Начните с x , следуйте стрелке, чтобы получить доступ к ее указателю. | |
4. Попробуйте разыменовать и , чтобы сохранить 13 в его указателе. Это дает сбой, потому что y не имеет указателя — он никогда не был назначен. | |
5. Присвоить у = х; , так что y указывает на х пуант. Теперь x и y указывают на одну и ту же точку — они «совместно используются». | |
6. Попробуйте разыменовать и , чтобы сохранить 13 в своем указателе. На этот раз это работает, потому что предыдущее задание дало и пуант. |