Разница между object-> function() и object.function() в C

#c #object #member #dereference

#c #объект #Участник #Разыменование

Вопрос:

Кто-нибудь может объяснить разницу между выполнением чего-то вроде:

 a->height();
  

и

 a.height();
  

Есть ли на самом деле разница?

Комментарии:

1. Просто любопытно, почему есть точки для этого вопроса?

Ответ №1:

В первом примере a — это указатель на объект, во втором примере это сам объект (или ссылка на объект). На самом низком уровне между ними нет четкой разницы.

Комментарии:

1. @AdrianMar, но выполняет ли это ту же операцию?

2. Еще одной возможностью для a.height() : a может быть ссылка на объект. По сути, ссылка — это указатель, но с точки зрения пользовательского кода ссылка является псевдонимом для рассматриваемого объекта.

3. Ну да, это просто другой способ доступа к объекту (прямо или косвенно, через его указатель). Метод вызывается практически таким же образом.

4. @Adrian: Подумайте о случае, когда a является указателем на экземпляр некоторого полного (т. Е. не абстрактного) базового класса, по сравнению со случаем, когда a является экземпляром этого класса. В этом случае a->height() вполне может выполняться совершенно иная функция, чем делает a.height() .

5. @Дэвид Хаммен: Да, но это на самом деле не имело бы отношения к разнице между . и -> . Это только иллюстрировало бы нормальное поведение виртуальной функции.

Ответ №2:

Это странный вопрос, если предположить, что мы говорим о встроенном -> . Это звучит немного как «в чем разница между молотком и яблоком». Обычно вопрос о «разнице» возникает, когда два варианта по крайней мере в некоторой степени взаимозаменяемы, оба применимы одновременно. Итак, люди спрашивали бы о «разнице», чтобы решить, какой вариант использовать. Здесь дело не в этом.

У вас не может быть обоих a->height() и a.height() действительных одновременно. Только одно из двух может быть допустимым, в зависимости от типа a . Т.е. у вас нет выбора, какую версию использовать. Первый вариант (с -> ) применим, если левая сторона является указателем на объект. Второй вариант (с помощью всего лишь . ) применим только тогда, когда левая сторона является самим значением объекта. Итак, вот и все, что нужно сделать.

-> Это просто сокращение для комбинации унарных * и . , означающее, что когда a является указателем a->height() , это эквивалентно (*a).height() . Итак, более разумным вопросом было бы о разнице между a->height() и (*a).height() . И ответ таков: разницы нет (опять же, пока мы рассматриваем встроенное -> )

Комментарии:

1. Вы сказали, что "You can't have both a->height() and a.height() valid at the same time." на самом деле вы можете иметь оба одновременно : ideone.com/7tZdS

2. @Nawaz: Конкретно по этой причине я явно заявляю, что рассматриваю только встроенную -> , в отличие от перегруженной -> .

Ответ №3:

В основном это сводится к тому, как вы объявили «a». Если вы сделаете:

 SomeClass a;
  

тогда «a» сам по себе является объектом, и вы используете «.» для ссылки на его члены.

Если вы сделаете:

 SomeClass * a;
a = [some expression that produces a pointer to a SomeClass object];
  

тогда «a» — это УКАЗАТЕЛЬ НА объект, а не сам объект. Затем вы используете обозначение «->».

(Есть некоторые потенциальные исключения из приведенных выше правил, но с ними не сталкивается большинство людей.)

Ответ №4:

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

Ответ №5:

operator-> , за которым следует вызов функции, приведет к тому, что возвращаемый указатель будет разыменован, а функция будет вызвана для результирующего объекта. Короче говоря, a->foo() это сокращение от (*a).foo() .

Помимо правильного ответа, который a->foo() требует a указывать на класс, определяющий foo , надуманный пример может рассказать что-то другое:

 struct xC {
   struct Member {
      void foo(){printf("xC::Member::foon");};
   };
   Member a;
   Member* operator->() { return amp;a; }
   // following function doesn't really make sense.
   void foo() { printf("xC::foon");};
};

int main(){
    xC x;
    x.foo(); // prints "xC::foo".  
    x->foo();// prints "xC::Member::foo"
}
  

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

Комментарии:

1. 1 Хороший ответ. Я написал аналогичный пример для комментариев, которые я сделал по другим ответам : ideone.com/7tZdS

Ответ №6:

Предполагая, что a это указатель на некоторый объект, тогда:

Это:

 a->height();
  

Это просто сокращение для:

 (*a).height();
  

Это позволяет вызывать метод через указатель, а не просто обычный объект (или ссылку).
На самом деле вам не нужно использовать -> version, но когда вы соединяете пару вызовов вместе, становится очевидно, почему это полезно.

 person->body()->arm(right)->hand()->finger(index)->ring()->activate();
  

Если вы напишете эту длинную строку, это:

 (*(*(*(*(*(*person).body()).arm(right)).hand()).finger(index)).ring()).activate();
  

Сложно соотнести отмену ссылки ( * ) с указателем, на который она отменяет ссылку. В то время как в первой версии -> объект слева — это указатель, объект справа — часть объекта после удаления ссылки на указатель.

Комментарии:

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

2. Вы имеете в виду, что класс Person должен иметь метод: activeRing(enum LeftRight, enum Finger) . Но спасибо за ссылку. Теперь у меня есть реальное именованное правило, которое я могу использовать для борьбы с чрезмерным использованием функций get () / set ().

3. да, идея в этом: вместо того, чтобы предоставлять доступ к объекту компонента, включите весь или часть интерфейса компонента в свой. Закон также разрешает вызовы методов для создаваемых вами объектов, поэтому я думаю, что приемлемой альтернативой (хотя, по-видимому, немного бессмысленной в данном конкретном случае) было бы person->createRingActivator(right, index).activate() .

Ответ №7:

Как уже говорили другие, если вы говорите о разыменовании указателя, то разница не будет очень существенной. Однако, что более важно, если a это экземпляр или ссылка и в нем определена operator-> функция, то вы можете увидеть совсем другое поведение. a.name() вызывал бы name() функцию a, в то время как a->name() мог бы вызывать name() функцию какого-либо другого типа наряду с любой другой работой, которую решил выполнить класс (это вызов функции, возвращающий указатель, поэтому класс ‘evil’ может делать практически все, что захочет, пока он возвращает действительный указатель). В основном это используется для интеллектуальных указателей, но в языке нет гарантии этого.

Ответ №8:

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

Комментарии:

1.-1 НЕВЕРНО x->y == (*x).y

2. Указатель указывает на функцию. Ссылка — это фактический адрес функции. Посмотрите на сгенерированный код на ассемблере.

3. Да, и вы не можете обращаться к одному из них с помощью синтаксиса другого, следовательно, -1.