深入研究:对变量以及指针重新赋值过程中原本的地址是否会改变。(按值传递机制的深入)
在研究按值传递机制时,发现一些模糊的概念。就是在对一个原本的(指针)变量重新给定另外一个值时,会不会改变这个变量原本所在的内存位置(即地址)。因此,决定深入研究一下。而且这也是必要的。
- 给一个变量重新赋值时,地址的变化
1 //验证变量在被赋值(以及被重赋值)时原本分配的内存地址是否会改变。 2 3 #include <stdio.h> 4 5 int main(void) 6 { 7 int a;//声明a,即已经给a分配一个内存地址 8 printf("声明a时的地址:%p\n", &a); 9 10 a = 1; //初始化a 11 printf("The address of origin: %p\n", &a);//输出a=1时的地址 12 13 //int a = 2; ----> 不能以这种方式对a重新赋值. 否则会产生错误:‘a’重定义 14 scanf("%d", &a);//对a重新赋值。 15 printf("The address of later: %p\n", &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。即地址一直不变,变得是这个地址里的数。(就像租房,有效期内房子一直不变,变的是住的人) - 在对指针指向的地址重新赋值时,指针所指向的地址是否会改变?
1 //验证对指针所指向的值重新赋值时,指针是否会改变。 2 #include <stdio.h> 3 4 int main(void) 5 { 6 int *p = NULL; 7 int a = 1; 8 printf("p指针存储的地址:%p\n", p); 9 printf("p指针的地址:%p\n", &p); 10 11 p = &a; 12 printf("p指针存储的a地址:%p\n", p); 13 printf("p指针的地址:%p\n", &p); 14 15 *p = 2; 16 printf("p指针存储的对a重新赋值时地址:%p\n", p); 17 printf("p指针的地址:%p\n", &p); 18 19 int b = 2; 20 p = &b; 21 printf("p指针存储的b地址:%p\n", p); 22 printf("p指针的地址:%p\n", &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。 - 在调用函数,重新赋值时,变量地址是否改变?
按值传递机制:给函数传递变元时,变元值不会直接传递给函数,而是先复制变元值的副本,存储在栈上,再使这个副本可用于函数,而不是使用初始值。
这个概念乍一看没什么难理解的。但是里面的含义也不仅仅停留在表面。不信?我们重点来谈谈:被调用函数修改属于调用函数的变量值的方式。(被调用函数和调用函数的关系就像被除数与除数的关系,不难理解吧?)
被调用函数修改放在调用函数括号内的变量值的唯一方式是:把变量的地址接收为变元值。(这句话想理解透没那么简单)
给被调用函数传递地址时,它只是传递地址的副本,而不是初始值。但是,副本仍是一个地址,仍引用最初的变量。这也是将变量地址传给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\n", &a); 9 10 change_int(&a); 11 printf("被调用后a = %d\n", a); 12 printf("a后来的地址: %p\n", &a); 13 14 return 0; 15 } 16 17 void change_int(int *a) 18 { 19 printf("在被调用函数里面a的地址:%p\n", &a); 20 *a = 3; 21 } 22 ~
在被调用函数里面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
那么疑问来了,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\n", a); //输出absdefg 11 change_piont(&a);//将指针变量a的地址作为变元 12 printf("%s\n", a);//输出ddd,即改变指针a存储的值成功。 13 14 int b = 2; 15 printf("origin = %p\n", &b);//b原来的地址:xxx 16 17 change_int(&b);//将变量b的地址作为变元。 18 printf("(later)b = %d\n",b);//调用后b的值,输出为3 19 printf("later = %p\n", &b);//经过调用一回后,b的地址:还是xxx 20 21 22 char c[5] = "abcd";//数组c本来的值:abcd 23 change_array(c,5); 24 printf("c = %s\n",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\n", *a); 34 } 35 36 void change_int(int *b) 37 { 38 printf("chang_int -> the address of b = %p\n", &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 * ****************************************************/
这里要注意了:指针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\n", &a); 9 printf("a本来存储的地址:%p\n", a); 10 11 change_piont(&a); 12 printf("调用后a的地址:%p\n",&a); 13 printf("调用后a存储的地址:%p\n",a); 14 15 printf("a = %s\n",a); 16 return 0; 17 } 18 19 void change_piont(char **a) 20 { 21 printf("在被调用函数内a的地址: %p\n",&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 * **********************************************/
- 结论:(1)在直接对变量重新赋值时,变量的地址不变。变的是存储在这个地址里的值。
(2)对于指针,有两种可能。一是通过指针对指针所指向的地址重新赋值。这种情况下指针指向的地址不变,变的是指针指向的地址里面的值。二是直接对指针变量重新赋值,这种情况指针指向的地址会改变。(看看第二点的代码就清楚了)
(3)在调用函数时,将(指针)变量的地址做为变元传递,并不是直接将这个变量原本的地址传递给函数调用,而是通过一个地址的副本,在改变变量的值时,变量存储的地址没有改变。特别要注意指针的地址,它是一个char**型。指针本来就是char*型。它的地址是char**型应该没什么疑问吧?