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

我刚开始讲指针,有点困惑。我知道&表示变量的地址,并且*可以用于指针变量前,以获得指针所指向的对象的值。但是当你使用数组、字符串或者使用变量的指针副本调用函数时,情况就不同了。很难看出其中的逻辑模式。

什么时候我应该使用&*?

282484 次浏览

好吧,看起来你的帖子被编辑了…

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]; }

会做同样的事情。

你有指针和值:

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
< p >编辑: 在数组的情况下,它们被处理得非常像指针。如果你认为它们是指针,你将使用*来获取它们内部的值,如上所述,但还有另一种更常见的使用[]操作符的方法:

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

事实上,你已经掌握了,没有什么你需要知道的了:-)

我想补充以下几点:

  • 这两种操作是截然相反的。&接受一个变量并给出地址,*接受一个地址并给出变量(或内容)。
  • 将数组传递给函数时,数组“降级”为指针。
  • 你实际上可以在间接上有多个层次(char **p意味着p是指向char的指针的指针。

至于事情的运作方式不同,其实不是这样的:

  • 如前所述,数组在传递给函数时降级为指针(指向数组中的第一个元素);它们不保存大小信息。
  • C语言中没有字符串,只有字符数组,按照惯例,表示以0 (\0)字符结束的字符串。
  • 当你将一个变量的地址传递给一个函数时,你可以去引用指针来改变变量本身(通常变量是按值传递的(数组除外))。

我觉得你有点糊涂了。你应该读一本关于指针的教程/书。

教程非常适合初学者(清楚地解释了&*是什么)。别忘了读Kenneth Reek的书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;
}

当你声明一个指针变量或函数形参时,使用*:

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);

数组通常就像指针一样。当你在函数中声明一个数组形参时,你可以很容易地声明它是一个指针(它的意思是一样的)。当你将一个数组传递给一个函数时,你实际上是在传递一个指向第一个元素的指针。

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

在处理数组和函数时,有一个模式;只是刚开始有点看不出来。

在处理数组时,记住以下内容是有用的:当数组表达式出现在大多数上下文中时,表达式的类型会从“T的n元素数组”隐式转换为“指向T的指针”,并且它的值被设置为指向数组中的第一个元素。此规则的例外情况是当数组表达式作为&sizeof操作符的操作数出现时,或者当它是一个字符串字面值在声明中用作初始化式时。

因此,当你调用一个以数组表达式作为参数的函数时,函数将接收一个指针,而不是数组:

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


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

这就是为什么你使用&操作符来对应scanf()中的“%s”参数:

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

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

在实践中,您可能永远不会使用&操作符调用带有数组表达式的函数,例如:

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


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

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

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

在处理函数和指针时,要记住的规则是:如果你想改变一个参数的值,并让它反映在调用代码中,你必须传递一个指针到你想修改的东西。同样,数组会给工作带来一些麻烦,但我们将首先处理正常情况。

记住C通过值传递所有函数参数;形式形参接收实际形参值的副本,对形式形参的任何更改都不会反映在实际形参中。常见的例子是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);

您将得到以下输出:

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

形式参数xyab是不同的对象,因此对xy的更改不会反映在ab中。因为我们想要修改ab的值,所以必须将y0传递给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);

现在您的输出将是

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

注意,在swap函数中,我们不改变xy的值,而是改变xy 指出的值。写入*x不同于写入x;我们没有更新x本身的值,我们从x获取一个位置并更新该位置中的值。

如果我们想修改一个指针值,这同样成立;如果我们写

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

然后我们修改输入参数stream的值,而不是stream 指出,所以改变streamin的值没有影响;为了让它工作,我们必须传递一个指针给指针:

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 = ...;
}

不能工作。

是的,这可能相当复杂,因为*在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数组)。

简单地说

  • &表示取地址,你会看到在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

我浏览了所有冗长的解释,所以转向新南威尔士大学的一段视频进行救援。下面是简单的解释:如果我们有一个单元格,其地址为x,值为7,那么请求值为7的地址的间接方法是&7,而请求地址为x的值的间接方法是*x。所以(cell: x , value: 7) == (cell: &7 , value: *x)。另一种研究方法是:John位于7th seat*7th seat将指向John,而71将给出72/ 7th seat的位置。这个简单的解释帮助了我,希望它也能帮助到其他人。下面是精彩视频的链接:74

下面是另一个例子:

#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;,我们就为指针分配了一个特定的位置。

一开始理解指针是很复杂的,你必须做练习 多多练习。 不要指望在第一次迭代时就能抓住它,也不要指望读到解释 并认为你已经理解了,因为很可能你没有……

如果你想要的不仅仅是理论上的理解,我建议遵循这个 斯坦福CS107课程和练习,

.

.

斯坦福CS107 by Jerry Cain

另一个非常有价值的工具是gdb,在那里你有一个交互式的 就像你用python写的那样。 使用gdb你可以玩和实验:

 (gdb) x pp.name
0x555555555060 <_start>:        0x8949ed31
(gdb) x &pp.name
0x7fffffffdc38: 0x55555060
(gdb) p &pp.name
$4 = (char **) 0x7fffffffdc38
(gdb) p *pp.name
$5 = 49 '1'