C中的指针:何时使用“&”号和星号?


298

我只是从指针开始,我有点困惑。我知道&是指变量的地址,*可以在指针变量的前面使用它来获取指针所指向的对象的值。但是,当您使用数组,字符串或使用变量的指针副本调用函数时,情况会有所不同。很难在所有这些内部看到一种逻辑模式。

什么时候应该使用&*


5
请说明您如何看待有时工作有所不同的事物。否则,我们必须猜测是什么使您感到困惑。
德鲁·多曼

1
同意尼尔·巴特沃思(Neil Butterworth)的观点。可能会从书本上获得更多的信息,而K&R的解释很清楚。
汤姆(Tom)2010年

144
我不同意所有人所说的那样在SO上问这类问题不是一个好主意。在Google上进行搜索时,SO已成为第一资源。您没有对这些答案给予足够的重视。阅读Dan Olson的回复。这个答案是真正有见地的,对新手有极大的帮助。RTFM是无益的,并且坦率地说非常无礼。如果您没有时间回答,请尊重那些花时间回答这些问题的人。我希望我可以@将其“送给”匿名人员,但显然他/她没有时间以任何有意义的方式作出贡献。
SSH

18
什么SSH这说的是千真万确的。有人大喊“ Just Google it”,但如今却相反:“只要看看StackOverflow”。这个问题对许多人有用。(因此,无需投票。)
MC Emperor

Answers:


610

您有指针和值:

int* p; // variable p is pointer to integer type
int i; // integer value

您可以使用将指针变成一个值*

int i2 = *p; // integer i2 is assigned with integer value that pointer p is pointing to

您可以通过以下方式将值转换为指针&

int* p2 = &i; // pointer p2 will point to the address of integer i

编辑:在数组的情况下,它们非常类似于指针。如果您将它们视为指针,*则将如上所述使用它们来获取它们内部的值,但是还有另一种更常见的使用[]运算符的方式:

int a[2];  // array of integers
int i = *a; // the value of the first element of a
int i2 = a[0]; // another way to get the first element

获取第二个元素:

int a[2]; // array
int i = *(a + 1); // the value of the second element
int i2 = a[1]; // the value of the second element

因此,[]索引运算符是运算符的一种特殊形式*,它的工作方式如下:

a[i] == *(a + i);  // these two statements are the same thing

2
为什么这不起作用?int aX[] = {3, 4}; int *bX = &aX;
Pieter

5
数组是特殊的,可以透明地转换为指针。这突出显示了从指针指向值的另一种方法,我将其添加到上面的说明中。
丹·奥尔森

4
如果我正确理解这一点,该示例int *bX = &aX;将不起作用,因为该示例aX已经返回了aX[0](即&aX[0]&aX的地址,因此将获得没有意义的地址的地址。它是否正确?
Pieter

6
您是正确的,尽管在某些情况下您可能实际上需要该地址的地址。在这种情况下,您可以将其声明为int ** bX =&aX,但这是更高级的主题。
丹·奥尔森

3
@丹,给定的int aX[] = {3,4};int **bX = &aX;是错误的。 &aX类型为“指向数组[2]的int指针”,而不是“指向”的指针int。具体来说,数组名称不会被视为指向unary的第一个元素的指针&。您可以做到:int (*bX)[2] = &aX;
Alok Singhal 2010年

28

处理数组和函数时有一种模式。起初很难看。

处理数组时,记住以下几点很有用:当数组表达式出现在大多数上下文中时,表达式的类型从“ T的N元素数组”隐式转换为“指向T的指针”,并且其值已设置指向数组中的第一个元素。该规则的例外情况是:数组表达式作为&or sizeof运算符的操作数出现,或者它是用作声明中的初始化程序的字符串文字。

因此,当您使用数组表达式作为参数调用函数时,该函数将收到一个指针,而不是数组:

int arr[10];
...
foo(arr);
...

void foo(int *arr) { ... }

这就是为什么您&运算符用于与“%s”相对应的参数的原因scanf()

char str[STRING_LENGTH];
...
scanf("%s", str);

由于进行了隐式转换,因此scanf()接收到一个char *指向str数组开头的值。对于以数组表达式作为参数调用的任何函数(对于任何str*函数*scanf*printf函数等)都适用。

实际上,您可能永远不会使用&运算符来调用带有数组表达式的函数,如下所示:

int arr[N];
...
foo(&arr);

void foo(int (*p)[N]) {...}

这样的代码不是很常见;您必须在函数声明中知道数组的大小,并且该函数仅适用于指向特定大小的数组的指针(指向T的10个元素的数组的指针与指向11个元素的数组的指针的类型不同T)。

当数组表达式作为操作&符的操作数出现时,结果表达式的类型为“ T的N元素数组的指针”或T (*)[N],它不同于指针数组(T *[N])和基本类型的指针(T *)。

处理函数和指针时,要记住的规则是:如果要更改参数的值并将其反映在调用代码中,则必须将指针传递给要修改的对象。同样,数组会在工作中投入一些精力,但是我们将首先处理正常情况。

请记住,C 按值传递所有函数参数。形式参数会收到实际参数中值的副本,形式参数的任何更改都不会反映在实际参数中。常见的示例是交换函数:

void swap(int x, int y) { int tmp = x; x = y; y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(a, b);
printf("after swap: a = %d, b = %d\n", a, b);

您将获得以下输出:

交换前:a = 1,b = 2
交换后:a = 1,b = 2

形式参数xy从不同的对象ab,所以要改变xy未在反射ab。由于我们要修改aand 的值b,因此必须将指向它们的指针传递给swap函数:

void swap(int *x, int *y) {int tmp = *x; *x = *y; *y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("after swap: a = %d, b = %d\n", a, b);

现在您的输出将是

交换前:a = 1,b = 2
交换后:a = 2,b = 1

请注意,在swap函数中,我们不更改xand y的值,而是what xy 指向的值。写信*x与写信不同x; 我们不会自行更新值x,而是从中获取位置x并在该位置更新值。

如果我们要修改指针值,也是如此。如果我们写

int myFopen(FILE *stream) {stream = fopen("myfile.dat", "r"); }
...
FILE *in;
myFopen(in);

然后我们在修改输入参数的值stream,而不是stream 指向的东西,因此改变stream对值无效in; 为了使它起作用,我们必须将指针传递给该指针:

int myFopen(FILE **stream) {*stream = fopen("myFile.dat", "r"); }
...
FILE *in;
myFopen(&in);

同样,阵列将一些活动扳手投入工作。当您将数组表达式传递给函数时,该函数接收的是一个指针。由于数组下标的定义方式,您可以在指针上使用下标运算符,就像在数组上使用下标运算符一样:

int arr[N];
init(arr, N);
...
void init(int *arr, int N) {size_t i; for (i = 0; i < N; i++) arr[i] = i*i;}

请注意,可能无法分配数组对象。即,你不能做类似的事情

int a[10], b[10];
...
a = b;

因此在处理指向数组的指针时要格外小心;就像是

void (int (*foo)[N])
{
  ...
  *foo = ...;
}

将无法正常工作。


16

简单地说

  • &表示地址of,您将看到在函数中使用占位符修改参数变量(如C中一样),参数变量按值传递,使用&表示按引用传递。
  • *表示对指针变量的取消引用,意味着获取该指针变量的值。
int foo(int *x){
   *x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(&y);  // Now y is incremented and in scope here
   printf("value of y = %d\n", y); // output is 6
   /* ... */
}

上面的示例说明了如何foo使用传递引用来调用函数,并与之进行比较

int foo(int x){
   x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(y);  // Now y is still 5
   printf("value of y = %d\n", y); // output is 5
   /* ... */
}

这是使用取消引用的说明

int main(int argc, char **argv){
   int y = 5;
   int *p = NULL;
   p = &y;
   printf("value of *p = %d\n", *p); // output is 5
}

上面的示例说明了如何获取地址的地址 y并将其分配给指针变量p。然后,通过将附加到它的前面来取消引用 p*以获得的值p,即*p


10

是的,由于*C在C / C ++中有多种用途,因此可能会非常复杂。

如果*出现在已经声明的变量/函数的前面,则意味着:

  • a)*允许访问该变量的值(如果该变量的类型是指针类型,或者重载了*运算符)。
  • b)*具有乘法运算符的含义,在这种情况下,必须在变量的左侧添加另一个变量*

如果*出现在变量或函数声明中,则表示该变量是指针:

int int_value = 1;
int * int_ptr; //can point to another int variable
int   int_array1[10]; //can contain up to 10 int values, basically int_array1 is an pointer as well which points to the first int of the array
//int   int_array2[]; //illegal, without initializer list..
int int_array3[] = {1,2,3,4,5};  // these two
int int_array4[5] = {1,2,3,4,5}; // are identical

void func_takes_int_ptr1(int *int_ptr){} // these two are identical
void func_takes int_ptr2(int int_ptr[]){}// and legal

如果&出现在变量或函数声明中,则通常意味着该变量是对该类型变量的引用。

如果&出现在已声明的变量的前面,则返回该变量的地址

另外,您应该知道,当将数组传递给函数时,除了数组类似于以0结尾的cstring(char数组)外,还必须始终传递该数组的数组大小。


1
@akmozo s / func_takes int_ptr2 / func_takes_int_ptr2 /(无效空间)
PixnBits

4

在声明指针变量或函数参数时,请使用*:

int *x = NULL;
int *y = malloc(sizeof(int)), *z = NULL;
int* f(int *x) {
    ...
}

注意:每个声明的变量都需要自己的*。

如果要获取值的地址,请使用&。如果要在指针中读取或写入值,请使用*。

int a;
int *b;
b = f(&a);
a = *b;

a = *f(&a);

通常将数组当作指针对待。在函数中声明数组参数时,可以轻松地将其声明为指针(含义相同)。当您将数组传递给函数时,实际上是在将指针传递给第一个元素。

函数指针是唯一不完全遵循规则的东西。您可以不使用&来获取函数的地址,也可以不使用*来调用函数指针。


4

我一直在浏览所有冗长的解释,因此转向新南威尔士大学的一段视频进行救援。以下是简单的解释:如果我们有一个具有地址x和值的单元格7,则间接请求值地址的方法7&7间接要求在地址处求值的方法x*x.So。(cell: x , value: 7) == (cell: &7 , value: *x)另一种查询方法是:John位于7th seat*7th seat它将指向John&John并将给出address/ location 7th seat。这个简单的解释对我有所帮助,希望对其他人也有所帮助。这是优秀视频的链接:单击此处。

这是另一个示例:

#include <stdio.h>

int main()
{ 
    int x;            /* A normal integer*/
    int *p;           /* A pointer to an integer ("*p" is an integer, so p
                       must be a pointer to an integer) */

    p = &x;           /* Read it, "assign the address of x to p" */
    scanf( "%d", &x );          /* Put a value in x, we could also use p here */
    printf( "%d\n", *p ); /* Note the use of the * to get the value */
    getchar();
}

附加组件: 在使用指针之前,一定要初始化指针,否则指针会指向任何东西,这可能会导致程序崩溃,因为操作系统会阻止您访问它不知道的内存。 p = &x;,我们为指针分配了特定位置。


3

其实,您已经拍了拍,没有什么需要知道的了:-)

我只需要添加以下内容:

  • 这两个操作在频谱的两端。&接受变量并给您地址,*接受地址并给您变量(或内容)。
  • 数组将指针传递给函数时会“降级”到指针。
  • 您实际上可以在间接上有多个级别(char **p意味着这p是一个指向a的指针的指针char

至于工作方式不同,不是真的:

  • 如前所述,数组在传递给函数时会降级为指针(指向数组的第一个元素);他们不保留尺寸信息。
  • C中没有字符串,只是字符数组,按照惯例,它们表示以零(\0)字符结尾的字符串。
  • 当您将变量的地址传递给函数时,可以取消引用指针以更改变量本身(通常,变量按值传递(数组除外))。

3

我觉得你有点困惑。您应该阅读有关指针的优秀教程/书。

教程是对于初学者非常好(明确解释了什么是&*是)。是的,别忘了读肯尼思·里克(Kenneth Reek)的《Pointers in C》一书。

&和之间的区别*非常明显。

例:

#include <stdio.h>

int main(){
  int x, *p;

  p = &x;         /* initialise pointer(take the address of x) */
  *p = 0;         /* set x to zero */
  printf("x is %d\n", x);
  printf("*p is %d\n", *p);

  *p += 1;        /* increment what p points to i.e x */
  printf("x is %d\n", x);

  (*p)++;         /* increment what p points to i.e x */
  printf("x is %d\n", x);

  return 0;
}

1

好的,看来您的帖子已被编辑...

double foo[4];
double *bar_1 = &foo[0];

看看如何使用&来获取数组结构开头的地址?以下

Foo_1(double *bar, int size){ return bar[size-1]; }
Foo_2(double bar[], int size){ return bar[size-1]; }

将做同样的事情。


这个问题被标记为C而不是C ++。
Prasoon Saurav,2010年

1
而且我已经删除了令人讨厌的提示<<
麦ies
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.