C указатели и ссылки для чайников: точки над i / Хабр

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 — одновременно и переменная и объект.

    Поэтому, более точное определение переменной — это «то, что было объявлено (если это не функция)». Т.е.

    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++.

Что такое указатель в 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++, Java и Pascal. Этот документ является сопутствующим документом для Pointer Fun с цифровым видео Binky или его можно использовать отдельно.

Это документ 106 в Стэнфордской образовательной библиотеке CS. Этот и другие бесплатные материалы доступны на сайте cslibrary.stanford.edu. Некоторые документы, связанные с этим, включают…

Раздел 1. Правила указателя

Одна из приятных особенностей указателей заключается в том, что правила, управляющие их работой, довольно просты. Правила можно накладывать друг на друга для получения сложных результатов, но отдельные правила остаются простыми.

1) Указатели и пуанты

Указатель хранит ссылку на что-то. К сожалению, не существует фиксированного термина для того, на что указывает указатель, и в разных языках программирования существует большое разнообразие вещей, на которые указывают указатели. Мы используем термин pointee для того, на что указывает указатель, и мы придерживаемся основных свойств отношения указатель/указатель, которые верны для всех языков. Термин «ссылка» означает почти то же самое, что и «указатель» — «ссылка» подразумевает обсуждение на более высоком уровне, в то время как «указатель» подразумевает традиционную скомпилированную языковую реализацию указателей в виде адресов. Для основных правил указателя/указателя, описанных здесь, термины фактически эквивалентны.


На приведенном выше рисунке показан указатель с именем x , указывающий на указатель, в котором хранится значение 42. Указатель обычно изображается в виде прямоугольника, а ссылка, которую он хранит, изображается в виде стрелки, начинающейся в прямоугольнике и ведущей к его указателю.

Выделение указателя и выделение указателя, на который он указывает, — это два отдельных шага. Вы можете думать о структуре указателя/указателя как о работе на двух уровнях. Оба уровня должны быть настроены, чтобы все работало. Наиболее распространенная ошибка заключается в том, что вы концентрируетесь на написании кода, управляющего уровнем указателя, но забываете настроить уровень указателя. Иногда операции с указателями, которые не касаются указателей, называются «поверхностными», а операции над указателями — «глубокими».

2) Разыменование

Операция разыменования начинается с указателя и следует за его стрелкой, чтобы получить доступ к его указателю. Целью может быть просмотр состояния указателя или изменение состояния указателя.

Операция разыменования указателя работает только в том случае, если указатель имеет указатель — указатель должен быть выделен, а указатель должен указывать на него. Самая распространенная ошибка в коде указателя — забывание настроить указатель. Наиболее распространенный сбой во время выполнения из-за этой ошибки в коде — сбой операции разыменования. В Java неправильное разыменование будет вежливо помечено системой выполнения. В скомпилированных языках, таких как C, C++ и Pascal, неправильное разыменование иногда приводит к сбою, а иногда и к повреждению памяти каким-то незаметным случайным образом. По этой причине ошибки указателей в скомпилированных языках трудно отследить.

3) Назначение указателя

Назначение указателя между двумя указателями заставляет их указывать на одного и того же указателя. Итак, присваивание y = x; заставляет y указывать на тот же указатель, что и x . Назначение указателя не касается указателей. Он просто изменяет один указатель, чтобы он имел ту же ссылку, что и другой указатель. После назначения указателя говорят, что два указателя «разделяют» указатель.

Раздел 2. Код Бинки, пример

В этом разделе представлен тот же пример кода, что и в Веселье указки с видео Бинки. Существуют версии кода на нескольких компьютерных языках. Все версии имеют одинаковую структуру и демонстрируют одни и те же основные правила и уроки работы с указателями; они просто различаются по своему синтаксису. Независимо от какого-либо конкретного языка, основная структура примера такова…

Предположим, у вас есть тип указателя с именем «Node», который содержит две вещи: int и указатель на другой Node (объявление для такого типа Node приведено ниже). С таким типом указателя вы можете расположить три указателя Node в структуре, где они указывали бы друг на друга вот так…

Указатель с именем x указывает на первый указатель Node. Первый узел содержит указатель на второй, второй содержит указатель на третий, а третий содержит указатель обратно на первый. Эту структуру можно построить, используя только те правила размещения, разыменования и назначения указателей, которые мы видели. Используя приведенное ниже объявление, каждый узел содержит целое число с именем , значение и указатель на другой узел с именем next .

Напишите код для построения структуры на приведенном выше рисунке. Для удобства вы можете использовать временные указатели в дополнение к х . Единственный требуемый новый синтаксис заключается в том, что в C оператор -> разыменовывает указатель для доступа к полю в указателе, поэтому -> значение обращается к полю с именем значение в указателе x .

Представленная здесь структура Node на самом деле является реальным типом данных, используемым для построения структуры данных «связанный список». Связанные списки представляют собой реалистичное прикладное использование указателей и являются отличной областью для развития ваших навыков работы с указателями.

Оставить комментарий

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

© 2019 Штирлиц Сеть печатных салонов в Перми

Цифровая печать, цветное и черно-белое копирование документов, сканирование документов, ризография в Перми.

1. Разместите два указателя x и y . Выделение указателей не назначьте любые указатели.
2. Выделите указатель и установите x , чтобы он указывал на него. Каждый язык имеет для этого свой синтаксис. Важно то, что память динамически выделяется для одного указателя, и 90 159 x 90 160 указывают на этот указатель.
3. Разыменование x для сохранения 42 в его указателе. Это базовый пример операции разыменования. Начните с x , следуйте стрелке, чтобы получить доступ к ее указателю.
4. Попробуйте разыменовать и , чтобы сохранить 13 в его указателе. Это дает сбой, потому что y не имеет указателя — он никогда не был назначен.
5. Присвоить у = х; , так что y указывает на х пуант. Теперь x и y указывают на одну и ту же точку — они «совместно используются».
6. Попробуйте разыменовать и , чтобы сохранить 13 в своем указателе. На этот раз это работает, потому что предыдущее задание дало и пуант.