【问题标题】:Pointer Swapping Rules指针交换规则
【发布时间】:2021-11-21 22:57:56
【问题描述】:

我对使用指针和数组的规则有一些疑问,因为我对传递数组的规则有点困惑。

void f1(int* x, int* y) {
  int * ptr = x;
  x = y;
  y = ptr;
}

void f2(int* x, int* y) {
  int val = *x;
  *x = *y;
  *y = val;
}
void f3(int* x, int* y) {
  int val = *x;
  x = y;
  y = &val;
}

假设我有两个数组,a[3] = {0,2,4}b[3] = {1,3,5}

如果我运行 f1(arr_a, arr_b);printf("%d\n", *arr_a); 行,它将打印出 0。我假设这是因为你只是将数组的指针传递给函数,它只是数组的副本,所以实际上没有任何修改。这个假设是否正确?

现在,如果我运行f2(arr_a + 1, arr_b + 1)printf("%d\n", *(arr_a + 1));,它的值会是3,这是因为我这次没有使用指针,所以我直接使用了一个整数(val),它被设置为等于索引为 1 处的数组的值,并交换这些值。我假设是因为这手动更改了地址,不像第一个函数,它使用一个指针,它是数组的副本?

最后,对于第三个,如果我运行 f3(arr_a + 1, arr_b + 1)printf("%d\n", *(arr_a + 1));,x 的值应该是 2,y 的值应该是 3,因为 x 没有与 y 交换,因为没有使用 * 运算符(所以地址不交换),并且 y 被设置为 val 的地址,这是一些随机地址,也不管不交换? 谢谢

【问题讨论】:

    标签: arrays c pointers


    【解决方案1】:

    它只是数组的一个副本,所以实际上没有任何修改。这个假设是否正确?

    没有创建数组的副本。当您访问一个数组时,它会被转换为指向第一个元素的指针(这条规则很少有例外)。因此,它只是指向数组arr_a 和数组arr_b 的第一个元素的指针,分别分配给函数f1() 参数(整数指针)xy。在f1() 函数中,指针xy 交换,即y 将指向数组arr_a 的第一个元素,x 将指向数组arr_b 的第一个元素。函数f1() 导致数组arr_aarr_b 的任何元素都没有变化。调用该函数后,打印语句

    printf("%d\n", *arr_a);
    

    正在打印0,因为*arr_a 等价于arr_a[0]1),它只是数组arr_a 的第一个元素,其值为0

    *arr_a -> *(arr_a + 0) -> *((arr_a) + (0)) -> arr_a[0]
    

    我假设是因为这手动更改了地址,不像第一个函数,它使用指针,它是数组的副本?

    不,它不会更改地址,它会更改地址处的 ,由指针 xy 指向,您已将其作为参数传递给函数 f2()。指针x 保存数组arr_a 的第二个元素的地址,并且在该地址处存在一个整数值。当您取消引用x(即*x)时,这意味着您正在访问该地址的值。当您将任何值分配给*x 时,这意味着您正在修改该地址的值。指针y 也是如此。当函数f2() 返回时,更改仍然存在,因为您修改了传递给此函数的地址处的值(使用指针xy)。

    现在arr_a 元素是{0, 3, 4}arr_b 元素是{1, 2, 5}

    打印语句

    printf("%d\n", *(arr_a + 1));
    

    将打印数组arr_a 的第二个元素,其值为3

    x 的值应该是 2,y 的值应该是 3,因为 x 没有与 y 交换,因为没有使用 * 运算符(所以地址没有交换),并且 y 被设置为 val 的地址,即一些随机地址,也不管不交换?

    在函数f3() 中,您传递数组arr_aarr_b 的第二个元素的地址。它们分别被分配给参数xy。让我们看看,函数f3() body 的每条语句执行后会发生什么:

    f3()的声明一

    int val = *x;
    

    指针x 指向数组arr_a 的第二个元素。取消引用x(即*x)将给出该地址处的值3,并将此值分配给函数val的局部变量f3()

    f3()的声明二

    x = y;
    

    指针y 指向数组arr_b 的第二个元素。在此语句之后,xy 都将指向数组 arr_b 的第二个元素。

    f3()的声明三

    y = &val;
    

    指针y 分配了局部变量val 的地址,该地址保存数组arr_a 的第二个元素的值的副本。

    所以,在退出函数 f3() 之前,x 指向 arr_b 的第二个元素,y 指向局部变量 val

    没有对传递给f3()的地址的任何值进行任何更改,因此数组arr_aarr_b在从函数f3()返回后状态将相同。

    arr_a 元素是 {0, 3, 4}arr_b 元素是 {1, 2, 5} 并且打印 arr_a 的第二个元素将导致输出 3


    1. 来自 C 标准#6.5.2.1

    下标运算符[]的定义是E1[E2]等同于(*((E1)+(E2)))..

    【讨论】:

    • 为了完成混淆,由于6.5.2.1的引用语句,表达式0[arr_a]等价于arr_a[0],完全有效。
    【解决方案2】:

    首先,当将数组类型作为函数参数传递时,实际上并没有传递数组,而是传递了第一个元素的地址。所以对于一个数组a,下面两个调用是等价的:

    f(a);
    f(&a[0]);
    

    关于你的具体例子:

    1. 调用f1(arr_a, arr_b);传递arr_a的第一个元素的地址和arr_b的第一个元素的地址。相当于f1(&arr_a[0], &arr_b[0]);。 除了传递给函数的地址之外,没有任何内容被复制。函数本身被赋予了它自己的两个地址的本地副本。即使它修改了它们,这些更改也仅限于函数,并且对调用者没有影响。它所做的只是进行一些局部变量赋值。事实上,一个好的优化器会完全消除函数体,因为它什么都不做。

    2. 调用f2(arr_a + 1, arr_b + 1); 传递arr_aarr_b 的第二个元素的地址。它相当于f2(&arr_a[1], &arr_b[1]); 同样,没有任何内容被复制。但是,函数f2 取消引用这些指针,交换它们的目标。因此,arr_aarr_b 中索引 1 处的值被交换。

    3. 调用f3(arr_a + 1, arr_b + 1); 传递arr_aarr_b 的第二个元素的地址。它相当于f3(&arr_a[1], &arr_b[1]); 同样,没有任何内容被复制。函数f3 加载arr_a[1] 的值,然后执行一些本地分配。和f1一样,这个函数没有任何作用,一个好的优化器会完全消除函数体。

    总而言之,函数f1f3 什么都不做,它们的主体可能会被优化掉。函数f2 是唯一有用的函数,即交换其两个指针参数的目标。

    【讨论】:

      【解决方案3】:

      它只是数组的一个副本,所以实际上没有任何修改。将 这个假设正确吗?

      在本次通话中:

      f1(arr_a, arr_b);
      

      这样写会更正确:

      f1(a, b);
      

      因为您声明了数组 a 和 b,所以没有复制任何内容。数组指示符 a 和 b 被隐式转换为指向它们的第一个元素的指针。上面的调用相当于:

      f1( &a[0], &b[0]);
      

      这些指针按值传递给函数f1。那就是函数处理指针值的副本。

      函数内:

      void f1(int* x, int* y) {
        int * ptr = x;
        x = y;
        y = ptr;
      }
      

      这些副本被交换。更改副本不会影响用作参数的原始数组。退出函数后,局部变量 x 和 y 将不再存在。

      为了清楚起见,请考虑以下代码 sn-p。假设有两个 int 类型的变量 a 和 b:

      int a = 0;
      int b = 1;
      

      然后有如下代码:

      int x = a;
      int y = b;
      
      int tmp = x;
      x = y;
      y = tmp;
      

      如您所见,分配给变量xy 的变量a 和b 的值的副本被交换了。变量ab 保持不变。

      现在,如果我运行 f2(arr_a + 1, arr_b + 1) 和 printf("%d\n", *(arr_a + 1));,它会导致值为 3,这是因为因为我是 这次不使用指针,我直接使用整数(val), 它被设置为等于索引为 1 处的数组的值, 并交换这些值。我假设因为这手动更改 地址,不像第一个函数,它使用一个指针,它是 数组的副本?

      在本次通话中:

      f2(a + 1, b + 1);
      

      您再次使用指针,但现在它们是指向数组第二个元素的指针。函数内f2

      void f2(int* x, int* y) {
        int val = *x;
        *x = *y;
        *y = val;
      }
      

      交换了指针指向的数组的第二个元素。例如,取消引用指针x,例如:

      *x = *y;
      

      您可以直接访问指针x 指向的对象,并且可以更改该对象。

      第三个函数f3:

      void f3(int* x, int* y) {
        int val = *x;
        x = y;
        y = &val;
      }
      

      只是没有意义。它什么也不交换。在函数的第一个声明中,局部变量val 获取指针x 指向的对象的值:

        int val = *x;
      

      那么局部变量x获取另一个局部变量y的值:

        x = y;
      

      最后局部变量y得到了局部变量val的地址:

        y = &val;
      

      退出函数后,它的所有局部变量 val、x 和 y 将不再存在。通过指针传递给函数的数组 a 和 b 中的对象都没有改变。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-12-21
        • 2012-10-14
        • 2014-08-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多