在研究按值傳遞機制時,發現一些模糊的概念。就是在對一個原本的(指針)變量重新給定另外一個值時,會不會改變這個變量原本所在的內存位置(即地址)。因此,決定深入研究一下。而且這也是必要的。
- 給一個變量重新賦值時,地址的變化
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**型應該沒什么疑問吧?