zoukankan      html  css  js  c++  java
  • 深入研究:对变量以及指针重新赋值过程中原本的地址是否会改变。(按值传递机制的深入)

    在研究按值传递机制时,发现一些模糊的概念。就是在对一个原本的(指针)变量重新给定另外一个值时,会不会改变这个变量原本所在的内存位置(即地址)。因此,决定深入研究一下。而且这也是必要的。

    1. 给一个变量重新赋值时,地址的变化
       1 //验证变量在被赋值(以及被重赋值)时原本分配的内存地址是否会改变。
       2  
       3  #include <stdio.h>
       4  
       5  int main(void)
       6  {
       7      int a;//声明a,即已经给a分配一个内存地址
       8      printf("声明a时的地址:%p
      ", &a);
       9  
      10      a = 1;  //初始化a
      11      printf("The address of origin: %p
      ", &a);//输出a=1时的地址
      12  
      13      //int a = 2; ----> 不能以这种方式对a重新赋值. 否则会产生错误:‘a’重定义
      14      scanf("%d", &a);//对a重新赋值。
      15      printf("The address of later: %p
      ", &a);//被重新赋值后的地址
      16  
      17      return 0;
      18  }

      运行结果:声明a时的地址:0x7ffc3cabc31c
                     The address of origin: 0x7ffc3cabc31c
                            2  ->这个2是我输入的。
                           The address of later: 0x7ffc3cabc31c
      结论:在声明时给变量a划分的地址,会在变量作用域内一直保持不变。原本a=1时,地址是xxx,在第a重新赋值时,会先将a内的数删除,然后再将新的数放进去,这个新的数的地址还是xxx。即地址一直不变,变得是这个地址里的数。(就像租房,有效期内房子一直不变,变的是住的人)

    2. 在对指针指向的地址重新赋值时,指针所指向的地址是否会改变?
       1 //验证对指针所指向的值重新赋值时,指针是否会改变。
       2  #include <stdio.h>
       3  
       4  int main(void)
       5  {
       6      int *p = NULL;
       7      int a = 1;
       8      printf("p指针存储的地址:%p
      ", p); 
       9      printf("p指针的地址:%p
      ", &p);
      10  
      11      p = &a; 
      12      printf("p指针存储的a地址:%p
      ", p); 
      13      printf("p指针的地址:%p
      ", &p);
      14  
      15      *p = 2;
      16      printf("p指针存储的对a重新赋值时地址:%p
      ", p); 
      17      printf("p指针的地址:%p
      ", &p);
      18  
      19      int b = 2;
      20      p = &b; 
      21      printf("p指针存储的b地址:%p
      ", p); 
      22      printf("p指针的地址:%p
      ", &p);
      23  
      24      return 0;
      25  }

       运行结果:p指针存储的地址:(nil)
                         p指针的地址:0x7ffe95265668
                         p指针存储的a地址:0x7ffe95265664
                         p指针的地址:0x7ffe95265668
                         p指针存储的对a重新赋值时地址:0x7ffe95265664
                         p指针的地址:0x7ffe95265668
                         p指针存储的b地址:0x7ffe95265660
                         p指针的地址:0x7ffe95265668
      分析:有点乱,但没关系,一句一句看。在将指针指向NULL时,实质上并没有给指针p分配存储的地址。即p不指向任何地址。所以输出了:p指针存储的地址:(nil)。而p本身的地址(即存储p的地址)为0x7ffe95265668,在整个main函数内,存储p指针的地址其实是一直不变的。这在第1点可以知道原因。p = &a这句是将p指向a所在的地址。*p = 2这句是通过*p对a重新赋值。期间,p存储的地址保持一致,即:0x7ffe95265664。p = &b这一句将p重新指向一个新的变量了。打个比方:(指针p包含的)地址:a本来是你家,后来你搬家了,那么地址肯定改变了,所以(指针p包含)地址也改变了。由0x7ffe95265664这个原本是指向a的地址 变为 指向b的地址:0x7ffe95265660。

    3. 在调用函数,重新赋值时,变量地址是否改变?
      按值传递机制:给函数传递变元时,变元值不会直接传递给函数,而是先复制变元值的副本,存储在栈上,再使这个副本可用于函数,而不是使用初始值。
      这个概念乍一看没什么难理解的。但是里面的含义也不仅仅停留在表面。不信?我们重点来谈谈:被调用函数修改属于调用函数的变量值的方式。(被调用函数和调用函数的关系就像被除数与除数的关系,不难理解吧?)
      被调用函数修改放在调用函数括号内的变量值的唯一方式是:把变量的地址接收为变元值。(这句话想理解透没那么简单)
                                                                                                       给被调用函数传递地址时,它只是传递地址的副本,而不是初始值。但是,副本仍是一个地址,仍引用最初的变量。这也是将变量地址传给scanf函数的原因,不传递地址,scanf这个被调用函数就没办法在最初的变量地址中存储值。还是看代码吧。
       1 #include <stdio.h>
       2  
       3  void change_int(int *a);
       4  
       5  int main(void)
       6  {
       7      int a = 2;
       8      printf("a本来的地址: %p
      ", &a);
       9  
      10      change_int(&a);
      11      printf("被调用后a = %d
      ", a); 
      12      printf("a后来的地址: %p
      ", &a);
      13  
      14      return 0;
      15  }
      16  
      17  void change_int(int *a) 
      18  {
      19      printf("在被调用函数里面a的地址:%p
      ", &a);
      20      *a = 3;
      21  }
      22  ~               
      输出结果:a本来存储的地址: 0x7ffc75a6f48c
                          在被调用函数里面a的地址:0x7ffc75a6f468
                          被调用后a = 3
                          a后来的存储地址: 0x7ffc75a6f48c
      分析结果:看到没?a本来存储的地址和后来存储的地址是一致的,说明被调用函数的作用是:通过调用函数,将main函数(不一定是在main函数里哦)中a的地址内的值改变。而a本身的地址是不变的。再看,在被调用函数(change_int(int *a) )里面a的地址:0x7ffc75a6f468,这说明什么?说明给被调用函数传递地址时,它只是传递地址的副本,而不是初始值。剩下没说的就是:在main函数里,a本来等于2的,后来通过调用函数,在被调用函数里(的*a=3)将a变成3,这又说明什么?要想改变变量的值,要把变量的地址作为为变元值。

      接下来要谈的是,将指针作为变元。这里你会知道我为什么说上面的一句话理解透没那么简单。先从最简单的入手,看代码:
      1  #include <stdio.h>
      2  
      3  int main(void)
      4  {
      5      char *a = "abc";
      6      scanf("%s",a);//这句目的是调用scanf函数对a重新赋值。
      7      printf("%s",a);
      8      return 0;
      9  }//这个代码的目的是将a中的abc变成sss
      这个代码可以编译连接成功。但是当输入:sss 按回车时,出现了这么一个错误:Segmentation fault (核心已转储),这个错误是由于地址出错。
      那么疑问来了,a是一个地址吧?那么我给被调用函数传递的是a地址啊,为什么不能成功改变a存储的值(即abs)呢?先看看代码
       1 #include <stdio.h>
       2  
       3  void change_piont(char **a);//用于改变指针所存储的值的函数。
       4  void change_int(int *b);//用于改变变量的值的函数。
       5  void change_array(char *c, int n);
       6  
       7  int main(void)
       8  {
       9      char *a = "absdefg";
      10      printf("a = %s
      ", a); //输出absdefg
      11      change_piont(&a);//将指针变量a的地址作为变元
      12      printf("%s
      ", a);//输出ddd,即改变指针a存储的值成功。
      13  
      14      int b = 2;
      15      printf("origin = %p
      ", &b);//b原来的地址:xxx
      16  
      17      change_int(&b);//将变量b的地址作为变元。
      18      printf("(later)b = %d
      ",b);//调用后b的值,输出为3
      19      printf("later = %p
      ", &b);//经过调用一回后,b的地址:还是xxx
      20  
      21  
      22      char c[5] = "abcd";//数组c本来的值:abcd
      23      change_array(c,5);
      24      printf("c = %s
      ",c);//输出结果:abcf
      25  
      26      return 0;
      27  }
      28  
      29  void change_piont(char **a)
      30  {
      31      char *b = "ddd";
      32      *a = b;
      33    // printf("%s
      ", *a);
      34  }
      35  
      36  void change_int(int *b)
      37  {
      38      printf("chang_int -> the address of b = %p
      ", &b);//这个地址和原本b的地址不一样,说明它是一个副本。
      39      *b = 3;
      40  }
      41  
      42  void change_array(char *c, int n)
      43  {
      44      c[3] = 'f';
      45  }
      46  /******************************************************
      47   * 原本输出结果:
      48   * a = absdefg
      49   * ddd
      50   * origin = 0x7ffc5c289f84
      51   * chang_int -> the address of b = 0x7ffc5c289f58
      52   * (later)b = 3
      53   * later = 0x7ffc5c289f84
      54   * c = abcf
      55   * ****************************************************/
      大多数都可以在注释上看明白,这里我重点要说这句(11句):change_piont(&a);//将指针变量a的地址作为变元。还记得上面的疑问吧?a是一个地址吧?那么我给被调用函数传递的是a地址啊,为什么不能成功改变a存储的值(即abs)。注意这里调用函数括号内与上一个代码中(scanf("%s",a))括号内的a有什么不同,多了一个取址符:&
      这里要注意了:指针a虽然是一个地址,但指针a也是一个变量。上面概念说了,改变一个变量所存储的值唯一的变法就是将它的地址作为变元。指针变量的地址是什么?是:&a。还是在看一次代码吧。
       1 #include <stdio.h>
       2  
       3  void change_piont(char **a);
       4  
       5  int main(void)
       6  {
       7      char *a = "abc";
       8      printf("a本来的地址:%p
      ", &a);
       9      printf("a本来存储的地址:%p
      ", a); 
      10  
      11      change_piont(&a);
      12      printf("调用后a的地址:%p
      ",&a);
      13      printf("调用后a存储的地址:%p
      ",a);
      14  
      15      printf("a = %s
      ",a);
      16      return 0;
      17  }
      18  
      19  void change_piont(char **a)
      20  {
      21      printf("在被调用函数内a的地址: %p
      ",&a);
      22      *a = "sss";
      23  
      24  }
      25  /************************************************
      26   * result:
      27   * a本来的地址:0x7ffe6bbd2088
      28   * a本来存储的地址:0x400680
      29   * 在被调用函数内a的地址: 0x7ffe6bbd2068
      30   * 调用后a的地址:0x7ffe6bbd2088
      31   * 调用后a存储的地址:0x400725
      32   * a = sss
      33   * **********************************************/
      看结果已经很清楚了。a本来的地址就是指针a本身的地址。a本来存储的地址就是本来存储在a指针的地址。举个例子吧。char *a = &b。a本来的地址就是:&a。a本来存储的地址就是:&b 或者 a。


    4. 结论:(1)在直接对变量重新赋值时,变量的地址不变。变的是存储在这个地址里的值。
                  (2)对于指针,有两种可能。一是通过指针对指针所指向的地址重新赋值。这种情况下指针指向的地址不变,变的是指针指向的地址里面的值。二是直接对指针变量重新赋值,这种情况指针指向的地址会改变。(看看第二点的代码就清楚了)
                  (3)在调用函数时,将(指针)变量的地址做为变元传递,并不是直接将这个变量原本的地址传递给函数调用,而是通过一个地址的副本,在改变变量的值时,变量存储的地址没有改变。特别要注意指针的地址,它是一个char**型。指针本来就是char*型。它的地址是char**型应该没什么疑问吧?
        
  • 相关阅读:
    【Apollo】Apollo 分布式部署
    【Spring】SpringBatch快速入门
    Nacos、Apollo、Spring Cloud Config微服务配置中心对比
    IDEA或Android Studio用书签功能标识代码 Stars
    Jetpack架构组件学习(0)——总结篇 Stars
    Android Studio导出APP的数据库db文件 Stars
    Jetpack架构组件学习(1)——LifeCycle的使用 Stars
    JavaFx Tooltip悬浮提示使用及自定义 Stars
    JavaFx WebView使用研究 Stars
    Jetpack架构组件学习(2)——ViewModel和Livedata使用 Stars
  • 原文地址:https://www.cnblogs.com/busui/p/5690789.html
Copyright © 2011-2022 走看看