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

Pointers in C: when to use the ampersand and the asterisk?

我刚开始用指针,我有点困惑。我知道&是一个变量的地址,*可以在指针变量前面使用,得到指针指向的对象的值。但是,当使用数组、字符串或使用变量的指针副本调用函数时,情况会有所不同。在这一切中很难看到一种逻辑模式。

我什么时候应该使用&*


您有指针和值:

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

使用*将指针转换为值:

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

使用&将值转换为指针:

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

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

1
2
3
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

要获取第二个元素:

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

因此,[]索引操作符是*操作符的一种特殊形式,其工作原理如下:

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


在处理数组和函数时有一个模式;刚开始有点难看到。

在处理数组时,记住以下几点很有用:当数组表达式出现在大多数上下文中时,表达式的类型将隐式地从"t的n元素数组"转换为"指向t的指针",其值将设置为指向数组中的第一个元素。此规则的例外情况是,数组表达式显示为&sizeof运算符的操作数,或者是用作声明中初始值设定项的字符串文字。

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

1
2
3
4
5
6
int arr[10];
...
foo(arr);
...

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

这就是为什么您不使用&运算符来处理与scanf()中的"%s"对应的参数:

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

由于隐式转换,scanf()接收到指向str数组开头的char *值。这适用于以数组表达式作为参数调用的任何函数(仅适用于str*函数、*scanf函数和*printf函数等)。

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

1
2
3
4
5
int arr[N];
...
foo(&arr);

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

这种代码并不常见;您必须在函数声明中知道数组的大小,并且该函数只与指向特定大小数组的指针一起工作(指向t的10元素数组的指针与指向t的11元素数组的指针的类型不同)。

当数组表达式显示为&运算符的操作数时,结果表达式的类型为"指向t的n元素数组的指针"或T (*)[N],这与指针数组(T *[N]和指向基类型的指针(T *不同。

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

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

1
2
3
4
5
6
7
8
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
"
, a, b);
swap(a, b);
printf("after swap: a = %d, b = %d
"
, a, b);

您将得到以下输出:

1
2
before swap: a = 1, b = 2
after swap: a = 1, b = 2

形式参数xyab是不同的对象,因此xy的变化不反映在ab中。由于我们想修改ab的值,我们必须将指针传递给swap函数:

1
2
3
4
5
6
7
8
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
"
, a, b);
swap(&a, &b);
printf("after swap: a = %d, b = %d
"
, a, b);

现在你的输出将是

1
2
before swap: a = 1, b = 2
after swap: a = 2, b = 1

注意,在交换函数中,我们不会更改xy的值,但是xy指向的值。写入*x与写入x不同;我们不更新x本身的值,我们从x获得一个位置,并更新该位置的值。

如果我们想修改一个指针值,这也是一样的;如果我们写

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

然后我们修改输入参数stream的值,而不是stream所指的值,因此更改streamin的值没有影响;为了使这一点起作用,我们必须向指针传递一个指针:

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

阵列再一次向工程中投入了一点活扳手。将数组表达式传递给函数时,函数接收的是指针。由于数组下标是如何定义的,因此可以在指针上使用下标运算符,方法与在数组上使用下标运算符的方法相同:

1
2
3
4
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;}

请注意,数组对象可能未被分配;也就是说,您不能执行类似的操作

1
2
3
int a[10], b[10];
...
a = b;

所以在处理指向数组的指针时要小心;比如

1
2
3
4
5
void (int (*foo)[N])
{
  ...
  *foo = ...;
}

不起作用。


简单地说

  • &表示的地址,您将看到在用于修改参数变量的函数的占位符中,参数变量是通过值传递的,使用与符号和方法通过引用传递。
  • *表示指针变量的解引用,即获取该指针变量的值。
1
2
3
4
5
6
7
8
9
10
11
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
"
, y); // output is 6
   /* ... */
}

上面的示例说明了如何使用pass-by引用调用函数foo,并与此进行比较

1
2
3
4
5
6
7
8
9
10
11
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
"
, y); // output is 5
   /* ... */
}

下面是使用取消引用的示例

1
2
3
4
5
6
7
int main(int argc, char **argv){
   int y = 5;
   int *p = NULL;
   p = &y;
   printf("value of *p = %d
"
, *p); // output is 5
}

上面说明了如何获取EDOCX1的地址(12),并将其分配给指针变量p。然后,我们将*附加到其前面,以取消引用p,得到p的值,即*p


是的,这是相当复杂的,因为EDCOX1的0度用于C/C++中的许多不同用途。

如果*出现在已声明的变量/函数前面,则表示:

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

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

1
2
3
4
5
6
7
8
9
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 aswell 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 indentical

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

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

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

此外,您应该知道,当将数组传递给函数时,也必须传递该数组的数组大小,除非该数组类似于以0结尾的CString(char数组)。


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

1
2
3
4
5
int *x = NULL;
int *y = malloc(sizeof(int)), *z = NULL;
int* f(int *x) {
    ...
}

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

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

1
2
3
4
5
6
int a;
int *b;
b = f(&a);
a = *b;

a = *f(&a);

数组通常被视为指针。当您在函数中声明数组参数时,您可以很容易地将其声明为指针(这意味着相同的事情)。当向函数传递数组时,实际上是向第一个元素传递指针。

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


我觉得你有点困惑。你应该阅读一本关于指针的好教程/书。

本教程非常适合初学者(清楚地解释了&*是什么)。是的,别忘了读肯尼思·里克写的《C中的指针》。

&*之间的差别非常明显。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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
"
, x);
  printf("*p is %d
"
, *p);

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

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

  return 0;
}


事实上,你已经把它拍下来了,你再也不需要知道什么了:—)

我只需添加以下位:

    百万千克1这两个操作是频谱的两端。&接受一个变量并给出地址,*接受一个地址并给出变量(或内容)。百万千克1百万千克1数组在传递给函数时会"降级"到指针。百万千克1百万千克1实际上,您可以有多个间接级别(char **p表示p是指向char的指针的指针。百万千克1

对于工作方式不同的事情,不是:

    百万千克1如前所述,数组在传递给函数时降级为指针(指向数组中的第一个元素);它们不保留大小信息。百万千克1百万千克1C中没有字符串,只是字符数组,按照约定,它表示以零(\0字符)结尾的一组字符。百万千克1百万千克1将变量的地址传递给函数时,可以取消对指针的引用以更改变量本身(通常变量是按值传递的(数组除外))。百万千克1


我看了一遍所有冗长的解释,转而看了新南威尔士大学的一段视频来救援。下面是一个简单的解释:如果我们有一个单元格的地址是x和值7,那么间接地要求值7的地址是&7,间接地要求值a的方式是地址x*x,所以(cell: x , value: 7) == (cell: &7 , value: *x),另一种方式是:John位于7th seat*7th seat指向John&John指向7th seat。这个简单的解释帮助了我,希望它也能帮助别人。以下是优秀视频的链接:单击此处。

下面是另一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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
"
, *p ); /* Note the use of the * to get the value */
    getchar();
}

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


好吧,看来你的文章被编辑了…

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

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

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

会做同样的事情。