Sử dụng toán tử mũi tên (->) trong C


257

Tôi đang đọc một cuốn sách có tên "Dạy cho bản thân C trong 21 ngày" (Tôi đã học Java và C # vì vậy tôi đang di chuyển với tốc độ nhanh hơn nhiều). Tôi đang đọc chương về con trỏ và toán tử-> (mũi tên) xuất hiện mà không có lời giải thích. Tôi nghĩ rằng nó được sử dụng để gọi các thành viên và chức năng (giống như tương đương với. toán tử (dấu chấm), nhưng dành cho các con trỏ thay vì các thành viên). Nhưng tôi không hoàn toàn chắc chắn.

Tôi có thể xin vui lòng nhận được một lời giải thích và một mẫu mã?


90
Lấy một cuốn sách tốt hơn. norvig.com/21-days.html
joshperry

9
qrdl là chính xác - sách "Tìm hiểu X trong Y ngày" nói chung là rác. Ngoài K & R, tôi cũng muốn giới thiệu "C Primer Plus" của Prata, đi sâu hơn K & R.
J. Taylor

3
@Steve Câu hỏi đó liên quan đến C ++. Gọi nó là một sự nhầm lẫn đối với tôi khi tôi bắt đầu đọc về quá tải toán tử trong câu trả lời khác, không liên quan đến C.
Johann

1
@Belton Loạt bài khó là xấu, anh chàng nói những thứ thậm chí không liên quan khi anh viết cuốn sách và anh không quan tâm đến những thực hành tốt.
Bálint

1
Liên kết Peter Norvig đến "Dạy cho chính bạn lập trình trong mười năm" là một trong những điều tôi thích. Đây là phiên bản truyện tranh giải thích cách thực hiện điều này trong 21 ngày, điều mà tôi không may nhớ là XKCD nhưng tôi đã nhầm: abstrusegoose.com/249
Ted

Câu trả lời:


462

foo->bartương đương với (*foo).bar, tức là nó nhận được thành viên được gọi bartừ struct footrỏ đến.


51
Điều đáng chú ý là nếu toán tử dereference đã được tạo hậu tố, như trong Pascal, ->toán tử hoàn toàn không cần thiết, vì nó sẽ tương đương với mức độ dễ đọc hơn nhiều foo*.bar. Toàn bộ các hàm typedef-ing với tất cả các dấu ngoặc đơn cũng sẽ được tránh.
Hầu tước Lorne

1
Vì vậy, foo*.bar(*foo).barcả hai sẽ tương đương với foo->bar? Thế còn Foo myFoo = *foo; myFoo.bar?
Aaron Franke

9
Không, anh ta chỉ nói NẾU những người tạo ra C sẽ biến toán tử dereference thành toán tử POSTfix thay vì PREfix thì mọi chuyện sẽ dễ dàng hơn. Nhưng nó là một toán tử tiền tố trong C.
reichhart 16/03/19

@ user207421 Coulfd bạn vui lòng cung cấp một mô tả ngắn hoặc liên kết đến "các hàm typedef-ing với tất cả các dấu ngoặc đơn" mà bạn đề cập? Cảm ơn.
RoG

1
@ user207421 nah, nó sẽ khiến nhiều phụ huynh hơn .. cho đến nay, có sự ưu tiên của () và [] ở bên phải phía trên * ở bên trái. nếu tất cả họ ở một bên, bạn sẽ đặt thêm cha mẹ. Tương tự trong biểu thức, vì xung đột với toán tử nhân. Pascal ^ có thể là một tùy chọn nhưng nó được dành riêng cho hoạt động bit, vẫn còn nhiều phụ huynh.
Swift - Thứ Sáu

129

Vâng, đó là nó.

Đây chỉ là phiên bản dấu chấm khi bạn muốn truy cập các phần tử của struct / class là con trỏ thay vì tham chiếu.

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;

Đó là nó!


3
Vì pvar chưa được khởi tạo, bạn sẽ khởi tạo nó như thế nào nếu bạn muốn pvar trỏ đến một cấu trúc mới, phải không pvar = &var?
CMCDragonkai

Câu hỏi cụ thể là về C, không có các lớp hoặc biến tham chiếu.
Sồi

1
hmm bạn không nên làm một malloc trước khi viết cho pvar struct foo * pvar; ?? pvar-> y viết vào không gian chưa phân bổ!
Zibri

Khởi tạo pvar: Khởi tạo tất cả các thành viên theo cách thủ công đến một số giá trị mặc định mà bạn muốn có hoặc sử dụng một cái gì đó như calloc () nếu không điền sẽ tốt cho bạn.
thiệu lại

2
không nên là: pvar = malloc (sizeof (struct foo)) hay malloc (sizeof (* pvar)) ??
Yuri Aps

33

a->bchỉ viết tắt (*a).btheo mọi cách (tương tự cho các hàm: a->b()viết tắt của từ (*a).b()).


1
Có tài liệu nào nói rằng nó cũng hoạt động theo cách đó cho các phương thức không?
AsheKetchum

28

Tôi chỉ cần thêm vào câu trả lời "tại sao?".

.là toán tử truy cập thành viên tiêu chuẩn có độ ưu tiên cao hơn *toán tử con trỏ.

Khi bạn đang cố truy cập vào phần bên trong của cấu trúc và bạn đã viết nó khi *foo.barđó trình biên dịch sẽ nghĩ muốn có phần tử 'bar' của 'foo' (là một địa chỉ trong bộ nhớ) và rõ ràng địa chỉ đó không có bất kỳ thành viên nào.

Do đó, bạn cần phải yêu cầu trình biên dịch trước (*foo)khi truy cập phần tử thành viên và sau đó truy cập phần tử thành viên : (*foo).bar, hơi vụng về để viết, vì vậy những người giỏi đã đưa ra một phiên bản tốc ký: foo->barđó là loại quyền truy cập thành viên của toán tử con trỏ.



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

Ở đây để truy cập các giá trị của ijchúng ta có thể sử dụng biến avà con trỏ pnhư sau : a.i, (*p).ip->iđều giống nhau.

Đây .là "Bộ chọn trực tiếp" và ->là "Bộ chọn gián tiếp".


2

Vâng, tôi phải thêm một cái gì đó là tốt. Cấu trúc hơi khác so với mảng vì mảng là con trỏ còn cấu trúc thì không. Vì vậy, hãy cẩn thận!

Hãy nói rằng tôi viết đoạn mã vô dụng này:

#include <stdio.h>

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

int main(void){

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

}

Ở đây con trỏ trỏ ptrđến địa chỉ ( ! ) Của biến cấu trúc audinhưng bên cạnh cấu trúc địa chỉ cũng có một đoạn dữ liệu ( ! )! Thành viên đầu tiên của khối dữ liệu có cùng địa chỉ với cấu trúc và bạn có thể lấy dữ liệu đó chỉ bằng cách hủy bỏ một con trỏ như thế này *ptr (không có dấu ngoặc nhọn) .

Nhưng nếu bạn muốn acess bất kỳ thành viên khác so với cái đầu tiên, bạn cần phải thêm một vấn thiết kế như .km, .kph, .kgđó là không có gì hơn offsets đến địa chỉ cơ sở của đoạn dữ liệu ...

Nhưng vì sự preceedence bạn không thể viết *ptr.kgnhư toán tử truy cập .được đánh giá trước khi toán tử tham chiếu *và bạn sẽ nhận được *(ptr.kg)đó là không thể như con trỏ không có thành viên! Và trình biên dịch biết điều này và do đó sẽ đưa ra một lỗi, ví dụ:

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

Thay vào đó, bạn sử dụng cái này (*ptr).kgvà bạn buộc trình biên dịch phải loại bỏ con trỏ thứ nhất và cho phép truy cập vào khối dữ liệuthứ 2 bạn thêm một phần bù (chỉ định) để chọn thành viên.

Kiểm tra hình ảnh này tôi đã thực hiện:

nhập mô tả hình ảnh ở đây

Nhưng nếu bạn có các thành viên lồng nhau, cú pháp này sẽ trở nên không thể đọc được và do đó ->được giới thiệu. Tôi nghĩ rằng khả năng đọc là lý do chính đáng duy nhất để sử dụng nó vì điều này ptr->kgdễ viết hơn nhiều so với(*ptr).kg .

Bây giờ hãy để chúng tôi viết điều này khác nhau để bạn thấy kết nối rõ ràng hơn. (*ptr).kg(*&audi).kgaudi.kg. Ở đây lần đầu tiên tôi sử dụng thực tế ptrlà một "địa chỉ audi" tức là &audivà thực tế là các toán tử "tham chiếu" &"dereference" * hủy bỏ nhau.


1

Tôi đã phải thực hiện một thay đổi nhỏ cho chương trình của Jack để chạy nó. Sau khi khai báo con trỏ struct pvar, trỏ nó đến địa chỉ của var. Tôi tìm thấy giải pháp này trên trang 242 của Lập trình 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;
}

Chạy lệnh này trong vim bằng lệnh sau:

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

Sẽ xuất:

5 - 14.30
6 - 22.40

3
mẹo vim: sử dụng %để đại diện cho tên tệp hiện tại. Giống như vậy:!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

Các ->nhà điều hành làm cho mã dễ đọc hơn so với* nhà điều hành trong một số tình huống.

Chẳng hạn như: (trích từ dự án 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;

};

Các _EFI_BLOCK_IO_PROTOCOL trúc chứa 4 thành viên con trỏ hàm.

Giả sử bạn có một biến struct _EFI_BLOCK_IO_PROTOCOL * pStructvà bạn muốn sử dụng *toán tử cũ tốt để gọi nó là con trỏ hàm thành viên. Bạn sẽ kết thúc với mã như thế này:

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

Nhưng với ->toán tử, bạn có thể viết như thế này:

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

Cái nào nhìn tốt hơn?


1
Tôi nghĩ rằng mã sẽ dễ đọc hơn nếu nó không có trong tất cả các mũ giống như được thanh thiếu niên gõ vào trò chuyện AOL từ những năm 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);
}

đầu ra là 5 5 5


0

Dot là một toán tử dereference và được sử dụng để kết nối biến cấu trúc cho một bản ghi cụ thể của cấu trúc. Ví dụ :

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

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

Theo cách đó, chúng ta có thể sử dụng toán tử dấu chấm để truy cập biến cấu trúc


6
Giá trị này làm gì thêm? Ví dụ này hơi kém so với các câu trả lời khác thực sự so sánh nó với ->. Ngoài ra câu hỏi này đã được trả lời 4,5 năm rồi.
EWit
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.