0

使用scanf函数时,不能简单地指定变量名。相反,我们必须在变量名称前加上一个 & 符号,例如int a; printf("enter your value"); scanf("%d", &a);.

我检查了在线资源寻找原因。他们中的大多数人会说:

  1. 我们需要修改该变量中的值。我们添加&以表示内存位置,以便可以更改该位置的值。

  2. 函数的参数scanf()具有指针类型:需要给它一个内存地址,以便它可以更改变量的值。

我是编码新手,所以我对此有点困惑。如果我们需要地址来修改内存包含的内容,为什么我们&在分配给变量时不使用,例如int a; a = 5;?在此示例中,我们还修改了该值。为什么我们不这样写:int a; &a = 5;

4

4 回答 4

2

...为什么我们&在分配给变量时不使用,例如int a; a = 5;?在此示例中,我们还修改了该值。为什么我们不这样写:int a; &a = 5;

C 具有从左值到值的自动转换。在, , 和中的x + y * z每一个都不是一个值而是指定一个对象; 它是为 、 或相关类型保留的一些内存,它告诉我们如何解释内存。但我们实际上希望这个表达式做的是将 的值乘以 的值并加上 的值。为了使代码更容易编写,C 会自动将对象的任何指定转换为它的值,在我们想要它的值的地方。它会自动从内存中获取值并在表达式中使用它。xyzxyzyzx

对此的规则在 C 2018(C 标准的 2018 年版本)第 6.3.2.1 段第 2 段中。该规则表示,在许多地方,左值会自动转换为对象的值。“左值”是标准用于指定对象的表达式的术语。我稍后会解释这个术语。

规则说:

除非是运算符的操作数sizeof,一元&运算符,运算符,++运算--符,或运算符的左操作数.或赋值运算符,将不具有数组类型的左值转换为存储在指定对象中的值(并且不再是左值);这叫做左值转换……</p>

目前,忽略除赋值运算符之外的所有内容。这条规则说:

a = x + y * z;

x, y, 和z被转换为它们的值,但a没有被转换,因为它是赋值运算符的左操作数。这意味着在赋值的左侧,我们没有值但有实际的对象。这就是允许赋值将值放入对象的原因。

这就是术语“左值”的来源——来自赋值的左侧,因为这是左值最常见或最突出的地方。回到转换规则的其他例外(的操作数sizeof等),这些是我们想要对对象本身进行操作的其他地方,而不是它的值。sizeof运算符给出对象的大小。&获取对象的地址。++--增加或减少存储在对象中的值。.引用结构或联合对象中的成员。

现在我们可以看到为什么我们必须将地址传递给scanf. 其实有两个原因。一是这种左值转换规则会将参数中的任何对象转换为对其值的函数调用。如果我们写scanf("%d", a);,a会自动转换成它的值。而且,在最初开发的 C 语言中,没有好的方法可以修改自动转换规则以使其不转换某些函数参数而不转换其他参数,因为编译器通常不知道函数期望什么参数。如果代码包含调用foo(a),编译器将不知道是否foo期望接收a对象的值或指定。a. 所以我们无法编写转换规则来有时转换函数参数,有时不转换。第二个原因是 C 无法在函数调用中传递对象指定。(我们可以传递一个指针,它传递对象的地址,然后函数必须通过使用*解引用指针将指针转换为对象指定。)

(一旦将函数原型声明添加到 C 中,就可以解决这两个问题并将对象传递给函数。C++ 使用它的引用类型来做到这一点。)

在 BLISS 编程语言中,没有左值到值的自动转换。每当您想使用对象的值时,您必须通过.在对象指示符之前放置 a 来明确指出这一点。例如,在 BLISS 中,你会写:

a = .x + .y * .z;

将 的值乘以 的值yz将 的值相加x,然后将结果存储在 中a

于 2022-01-21T14:01:56.710 回答
0

为什么我们不这样写: int a; &a = 5;?

  1. 因为我们无法写入地址(从技术上讲,&a它不是l-value)。
  2. 为什么杂乱无章?没有理智的语言会使分配比应有的更难。
  3. 一些语言(通常是解释性的)允许你建议的东西(例如 shell 的getopt abc varibablename),但 C 的设计者决定不这样做。
于 2022-01-21T13:15:13.317 回答
0

系好安全带,这需要几分钟。

当您使用参数(参数)调用函数时,对于函数如何处理这些参数,有几种不同的评估策略。C 使用的一种称为“按值调用”。当你调用一个函数时

foo( x, y, z );

每个表达式 xyz都被完全评估,这些评估的结果就是传递给函数的结果。在函数定义中

void foo( int a, int b, int c ) 
{
  a = b + c;
} 

每个形式参数abc是内存中的不同对象,x它们接收 、 和 的值y,但更新不会以任何方式影响。这是一个直接说明这一点的示例:zxyzax

#include <stdio.h>

void foo( int a, int b, int c )
{
  a = b + c;
  printf( "a = %d\n", a );
}

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

  printf( "before foo: x = %d, y = %d\n" );
  foo( x, y, x + y );
  printf( "after foo:  x = %d, y = %d\n" );

  return 0;
}

运行时,此代码给出输出:

before foo: x = 1, y = 2
a = 5
after foo:  x = 1, y = 2

如您所见,更新a对 的值没有影响x

为了让函数改变参数的值,我们必须传递一个指向该参数的指针。如果我们希望更改 ina反映在 中x,我们必须使用一元(address-of) 运算符在函数调用中传递地址,并使用一元 ( dereference ) 运算符在函数定义中取消引用它:x&*

#include <stdio.h>

void foo( int *a, int b, int c )
{
  *a = b + c;
  printf( "*a = %d\n", *a );
}

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

  printf( "before foo: x = %d, y = %d\n", x, y );
  foo( &x, y, x + y );
  printf( "after foo:  x = %d, y = %d\n", x, y );

  return 0;
}

在这种情况下,a不会收到 的值x,而是收到它的地址,给我们这种情况:

 a == &x // int * == int *
*a ==  x // int == int

表达式 *ainfoo充当 in 的同义词x,因此main向 写入新值*a等同于向 写入新值x

最终,这就是为什么您需要在调用时将指针传递给要更新的内容scanf(或任何其他需要更新参数的函数)。

注意- 数组很奇怪。除非它是sizeof或一元运算符的操作数&,或者是用于在声明中初始化字符数组的字符串字面量,否则“N 元素数组”类型的表达式T将被转换或“衰减”为键入“pointer to T”,表达式的值将是数组第一个元素的地址。换句话说,如果你有这样的代码:

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

编译器将arr函数调用中的表达式替换为等效于 的表达式&arr[0],因此bar它不会获取整个数组的副本,而是获取第一个元素的地址:

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

这就是为什么在读取字符串时使用运算符的原因- 您将数组表达式作为参数传递,它在调用之前隐式转换为指针:&scanf

 int val;
 char name[10];

 scanf( "%d %s", &val, name );

这种行为有原因的,但这有点超出了本次讨论的范围。请记住,数组很奇怪。

于 2022-01-21T15:37:39.537 回答
0

因为您想scanf()接受输入,并将该数据写入您需要数据所在的变量的内存位置。

&表示您正在传递该变量的内存地址。

所以:

int x = 0; 
// int is usually 4 bytes, so x is 4 bytes.
// And it has a memory address, for example: 0x12345678

scanf("%d", &x); // basically passes 0x12345678 to scanf

// scanf assumes the argument to be a pointer, so it writes 
// to memory address 0x12345678.
// You have specified %d, so scanf assumes that the first argument
// next to the format-string("%d") is an int * (int pointer)

为什么一定要传递地址?因为您希望 scanf 写入 (x) 的内存地址,而不是内存地址 (x)。

这样做会导致未定义的行为:

int x = 0;
scanf("%d", (int *)x);
// scanf will write to address 0x0, since x is 0

有趣的事实:NULLC 通常定义为(void *)0. 所以上面等价于scanf("%d", NULL);

=是运算符,不是函数调用,所以它已经知道变量的内存地址。所以它不需要&.

于 2022-01-21T12:50:25.760 回答