【问题标题】:How can I understand the concept of pointers (*) and address-of (&) operators?如何理解指针 (*) 和地址 (&) 运算符的概念?
【发布时间】:2019-07-21 03:09:42
【问题描述】:

我试图理解这两个运算符的意义,所以我为此编写了这段代码。

#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),否则它将不起作用,这意味着我将传递指向该函数的指针。但为什么需要这样做?

另外,我查找了“通过引用传递”。根据tutorialspoint.com,它的定义是:

将参数传递给函数的引用调用方法复制 将参数的地址放入形式参数中。在 - 的里面 函数,地址用于访问实际使用的参数 通话。这意味着对参数所做的更改会影响传递的 论据。

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

我哪里出错了?

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

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

【问题讨论】:

标签: c pointers pass-by-reference pass-by-value call-by-value


【解决方案1】:

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

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

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&amp;x的类型为int *(指向int的指针),它们的值是x的(虚拟)地址。 表达式 *px 的类型为int,它们的值为10

一个有效的1对象指针值是通过以下三种方式之一获得的(函数指针也是一个东西,但我们这里就不深入了):

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

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

这样的数组
int a[10];

并将该数组表达式作为参数传递给类似的函数

foo( a );

那么在函数调用之前,表达式a从“int的10元素数组”类型转换为“指向int的指针”,a的值就是@的地址987654356@。所以函数实际接收的是指针值,而不是数组:

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

"add""five to two" 等字符串字面量是数组表达式 - "add" 的类型为“char 的 4 元素数组”,"five to two" 的类型为“char 的 12 元素数组”(一个由于字符串终止符,N 个字符的字符串需要至少 N+1 个元素来存储)。

在声明中

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

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

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

analyse_inst( mnemonic, operands );

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

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

至于“通过引用”位......

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

在这种情况下,swap 中的 表达式 *a*bmain 中的表达式 xy 引用相同的对象,因此更改到*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 存储一个指针值。如果我们想将一个新的指针值写入varfoo,那么我们仍然必须传递一个指向var 的指针作为参数。由于var 的类型为P *,那么表达式 &amp;var 的类型为P **


  1. 如果指针值在该对象的生命周期内指向该对象,则它是有效的。
  2. 左值是引用对象的表达式,以便可以读取或修改对象的值。
  3. 信不信由你,这条规则是有充分理由的,但这意味着在大多数情况下,数组表达式失去了它们的“数组性”,导致第一次学习该语言的人很困惑。

【讨论】:

    【解决方案2】:

    当您编写类似char *mnemonic 的内容时,这意味着您正在创建一个指针变量(将保存另一个变量的地址的变量),但由于mnemonic 的数据类型是char,它将保存仅具有 char 数据类型的变量。

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

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

    &amp;返回变量的地址,这意味着对存储变量的内存位置的引用。

    希望这会有所帮助。

    【讨论】:

      【解决方案3】:

      这意味着我将传递指向函数的指针。但为什么需要这样做?

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

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

      因此,虽然字符串本身可以说是“通过引用传递”,但严格来说,C 实际上只允许通过值传递参数。 指针本身是按值传递的。您的函数参数将是您在 main() 中分配的指针的副本。但它们指向与 main() 中的指针相同的字符串。

      由此可知,通过引用传递一个变量,然后修改该值意味着函数外部的同一个变量也会被更改;

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

      现在事实证明,每当您在表达式中使用我示例中的数组时,它都会“衰减”为指向第一个元素的指针。所以我们实际上无法将数组按值传递给函数,因为 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");
      }
      

      【讨论】:

        【解决方案4】:

        C 中的字符串存储为字符数组,以具有值'\0'(“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,它是一个保存相同值副本的不同变量,在函数内部递增'没关系。增加的值是函数本地的,一旦超出范围就会被丢弃。

        【讨论】:

        • 啊,谢谢。这更有意义。因为我要传递的是数组,所以代码analyse_inst(&amp;mnemonic, &amp;operands) 是不允许的,对吧?
        • @Tina 您没有传递数组,它们不能直接传递给 C 中的函数。发生的情况是数组的名称有时会转换为指向数组第一个元素的指针。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-03-05
        • 2020-07-03
        • 2011-06-04
        • 1970-01-01
        • 1970-01-01
        • 2014-01-12
        • 1970-01-01
        相关资源
        最近更新 更多