C++: Почему статически созданная переменная может быть передана функции, ожидающей ссылку?
Я давно программирую на C++, но, конечно, не буду называть себя экспертом. Этот вопрос не задают, чтобы решить практическую проблему, которая у меня есть, это больше о понимании того, что делает C++.
Представьте, что у меня есть функция, которая ожидает одного параметра:
void doSomething(SomeClass& ref)
{
// do something interesting
}
(Примечание: параметр является ссылкой на SomeClass). Затем я вызываю функцию следующим образом:
int main(int argc, char *argv[])
{
SomeClass a;
doSomething(a);
}
Почему этот законный C++? Функция ожидает ссылку на SomeClass, но я передаю ей статически размещенную переменную типа SomeClass. Ссылка как указатель нет? Если мы заменим ссылку указателем, компилятор пожалуется. Почему ссылка отличается от указателя таким образом, что происходит за кулисами?
Извините, если это глупый вопрос, просто он меня глючит!
8 ответов
Я думаю, вы бы лучше поняли это, если бы перестали думать о ссылках, как об аналогах с указателями. Я бы сказал, что есть две причины, почему это сравнение сделано:
- Ссылки позволяют передавать объекты в функции, позволяя изменять их. Это был популярный пример использования указателей на C.
- Реализация указателей и ссылок обычно почти одинакова после компиляции.
Но это разные вещи. Вы можете думать о ссылках как о способе дать новое имя объекту. int& x = y;
говорит, что я хочу дать новое имя объекту, который я сейчас называю y
, Это новое имя x
, Оба из них идентифицируют, x
а также y
теперь оба ссылаются на один и тот же объект.
Вот почему вы передаете сам объект в качестве ссылки. Вы говорите, что хотите, чтобы функция имела собственный идентификатор для ссылки на объект, который вы передаете. Если вы не поместите амперсанд в список параметров, тогда объект будет скопирован в функцию. Это копирование часто не требуется.
Возможно, ваша путаница возникает из-за того, что если у вас есть две функции:
void doThingA(int a) {
a=23;
}
void doThingB(int &a) {
a=23;
}
Звонки им выглядят одинаково, но на самом деле они очень разные:
int a=10;
doThingA(a);
doThingB(a);
В первом случае, doThingA(int), создает совершенно новую переменную со значением 10, присваивает ей 23 и возвращает. Исходная переменная в вызывающей стороне остается неизменной. Во втором случае doThingB(int&), где переменная передается по ссылке, создается новая переменная с тем же адресом, что и переданная переменная. Это то, что люди имеют в виду, когда говорят, что передача по ссылке похожа на передачу по указателю. Поскольку обе переменные имеют один и тот же адрес (занимают место в памяти), когда doThingB (int &) изменяет передаваемое значение, переменная в вызывающей программе также изменяется.
Мне нравится думать об этом как о передаваемом указателе без всего этого раздражающего синтаксиса указателя. Тем не менее, я считаю, что функции, которые изменяют переменные, передаваемые по ссылке, сбивают с толку, и я почти никогда не делаю этого. Я бы либо прошел по константной ссылке
void doThingB(const int &a);
или, если я хочу изменить значение, явно передать указатель.
void doThingB(int *a);
SomeClass a();
Это сигнатура функции, а не объект.
Так должно быть
SomeClass a; // a is an object
Тогда ваш код действителен.
Почему этот законный C++?
(при условии, что вы исправили предыдущую точку) Стандарт C++ говорит, что если ваш атрибут функции является ссылкой, то вы должны предоставить объект, имеющий имя (значение l). Так вот, это законно. Если бы это была константная ссылка, вы могли бы даже предоставить временную (r-значение, у которого нет имени).
Функция ожидает ссылку на SomeClass, но я передаю ей статически размещенную переменную типа SomeClass.
Он ожидает ссылку на неконстантный экземпляр SomeClass, что вы и предоставили. Этот экземпляр не является статичным, он просто размещается в стеке. Распределение объекта не имеет ничего общего с тем, как им можно манипулировать, только область видимости. То, как объект распределяется (в стеке, как здесь, или в куче с помощью new/delete), только говорит о времени жизни объекта. Даже статический объект может быть передан в вашу функцию, если он не const.
Я думаю, что вы смешиваете некоторые понятия языка здесь...
Ссылка как указатель нет?
Нет.
Ссылка - это псевдоним объекта. Не больше, не меньше.
Ладно, на самом деле он реализован как указатель со специальными правилами, но это не так во всех случаях: компилятор может реализовать его любым удобным для него способом. В случае атрибута функции, он часто реализуется как указатель. Но вам не нужно даже знать об этом.
Для вас это просто псевдоним объекта.
Если мы заменим ссылку указателем, компилятор пожалуется. Почему ссылка отличается от указателя таким образом, что происходит за кулисами?
Я полагаю, твоя первая ошибка делала тебя неясной?
Вы не передаете это "статически размещаемую переменную типа SomeClass", вы передаете ей ссылку на SomeClass
объект, который вы создали в стеке main()
,
Ваша функция
void doSomething(SomeClass& ref)
Вызывает ссылку на a
в основном, чтобы быть переданным. Вот что &
после того, как тип в списке параметров делает.
Если вы оставили &
, затем SomeClass
s (a
s) будет вызван конструктор копирования, и ваша функция получит новую локальную копию a
объект в main()
; в этом случае все, что вы сделали с ref
в функции не будет видно еще в main()
Ваш код неверен - SomeClass a();
является предварительным объявлением функции a
возвращая SomeClass
экземпляр - такое объявление недопустимо в области действия функции.
Предполагая, что вы имели в виду SomeClass a;
:
Ссылка очень похожа на указатель в большинстве практических способов - главное отличие состоит в том, что вы не можете юридически иметь ссылку на NULL, тогда как вы можете иметь указатель на NULL. Как вы заметили, синтаксис для указателей и ссылок отличается - вы не можете передать указатель там, где ожидается ссылка.
Если вы думаете о ссылке как о "указателе, который не может быть нулевым и не может быть указан в другом месте", вы в значительной степени охвачены. Вы передаете что-то, что относится к вашему местному a
экземпляр - если doSomething
изменяет свой параметр, то это действительно непосредственно модифицирует ваш локальный a
,
Хотя на этот вопрос уже был дан адекватный ответ, я не могу удержаться от того, чтобы поделиться еще несколькими словами о родственной языковой функции "Ссылки в C++".
Как программисты на Си, у нас есть две опции при передаче переменных в функции:
- Передать значение переменной (создание новой копии)
- Передайте указатель на переменную.
Когда дело доходит до C++, мы обычно имеем дело с объектами. Копировать такие объекты при каждом вызове функции, который должен работать с этим объектом, не рекомендуется из-за нехватки места (а также скорости). Передача адреса переменной имеет свои преимущества (с помощью подхода с указателем), и хотя мы можем сделать указатель "const", чтобы избежать каких-либо изменений с помощью указателя, синтаксис с указателями является довольно неуклюжим (пропустите оператор разыменования в месте или два и в конечном итоге тратить часы на отладку!).
C++, предоставляя "ссылки", упаковывает лучшее из обоих вариантов:
- Ссылка может быть понята как передача адреса
- Синтаксис использования ссылки такой же, как и работа с самой переменной.
- Ссылка всегда будет указывать на "что-то". Следовательно, нет исключений "нулевой указатель".
Кроме того, если мы делаем ссылку "const", мы запрещаем любые изменения исходной переменной.
Ссылка не указатель. Вы просто передаете параметры как "по значению" и используете его. Под капотом будет использоваться только указатель, но это только под капотом.
Ссылка - это ничто иное, как указатель, это псевдоним - новое имя - для какого-то другого объекта. Это одна из причин того, чтобы иметь оба!
Учти это:
Someclass a;
Someclass& b = a;
Someclass& c = a;
Здесь мы сначала создаем объект a
и тогда мы говорим, что b
а также c
другие имена для того же объекта. Ничего нового не создано, только два дополнительных имени.
когда b
или же c
является параметром для функции, псевдоним для a
делается доступным внутри функции, и вы можете использовать его для ссылки на реальный объект.
Это так просто! Вам не нужно прыгать через петли с &
, *
, или же ->
как при использовании указателей.