การใช้ตัวดำเนินการลูกศร (->) ใน C


257

ฉันกำลังอ่านหนังสือที่ชื่อว่า "Teach Yourself C in 21 Days" (ฉันได้เรียนรู้ Java และ C # แล้วดังนั้นฉันจึงเคลื่อนไหวได้เร็วขึ้นมาก) ผมอ่านบทที่เกี่ยวกับตัวชี้และ->(ลูกศร) ประกอบขึ้นมาโดยไม่มีคำอธิบาย ฉันคิดว่ามันถูกใช้เพื่อเรียกสมาชิกและฟังก์ชั่น (เช่นเทียบเท่าของตัวดำเนินการ.(dot) แต่สำหรับตัวชี้แทนสมาชิก) แต่ฉันไม่แน่ใจทั้งหมด

ฉันขอคำอธิบายและตัวอย่างโค้ดได้ไหม


90
รับหนังสือดีกว่า norvig.com/21-days.html
joshperry

9
qrdl ถูกต้อง - หนังสือ "เรียนรู้ X ใน Y" เป็นขยะทั่วไป นอกจาก K&R ฉันจะแนะนำ "C Primer Plus" ของ Prata ซึ่งลึกเข้าไปมากกว่า K&R
เจ. เทย์เลอร์

3
@Steve คำถามนั้นเกี่ยวข้องกับ C ++ เรียกมันว่าทำให้เกิดความสับสนสำหรับฉันเมื่อฉันเริ่มอ่านเกี่ยวกับการบรรทุกเกินพิกัดในคำตอบอื่น ๆ ซึ่งไม่เกี่ยวข้องใน C.
โยฮันน์

1
@ เบลตันซีรีส์ที่ยากลำบากนั้นไม่ดีชายคนนั้นพูดถึงสิ่งที่ไม่เกี่ยวข้องแม้แต่ตอนที่เขาเขียนหนังสือและเขาไม่สนใจวิธีปฏิบัติที่ดี
Bálint

1
ลิงก์ Peter Norvig ไปที่ "สอนตัวเองการเขียนโปรแกรมในสิบปี" เป็นเรื่องที่ยอดเยี่ยมมากหนึ่งในรายการโปรดของฉัน นี่เป็นเวอร์ชั่นการ์ตูนที่อธิบายวิธีทำใน 21 วันซึ่งฉันจำได้ว่าเป็น XKCD แต่ฉันผิด: abstrusegoose.com/249
Ted

คำตอบ:


462

foo->barเทียบเท่ากับ(*foo).barคือได้รับสมาชิกที่เรียกbarจากโครงสร้างที่fooชี้ไปที่


51
มันน่าสังเกตว่าถ้าผู้ประกอบการที่ถูกอ้างถึงทำ postfix เหมือนใน Pascal ->ผู้ปฏิบัติงานก็คงไม่ต้องการเลยเพราะมันจะเทียบเท่ากับที่อ่านได้ง่ายfoo*.barกว่า ความยุ่งเหยิงของฟังก์ชั่น typedef-ing พร้อมกับวงเล็บทั้งหมดจะถูกหลีกเลี่ยงเช่นกัน
มาร์ควิสแห่ง Lorne

1
ดังนั้นfoo*.barและ(*foo).barทั้งสองจะเท่ากันfoo->bar? เกี่ยวกับFoo myFoo = *foo; myFoo.barอะไร
Aaron Franke

9
ไม่เขาเพียงแค่บอกว่าถ้าผู้สร้าง C จะทำให้ผู้ดำเนินการอ้างอิงเป็นผู้ดำเนินการ POSTfix แทนคำนำหน้ามันจะง่ายกว่ามาก แต่มันเป็นตัวดำเนินการคำนำหน้าใน C.
reichhart

@ user207421 Coulfd คุณโปรดให้คำอธิบายสั้น ๆ หรือลิงค์ไปยัง "ฟังก์ชั่น typedef-ing พร้อมวงเล็บพิเศษทั้งหมด" ที่คุณพูดถึง? ขอบคุณ
RoG

1
@ user207421 ไม่นะมันจะทำให้ผู้ปกครองมากขึ้น .. จนถึงตอนนี้มีลำดับความสำคัญของ () และ [] ทางด้านขวาด้านบน * ทางซ้าย หากพวกเขาทั้งหมดในด้านหนึ่งคุณจะได้ผู้ปกครองมากขึ้น เหมือนกันในนิพจน์เนื่องจากขัดแย้งกับตัวดำเนินการคูณ Pascal ^ อาจเป็นตัวเลือก แต่ถูกสงวนไว้สำหรับการใช้งานบิตผู้ปกครองยังคงมากขึ้น
Swift - Friday Pie

129

ใช่แค่นั้นแหละ

เป็นเพียงรุ่นจุดเมื่อคุณต้องการเข้าถึงองค์ประกอบของ struct / class ที่เป็นตัวชี้แทนที่จะเป็นตัวอ้างอิง

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 ชี้ไปที่ struct ใหม่นั่นไม่ใช่pvar = &var?
CMCDragonkai

คำถามนี้เกี่ยวกับ C โดยเฉพาะซึ่งไม่มีคลาสหรือตัวแปรอ้างอิง
โอ๊ก

1
อืมคุณไม่ควรทำ malloc ก่อนเขียนถึง pvar struct foo * pvar; ?? pvar-> y เขียนไปยังพื้นที่ที่ไม่ได้ปันส่วน!
Zibri

การกำหนดค่าเริ่มต้น pvar: เริ่มต้นสมาชิกทั้งหมดด้วยตนเองเป็นค่าเริ่มต้นบางอย่างที่คุณต้องการมีหรือใช้บางอย่างเช่น calloc () หากการเติมเต็มเป็นศูนย์จะเป็นผลดีสำหรับคุณ
reichhart

2
ไม่ควรเป็น: pvar = malloc (sizeof (struct foo)) หรือ malloc (sizeof (* pvar)) ??
ยูริ Aps


28

ฉันแค่เพิ่มคำตอบ "ทำไม"

.เป็นตัวดำเนินการเข้าถึงสมาชิกมาตรฐานที่มีลำดับความสำคัญสูงกว่า*ตัวดำเนินการตัวชี้

เมื่อคุณพยายามเข้าถึง internals ของ struct และคุณเขียนมัน*foo.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มีทั้งหมดเดียวกัน

นี่.คือ "Direct Selector" และ->เป็น "Indirect Selector"


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 (ไม่มีเครื่องหมายปีกกา) (ไม่มีวงเล็บ)

แต่หากคุณต้องการ acess สมาชิกอื่น ๆ ที่ไม่ใช่คนแรกที่คุณต้องเพิ่ม designator เช่น.km, .kph, .kgซึ่งเป็นอะไรมากไปกว่าการชดเชยไปยังที่อยู่ฐานของก้อนของข้อมูล ...

แต่เนื่องจากการpreceedenceคุณไม่สามารถเขียน*ptr.kgเป็นผู้ประกอบการเข้าถึง.การประเมินก่อนที่จะดำเนินการ dereference *และคุณจะได้รับ*(ptr.kg)ซึ่งเป็นไปไม่ได้เป็นตัวชี้ไม่มีสมาชิก! และคอมไพเลอร์รู้เรื่องนี้และจะออกข้อผิดพลาดเช่น:

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

แต่คุณใช้สิ่งนี้(*ptr).kgและคุณบังคับให้คอมไพเลอร์ไปที่การปฏิเสธตัวชี้อันดับที่ 1และเปิดใช้งาน acess ให้กับกลุ่มข้อมูลและอันดับที่สองคุณเพิ่มออฟเซ็ต (ตัวออกแบบ) เพื่อเลือกสมาชิก

ตรวจสอบภาพนี้ฉันทำ:

ป้อนคำอธิบายรูปภาพที่นี่

แต่ถ้าคุณมีสมาชิกที่ซ้อนกันไวยากรณ์นี้จะกลายเป็นอ่านไม่ได้และดังนั้นจึง->ได้รับการแนะนำ ผมคิดว่าการอ่านเป็นเหตุผลเดียวที่สมเหตุสมผลสำหรับการใช้มันเช่นนี้ง่ายมากที่จะเขียนมากกว่าptr->kg(*ptr).kg

ตอนนี้ให้เราเขียนสิ่งนี้แตกต่างกันเพื่อให้คุณเห็นการเชื่อมต่อที่ชัดเจนยิ่งขึ้น (*ptr).kg⟹ ⟹(*&audi).kg audi.kgนี่คือครั้งแรกที่ผมใช้ความจริงที่ว่าptrเป็น"ที่อยู่ของaudi" IE &audiและความจริงที่ว่า"อ้างอิง" &และ"dereference" *ผู้ประกอบการยกเลิกการออก eachother


1

ฉันต้องทำการเปลี่ยนแปลงเล็กน้อยกับโปรแกรมของแจ็คเพื่อให้มันทำงานได้ หลังจากประกาศ pvar ตัวชี้ struct ชี้ไปที่ที่อยู่ของ var ฉันพบวิธีแก้ปัญหานี้ในหน้า 242 ของการเขียนโปรแกรมของ Stephen Kochan ใน 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;
}

รันสิ่งนี้เป็นกลุ่มด้วยคำสั่งต่อไปนี้:

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

จะส่งออก:

5 - 14.30
6 - 22.40

3
คำแนะนำเป็นกลุ่ม: ใช้%เพื่อแสดงชื่อไฟล์ปัจจุบัน เช่น:!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

->ประกอบการทำให้โค้ดอ่านได้มากขึ้นกว่า*ผู้ประกอบการในบางสถานการณ์

เช่น: (อ้างอิงจากโครงการ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_PROTOCOLstruct มีสมาชิก 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 เป็นตัวดำเนินการ dereference และใช้เพื่อเชื่อมต่อตัวแปรโครงสร้างสำหรับบันทึกโครงสร้างเฉพาะ เช่น :

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

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

ด้วยวิธีนี้เราสามารถใช้ตัวดำเนินการ dot เพื่อเข้าถึงตัวแปรโครงสร้าง


6
สิ่งนี้เพิ่มมูลค่าอะไร? ตัวอย่างที่เป็นที่น่าสงสารนิดเมื่อเทียบกับคำตอบอื่น ๆ ->ซึ่งอันที่จริงเปรียบเทียบกับ อีกทั้งยังได้ตอบคำถามนี้เป็นเวลา 4.5 ปีแล้ว
EWit
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.