在近期的學習中,關於堆棧,傳值與傳地址,實參和形參給我搞的太糊塗了,頭髮都快掉光啦。
然而在複習軟考題的時候,突然有種茅塞頓開的感覺,對三者重新認識了一下,發現並不是很繁瑣。
本文就「三者到底是什麼東東」,「三者的聯繫」按照我目前的理解進行一下剖析,順便附上能夠啓發我的那道軟考題及答案。
一、 能夠令我茅塞頓開的軟考題
答案見文章底部,解析就是此博客。
1.函數調用時基本的參數傳遞方式有傳值與傳地址兩種()。
A.在傳值方式下,形參將值傳給實參
B.在傳值方式下,實參不能是數組元素
C.在傳地址方式下,形參和實參間可以實現數據的雙向傳遞
D.在傳地址方式下,實參可以是變量也可以是表達式
2.以下關於傳值調用與引用調用的敘述中,正確的是()。
①在傳值調用方式下,可以實現形參和實參間雙向傳遞數據的效果
②在傳值調用方式下,實參可以是變量,也可以是常量和表達式
③在引用調用方式下,可以實現形參和實參間雙向傳遞數據的效果
④在引用調用方式下,實參可以是變量,也可以是常量和表達式
A.①③
B.①④
C.②③
D.②④
二、 堆與棧、傳值與傳引用、實參與形參的辨析
1. 堆與棧
堆與棧是兩種數據結構。
本文不做深入討論,給出一些博客鏈接,請讀者自己學習。
https://blog.csdn.net/myqq1418/article/details/81584761
https://blog.csdn.net/weixin_33755847/article/details/91423041
https://juejin.im/post/5c80dcf7f265da2dbf5f3321
https://blog.csdn.net/PengPengBlog/article/details/52737352
1.1 爲什麼要採用堆棧
首先要明白,堆棧是用來臨時儲存數據的。
如果你對操作系統有基本的瞭解,那麼你就會明白,計算機是靠二進制的代碼(即機器語言)控制硬件實現各種操作的。由於用機器語言編寫代碼過於麻煩,開發人員又發明高級程序語言去代替機器語言進行編程。爲了讓機器識別代碼,還需要將用高級程序語言寫好的代碼經過編譯轉換成目標代碼(彙編語言或機器語言。若轉換的目標代碼爲彙編語言,則還需將彙編語言轉換成機器語言,編程語言歷史不詳談。),之後交給CPU去處理運行。
我們寫好的,能夠跑起來的代碼,可以叫做程序(程序包括好幾個元素)。進程就是把程序(簡單理解,就是我們寫的代碼)通過CPU進行一步步的執行的過程。那,當我們程序跑起來後(即成爲進程),我們代碼當中編寫的變量,表達式,常量都會進行存儲,以用於CPU識別和處理。當我們的程序被關閉之後或程序中某一部分被關閉之後,我們代碼中的變量,常量,和表達式失去了效用,變得沒有意義。那麼如果這些垃圾數據存在於儲存器中,除了佔內存,沒有任何意義。所以不如把這些用於進程運行所需要的數據進行臨時存儲,當程序被關閉,或程序某個子程序被關閉,回收這些垃圾資源,豈不美哉!
基於這種考量,將部分的變量,常量,表達式放置於堆棧當中。
爲什麼說是部分的變量,常量,和表達式,請自行進行拓展,附贈一個博客鏈接:
https://blog.csdn.net/yu97271486/article/details/80444587
棧:存放的空間少,但是存取速度快。
堆:存放的空間大,但是速度比較慢。
1.2 情景釋疑
我相信能夠對本文所研究問題有疑惑的讀者,肯定是上過學的,肯定是見過黑板的,沒錯,就是與白粉筆配套的黑板。現在,我們再回到那個青春無悔的課堂。
鈴鈴鈴,上課啦。
數學老師剛站在講臺上,整理了一下書說道:“同學們,這堂課我們上數學課,本節課我們學習兩部分內容,數組和向量”。
數學老師拿起一根白粉筆,在一塵不染的黑板上寫下了“數組”兩個大字,之後馬不停蹄地用粉筆寫下了數組的相關概念,並進行了舉例。同學們都很聰明,很快地就理解了數組的知識。數學老師很高興地說:“沒成想同學們能夠領悟的這麼快,比我之前帶過的學生強多啦,那麼接下來,我們就繼續講向量吧,我把黑板上的東西都擦掉了啊”
說時遲那時快,數學老師三下五除二就把黑板擦的一乾二淨。
同樣的,數學老師又在黑板上寫下了“向量”兩個大字,以及其概念和舉例。
時間過得很快,下課鈴響起,數學老師面向同學,語重心長地說:“再難的事情都怕有心人,堅持下去。”語畢,數學老師轉身,將黑板擦的一乾二淨。
情景字典:
數學課:程序
數組:程序的某個部分或子程序
向量:程序的另一個個部分或子程序
正在上的數學課:進程
黑板:堆空間或棧空間
黑板上的字:存放於堆棧的數據(變量,常量,表達式,數組等等)
在黑板上寫字:在堆棧中存放數據
擦黑板:回收堆棧中的數據
情景思想:
一節數學課,就50分鐘,上完之後就換下一堂課。當數學課結束的時候,要把黑板擦乾淨,以便於下一堂課的任課老師使用。那麼堆棧也是一樣的,堆和棧產生的空間一般來講應該是在內存當中。一堂數學課就相當於一個進程,當數學課下課的時候(即進程結束),勢必要把黑板擦乾淨(清理在虛擬內存當中開闢的堆棧空間的數據。)
2. 傳值和傳引用
傳值和傳引用一般是用於傳參(最起碼我這個階段是),理解難度也就一般吧。
同上,給出一些優質博客鏈接
https://blog.csdn.net/Jamesjjjjj/article/details/88034115
https://blog.csdn.net/weixin_37887248/article/details/83271894
https://bbs.csdn.net/topics/10446264(裏面的討論很精彩)
2.1 值類型和引用類型
要想明白傳值傳引用,先理解值類型和引用類型數據爲佳。
2.1.1 值類型
值類型的數據存放在申請的棧空間上,並不涉及堆。
這句話是什麼意思呢?上圖吧。
例:
int a=5;(在struct中寫的代碼)
首先,int類型數據是值類型數據,int a=5,相當於向棧申請了一個空間,並在棧空間上存上了數據。
那麼,哪些數據類型是值類型呢?
以C#爲代表,其值類型有結構體(數值類型,bool型,用戶定義的結構體),枚舉,可空類型。
也就是說,值類型數據會存放在棧上,與堆無關。
但是,當值類型做字段時,有一些不同,後邊會說。
2.1.2 引用類型
引用類型數據和值類型數據有一些不同,引用類型多出了一個地址的概念。
什麼是地址呢?我目前理解的就是引用類型的值所在內存中的邏輯地址。引用類型的值存放的位置在堆空間上,上文講堆棧的時候說過,堆空間的空間大,但是堆存取速度慢,所以,在棧上存放引用類型值的地址,以便於彌補速度上的不足(在我理解的是這樣,不知道還出於什麼樣的因素進行的考量),棧上的地址指向堆上的的值。按照規矩,上圖:
例:string a = “Hello,world!”;
那麼,引用類型數據都有哪些種類呢?
數組,用戶定義的類、接口、委託,object,字符串(字符串很特殊)。
2.1.3 值類型的特別之處
不知道你是否想過這樣的一個問題:如果一個值類型數據是引用類型的成員,那麼該如何進行儲存?
舉個例子:
class Program //第一段代碼
{
static void Main(string[] args)
{
People p1 = new People(); //實例化一個people類,p1
People p2 = new People(); //實例化一個people類,p2
p1.Age = 18; //爲p1的Age屬性賦值
p2 = p1;
p2.Age = 20;
Console.WriteLine(p1.Age); //20
Console.WriteLine(p2.Age); //20
Console.ReadKey();
}
}
public class People //People是一個引用類型(class)
{
int age; //age是一個值類型(int)
public int Age { get; set; } //Age是一個值類型(int),Age是People的成員
}
那麼此時,age存放在哪裏了呢?
存放在P1開闢的堆空間裏面了。之前不是說值類型保存在棧當中嗎?
要明白,我們如果想要存數據,首先要開闢空間,之後在我們開闢的空間內存數據。就好比我們去飯店喫飯,你得先告訴前臺,你有幾個人喫飯,人家才能給你安排房間。那麼如何開闢空間呢?new。當我們的值類型int數據做字段的時候,並沒有通過new去開闢空間,所以就跟隨people一起儲存。people是引用類型,值存放在堆上,所以做字段的值類型也存放在堆上。所以準確的來講,值類型數據不一定存在棧上。而引用類型和值類型不同,值一定放在堆上。
2.1.4 String請區別對待
String類型是不可修改的,這一特殊的地方讓string類型表現得如同值類型一般,但是請記住,string是引用類型。具體原理呢…累了,目前不想寫,你們查閱別人的資料吧,寫的挺明白的。
2.2 傳值和傳引用
當我們瞭解完值類型和引用類型之後,我們再去理解傳值和傳地址就簡單的多了。
2.2.1傳值
什麼是傳值?就是把值類型和數據類型的值,先複製一份,再將複製的值進行傳輸。怕你們腦闊亂,給你們上圖:
例:
代碼爲上面的代碼,只是把People的類型從class改爲struct
class Program //第二段代碼
{
static void Main(string[] args)
{
People p1 = new People(); //實例化一個people類,p1
People p2 = new People(); //實例化一個people類,p2
p1.Age = 18; //爲p1的Age屬性賦值
p2 = p1;
p2.Age = 20; //見下圖的P2’
Console.WriteLine(p1.Age); //18
Console.WriteLine(p2.Age); //20
Console.ReadKey();
}
}
public struct People //People是一個值類型(struct,結構)
{
int age; //age是一個值類型(int)
public int Age { get; set; } //Age是一個值類型(int),Age是People的成員
}
①.P1、P2分別申請空間。【代碼爲兩次new】
②.在P1申請的空間內存放18【p1.age=18】
③.將P1空間內的值,即18,進行一次複製。 【p2=p1】
④.將複製的值——18,傳到p2所申請的空間內。【p2=p1】
⑤.將p2空間內的18,修改成20.。【p2.age=20】
發生傳值的代碼爲【p2=p1】,傳值很簡單。
2.2.2 傳引用
傳引用,即傳地址。
什麼類型數據會有地址?引用類型(不知道爲什麼的讀者,看文章2.1.2部分)。
所以當我們進行傳引用的時候,並不是把值傳過去,而是把地址傳過去。繼續上圖:
例(第一段代碼,即people爲class,略去不寫,請上翻。):
①.P1、P2分別申請空間。【代碼爲兩次new】
②.在P1申請的空間內存放18【p1.age=18】
③.將P1的地址傳到p2的地址上,覆蓋P2原地址,p1和p2指向同一個值。【p2=p1】
④.將p1、p2共同指向的空間內的18,修改成20.。【p2.age=20】
所以,此時輸出p1.age和p2.age都是20。、
2.2.3 推測的棧空間結構
此次是我根據我學到的知識自己推測的。
第一種可能:
第二種可能:
2.3 其他問題
㈠.可以用傳引用的方式傳遞值類型數據嗎?【答案已修正】
答:可以。首先得明白,傳引用傳的是地址。無論是堆和棧,他們都是在內存當中開闢的空間,所以所有的值都會有自己的邏輯地址。
㈡.可以用傳值的方式傳遞引用類型數據嗎?
答:不清楚,但是我覺得不可以。除卻有指針的語言外,現行的c#,java都是參數是什麼類型,就用什麼方式傳遞,方便你們理解這句話,舉個例子吧。
例:
void yell(int a,object x)
{
//a是值類型,用傳值的方式傳遞;
//o是引用類型,用傳引用的方式傳遞。
}
如果你想直接取引用類型的值而不是地址,指針可以作爲輔助做到,但是好多語言不支持指針啦,所以不行。
3.實參和形參
終於能介紹一點比較好理解的概念啦。
3.1 形參
void yell(int a,object x)
{
//a是值類型,用傳值的方式傳遞;
//o是引用類型,用傳引用的方式傳遞。
}
此時的a和x都是形參,指的是形式上的參數,是參數的接收方。
3.2 實參
class Program
{
static void Main(string[] args)
{
People p1 = new People();
object o1 = new object();
p1.yell(15, o1); //看這裏!!!
Console.ReadKey();
}
}
public class People
{
public void yell(int a,object x)
{
}
}
此時的15,o1就是實參,是參數的發送方。其將15傳遞給a;將o1傳遞給x。
話說到這裏啦,什麼叫雙向傳遞的效果?
修改x相當於修改o1,修改o1相當修改x。
三、總結補充
在傳值的情況下:
①.實參:變量,常量,表達式,數組,函數調用
②.形參:變量
在傳引用的情況下:
①.實參:變量,數組,(常量可不可以不清楚,軟考解析中說不可以,但是printf函數或console.WriteLine中,參數是可以爲“Hello,world!”的)
②.形參:變量
我認爲形參只能是變量,因爲形參是用來接收各種確定的值的,這種特性要求形參必須爲不確定的。
本篇博客爲了便於理解,在寫作上費了好多腦細胞,喜歡的話,請評論,點贊,關注,O(∩_∩)O哈哈~