如何理解指针 (*) 和寻址 (&) 运算符的概念?

How can I understand the concept of pointers (*) and address-of (&) operators?

我想了解这两个运算符的意义,所以我写这段代码就是为了这个目的。

#include <stdio.h>
#include <string.h>

int main()
{
    char *mnemonic, *operands;

    mnemonic = "add";
    operands = "five to two";

    analyse_inst(mnemonic, operands);

}

void analyse_inst(char mnemonic, char operands)
{
    printf("%s", mnemonic);
    printf("%s", operands);
}

但是,我注意到它不会工作,除非我将 analyse_inst() 函数的参数更改为 analyse_inst(char * mnemonic, char * operands),这意味着我将传递指向该函数的指针。但为什么需要这样呢?

另外,我查了一下"passing by reference.",根据tutorialspoint.com,它的定义是:

The call by reference method of passing arguments to a function copies the address of an argument into the formal parameter. Inside the function, the address is used to access the actual argument used in the call. It means the changes made to the parameter affect the passed argument.

据此,我了解到通过引用传递变量然后修改该值将意味着函数外部的相同变量也会被更改;而按值传递变量不会更改位于函数外部的相同变量。

我哪里出错了吗?

如何修改我的代码,以便通过引用传递这两个变量?

(P.S。我已经阅读了关于同一主题的其他 Stack Overflow 线程,但如果有人能在我编写的代码的上下文中进行解释,我将不胜感激)

C 中的字符串存储为字符数组,以值为 '[=11=]' ("NIL") 的字符结尾。您不能直接传递数组,因此使用指向第一个字符的指针,这就是为什么必须将 char *s 传递给函数才能访问字符串的原因。

字符通常比指针小得多(想想 8 位与 32/64 位),因此您不能将指针值压缩到单个字符中。

C没有引用传递;它仅按价值传递。有时,该值 尽可能接近语言的引用(即指针),但随后该指针又按值传递。

考虑一下:

static void put_next(const char *s)
{
  putchar(*s++);
}

int main(void)
{
  const char *string = "hello";
  put_next(string);
  put_next(string);
}

这将打印 hh,因为它每次都被传递相同的值 string,事实上 s,这是一个不同的变量,持有相同值的副本, 在函数内部递增并不重要。增加的值是函数的局部值,一旦超出范围就会被丢弃。

which means that I will be passing pointers to the function. But why is that required?

因为您在 main 中拥有的是指针,而 printf("%s" 期望的是 char*.

"Pass by reference" 是编程中的广义术语,意思是传递地址而不是对象的副本。在您的情况下,您将指针传递给每个字符串的第一个元素,而不是复制整个字符串,因为那样会浪费执行时间和内存。

所以虽然字符串本身可以说是"passed by reference",但严格来说C实际上只允许按值传递参数。 指针本身 是按值传递的。您的函数参数将是您在 main() 中分配的指针的副本。但是它们指向与 main() 中的指针相同的字符串。

From that, I got that passing a variable by reference and then modifying that value would mean that the same variable outside the function would be changed as well;

确实,您可以通过指针从函数内部更改字符串,然后它会影响 main() 中的字符串。但在这种情况下,您没有分配任何内存来修改 - 您将尝试修改字符串文字 "...",这将是一个错误。如果要修改字符串,则应该在 main() 中将它们声明为数组:char mnemonic[] = "add";

现在事实证明,每当您在表达式中使用像我的示例中那样的数组时,它 "decays" 都会变成指向第一个元素的指针。所以我们实际上无法将数组按值传递给函数,因为 C 语言会在行之间将其更改为指向第一个元素的指针。

您可以尝试使用此代码:

#include <stdio.h>
#include <string.h>

void analyse_inst(char* mnemonic, char* operands);

int main()
{
    char mnemonic[] = "add";
    char operands[] = "five to two";

    analyse_inst(mnemonic, operands);
    printf("%s\n", mnemonic);
}

void analyse_inst(char* mnemonic, char* operands)
{
    printf("%s ", mnemonic);
    printf("%s\n", operands);

    strcpy(mnemonic, "hi");
}

当你写类似 char *mnemonic 的东西时,这意味着你正在创建一个指针变量(将保存另一个变量地址的变量)但是因为 mnemonic 的数据类型是 char 它将仅保存 char 数据类型的变量地址。

现在,在你的代码中你写了 mnemonic = "add" 所以这里 "add" 是字符串,它是字符数组,助记符指向该数组的基址。

并且在调用函数时您正在传递这些 char arrays 的引用,因此您需要将 void analyse_inst(char mnemonic, char operands) 更改为 void analyse_inst(char *mnemonic, char *operands) 以获取这些相应指针变量中的引用。原因相同我们需要指针变量来保存引用

& returns是变量的地址,也就是对存储变量的内存位置的引用。

希望这会有所帮助。

我将在您的代码上下文中进行讨论,但我想先了解一些基础知识。

声明中,一元*运算符表示被声明的事物具有指针类型:

T *p;       // for any type T, p has type "pointer to T"
T *p[N];    // for any type T, p has type "N-element array of pointer to T"
T (*p)[N];  // for any type T, p has type "pointer to N-element array of T"
T *f();     // for any type T, f has type "function returning pointer to T"
T (*f)();   // for any type T, f has type "pointer to function returning T"

一元运算符 * 的优先级低于后缀 [] 下标和 () 函数运算符,因此如果您想要指向数组或函数的指针,* 必须明确地与标识符分组。

表达式中,一元*运算符取消引用指针,允许我们访问指向的对象或功能:

int x;
int *p;
p = &x;  // assign the address of x to p
*p = 10; // assigns 10 to x via p - int = int

以上代码执行后,以下为真:

 p == &x       // int * == int *
*p ==  x == 10 // int   == int   == int

表达式p&x的类型为int *(指向int的指针),它们的值是x 的(虚拟)地址。 表达式 *px 的类型为 int,它们的值为 10

一个有效的1对象指针值可以通过以下三种方式之一获取(函数指针也是一回事,这里不赘述):

  • 在左值上使用一元 & 运算符2 (p = &x;);
  • 通过 malloc()calloc()realloc() 分配动态内存;
  • 以及与您的代码相关的内容,使用不带 &sizeof 运算符的 数组表达式

除非它是 sizeof 或一元 & 运算符的操作数,或者是用于在声明中初始化字符数组的字符串文字,表达式[ "N-element array of T"类型的=176=]转换("decays")为"pointer to T"类型的表达式,表达式的值为数组[=163]首元素的地址=]3。所以,如果你创建一个像

这样的数组
int a[10];

并将该数组表达式作为参数传递给像

这样的函数
foo( a );

然后在调用函数之前,表达式a从类型“10-element array of int”转换为"pointer to int",a的值] 是 a[0] 的地址。所以函数实际接收到的是一个指针值,而不是一个数组:

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

"add""five to two" 这样的字符串文字是数组表达式 - "add" 的类型是“char 的 4 元素数组”,"five to two" 的类型是"12-element array of char"(一个N字符的字符串需要至少存储N+1个元素,因为字符串终止符)。

在报表中

mnemonic = "add";
operands = "five to two";

字符串字面量都不是 sizeof 或一元 & 运算符的操作数,并且它们不用于在声明中初始化字符数组,因此两个表达式都转换为类型char *,它们的值是每个数组第一个元素的地址。 mnemonicoperands 都被声明为 char *,所以这没问题。

由于mnemonicoperands的类型都是char *,当你调用

analyse_inst( mnemonic, operands );

函数形式参数的类型也必须是char *:

void analyse_inst( char *mnemonic, char *operands ) 
{
  ...
}

至于 "pass by reference" 位...

C 按值 传递所有函数参数。这意味着函数定义中的形式参数与函数调用中的实际参数在内存中是不同的对象,并且对形式参数所做的任何更改都不会反映在实际参数中。假设我们写一个 swap 函数为:

int swap( int a, int b )
{
  int tmp = a;
  a = b;
  b = tmp;
}

int main( void )
{
  int x = 2;
  int y = 3;

  printf( "before swap: x = %d, y = %d\n", x, y );
  swap( x, y );
  printf( "after swap: x = %d, y = %d\n", x, y );
  ...
}

如果编译 运行 这段代码,您会发现 xy 的值在调用 swap 后没有改变 - ab 的更改对 xy 没有影响,因为它们在内存中是不同的对象。

为了让swap函数起作用,我们必须将指针传递给xy:

void swap( int *a, int *b )
{
  int tmp = *a;
  *a = *b;
  *b = tmp;
}

int main( void )
{
  ...
  swap( &x, &y );
  ...
}

在这种情况下,表达式 *aswap 中的 *b 与表达式 xymain中,因此对*a*b的更改反映在xy中:

 a == &x,  b == &y
*a ==  x, *b ==  y

所以,一般来说:

void foo( T *ptr ) // for any non-array type T
{
  *ptr = new_value(); // write a new value to the object `ptr` points to
}

void bar( void )
{
  T var;
  foo( &var ); // write a new value to var
}

对于指针类型也是如此——将T替换为指针类型P *,我们得到以下内容:

void foo( P **ptr ) // for any non-array type T
{
  *ptr = new_value(); // write a new value to the object `ptr` points to
}

void bar( void )
{
  P *var;
  foo( &var ); // write a new value to var
}

在这种情况下,var 存储一个指针值。如果我们想通过foovar写入一个新的指针值,那么我们仍然必须传递一个指向var的指针作为参数。由于 var 的类型为 P *,因此 表达式 &var 的类型为 P **


  1. 如果指针值指向对象生命周期内的对象,则该指针值有效。
  2. 左值是一个引用对象的表达式,因此可以读取或修改该对象的值。
  3. 信不信由你,这条规则有一个很好的理由,但这意味着数组表达式在大多数情况下会失去它们的 "array-ness",导致初次学习该语言的人产生很多困惑。