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

问题描述 投票:0回答:4

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

#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
4个回答
2
投票

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

因为你的主要是指针,printf("%s"期望的是char*

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

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

从那时起,我得到了通过引用传递变量然后修改该值意味着函数外部的同一变量也将被更改;

实际上,您可以通过指针从函数内部更改字符串,然后它会影响main()中的字符串。但在这种情况下,你没有分配任何内存来修改 - 你会尝试修改字符串文字"...",这本来就是一个bug。如果你要修改字符串,你应该在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");
}

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)以获取这些相应指针变量中的引用。原因是一样的我们需要指针变量来保存引用。

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

希望这会有所帮助。


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,它是一个不同的变量,持有相同值的副本,在函数内部递增并不重要。递增的值是函数的本地值,一旦超出范围就会被丢弃。


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

以三种方式之一获取valid1对象指针值(函数指针也是一个东西,但我们不会在这里进入它们):

  • 在lvalue2(&)上使用一元p = &x;算子;
  • 通过malloc()calloc()realloc()分配动态内存;
  • 并且,与您的代码相关的是什么,使用不带&sizeof运算符的数组表达式。

除非它是sizeof或一元&运算符的操作数,或者是用于初始化声明中的字符数组的字符串文字,否则将“T的N元素数组”类型的表达式转换(“衰减”)为“指向T的指针”类型的表达式,表达式的值是array3的第一个元素的地址。所以,如果你创建一个像这样的数组

int a[10];

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

foo( a );

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

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或一元&运算符的操作数,并且它们不用于在声明中初始化字符数组,因此两个表达式都转换为类型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 );
  ...
}

在这种情况下,*a中的*bswap表达式与x中的ymain表达式相同,因此*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 *类型,因此表达式&var具有P **类型。


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

© www.soinside.com 2019 - 2024. All rights reserved.