关于堆栈、传值与传引用、实参和形参的猜想。——很精彩,别错过。

在近期的学习中,关于堆栈,传值与传地址,实参和形参给我搞的太糊涂了,头发都快掉光啦。
然而在复习软考题的时候,突然有种茅塞顿开的感觉,对三者重新认识了一下,发现并不是很繁琐。
本文就「三者到底是什么东东」,「三者的联系」按照我目前的理解进行一下剖析,顺便附上能够启发我的那道软考题及答案。

一、 能够令我茅塞顿开的软考题

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

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哈哈~

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