C ++ Возвращающиеся указатели / ссылки - PullRequest
44 голосов
/ 15 мая 2010

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

Я, однако, смущаюсь, когда вижу такие вещи:

int* returnA() {
    int *j = &a;
    return j;
}

int* returnB() {
    return &b;
}

int& returnC() {
    return c;
}

int& returnC2() {
    int *d = &c;
    return *d;
}
  1. В returnA() Я прошу вернуть указатель; просто чтобы уточнить это работает, потому что j это указатель?
  2. В returnB() Я прошу вернуть указатель; так как указатель указывает на адрес, причина, по которой returnB() работает, заключается в том, что я возвращаю &b?
  3. В returnC() Я прошу вернуть адрес int. Когда я возвращаю c, оператор & автоматически "добавляется" c?
  4. В returnC2() Я снова прошу вернуть адрес int. *d работает, потому что указатели указывают на адрес?

Предположим, что a, b, c инициализируются как целые числа как Global.

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

Ответы [ 6 ]

72 голосов
/ 15 мая 2010

Хотя Петр ответил на ваш вопрос, одна вещь, которая явно вас смущает, это символы * и &. Сложность в том, чтобы разобраться с этим, состоит в том, что они оба имеют два разных значения, которые имеют отношение к косвенности (даже исключая третьи значения * для умножения и & для побитового и).

  • *, когда используется как часть типа указывает, что тип является указателем: int - это тип, поэтому int* - это тип указатель на тип int, а int** является указатель на указатель на тип int.

  • & при использовании в качестве части типа указывает, что тип является ссылкой. int является типом, поэтому int& является ссылкой на int (нет такой вещи как ссылка на ссылку). Ссылки и указатели используются для похожих вещей, но они совершенно разные и не взаимозаменяемы. Ссылку лучше всего рассматривать как псевдоним или альтернативное имя для существующей переменной. Если x является int, то вы можете просто назначить int& y = x, чтобы создать новое имя y для x. После слов x и y могут использоваться взаимозаменяемо для обозначения одного и того же целого числа. Два основных значения этого состоят в том, что ссылки не могут иметь значение NULL (поскольку должна существовать исходная переменная для ссылки), и что вам не нужно использовать какой-либо специальный оператор для получения исходного значения (потому что это просто альтернативное имя, не указатель). Ссылки также не могут быть переназначены.

  • * при использовании в качестве унарного оператора выполняет операцию, называемую разыменованием (которая не имеет ничего общего со ссылками types !). Эта операция имеет смысл только для указателей. Когда вы разыменовываете указатель, вы возвращаете то, на что он указывает. Так, если p является указателем на int, *p является указателем на int.

  • & при использовании в качестве унарного оператора выполняет операцию под названием address-of . Это довольно очевидно; если x является переменной, то &x является адресом x. Адрес переменной может быть назначен указателю на тип этой переменной. Так, если x является int, тогда &x может быть назначен указателю типа int*, и этот указатель будет указывать на x. Например. если вы присваиваете int* p = &x, то *p может использоваться для получения значения x.

Помните, что суффикс типа & предназначен для ссылок и не имеет ничего общего с унарной операцией &, которая имеет отношение к получению адресов для использования с указателями. Два использования совершенно не связаны. И * в качестве суффикса типа объявляет указатель, в то время как * в качестве унарного оператора выполняет действие с указателями.

27 голосов
/ 15 мая 2010

В returnA () я прошу вернуть указатель; просто чтобы уточнить это работает, потому что J указатель?

Да, int *j = &a инициализирует j для указания a. Затем вы возвращаете значение j, то есть адрес a.

В returnB () я прошу вернуть указатель; так как указатель указывает на адрес, причина, по которой returnB () работает, заключается в том, что я возвращаю & b?

Да. Здесь происходит то же самое, что и выше, только за один шаг. &b дает адрес b.

В returnC () я прошу вернуть адрес int. Когда я возвращаюсь, оператор & автоматически добавляется?

Нет, это ссылка на int, которая возвращается. Ссылка не является адресом так же, как указатель - это просто альтернативное имя для переменной. Поэтому вам не нужно применять оператор &, чтобы получить ссылку на переменную.

В returnC2 () я снова прошу вернуть адрес int. * D работает, потому что указатели указывают на адрес?

Опять же, это ссылка на int, которая возвращается. *d относится к исходной переменной c (что бы это ни было), на которую указывает c. И это неявно можно превратить в ссылку, как в returnC.

Указатели обычно не указывают на адрес (хотя они могут - например, int** - указатель на указатель на int). Указатели являются адресом чего-либо. Когда вы объявляете указатель как something*, то something - это то, на что указывает ваш указатель. Таким образом, в приведенном выше примере int** объявляет указатель на int*, который сам по себе является указателем.

5 голосов
/ 16 марта 2014

Тайлер, это было очень полезным объяснением, я провел некоторый эксперимент с использованием отладчика visual studio, чтобы еще больше уточнить эту разницу:

int sample = 90;
int& alias = sample;
int* pointerToSample  = &sample;

Name                  Address                        Type
&alias                0x0112fc1c {90}                int *
&sample               0x0112fc1c {90}                int *
pointerToSample       0x0112fc1c {90}                int *
*pointerToSample    90                       int
alias   90                                       int &
&pointerToSample      0x0112fc04 {0x0112fc1c {90}}   int * *

Макет памяти

PointerToSample       Sample/alias
_______________......____________________
0x0112fc1c |          |   90   |
___________|___.....__|________|_______...

[0x0112fc04]  ...      [0x0112fc1c
2 голосов
/ 15 мая 2010

Все ваши примеры приводят к неопределенному поведению во время выполнения. Вы возвращаете указатели или ссылки на элементы, которые исчезают после выхода из функции.

Позвольте мне уточнить:

int * returnA()
{
  static int a;  // The static keyword keeps the variable from disappearing. 
  int * j = 0;   // Declare a pointer to an int and initialize to location 0.
  j = &a;        // j now points to a.
  return j;      // return the location of the static variable (evil).
}

В вашей функции переменная j назначена для указания на временное местоположение a. При выходе из вашей функции переменная a исчезает, но ее прежнее местоположение возвращается через j. Поскольку a больше не существует в месте, указанном j, при доступе к *j произойдет неопределенное поведение.

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

Будучи педантичным, возвращаемые указатели должны быть объявлены как указывающие на постоянные данные. Возвращенные ссылки должны быть константными:

const char * Hello()
{
  static const char text[] = "Hello";
  return text;
}

Вышеуказанная функция возвращает указатель на постоянные данные. Другой код может получить доступ (прочитать) к статическим данным, но не может быть изменен.

const unsigned int& Counter()
{
  static unsigned int value = 0;
  value = value + 1;
  return value;
}

В приведенной выше функции value инициализируется в ноль при первой записи. Все последующие исполнения этой функции приводят к увеличению value на единицу. Функция возвращает ссылку на постоянное значение. Это означает, что другие функции могут использовать значение (издалека), как если бы оно было переменной (без разыменования указателя).

В моем мышлении указатель используется для необязательного параметра или объекта. Ссылка передается, когда объект должен существовать. Внутри функции ссылочный параметр означает, что значение существует, однако указатель должен быть проверен на нулевое значение перед разыменованием его. Кроме того, со ссылкой, есть дополнительная гарантия, что целевой объект является действительным. Указатель может указывать на неверный адрес (не ноль) и вызывать неопределенное поведение.

2 голосов
/ 15 мая 2010

В returnC () и returnC2 () вы не просите вернуть адрес.

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

Все, что вы знаете, это ссылка на конкретный объект.
Сама ссылка - это не объект, а альтернативное имя.

1 голос
/ 15 мая 2010

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

Недостатком этого является то, что вы не можете изменить то, к чему они относятся - они связаны во время создания.

...