Використання оператора стрілки (->) в С


257

Я читаю книгу під назвою "Навчи себе C за 21 день" (я вже навчився Java та C #, тому я рухаюся значно швидшими темпами). Я читав розділ про вказівники і оператор-> (стрілка) підійшов без пояснень. Я думаю, що він використовується для виклику членів та функцій (як еквівалент оператора (крапка), але для покажчиків замість членів). Але я не зовсім впевнений..

Чи можу я отримати пояснення та зразок коду?


90
Отримайте кращу книгу. norvig.com/21-days.html
joshperry

9
qrdl вірно - книги "Дізнайтеся X за Y дні" - це взагалі сміття. Окрім K&R, я б також рекомендував «C Primer Plus» Prata, який заглиблюється більше, ніж K&R.
Дж. Тейлор

3
@Steve Це питання стосується C ++. Називаючи це викликало деяку плутанину у мене, коли я почав читати про перевантаження оператора в тій іншій відповіді, що не стосується C.
Йоган

1
@Belton Трудний серіал поганий, хлопець каже речі, які навіть не були актуальними, коли він писав книгу, і він не переймається хорошими практиками.
Bálint

1
Посилання Пітера Норвіга до "Навчи себе програмування через десять років" - це чудово, одне з моїх улюблених. Ось комічна версія, яка пояснює, як це зробити за 21 день, який я, на жаль, запам'ятав як XKCD, але помилявся: abstrusegoose.com/249
Тед

Відповіді:


462

foo->barеквівалентний (*foo).bar, тобто отримує член, викликаний barіз структури, на яку fooвказує.


51
Варто зазначити, що якби оператор розвідки робив постфікс, як у Паскаля, ->оператор взагалі не був би потрібний, оскільки це було б еквівалентно набагато більш розбірливому foo*.bar. Не вдалося б також уникнути цілого безладу функцій набору тексту з усіма додатковими дужками.
Маркіз Лорн

1
Так чи було б foo*.barі те і (*foo).barінше рівнозначним foo->bar? Про що Foo myFoo = *foo; myFoo.bar?
Аарон Франке

9
Ні, він просто говорив IF творці C зробили б оператор разименованія , як Postfix оператора замість PREfix то це було б легше. Але це оператор префікса в C.
рейххарт

@ user207421 Coulfd, будь ласка, дайте короткий опис або посилання на "набір функцій з усіма додатковими дужками", які ви згадуєте? Дякую.
RoG

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

129

Так, це все.

Це просто точка-версія, коли ви хочете отримати доступ до елементів структура / класу, що є вказівником замість посилання.

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
хм, чи не варто вам робити малак, перш ніж писати в 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тоді компілятор подумав би, що потрібен елемент 'bar' 'foo' (що є адресою в пам'яті), і очевидно, що проста адреса не має членів.

Таким чином, вам потрібно попросити компілятора спочатку знеструмити те, (*foo)а потім отримати доступ до елемента-члена:, (*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-ю дереференцію вказівника і вмикати доступ до фрагменту даних, а 2-го додаєте зміщення (позначення) для вибору члена.

Перевірте це зображення, яке я зробив:

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

Але якби ви вклали членів, цей синтаксис став би нечитабельним і тому ->був введений. Я думаю, що читабельність є єдиною виправданою причиною його використання, оскільки це ptr->kgнабагато простіше написати, ніж (*ptr).kg.

Тепер напишемо це по-іншому, щоб ви бачили зв’язок чіткіше. (*ptr).kg(*&audi).kgaudi.kg. Тут я вперше застосував той факт, що ptrце "адреса audi", тобто &audiфакт, що оператори "посилання" & та "відвантаження" * скасовують будь-яке інше.


1

Мені довелося внести невелику зміну в програму Джека, щоб змусити її запускатись. Після оголошення структурного вказівника pvar, вкажіть його на адресу var. Я знайшов це рішення на сторінці 242 Програмування Стівена Кочана в С.

#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;

};

The _EFI_BLOCK_IO_PROTOCOLСтруктура містить 4 членів покажчика функції.

Припустимо, у вас є змінна struct _EFI_BLOCK_IO_PROTOCOL * pStruct, і ви хочете використовувати старий добрий *оператор для виклику вказівника функції члена. Ви отримаєте такий код:

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

Але з ->оператором ви можете написати так:

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

Що виглядає краще?


1
Я думаю, що код був би читабельнішим, якби він не у всіх шапках, як він набраний підлітками в чаті AOL з 90-х.
thang

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.