Использование оператора стрелки (->) в C


257

Я читаю книгу под названием «Обучи себя C за 21 день» (я уже выучил Java и C #, поэтому я двигаюсь гораздо быстрее). Я читал главу об указателях, и оператор-> (стрелка) появился без объяснения причин. Я думаю, что он используется для вызова членов и функций (например, эквивалент оператора (точка), но для указателей вместо членов). Но я не совсем уверен..

Могу ли я получить объяснение и пример кода?


90
Получить лучшую книгу. norvig.com/21-days.html
joshperry

9
qrdl верен - книги "Learn X in Y days", как правило, являются мусором. В дополнение к K & R, я бы также порекомендовал Prata "C Primer Plus", который гораздо глубже, чем K & R.
Дж. Тейлор

3
@ Стив Этот вопрос касается C ++. Когда я начал читать о перегрузке операторов в этом другом ответе, это вызвало у меня некоторое замешательство, что не относится к C.
Иоганн

1
@ Белтон Трудный путь плох, парень говорит вещи, которые даже не были актуальны, когда он писал книгу, и он не заботится о хороших методах.
Балинт

1
Ссылка Питера Норвига на «Обучи себя программированию за десять лет» великолепна, одна из моих любимых. Вот комическая версия, которая объясняет, как это сделать за 21 день, который я, к сожалению, запомнил как XKCD, но я ошибался: abstrusegoose.com/249
Тед

Ответы:


462

foo->barэквивалентно (*foo).bar, то есть он получает член, вызванный barиз структуры, на которую fooуказывает.


51
Стоит отметить, что если бы оператор разыменования был сделан постфиксным, как в Pascal, ->оператор вообще не понадобился бы, поскольку он был бы эквивалентен гораздо более разборчивому foo*.bar. Также можно было бы избежать всей путаницы функций определения типов со всеми дополнительными скобками.
Маркиз Лорн

1
Так будет ли foo*.barи то и (*foo).barдругое эквивалентно foo->bar? Как насчет Foo myFoo = *foo; myFoo.bar?
Аарон Франке

9
Нет, он просто говорит, что если бы создатели C сделали бы оператор разыменования как оператор POSTfix вместо PREfix, тогда это было бы проще. Но это префиксный оператор в C.
Рейххарт

@ user207421 Не могли бы вы дать краткое описание или ссылку на "функции определения типа со всеми дополнительными скобками", которые вы упомянули? Спасибо.
RoG

1
@ user207421 нет, это привело бы к появлению большего числа родителей .. пока что есть приоритет () и [] справа вверху * слева. если они все на одной стороне, вы поставите больше родителей. То же самое в выражениях из-за конфликта с оператором умножения. Паскаль ^ может быть вариантом, но он был зарезервирован для битовой операции, еще больше родителей.
Свифт - Пирог в пятницу

130

Да это оно.

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

struct foo
{
  int x;
  float y;
};

struct foo var;
struct foo* pvar;
pvar = malloc(sizeof(pvar));

var.x = 5;
(&var)->y = 14.3;
pvar->y = 22.4;
(*pvar).x = 6;

Это оно!


3
Поскольку pvar не инициализирован, как бы вы его инициализировали, если бы хотели, чтобы pvar указывал на новую структуру, это не так pvar = &var?
CMCDragonkai

Вопрос был конкретно о C, который не имеет классов или ссылочных переменных.
Дуб

1
хмм, не стоит ли делать malloc перед записью в pvar struct foo * pvar; ?? pvar-> y пиши в незанятое пространство!
Зибри

Инициализация pvar: Инициализируйте все элементы вручную с некоторыми значениями по умолчанию, которые вы хотите использовать, или используйте что-то вроде calloc (), если для вас подойдет нулевое заполнение.
Рейххарт

2
не должно ли это быть: pvar = malloc (sizeof (struct foo)) или malloc (sizeof (* pvar)) ??
Юрий

33

a->bэто просто сокращение (*a).bво всех отношениях (то же самое для функций: a->b()сокращение от (*a).b()).


1
Есть ли документация, в которой говорится, что это также работает для методов?
AsheKetchum

28

Я бы просто добавил к ответам «почему?».

.стандартный оператор доступа к элементу, который имеет более высокий приоритет, чем *оператор указателя.

Когда вы пытаетесь получить доступ к внутренним объектам структуры и пишете это так, *foo.barкомпилятор может захотеть получить элемент 'foo' в 'foo' (который является адресом в памяти) и, очевидно, этот простой адрес не имеет никаких членов.

Таким образом, вам нужно попросить компилятор сначала разыменовать whith, (*foo)а затем получить доступ к элементу member:, (*foo).barкоторый немного неуклюже писать, так что хорошие люди придумали сокращенную версию: foo->barэто своего рода доступ к элементу с помощью оператора указателя.



10
struct Node {
    int i;
    int j;
};
struct Node a, *p = &a;

Здесь , чтобы получить доступ к значениям iи jмы можем использовать переменные aи указатель pследующим образом : a.i, (*p).iи p->iвсе же.

Вот .«Прямой селектор» и ->«Косвенный селектор».


2

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

Допустим, я пишу этот бесполезный кусок кода:

#include <stdio.h>

typedef struct{
        int km;
        int kph;
        int kg;
    } car;

int main(void){

    car audi = {12000, 230, 760};
    car *ptr = &audi;

}

Здесь указатель ptrуказывает на адрес ( ! ) Структурной переменной, audiно кроме адресной структуры также есть кусок данных ( ! )! Первый элемент блока данных имеет тот же адрес, что и сама структура, и вы можете получить его данные, только разыменовав указатель, как этот *ptr (без скобок) .

Но если вы хотите Асесс любого другого члена , чем первый, вы должны добавить условное обозначение , как .km, .kph, .kgкоторые являются не более , чем смещение к базовому адресу порции данных ...

Но из-за старшинства вы не можете написать*ptr.kg как оператор доступа .оценивается перед оператором разыменования, *и вы получите, *(ptr.kg)что невозможно, так как указатель не имеет членов! И компилятор знает это и поэтому выдаст ошибку, например:

error: ptr is a pointer; did you mean to use ‘->’?
  printf("%d\n", *ptr.km);

Вместо этого вы используете это, (*ptr).kgи вы заставляете компилятор 1-й разыменования указателя и включить Асесс в порции данных и второго добавления смещения (обозначение) , чтобы выбрать элемент.

Проверьте это изображение, которое я сделал:

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

Но если бы вы имели вложенные члены, этот синтаксис стал бы нечитаемым и, следовательно, -> был введен. Я думаю, что удобочитаемость является единственной оправданной причиной для его использования, поскольку это ptr->kgгораздо проще, чем писать (*ptr).kg.

Теперь давайте напишем это по-другому, чтобы вы видели связь более четко. (*ptr).kg(*&audi).kgaudi.kg. Здесь я впервые использовал тот факт, что ptrэто «адрес audi», т. &audiЕ. И тот факт, что операторы «ссылка» & и «разыменование» * отменяют друг друга.


1

Я должен был сделать небольшое изменение в программе Джека, чтобы заставить ее работать. После объявления структурного указателя pvar укажите его адрес var. Я нашел это решение на странице 242 Программирования Стивена Кочана в C.

#include <stdio.h>

int main()
{
  struct foo
  {
    int x;
    float y;
  };

  struct foo var;
  struct foo* pvar;
  pvar = &var;

  var.x = 5;
  (&var)->y = 14.3;
  printf("%i - %.02f\n", var.x, (&var)->y);
  pvar->x = 6;
  pvar->y = 22.4;
  printf("%i - %.02f\n", pvar->x, pvar->y);
  return 0;
}

Запустите это в vim с помощью следующей команды:

:!gcc -o var var.c && ./var

Будет выводить:

5 - 14.30
6 - 22.40

3
Совет vim: используйте %для представления текущего имени файла. !gcc % && ./a.out
Вот

1
#include<stdio.h>

int main()
{
    struct foo
    {
        int x;
        float y;
    } var1;
    struct foo var;
    struct foo* pvar;

    pvar = &var1;
    /* if pvar = &var; it directly 
       takes values stored in var, and if give  
       new > values like pvar->x = 6; pvar->y = 22.4; 
       it modifies the values of var  
       object..so better to give new reference. */
    var.x = 5;
    (&var)->y = 14.3;
    printf("%i - %.02f\n", var.x, (&var)->y);

    pvar->x = 6;
    pvar->y = 22.4;
    printf("%i - %.02f\n", pvar->x, pvar->y);

    return 0;
}

1

->Оператор делает код более читаемым , чем* оператор в некоторых ситуациях.

Например: (цитируется по проекту EDK II )

typedef
EFI_STATUS
(EFIAPI *EFI_BLOCK_READ)(
  IN EFI_BLOCK_IO_PROTOCOL          *This,
  IN UINT32                         MediaId,
  IN EFI_LBA                        Lba,
  IN UINTN                          BufferSize,
  OUT VOID                          *Buffer
  );


struct _EFI_BLOCK_IO_PROTOCOL {
  ///
  /// The revision to which the block IO interface adheres. All future
  /// revisions must be backwards compatible. If a future version is not
  /// back wards compatible, it is not the same GUID.
  ///
  UINT64              Revision;
  ///
  /// Pointer to the EFI_BLOCK_IO_MEDIA data for this device.
  ///
  EFI_BLOCK_IO_MEDIA  *Media;

  EFI_BLOCK_RESET     Reset;
  EFI_BLOCK_READ      ReadBlocks;
  EFI_BLOCK_WRITE     WriteBlocks;
  EFI_BLOCK_FLUSH     FlushBlocks;

};

_EFI_BLOCK_IO_PROTOCOL содержит 4 члена указателя функции.

Предположим, у вас есть переменная struct _EFI_BLOCK_IO_PROTOCOL * pStruct, и вы хотите использовать старый добрый *оператор для вызова ее указателя на функцию-член. В итоге вы получите такой код:

(*pStruct).ReadBlocks(...arguments...)

Но с ->оператором вы можете написать так:

pStruct->ReadBlocks(...arguments...),

Который выглядит лучше?


1
Я думаю, что код был бы более читабельным, если бы он был не во всех заглавных буквах, как это было напечатано подростками в чате AOL из 90-х.
Тханг

1
#include<stdio.h>
struct examp{
int number;
};
struct examp a,*b=&a;`enter code here`
main()
{
a.number=5;
/* a.number,b->number,(*b).number produces same output. b->number is mostly used in linked list*/
   printf("%d \n %d \n %d",a.number,b->number,(*b).number);
}

выход 5 5 5


0

Dot является оператором разыменования и используется для соединения структурной переменной для конкретной записи структуры. Например:

struct student
    {
      int s.no;
      Char name [];
      int age;
    } s1,s2;

main()
    {
      s1.name;
      s2.name;
    }

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


6
Какую ценность это добавляет? Пример немного плох по сравнению с другими ответами, которые фактически сравнивают его с ->. Также на этот вопрос уже 4,5 года.
EWit
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.