Указатель против ссылки


256

Что было бы лучше, если дать функции исходную переменную для работы:

unsigned long x = 4;

void func1(unsigned long& val) {
     val = 5;            
}
func1(x);

или:

void func2(unsigned long* val) {
     *val = 5;
}
func2(&x);

IOW: есть ли какая-то причина, чтобы выбирать между собой?


1
Ссылки, конечно, ценны, но я из Си, где указатели есть везде. Нужно сначала разбираться с указателями, чтобы понять ценность ссылок.
Джей Д

Как это согласуется с такой целью, как прозрачность ссылок из функционального программирования? Что делать, если вы всегда хотите, чтобы функции возвращали новые объекты и никогда не изменяли внутреннее состояние, особенно если переменные не переданы в функцию. Есть ли способ, которым эта концепция все еще используется с указателями и ссылками на языке, подобном C ++. (Обратите внимание, я предполагаю , что кто - то уже имеет цель ссылочной прозрачности Я не заинтересован говорить о том , является ли это хорошая цель иметь..)
Ely

Предпочитаю ссылки. Пользовательские указатели, когда у вас нет выбора.
Ферруччо

Ответы:


285

Мое правило:

Используйте указатели, если вы хотите выполнять с ними арифметику указателей (например, увеличивать адрес указателя для перехода по массиву) или если вам когда-либо придется передавать NULL-указатель.

Используйте ссылки в противном случае.


10
Отличный момент, когда указатель равен NULL. Если у вас есть параметр указателя, то вы должны либо явно проверить, что он не равен NULL, либо выполнить поиск во всех случаях использования функции, чтобы убедиться, что она никогда не равна NULL. Это усилие не требуется для ссылок.
Ричард Корден

26
Объясните, что вы подразумеваете под арифметикой. Новый пользователь может не понимать, что вы хотите изменить то, на что указывает указатель.
Мартин Йорк,

7
Мартин, Под арифметикой я подразумеваю, что вы передаете указатель на структуру, но знаете, что это не простая структура, а ее массив. В этом случае вы можете индексировать его, используя [], или выполнять арифметику, используя ++ / - для указателя. Это разница в двух словах.
Нильс Пипенбринк

2
Мартин, ты можешь делать это только с указателями напрямую. Не со ссылками. Конечно, вы можете взять указатель на ссылку и сделать то же самое на практике, но если вы сделаете это, вы закончите с очень грязным кодом ..
Нильс Пипенбринк

1
Как насчет полиморфизма (например Base* b = new Derived())? Это похоже на случай, который не может быть обработан без указателей.
Крис Редфорд

72

Я действительно думаю, что вы выиграете от установления следующих правил кодирования вызовов функций:

  1. Как и во всех других местах, всегда будьте constверны.

    • Примечание. Это означает, среди прочего, что только выходные значения (см. Пункт 3) и значения, переданные по значению (см. Пункт 4), могут не иметь constспецификатора.
  2. Передайте значение по указателю только в том случае, если значение 0 / NULL является допустимым вводом в текущем контексте.

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

    • Обоснование 2: Как называется , вы знаете, что все, что приходит, находится в пригодном для использования состоянии. Следовательно, для этого значения не требуется выполнять NULL-проверку или обработку ошибок.

    • Обоснование 3: Обоснования 1 и 2 будут выполняться компилятором . Всегда перехватывайте ошибки во время компиляции, если можете.

  3. Если аргумент функции является выходным значением, то передайте его по ссылке.

    • Обоснование: мы не хотим нарушать пункт 2 ...
  4. Выберите «передать по значению» вместо «передать по константной ссылке», только если значение представляет собой POD ( простая старая структура данных ) или достаточно мало (по памяти) или другими способами достаточно дешево (по времени) для копирования.

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

В нем отсутствует указание, когда: ... "когда использовать const &" ... Указание 2 должно быть записано "для значений [in], передаваться только по указателю, если NULL допустим. В противном случае используйте ссылку на const (или для") небольшие "объекты, копия) или ссылка, если это значение [out]. Я отслеживаю этот пост, чтобы потенциально добавить +1.
paercebal

Пункт 1 охватывает случай, который вы описываете.
Иоганн Герелл

Передать выходной параметр по ссылке немного сложно, если он не конструируется по умолчанию. Это довольно часто встречается в моем коде - единственная причина, по которой функция создает этот объект, заключается в том, что это не тривиально.
MSalters

@MSalters: Если вы собираетесь выделять память внутри функции (что, я думаю, означает то, что вы имеете в виду), то почему бы просто не вернуть указатель на выделенную память?
Клейст

@Kleist: от имени @MSalters есть много возможных причин. Один из них заключается в том, что вы, возможно, уже выделили память для заполнения, например, предварительно настроенного размера std::vector<>.
Иоганн Герелл

24

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

Хотя есть несколько различных возможностей (независимо от того, может ли что-либо быть NULL) с указателем, наибольшее практическое различие для выходного параметра - это чисто синтаксис. Например, руководство по стилю Google C ++ ( https://google.github.io/styleguide/cppguide.html#Reference_Arguments ) обязывает только указатели для выходных параметров и разрешает только ссылки, которые являются константными. Аргументация заключается в удобочитаемости: что-то с синтаксисом значения не должно иметь смысловой смысл указателя. Я не предполагаю, что это обязательно правильно или неправильно, но я думаю, что дело здесь в том, что это вопрос стиля, а не правильности.


Что это означает, что ссылки имеют синтаксис значения, но семантическое значение указателя?
Эрик Эндрю Льюис

Похоже, что вы передаете копию, поскольку часть «передача по ссылке» видна только из определения функции (синтаксис значения), но вы не копируете передаваемое значение, по сути, вы передаете указатель изнутри, что позволяет функция для изменения вашего значения.
phant0m

Не следует забывать, что руководство по стилю Google C ++ очень ненавистно.
Дедупликатор

7

Вы должны передать указатель, если вы собираетесь изменить значение переменной. Даже если технически передать ссылку или указатель - это одно и то же, передача указателя в вашем случае использования более читабельна, так как «рекламирует» тот факт, что значение будет изменено функцией.


2
Если вы следуете рекомендациям Иоганна Герелла, неконстантная ссылка также объявляет переменную переменную, поэтому указатель здесь не имеет такого преимущества.
Александр Кондрацкий

4
@AlexanderKondratskiy: вы упускаете суть ... вы не можете сразу увидеть на месте вызова , принимает ли вызываемая функция параметр в качестве constили не является constссылкой, но вы можете увидеть, был ли параметр передан как ala &xvs. x, и использовать это соглашение, чтобы закодировать, может ли параметр быть изменен. (Тем не менее, бывают случаи, когда вы хотите передать constуказатель, поэтому условное обозначение - всего лишь подсказка. Аргументированное подозрение, что что-то может быть изменено, когда этого не произойдет, менее опасно, чем думать, что этого не произойдет, когда это произойдет. ....)
Тони Делрой

5

Если у вас есть параметр, в котором вам может потребоваться указать отсутствие значения, обычной практикой является присвоение параметру значения указателя и передача значения NULL.

Лучшим решением в большинстве случаев (с точки зрения безопасности) является использование boost :: option . Это позволяет передавать необязательные значения по ссылке, а также в качестве возвращаемого значения.

// Sample method using optional as input parameter
void PrintOptional(const boost::optional<std::string>& optional_str)
{
    if (optional_str)
    {
       cout << *optional_str << std::endl;
    }
    else
    {
       cout << "(no string)" << std::endl;
    }
}

// Sample method using optional as return value
boost::optional<int> ReturnOptional(bool return_nothing)
{
    if (return_nothing)
    {
       return boost::optional<int>();
    }

    return boost::optional<int>(42);
}


4

указатели

  • Указатель - это переменная, которая содержит адрес памяти.
  • Объявление указателя состоит из базового типа, * и имени переменной.
  • Указатель может указывать на любое количество переменных в жизни
  • Указатель, который в данный момент не указывает на допустимое место в памяти, получает значение null (которое равно нулю)

    BaseType* ptrBaseType;
    BaseType objBaseType;
    ptrBaseType = &objBaseType;
  • & Является унарным оператором, который возвращает адрес памяти своего операнда.

  • Оператор разыменования (*) используется для доступа к значению, хранящемуся в переменной, на которую указывает указатель.

       int nVar = 7;
       int* ptrVar = &nVar;
       int nVar2 = *ptrVar;

Ссылка

  • Ссылка (&) похожа на псевдоним существующей переменной.

  • Ссылка (&) похожа на константный указатель, который автоматически разыменовывается.

  • Обычно используется для списков аргументов функции и возвращаемых значений функции.

  • Ссылка должна быть инициализирована при ее создании.

  • Как только ссылка инициализируется на объект, она не может быть изменена для ссылки на другой объект.

  • Вы не можете иметь пустые ссылки.

  • Ссылка const может ссылаться на const int. Это делается с помощью временной переменной со значением const

    int i = 3;    //integer declaration
    int * pi = &i;    //pi points to the integer i
    int& ri = i;    //ri is refers to integer i – creation of reference and initialization

введите описание изображения здесь

введите описание изображения здесь


3

Ссылка - это неявный указатель. В основном вы можете изменить значение, на которое указывает ссылка, но вы не можете изменить ссылку, чтобы указать на что-то другое. Таким образом, мои 2 цента состоят в том, что если вы хотите только изменить значение параметра, передайте его как ссылку, но если вам нужно изменить параметр, чтобы он указывал на другой объект, передайте его с помощью указателя.


3

Рассмотрим ключевое слово C #. Компилятору требуется, чтобы вызывающий метод применял ключевое слово out к любым аргументам out, даже если он уже знает, есть ли они. Это предназначено для улучшения читабельности. Хотя с современными IDE я склонен думать, что это работа для подсветки синтаксиса (или семантики).


опечатка: семантическая, а не семантическая; +1 Я согласен с возможностью выделения вместо записи (C #) или & (в случае C, без ссылок)
peenut

2

Передавайте по постоянной ссылке, если нет причин, по которым вы хотите изменить / сохранить содержимое, которое вы передаете.

Это будет самый эффективный метод в большинстве случаев.

Убедитесь, что вы используете const для каждого параметра, который вы не хотите изменять, поскольку это не только защищает вас от каких-либо глупостей в функции, но и дает другим пользователям хорошее представление о том, что функция делает с переданными значениями. Это включает создание указателя const, когда вы хотите изменить только то, на что указывает ...


2

указатели:

  • Может быть назначен nullptr (или NULL).
  • На сайте вызова вы должны использовать, &если ваш тип не является указателем, явно указав, что вы модифицируете свой объект.
  • Указатели могут быть отскок.

Ссылки:

  • Не может быть нулевым.
  • После привязки не может измениться.
  • Абоненты не должны явно использовать &. Это иногда считается плохим, потому что вы должны перейти к реализации функции, чтобы увидеть, изменился ли ваш параметр.

Небольшое замечание для тех, кто не знает: nullptr или NULL - это просто 0. stackoverflow.com/questions/462165/…
Сергей Федоров

2
nullptr отличается от 0. Попробуйте int a = nullptr; stackoverflow.com/questions/1282295/what-exactly-is-nullptr
Йохан Лундберг,

0

Ссылка похожа на указатель, за исключением того, что вам не нужно использовать префикс ∗ для доступа к значению, на которое ссылается ссылка. Кроме того, нельзя сделать ссылку для ссылки на другой объект после его инициализации.

Ссылки особенно полезны для указания аргументов функции.

Для получения дополнительной информации см. «Путешествие по С ++» Бьярна Страуструпа (2014). Страницы 11-12.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.