文章目錄
- 1.內存和地址
- 1.1 內存
- 1.2 究竟該如何理解編址
- 2.指針變量和地址
- 2.1 取地址操作符(&)
- 2.2 指針變量和解引?操作符(*)
- 2.2.1 指針變量
- 2.2.2 如何拆解指針類型
- 2.2.3 解引?操作符
- 2.3 指針變量的??
- 3.指針變量類型的意義
- 3.1 指針的解引?
- 3.2 指針+-整數
- 3.3 void* 指針
- 4.const修飾指針
- 4.1 const修飾變量
- 4.2 const修飾指針變量
- 4.2.1.const在`*`左邊
- 4.2.2.const在`*`右邊
- 5.指針運算
- 5.1 指針+- 整數
- 5.2 指針-指針
- 5.3 指針的關系運算
- 6.野指針
- 6.1 野指針成因
- 6.1.1.指針未初始化
- 6.1.2.指針越界訪問
- 6.1.3.指針指向的空間釋放
- 6.2 如何規避野指針
- 6.2.1 指針初始化
- 6.2.2 ??指針越界
- 6.2.3 指針變量不再使?時,及時置NULL,指針使?之前檢查有效性
- 6.2.4 避免返回局部變量的地址
- 7.assert斷言
- 8.指針的使用和傳址調用
- 8.1 strlen的模擬實現
- 8.2 傳值調?和傳址調?
1.內存和地址
1.1 內存
生活中,每個房間有了房間號,就能提高效率,能快速的找到房間。
CPU(中央處理器)在處理數據的時候,需要的數據是在內存中讀取的,處理后的數據也會放回內存中,那這些內存空間如何高效的管理呢?
其實也是把內存劃分為一個個的內存單元,每個內存單元的大小取1個字節
。
一個比特位可以存儲一個2進制的位1或者0。
bit - 比特位
Byte - 字節
KB
MB
GB
TB
PB
- 1Byte = 8bit
- 1KB = 1024Byte
- 1MB = 1024KB
- 1GB = 1024MB
- 1TB = 1024GB
- 1PB = 1024TB
每個內存單元,相當于一個學生宿舍,一個字節空間里面能放8個比特位,就好比同學們住的八人間,每個人是一個比特位。
每個內存單元也都有一個編號(這個編號就相當于宿舍房間的門牌號),有了這個內存單元的編號,CPU就可以快速找到一個內存空間。
生活中我們把門牌號也叫地址,在計算機中我們把內存單元的編號也稱為地址。C語言中給地址起了新的名字叫:指針。
所以我們可以理解為:
內存單元的編號 == 地址 == 指針
1.2 究竟該如何理解編址
CPU訪問內存中的某個字節空間,必須知道這個字節空間在內存的什么位置,而因為內存中字節很多,所以需要給內存進行編址(就如同宿舍很多,需要給宿舍編號一樣)。計算機中的編址,并不是把每個字節的地址記錄下來,而是通過硬件設計完成的。
首先,必須理解,計算機內是有很多的硬件單元,而硬件單元是要互相協同工作的。所謂的協
同,至少相互之間要能夠進行數據傳遞。
但是硬件與硬件之間是互相獨立的,那么如何通信呢?答案很簡單,用"線"連起來。
而CPU和內存之間也是有大量的數據交互的,所以,兩者必須也用線連起來。
不過,我們今天只關心地址總線。
我們可以簡單理解,32位機器有32根地址總線,每根線只有兩態,表示0,1【電脈沖有無】,那么一根線,就能表示2種含義,2根線就能表示4種含義,依次類推。32根地址線,就能表示2^32種含義,每一種含義都代表一個地址。
地址信息被下達給內存,在內存上,就可以找到該地址對應的數據,將數據在通過數據總線傳入CPU內寄存器。
2.指針變量和地址
2.1 取地址操作符(&)
理解了內存和地址的關系,我們再回到C語言,在C語言中創建變量其實就是向內存申請空間。
#include <stdio.h>
int main()
{int a = 10;return 0;
}
為了方便查看內存空間,我們可以換到x86編譯環境。
a = 10
,這里為什么是0a
呢?因為這里是十六進制數。
比如,上述的代碼就是創建了整型變量a,內存中申請4個字節,用于存放整數10,其中每個字節都有地址,上圖中4個字節的地址分別是:
0x003CF734
0x003CF735
0x003CF736
0x003CF737
這里變量的名字a,僅僅是給程序員看的,編譯器是不看名字的,編譯器是通過地址來找內存單元的。
我們如何能得到a的地址呢?
這里就得學習一個操作符(&)-取地址操作符。
#include <stdio.h>
int main()
{int a = 10;&a;//通過取地址操作符:& 來取出a的地址printf("%p\n", &a);//%p是專門用來打印地址的。return 0;
}
打印:
0x003CF734
&a取出的是a所占4個字節中地址較小的字節的地址。
整型變量占用4個字節,我們只要知道了第一個字節地址,順藤摸瓜訪問到4個字節的數據也是可行的。
2.2 指針變量和解引?操作符(*)
2.2.1 指針變量
我們通過取地址操作符(&)拿到的地址是一個數值,比如:0x006FFD70,這個數值有時候也是需要存儲起來,方便后期再使用的,那我們把這樣的地址值存放在哪里呢?
答案是:指針變量中。
#include <stdio.h>
int main()
{int a = 10;int * pa = &a;//取出a的地址并存儲到指針變量pa中//pa是一個變量,這個變量pa是用來存放地址(指針)的,pa叫指針變量return 0;
}
pa
是指針變量的名字
int*
是指針變量的類型
2.2.2 如何拆解指針類型
a
是一個int
類型的變量,里面存了10,地址是0x003CF734
。
我們存起來這個地址是不是又要創建一個變量?
pa
變量里面存的是0x003CF734
,類型是int*
。
*
表示pa
是指針變量。
int
表示pa
指向的變量a
的類型是int
。
然后我們依樣畫葫蘆:
char ch = 'w';
w的指針變量是是怎么寫的?
char* pc = &ch;
指針變量也是一種變量,這種變量就是用來存放地址的,存放在指針變量中的值都會理解為地址。
2.2.3 解引?操作符
我們將地址保存起來,未來是要使用的,那怎么使用呢?
我們只要拿到了地址(指針),就可以通過地址(指針)找到地址(指針)指向的對象,這里必須學習一個操作符叫解引用操作符(*)。
int main()
{int a = 100;int* pa = &a;*pa = 0;//* 解引用操作符(間接訪問操作符)//*pa 就是 areturn 0;
}
代碼中第5行就使用了解引用操作符, *pa
的意思就是通過pa
中存放的地址,找到指向的空間,*pa
其實就是a
變量。
所以*pa = 0
,這個操作符是把a
改成了0。
這個操作和直接把a賦值為0作用是一樣的,但是多了一種的途徑,寫代碼就會更加靈活。
2.3 指針變量的??
32位機器假設有32根地址總線,每根地址線出來的電信號轉換成數字信號后是1或者0,那我們把32根地址線產生的2進制序列當做一個地址,那么一個地址就是32個bit位,需要4個字節才能存儲。
如果指針變量是用來存放地址的,那么指針變量的大小就得是4個字節的空間才可以。
同理64位機器,假設有64根地址線,一個地址就是64個二進制位組成的二進制序列,存儲起來就需要8個字節的空間,指針變量的大小就是8個字節。
#include <stdio.h>
//指針變量的大小取決于地址的大小
//32位平臺下地址是32個bit位(即4個字節)
//64位平臺下地址是64個bit位(即8個字節)
int main()
{printf("%zd\n", sizeof(char *));printf("%zd\n", sizeof(short *));printf("%zd\n", sizeof(int *));printf("%zd\n", sizeof(double *));return 0;
}
![]() | ![]() |
---|---|
X86環境 | X64環境 |
-
32位平臺下地址是32個bit位,指針變量大小是4個字節
-
64位平臺下地址是64個bit位,指針變量大小是8個字節
注意:指針變量的大小和類型是無關的,只要指針類型的變量,在相同的平臺下,大小都是相同的。
3.指針變量類型的意義
指針變量的大小和類型無關,只要是指針變量,在同一個平臺下,大小都是一樣的,為什么還要有各種各樣的指針類型呢?
其實指針類型是有特殊意義的。
3.1 指針的解引?
代碼1:
#include <stdio.h>
int main()
{int n = 0x11223344;int *pi = &n; *pi = 0; return 0;
}
代碼2:
#include <stdio.h>
int main()
{int n = 0x11223344;char *pc = (char *)&n;*pc = 0;return 0;
}
上面n的賦值之所以寫0x11223344
這個東西,是因為寫小了是放不滿4個字節的,看變化就不明顯了。
通過調試我們可以看到,代碼1會將n的4個字節全部改為0,但是代碼2只是將n的第一個字節改為0。
我們可以得到一個結論:指針的類型決定了,對指針解引用的時候有多大的權限(一次能操作幾個字節)。
比如: char* 的指針解引用就只能訪問一個字節,而 int* 的指針的解引用就能訪問四個字節。
3.2 指針±整數
#include <stdio.h>
int main()
{int n = 20;int* pc = &n;char* pi = &n;printf("&n = %p\n", &n);printf("pc = %p\n", pc);printf("pi = %p\n", pi);printf("&n+1 = %p\n", &n+1);printf("pc+1 = %p\n", pc + 1);printf("pi+1 = %p\n", pi + 1);return 0;
}
打印:
&n
和&n+1
地址相差4。
pc
和pc+1
地址相差4。
pi
和pi+1
地址相差1。
為了方便觀看,下面圖里面我們把a改一下。
加一和減一是一樣的過程。
我們可以得到結論:指針的類型決定了指針向前或者向后走一步有多大(距離)。
3.3 void* 指針
在指針類型中有一種特殊的類型是 void * 類型的,可以理解為無具體類型的指針(或者叫泛型指針),這種類型的指針可以用來接受任意類型地址。但是也有局限性, void* 類型的指針不能直接進行指針的±整數和解引用的運算。
int main()
{int a = 10;int* pa = &a;//&a是int*char* pc = &a;//&a是int*return 0;
}
在上面的代碼中,將一個int類型的變量的地址賦值給一個char類型的指針變量。編譯器給出了一個警告,是因為類型不兼容。
而使用void類型就不會有這樣的問題。
#include <stdio.h>
int main()
{int a = 10;char ch = 'w';void* pv1 = &a;//&a是int*void* pv2 = &ch;//&ch是char*return 0;
}
解引用:
#include <stdio.h>
int main()
{int a = 10;void* pa = &a;void* pc = &a;*pa = 10;//void* 是無具體類型指針,它解引用的時候不知道解析幾個字節*pc = 0;pv1 + 1;//這個也不行,因為它是無具體類型指針,你加一的時候打算跳過幾個字節?return 0;
}
編譯報錯。
因為void* 類型的指針不能直接進行指針的±整數和解引用的運算。
那么 void* 類型的指針到底有什么用呢?
一般 void* 類型的指針是使用在函數參數的部分,用來接收不同類型數據的地址,這樣的設計可以實現泛型編程的效果。使得一個函數來處理多種類型的數據。
4.const修飾指針
4.1 const修飾變量
變量是可以修改的,如果把變量的地址交給一個指針變量,通過指針變量的也可以修改這個變量。但是如果我們希望一個變量加上一些限制,不能被修改,怎么做呢?
我們可以通過const。
#include <stdio.h>
int main()
{int m = 0;m = 20;//m可以被修改const int n = 0;n = 20;//n不能被修改return 0;
}
const修飾的變量叫做常變量,這個被修飾的變量本質上還是變量,只是不能被修改。
怎么證明這個是常變量而不是常量呢?
int n = 10;
int arr[n];
這里會報錯,因為C99語法之前是不支持變長數組的,數組大小是需要常量,常量表達式來制定的,不能是變量。
const int n = 10;
int arr[n];
這里還是會報錯,說明const修飾的變量n不是常量,而是常變量。
但是如果我們繞過n,使用n的地址,去修改n就能做到了。
int main()
{const int n = 0;printf("n = %d\n", n);int*p = &n;*p = 20;printf("n = %d\n", n);return 0;
}
打印:
n = 0
n = 20
這里確實修改了const修飾的n,但是我們還是要思考一下,為什么n要被const修飾呢?
就是為了不能被修改。
如果p拿到n的地址就能修改n,這樣就打破了const的限制,這是不合理的,所以應該讓p拿到n的地址也不能修改n,那接下來怎么做呢?
4.2 const修飾指針變量
一般來講const修飾指針變量,放在*
左邊和*
右邊,意義是不一樣的。
先看個小例子:
int main() {int n = 10;int m = 100;int* p = &n;//p里面存放著n的地址,p本身也有個地址*p = 20;//通過p里面存放著n的地址來找到n,改變n的值,p本身地址不變//不會報錯p = &m;//把p里存放的n的地址改成m的地址,p本身地址不變//不會報錯return 0;
}
關于指針變量p有3個相關的值:
p,p里面存放著一個地址
*p,p指向的那個對象
&p,表示的是p變量的地址
然后我們加入const:
4.2.1.const在*
左邊
int main() {int n = 10;int m = 100;int const* p = &n;//const int* p = &n;這個和上面那行一樣*p = 20;//會報錯p = &m;//不會報錯return 0;
}
為什么會報錯呢?
因為const放在這里限制的是*p
,也就是p指向的對象n。但是沒有限制p本身。也就是說可以修改p,但是不能通過*p
修改n。
這里定義n的時候沒加const,所以可以直接更改n,但是不能通過*p
修改n。
如果定義n的時候加了const,那么不可以直接更改n,也不能通過*p
修改n。
所以,const修飾指針變量,限制的是指針指向的內容,就是不能通過指針變量p來修改p所指向的內容。但是指針變量p本身是可以改變的。
4.2.2.const在*
右邊
int main() {int n = 10;int m = 100;int* const p = &n;*p = 20;//不會報錯p = &m;//會報錯return 0;
}
這里的const修飾的是p本身,沒有修飾*p
。
也就是我們不能改變p里面存放的值,但是我們可以通過*p
來改變n的值。
const如果放在*
的右邊,修飾的是指針變量本身,保證了指針變量的內容不能修改,但是指針指向的內容,可以通過指針改變。
5.指針運算
5.1 指針± 整數
因為數組在內存中是連續存放的,只要知道第一個元素的地址,順藤摸瓜就能找到后面的所有元素。
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
我們來看一下下面的代碼:
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);for(i=0; i<sz; i++){printf("%d ", *(p+i));//p+i 這里就是指針+整數}return 0;
}
打印:
1 2 3 4 5 6 7 8 9 10
int*p;
p+i
是跳過i*sizeof(int)
個字節
指針類型決定了指針+1的步長,決定了指針解引用的權限。
數組在內存中是連續存放的
然后我們看一下下面的例子:
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };char* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", *p);//p+i 這里就是指針+整數p += 4;}return 0;
}
打印:
1 2 3 4 5 6 7 8 9 10
乍一看是不是感覺沒問題?其實這個代碼是有問題的。
因為p是char*
類型的,這里的p += 4;
會只讀取每個int
類型(int類型4字節,char類型1字節)的第一個字節,跳過每個int
類型的后3個字節。
數字小的時候可能看不出什么,但是數字大了就可能會打印奇怪的東西。
int main()
{int arr[10] = { 100,200,3,4,5,6,7,8,9,10 };char* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%d ", *p);//p+i 這里就是指針+整數p += 4;}return 0;
}
打印:
100 -56 3 4 5 6 7 8 9 10
這里的-56是怎么來的呢?我也很好奇,就算了一下。
-56原碼:1011 1000
-56反碼:1100 0111
-56補碼:1100 1000
-56的補碼,如果看成無符號二進制數的話,剛好是200。
200存到int類型里面是這樣的:因為int類型4字節
200原碼:00000000 00000000 00000000 11001000
200補碼:00000000 00000000 00000000 11001000
但是char類型就1個字節,計算機里面又是看補碼的。
補碼:1100 1000
反碼:1100 0111
原碼:1011 1000 ---> -56
于是就得到-56
這個東西。
5.2 指針-指針
指針-指針
的絕對值是指針和指針之間元素的個數。
指針-指針
計算的前提條件是兩個指針指向的是同一個空間。
int main()
{int arr[10] = { 1,200,3,4,5,6,7,8,9,10 };printf("%d\n", &arr[9] - &arr[0]);return 0;
}
打印:
9
用一個函數求字符串長度:
int main()
{char arr[] = "abcdef";size_t len = strlen(arr);//strlen統計\0之前的字符個數//arr是arr[]數組首元素地址,相當于&arr[0]printf("%zd\n", len);return 0;
}
打印:
6
寫一個函數求字符串長度:(指針++)
//size_t是一種無符號整型
size_t my_strlen(char* p) {size_t count = 0;//計數器while (*p != '\0') {count++;p++;}return count;
}int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%zd\n", len);return 0;
}
打印:
6
或者我們也可以用指針-指針
size_t my_strlen(char* p) {char* start = p;char* end = p;while (*end != '\0') {end++;}return end-start;
}int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%zd\n", len);return 0;
}
打印:
6
注意:
指針+指針
沒啥意義。
就像日期-日期可以得到間隔天數,日期+日期得到的東西沒啥用。
5.3 指針的關系運算
實際上就是比較指針的大小 。
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int sz = sizeof(arr)/sizeof(arr[0]);while(p < &arr[sz]) //指針的大小比較{printf("%d ", *p);p++;}return 0;
}
打印:
1 2 3 4 5 6 7 8 9 10
6.野指針
野指針就是指針指向的位置是不可知的(隨機的、不正確的、沒有明確限制的)
6.1 野指針成因
6.1.1.指針未初始化
#include <stdio.h>
int main()
{ int *p;//局部變量指針未初始化,默認為隨機值*p = 20;return 0;
}
6.1.2.指針越界訪問
#include <stdio.h>
int main()
{int arr[10] = {0};int *p = &arr[0];int i = 0;for(i=0; i<=11; i++){//當指針指向的范圍超出數組arr的范圍時,p就是野指針*(p++) = i;}return 0;
}
6.1.3.指針指向的空間釋放
#include <stdio.h>
int* test()
{int n = 100;return &n;
}
int main()
{int*p = test();printf("%d\n", *p);return 0;
}
test函數調用完就銷毀了,p得到的地址就不屬于當前程序了。
6.2 如何規避野指針
6.2.1 指針初始化
如果明確知道指針指向哪里就直接賦值地址,如果不知道指針應該指向哪里,可以給指針賦值NULL。
NULL 是C語言中定義的一個標識符常量,值是0,0也是地址,這個地址是無法使用的,讀寫該地址會報錯。
int main()
{int num = 10;int*p1 = #int*p2 = NULL;*p2 = 200;//寫這行的話程序會崩掉,因為空指針是不能訪問的。return 0;
}
我們也可以這樣:
int main()
{int* p = NULL;if(p!=NULL){*p = 200;}return 0;
}
這樣就沒問題啦~
6.2.2 ??指針越界
一個程序向內存申請了哪些空間,通過指針也就只能訪問哪些空間,不能超出范圍訪問,超出了就是越界訪問。
6.2.3 指針變量不再使?時,及時置NULL,指針使?之前檢查有效性
當指針變量指向一塊區域的時候,我們可以通過指針訪問該區域,后期不再使用這個指針訪問空間的時候,我們可以把該指針置為NULL。因為約定俗成的一個規則就是:只要是NULL指針就不去訪問,同時使用指針之前可以判斷指針是否為NULL。
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int i = 0;for(i=0; i<10; i++){*(p++) = i;}//p++是先用后加的,所以用完之后會越界//此時p已經越界了,可以把p置為NULLp = NULL;//下次使用的時候,判斷p不為NULL的時候再使用p = &arr[0];//重新讓p獲得地址if(p != NULL) //判斷{//...}return 0;
}
6.2.4 避免返回局部變量的地址
不要返回局部變量的地址。
7.assert斷言
assert.h
頭文件定義了宏 assert()
,用于在運行時確保程序符合指定條件,如果不符合,就報錯終止運行。這個宏常常被稱為“斷言”。
assert(p != NULL);
上面代碼在程序運行到這一行語句時,驗證變量 p 是否等于 NULL 。如果確實不等于 NULL ,程序繼續運行,否則就會終止運行,并且給出報錯信息提示。
assert() 宏接受一個表達式作為參數。
如果該表達式為真(返回值非零), assert() 不會產生任何作用,程序繼續運行。
如果該表達式為假(返回值為零), assert() 就會報錯,在標準錯誤流 stderr 中寫入一條錯誤信息,顯示沒有通過的表達式,以及包含這個表達式的文件名和行號。
使用 assert() 有幾個好處:
它不僅能自動標識文件和出問題的行號,還有一種無需更改代碼就能開啟或關閉 assert() 的機制。如果已經確認程序沒有問題,不需要再做斷言,就在 #include <assert.h> 語句的前面,定義一個宏 NDEBUG 。
#define NDEBUG
#include <assert.h>
然后,重新編譯程序,編譯器就會禁用文件中所有的 assert()
語句。如果程序又出現問題,可以移除這條 #define NDEBUG
指令(或者把它注釋掉),再次編譯,這樣就重新啟用了 assert()
語句。
缺點
:因為引入了額外的檢查,增加了程序的運行時間。
一般我們可以在 Debug
中使用,在 Release
版本中選擇禁用 assert 就行,在 VS 這樣的集成開發環境中,在 Release
版本中,直接就是優化掉了。
8.指針的使用和傳址調用
8.1 strlen的模擬實現
#include <assert.h>
size_t my_strlen(const char * str)//這里加個const是為了防止arr通過str被修改
{//這里加了const會增強代碼的健壯性(魯棒性)size_t count = 0;assert(str != NULL);//斷言while(*str){count++;str++;}return count;
}
int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%d\n", len);return 0;
}
打印:
6
8.2 傳值調?和傳址調?
學習指針的目的是使用指針解決問題,那什么問題,非指針不可呢?
例如:寫一個函數,交換兩個整型變量的值
#include <stdio.h>
void Swap1(int x, int y)
{int tmp = x;x = y;y = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交換前:a=%d b=%d\n", a, b);Swap1(a, b);printf("交換后:a=%d b=%d\n", a, b);return 0;
}
打印:
10 20
交換前:a=10 b=20
交換后:a=10 b=20
咦?為什么沒產生交換效果呢?
因為a和ba和b在main函數內部創建,形參x和y在Swap1函數內部創建并接收a和b的值。
x和y是獨立的空間,在Swap1函數內部交換x和y的值,自然不會影響a和b。
Swap1函數在使用的時候,是把變量本身直接傳遞給了函數,這種調用函數的方式我們之前在函數的時候就知道了,這種叫傳值調用。
說明:實參傳遞給形參的時候,形參會單獨創建一份臨時空間來接收實參,對形參的修改不影響實參。
所以Swap1失敗。
怎樣解決?
我們現在要解決的就是當調用Swap函數的時候,Swap函數內部操作的就是main函數中的a和b,直接將a和b的值交換了。
那么就可以使用指針了,在main函數中將a和b的地址傳遞給Swap函數,Swap函數里邊通過地址間接的操作main函數中的a和b,并達到交換的效果
void Swap2(int*px, int*py)
{int tmp = 0;tmp = *px;*px = *py;*py = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交換前:a=%d b=%d\n", a, b);Swap2(&a, &b);printf("交換后:a=%d b=%d\n", a, b);return 0;
}
打印:
10 20
交換前:a=10 b=20
交換后:a=20 b=10
成功交換了。
這里調用Swap2函數的時候是將變量的地址傳遞給了函數,這種函數調用方式叫:傳址調用。
傳址調用,可以讓函數和主調函數之間建立真正的聯系,在函數內部可以修改主調函數中的變量。
所以未來函數中只是需要主調函數中的變量值來實現計算,就可以采用**
傳值調用
**。如果函數內部要修改主調函數中的變量的值,就需要**
傳址調用
**。