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.

Ссылки | 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, а также в файле упражнений этого фильма.

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

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

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