關於堆棧、傳值與傳引用、實參和形參的猜想。——很精彩,別錯過。

在近期的學習中,關於堆棧,傳值與傳地址,實參和形參給我搞的太糊塗了,頭髮都快掉光啦。
然而在複習軟考題的時候,突然有種茅塞頓開的感覺,對三者重新認識了一下,發現並不是很繁瑣。
本文就「三者到底是什麼東東」,「三者的聯繫」按照我目前的理解進行一下剖析,順便附上能夠啓發我的那道軟考題及答案。

一、 能夠令我茅塞頓開的軟考題

答案見文章底部,解析就是此博客。

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的成員
    }

[外鏈圖片轉存失敗,源站可能有防盜在這裏插入!鏈機制,建描述]議將圖片上https://傳(imblog.csdnimg.cn/201007171533225.png?x-oss-pgocess=image/9aUermark,type_ZmruZ3poZW5naGVpdGk,shadow_wtFB10,text_aHR0cHM6Ly9ibG9nLmzubmVZL0NqeF84NDIx,size_10,color_FFFFFF,t_75230)(https://img-blog.Pcsdnimg.cn/20191007171533225.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NqeF85NDIx,size_16,color_FFFFFF,t_70)]
①.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哈哈~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章