Wykorzystanie operatora strzałek (->) w C


257

Czytam książkę zatytułowaną „Naucz się C w 21 dni” (już nauczyłem się Java i C #, więc poruszam się w znacznie szybszym tempie). Czytałem rozdział na temat wskaźników i operator-> (strzałka) pojawił się bez wyjaśnienia. Myślę, że służy do wywoływania członków i funkcji (jak odpowiednik operatora (kropka), ale dla wskaźników zamiast członków). Ale nie jestem do końca pewien..

Czy mogę prosić o wyjaśnienie i próbkę kodu?


90
Zdobądź lepszą książkę. norvig.com/21-days.html
joshperry

9
qrdl jest poprawny - książki „Naucz się X za Y dni” są na ogół śmieciami. Oprócz K&R poleciłbym również „C Primer Plus” firmy Prata, który jest bardziej głębszy niż K&R.
J. Taylor

3
@ Steve To pytanie dotyczy C ++. Nazwanie tego spowodowało dla mnie pewne zamieszanie, kiedy zacząłem czytać o przeciążeniu operatora w tej innej odpowiedzi, co nie ma znaczenia w C.
Johann

1
@Belton Trudna seria jest zła, facet mówi rzeczy, które nie były nawet istotne, kiedy napisał książkę, i nie dba o dobre praktyki.
Bálint

1
Link do Petera Norviga do „Naucz się programowania za dziesięć lat” jest świetny, jeden z moich ulubionych. Oto komiczna wersja, która wyjaśnia, jak to zrobić w ciągu 21 dni, co niestety zapamiętałem jako XKCD, ale się myliłem: abstrusegoose.com/249
Ted

Odpowiedzi:


462

foo->barjest równoważne (*foo).bar, tzn. pobiera członka wywołanego barze struktury, która foowskazuje.


51
Warto zauważyć, że gdyby operator dereferencji został wprowadzony po poprawce, tak jak w Pascalu, ->operator nie byłby wcale potrzebny, ponieważ byłby to odpowiednik znacznie bardziej czytelny foo*.bar. Można by uniknąć całego bałaganu funkcji pisania na maszynie ze wszystkimi dodatkowymi nawiasami.
Markiz Lorne

1
Czy foo*.bari (*foo).baroba byłyby równoważne foo->bar? Co Foo myFoo = *foo; myFoo.bar?
Aaron Franke,

9
Nie, on tylko mówi, JEŻELI twórcy C zmieniliby operatora dereferencyjnego jako operatora POSTfix zamiast PREfix, byłoby to łatwiejsze. Ale jest operatorem prefiksu w C.
Reichhart

@ user207421 Coulfd, proszę podać krótki opis lub link do „funkcji pisania na klawiaturze ze wszystkimi dodatkowymi nawiasami”, o których wspominasz? Dzięki.
RoG

1
@ user207421 nie, spowodowałoby to więcej rodziców .. do tej pory priorytetem jest () i [] po prawej stronie powyżej * po lewej stronie. jeśli wszyscy są po jednej stronie, umieścisz więcej rodziców. To samo w wyrażeniach, z powodu konfliktu z operatorem mnożenia. Pascal ^ może być opcją, ale był zarezerwowany do operacji bitowej, jeszcze więcej rodziców.
Swift - Friday Pie

129

Tak, to jest to.

Jest to wersja kropkowa, gdy chcesz uzyskać dostęp do elementów struktury / klasy, która jest wskaźnikiem zamiast odniesienia.

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;

Otóż ​​to!


3
Skoro pvar jest niezainicjowany, jak byś go zainicjował, gdyby chciał, aby pvar wskazywał na nową strukturę, prawda pvar = &var?
CMCDragonkai

Pytanie dotyczyło w szczególności C, które nie ma klas ani zmiennych referencyjnych.
Oak

1
hmm, nie powinieneś robić malloc przed napisaniem do pvar struct foo * pvar; ?? pvar-> y napisz do nieprzydzielonego miejsca!
Zibri

Inicjalizacja pvar: ręcznie zainicjuj wszystkich członków do domyślnych ustawień, które chcesz mieć, lub użyj czegoś takiego jak calloc (), jeśli wypełnienie zerem byłoby dla ciebie odpowiednie.
reichhart

2
nie powinno być: pvar = malloc (sizeof (struct foo)) lub malloc (sizeof (* pvar)) ??
Yuri Aps

33

a->bjest po prostu skrótem (*a).bpod każdym względem (to samo dla funkcji: a->b()jest skrótem od (*a).b()).


1
czy istnieje dokumentacja, która mówi, że tak też działa w przypadku metod?
AsheKetchum

28

Dodałbym do odpowiedzi „dlaczego?”.

.jest standardowym operatorem dostępu do elementu, który ma wyższy priorytet niż *operator wskaźnika.

Kiedy próbujesz uzyskać dostęp do wewnętrznych struktur i napisałeś to, *foo.barwtedy kompilator pomyślałby, że chce mieć element „bar” z „foo” (który jest adresem w pamięci) i oczywiście ten adres nie ma żadnych elementów.

Dlatego musisz poprosić kompilator, aby najpierw wyrenderował opcję whith, (*foo)a następnie uzyskał dostęp do elementu członkowskiego: (*foo).barco jest nieco niezdarne do pisania, więc dobrzy ludzie wymyślili skróconą wersję: foo->barktóra jest rodzajem dostępu do członka przez operatora wskaźnika.



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

Tutaj aby uzyskać dostęp do wartości ii jmożemy użyć zmiennej ai wskaźnik pw następujący sposób: a.i, (*p).ii p->iwszystkie są takie same.

Oto .„Selektor bezpośredni” i ->„Selektor pośredni”.


2

Cóż, muszę też coś dodać. Struktura jest nieco inna niż tablica, ponieważ tablica jest wskaźnikiem, a struktura nie. Więc uważaj!

Powiedzmy, że piszę ten bezużyteczny fragment kodu:

#include <stdio.h>

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

int main(void){

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

}

Tutaj wskaźnik ptrwskazuje na adres ( ! ) Zmiennej struktury, audiale oprócz struktury adresu ma również fragment danych ( ! )! Pierwszy element fragmentu danych ma ten sam adres, co sama struktura, i możesz uzyskać dane, usuwając tylko taki wskaźnik *ptr (bez nawiasów klamrowych) .

Ale jeśli chcesz uzyskać dostęp, innego członka niż pierwszy, trzeba dodać oznacznik jak .km, .kph, .kgktóre nie są niczym więcej niż offsetu do adresu bazowego w porcji danych ...

Ale ze względu na preceedence nie można zapisać *ptr.kgjako operator dostępu .jest oceniany przed operatorem dereference *i można dostać *(ptr.kg), co nie jest możliwe, gdyż wskaźnik nie ma z nami! Kompilator wie o tym i dlatego spowoduje błąd, np .:

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

Zamiast tego używasz tego (*ptr).kgi zmuszasz kompilator do 1. dereferencji wskaźnika i umożliwienia dostępu do części danych i 2. dodajesz przesunięcie (desygnator), aby wybrać element członkowski.

Sprawdź to zdjęcie, które wykonałem:

wprowadź opis zdjęcia tutaj

Ale jeśli zagnieździlibyśmy członków, ta składnia stałaby się nieczytelna i dlatego ->została wprowadzona. Myślę, że czytelność jest jedynym uzasadnionym powodem korzystania z niej, ponieważ ptr->kgjest to o wiele łatwiejsze do napisania niż(*ptr).kg .

Teraz napiszmy to inaczej, aby lepiej widzieć połączenie. (*ptr).kg(*&audi).kgaudi.kg. Tutaj najpierw wykorzystałem fakt, że ptrjest to „adres audi”, tj. &audiFakt, że operatory „referencyjny” & i „dereferencyjny” wzajemnie się * znoszą.


1

Musiałem wprowadzić niewielką zmianę w programie Jacka, aby go uruchomić. Po zadeklarowaniu wskaźnika struct pvar, wskaż go na adres var. Znalazłem to rozwiązanie na stronie 242 programowania Stephena Kochana w 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;
}

Uruchom to w vimie za pomocą następującego polecenia:

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

Wyjdzie:

5 - 14.30
6 - 22.40

3
vim wskazówka: użyj %do przedstawienia bieżącej nazwy pliku. Tak jak:!gcc % && ./a.out
jibberia,

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

->Operatora sprawia, że kod jest bardziej czytelny niż *operator w niektórych sytuacjach.

Takich jak: (cytowany z projektu 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;

};

Struktura _EFI_BLOCK_IO_PROTOCOLzawiera 4 elementy wskaźnika funkcji.

Załóżmy, że masz zmienną struct _EFI_BLOCK_IO_PROTOCOL * pStructi chcesz użyć starego dobrego *operatora, aby wywołać jego wskaźnik funkcji członka. Otrzymasz kod taki jak ten:

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

Ale z ->operatorem możesz napisać w ten sposób:

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

Który wygląda lepiej?


1
Myślę, że kod byłby bardziej czytelny, gdyby nie był pisany wielkimi literami, tak jak jest napisany przez nastolatków na czacie AOL z lat 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);
}

wyjście wynosi 5 5 5


0

Kropka jest operatorem dereferencyjnym i służy do łączenia zmiennej struktury dla określonego zapisu struktury. Np .:

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

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

W ten sposób możemy użyć operatora kropki, aby uzyskać dostęp do zmiennej struktury


6
Jaką wartość to dodaje? Przykład jest nieco słaby w porównaniu z innymi odpowiedziami, które faktycznie go porównują ->. Odpowiedzi na to pytanie udzielono już od 4,5 roku.
EWit
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.