C中的箭头运算符(->)用法


257

我正在读一本叫做《 21天自学C》的书(我已经学过Java和C#,所以我的步伐要快得多)。我正在阅读有关指针的章节,->(箭头)运算符没有解释就出现了。我认为它用于调用成员和函数(类似于.(点)运算符,但用于指针而不是成员)。但是我不确定。

我可以得到一个解释和一个代码示例吗?


90
获得一本更好的书。norvig.com/21-days.html
joshperry

9
qrdl是正确的-“在Y天内学习X”书籍通常都是垃圾。除了K&R,我还推荐Prata的“ C Primer Plus”,它比K&R更深入。
J. Taylor

3
@Steve这个问题与C ++有关。当我开始在其他答案中阅读有关运算符重载的信息时,称它为我有些困惑,这与C语言无关
Johann

1
@Belton艰难的方式系列是不好的,这个家伙说的东西在他写这本书时甚至都没有意义,而且他也不在乎好的做法。
巴林特

1
Peter Norvig链接到“十年自学编程”的链接很棒,是我的最爱之一。下面是其中介绍了如何在21天,我记得很不幸作为XKCD但我错了这样做的漫画版:abstrusegoose.com/249
泰德

Answers:


462

foo->bar等价于(*foo).bar,即它barfoo指向的结构中获取调用的成员。


51
值得注意的是,如果将取消引用运算符设为后缀(如在Pascal中),则->根本就不需要该运算符,因为它等同于更易读的foo*.bar。还要避免所有带有多余括号的类型定义函数的混乱。
罗恩侯爵

1
那么foo*.bar(*foo).bar两者都等于foo->bar?那Foo myFoo = *foo; myFoo.bar
亚伦弗兰克

9
不,他只是说,如果 C的创建者将取消引用运算符作为POSTfix运算符而不是PREfix运算符,那么它将更加容易。但是它是C中的前缀运算符
。– reichhart

@ user207421 Coulfd,请简短描述或链接到您提到的“带有所有额外括号的typedef-ing函数”?谢谢。
RoG

1
@ user207421不,这会导致更多的父母。.到目前为止,(*)和[]的优先级位于*上方的右上方。如果他们全都放在一边,您会增加父母的数量。表达式中的相同,因为与乘法运算符冲突。Pascal ^可能是一个选项,但保留给位操作,还有更多的父母。
斯威夫特-

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指向新的结构,那您将如何初始化它pvar = &var呢?
CMCDragonkai 2015年

这个问题专门针对C,因为C没有类或引用变量。
橡树

1
嗯,你不应该在写pvar之前做一个malloc struct foo * pvar; ?? pvar-> y写入未分配的空间!
Zibri

pvar初始化:手动将所有成员初始化为您想要的一些默认值,或者,如果零填充对您合适,则使用calloc()之类的方法。
reichhart

2
不应该是:pvar = malloc(sizeof(struct foo))或malloc(sizeof(* pvar))?
Yuri Aps

33

a->b(*a).b在各个方面都只是一个缩写(对于函数而言a->b()是相同的:是的缩写(*a).b())。


1
是否有文档说它对方法也是如此?
AsheKetchum

28

我只是在答案中加上“为什么?”。

. 是标准成员访问运算符,其优先级高于 *指针运算符。

当您尝试访问结构的内部结构时,您将其写为 *foo.bar编译器会认为需要'foo'的'bar'元素(这是内存中的地址),并且显然,仅地址没有任何成员。

因此,您需要先让编译器先解除引用(*foo),然后再访问member元素:(*foo).bar,这写起来有点笨拙,所以老兄提出了一个简写形式:foo->bar这是指针操作符对成员的一种访问。



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

这里访问的价值观ij我们可以使用变量a和指针p如下:a.i(*p).ip->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和你迫使编译器第一次取消引用指针,使接取到数据块第二你加一个偏移量(标志),以选择成员。

检查我制作的这张图片:

在此处输入图片说明

但是,如果您有嵌套成员,则该语法将变得不可读,因此->被引入。我认为可读性是使用它的唯一正当理由,因为这ptr->kg比起编写起来容易得多(*ptr).kg

现在,让我们用不同的方式写这个,以便您更清楚地看到连接。(*ptr).kg(*&audi).kgaudi.kg。在这里,我首先使用的事实ptr“的地址audi”,“引用”“取消引用”运算符相互抵消的&audi事实。 & *


1

我必须对Jack的程序做些小改动才能使其运行。声明结构指针pvar后,将其指向var的地址。我在Stephen Kochan的C语言编程的第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
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_PROTOCOL结构包含4个函数指针成员。

假设您有一个变量struct _EFI_BLOCK_IO_PROTOCOL * pStruct,并且您想要使用良好的旧*运算符来调用它的成员函数指针。您将得到如下代码:

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

但是使用->运算符,您可以这样编写:

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

哪个看起来更好?


1
我认为,如果代码不像90年代AOL聊天中的青少年那样大写,那么代码将更具可读性。

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 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.