C dilinde ok operatörü (->) kullanımı


257

"21 Günde Kendine C Öğret" adlı bir kitap okuyorum (Java ve C # öğrendim, bu yüzden çok daha hızlı ilerliyorum). İşaretçiler hakkındaki bölümü okuyordum ve ->(ok) operatörü açıklama yapmadan geldi. Üyeleri ve işlevleri çağırmak için kullanıldığını düşünüyorum ( .(nokta) operatörünün eşdeğeri gibi , ancak üyeler yerine işaretçiler için). Ama tam olarak emin değilim.

Bir açıklama ve kod örneği alabilir miyim?


91
Daha iyi bir kitap al. norvig.com/21-days.html
joshperry

9
qrdl doğrudur - "Y gün içinde X öğrenin" kitapları genellikle çöptür. K&R'ye ek olarak, Prata'nın K&R'den daha derinliğe giren "C Primer Plus" ı da öneririm.
J. Taylor

3
@Steve Bu soru C ++ ile ilgilidir. C ile ilgili olmayan başka bir cevapta operatörün aşırı yüklenmesi hakkında okumaya başladığımda benim için bir karışıklık yarattı
Johann

1
@Belton Zor yol serileri kötü, adam kitabı yazarken bile alakalı olmayan şeyler söylüyor ve iyi uygulamaları umursamıyor.
Bálint

1
Peter Norvig "On Yılda Kendini Programlamayı Öğret" bağlantısının en iyilerinden biri. İşte 21 gün içinde bunun nasıl yapılacağını açıklayan komik sürüm, ne yazık ki bir XKCD olarak hatırladım ama yanılmışım: abstrusegoose.com/249
Ted

Yanıtlar:


463

foo->bareşittir (*foo).bar, yani üyeyi işaret bareden yapıdan çağırır foo.


51
Dereference operatörü Pascal'da olduğu gibi postfix yapılmış ->olsaydı , operatöre çok daha okunaklı olana eşdeğer olacağı için hiç gerek duymayacağını belirtmek gerekir foo*.bar. Tüm ekstra parantezlerle yazım işlevlerinin tüm karmaşasından da kaçınılabilirdi.
Lorne Marquis

1
Peki foo*.barve (*foo).barher ikisi de eşit olur foo->barmu? Ne olmuş Foo myFoo = *foo; myFoo.bar?
Aaron Franke

9
Hayır, sadece söylediğini EĞER C yaratıcıları yerine önekin sonek operatörü olarak KQUEUE operatör o zaman daha kolay olurdu yapmış olur. Ancak C'de bir önek operatörüdür
reichhart

@ user207421 Coulfd kısa bir açıklama veya "tüm ekstra parantezleri ile tip tanımlama işlevleri" link lütfen? Teşekkürler.
RoG

1
@ user207421 nah, daha fazla ebeveyne neden olur .. şimdiye kadar, solda () ve [] önceliği * vardır. eğer hepsi bir tarafa, daha fazla ebeveyn koyacaksınız. Çarpım operatörü ile çakışma nedeniyle ifadelerde de aynı. Pascal ^ bir seçenek olabilir, ancak daha fazla ebeveyn olan bit işlemi için ayrılmıştır.
Swift - Cuma Pastası

130

Evet, bu o.

Referans yerine işaretçi olan bir yapı / sınıfın öğelerine erişmek istediğinizde yalnızca nokta sürümüdür.

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;

Bu kadar!


3
Pvar başlatılmadığından, pvar'ın yeni bir yapıya işaret etmesini istiyorsanız, nasıl başlatabilirsiniz pvar = &var?
CMCDragonkai

Soru özellikle sınıfları veya referans değişkenleri olmayan C ile ilgiliydi.
Meşe

1
hmm, pvar struct foo * pvar'a yazmadan önce bir malloc yapmamalısınız; ?? pvar-> y ayrılmamış alana yaz!
18'de Zibri

pvar başlatma: Tüm üyeleri elinizdeki bazı varsayılanlara el ile başlatın veya sıfır dolgu sizin için uygunsa calloc () gibi bir şey kullanın.
reichhart

2
olmamalı: pvar = malloc (sizeof (struct foo)) veya malloc (sizeof (* pvar)) ??
Yuri Aps

33

a->b(*a).bher şekilde kısadır (işlevler için aynıdır: a->b()kısaltmasıdır (*a).b()).


1
yöntemler için de bu şekilde çalıştığını söyleyen belgeler var mı?
AsheKetchum

28

Sadece "neden?"

.*işaretçi operatöründen daha yüksek önceliğe sahip standart üye erişim operatörüdür.

Bir yapının içlerine erişmeye çalıştığınızda ve bunu yazdığınızda *foo.bar, derleyici 'foo'nun (bellekte bir adres olan) bir' çubuk 'öğesini istemeyi düşünür ve açıkçası bu adresin herhangi bir üyesi yoktur.

Bu yüzden derleyiciden ilk dereference'den nereye başvurmasını istemeniz (*foo)ve daha sonra üye öğeye erişmeniz gerekir: (*foo).barki bu yazı yazmak için biraz sakıncalıdır, böylece iyi insanlar kısa bir versiyonla gelir: foo->barişaretçi operatörü tarafından üye erişimi.



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

İşte değerlerini erişmek ive jbiz değişkeni kullanabilirsiniz ave işaretçi paşağıdaki gibi: a.i, (*p).ive p->ihepsi aynı.

İşte ."Doğrudan Seçici" ve ->"Dolaylı Seçici" dir.


2

Ben de bir şeyler eklemeliyim. Dizi bir işaretçi olduğu ve yapı olmadığı için yapı diziden biraz farklıdır. Yani dikkatli ol!

Diyelim ki bu işe yaramaz kod parçasını yazıyorum:

#include <stdio.h>

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

int main(void){

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

}

Burada işaretçi yapı değişkeninin ptradresini ( ! ) Gösterir,audi ancak adres yapısının yanında da bir veri kümesi ( ! ) Vardır ! Veri yığınının ilk üyesi, yapının kendisiyle aynı adrese sahiptir ve yalnızca böyle bir işaretçiyi kaydırarak *ptr (parantez olmadan) verilerini alabilirsiniz .

Eğer birincisinden daha başka üyesini erişmesine istiyorsanız Ama, böyle bir planlayýcýsý eklemek zorunda .km, .kph, .kgdaha taban adresine uzaklıklar daha hiçbir şey hangi verinin yığın ...

Ancak önkoşul nedeniyle, *ptr.kgerişim operatörü .dereference operatörü önce değerlendirildiğinde yazamazsınız *ve *(ptr.kg)işaretçinin üyesi olmadığı için mümkün olmaz! Derleyici bunu bilir ve bu nedenle bir hata verir:

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

Bunun yerine bunu kullanırsınız (*ptr).kgve derleyiciyi işaretçiyi 1. referanstan ayırmaya zorlar ve veri yığınına erişim sağlar ve 2. üyeyi seçmek için bir ofset (işaretleyici) eklersiniz.

Yaptığım bu görüntüyü kontrol et:

resim açıklamasını buraya girin

Ancak yuvalanmış üyeleriniz varsa, bu sözdizimi okunamaz hale gelir ve bu nedenle ->tanıtılır. Bence okunabilirlik onu kullanmak için sadece haklı bir sebep, çünkü bu ptr->kgyazmaktan çok daha kolay (*ptr).kg.

Şimdi bağlantıyı daha net görebilmeniz için bunu farklı yazalım. (*ptr).kg(*&audi).kgaudi.kg. İşte ilk gerçeğini kullanılan ptrbir olan "adresi audi" yani &audive gerçeği "referans" & ve "KQUEUE" * operatörler birbirinden out iptal edin.


1

Çalıştırmak için Jack'in programında küçük bir değişiklik yapmam gerekiyordu. Yapı işaretçisi pvarını bildirdikten sonra var. Bu çözümü Stephen Kochan'ın C'deki Programlama'nın 242. sayfasında buldum.

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

Bunu aşağıdaki komutla vim'de çalıştırın:

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

Çıktı olacak:

5 - 14.30
6 - 22.40

3
vim tip: %geçerli dosya adını temsil etmek için kullanın . !gcc % && ./a.out
Şöyle

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

->Operatör daha kod daha okunabilir kılan *bazı durumlarda operatör.

Örneğin: ( EDK II projesinden alıntılanmıştır )

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_PROTOCOLYapı 4 işlev işaretçisi üyeleri içerir.

Bir değişkeniniz olduğunu struct _EFI_BLOCK_IO_PROTOCOL * pStructve *üye işlev işaretçisi olarak adlandırmak için eski eski operatörü kullanmak istediğinizi varsayalım . Bunun gibi bir kodla karşılaşacaksınız:

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

Ancak ->operatörle şu şekilde yazabilirsiniz:

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

Hangisi daha iyi görünüyor?


1
Ben 90s AOL sohbet gençler tarafından yazılan gibi tüm kapaklar içinde değilse kodu daha okunabilir olacağını düşünüyorum.
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);
}

çıktı 5 5 5


0

Nokta bir dereference operatörüdür ve belirli bir yapı kaydı için yapı değişkenini bağlamak için kullanılır. Örneğin :

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

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

Bu şekilde yapı değişkenine erişmek için bir nokta operatörü kullanabiliriz


6
Bu ne katıyor? Örnek, gerçekte karşılaştırılan diğer cevaplara kıyasla biraz zayıftır ->. Ayrıca bu soru 4,5 yıldır cevaplandı.
EWit
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.