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

时间:2019-02-27 11:44:44

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

我试图理解这两个运算符的重要性,因此我仅为该目的编写了这段代码。

#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线程,但是如果有人可以在我编写的代码的上下文中对其进行解释,我将不胜感激)

4 个答案:

答案 0 :(得分:2)

  

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

因为您的主要内容是指针,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");
}

答案 1 :(得分: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)才能在这些指针变量中获取引用。原因相同。我们需要指针变量来保存引用

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

希望这会有所帮助。

答案 2 :(得分:1)

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是一个不同的变量,它持有相同的值,在函数内部增加无关紧要。递增的值是该函数的局部值,一旦超出范围,则将其丢弃。

答案 3 :(得分: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"

一元*运算符的优先级比后缀[]下标和()函数运算符的优先级低,因此,如果要使用指向数组或函数的指针,则{{1} }必须与标识符明确分组。

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

*

执行上述代码后,将满足以下条件:

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*p的类型为x,其值为int

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

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

除非它是sizeof或一元sizeof运算符的操作数,或者是用于初始化声明中的字符数组的字符串文字,否则 expression 为类型“ &的N元素数组被转换(“衰减”)为类型为“ T的指针”的表达式,该表达式的值是该元素的第一个元素的地址数组 3 。因此,如果您创建类似

的数组
T

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

int a[10];

然后在调用函数之前,将表达式foo( a ); 从类型“ a的10元素数组”转换为“指向int的指针”,并将{{ 1}}是int的地址。因此,函数实际接收的是指针值,而不是数组:

a

a[0]void foo( int *a ) { ... } 这样的字符串文字都是数组表达式-"add"的类型为“ "five to two"的4元素数组”,而"add"的类型为“ 12” -char“的元素数组(由于字符串终止符,N个字符的字符串至少需要存储N + 1个元素)。

在声明中

"five to two"

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

由于mnemonicoperands的类型均为char *,因此在您致电时

mnemonic

函数形式参数的类型也必须为operands

char *

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

C按值传递所有函数参数 。这意味着函数定义中的形式参数是内存中与函数调用中实际参数不同的对象,对形式参数的任何更改都不会反映在实际参数中。假设我们将analyse_inst( mnemonic, operands ); 函数编写为:

char *

如果编译并运行此代码,则会看到在调用void analyse_inst( char *mnemonic, char *operands ) { ... } 之后swapint 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 ); ... } 的值没有改变-对{{1 }}和xyswap没有影响,因为它们是内存中的不同对象。

为了使a函数起作用,我们必须将 pointers 传递给bx

y

在这种情况下,swap中的表达式 xy与表达式void swap( int *a, int *b ) { int tmp = *a; *a = *b; *b = tmp; } int main( void ) { ... swap( &x, &y ); ... } 和{{1 }}在*a中,因此对*bswap的更改反映在xy中:

main

所以,通常:

*a

对于指针类型也是如此-用指针类型*b替换x,我们得到以下信息:

y

在这种情况下, 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,因此表达式 var的类型为foo


  1. 如果指针值指向该对象的生存期内的对象,则该值有效。
  2. 左值是引用对象的表达式,以便可以读取或修改对象的值。
  3. 信不信由你,这是有充分理由的,但是这意味着数组表达式在大多数情况下会失去其“数组性质”,从而导致首先学习该语言的人们感到困惑。