Csharp中的值类型和引用类型的值传递和引用传递

本文探讨了C#中的值类型(如byte、int)和引用类型(如class、string)在栈与堆上的存储原理,通过实例展示了值传递与引用传递的不同,以及如何利用return替代引用传递。重点讲解了堆与栈的特点、优缺点和它们在内存管理中的角色。
摘要由CSDN通过智能技术生成


为了巩固Csharp中的基础知识,为快要到来的面试打基础的过程中,我发现了个奇怪的事情,就是我对这个值类型和引用类型,以及值类型、引用类型的各种传递的定义以及表现表示各种不理解以及一脸懵,我作为要作为一个使用C#作为语言进行软件开发的准软件开发工程师来说,是很有必要的。

堆和栈

要搞明白啥是值类型啥是引用类型,我首先想到,这玩意拿过来就是要用的类似物品一样的东西,我不去引用更多的名词和定义以免自己回看的时候让自己一脸懵,但是首先,既然值类型和引用类型都是“东西”,或者是要传递的“物品”,现实生活中的快递啥的,都是需要一个仓库甚至是车厢去存放快件,所以这里面即将引入的概念就是用于存放这个值类型和引用类型的“物品”的这么一个地方——堆和栈

这玩意字面上很好理解,就是一个空白的地方,往上垒东西就行,但是语文稍微好点或者语感稍微准确点的读者可能会发现(没有说看不起语感不好的人哦哈哈哈哈哈哈),咱这个堆,听起来给人第一感觉就是一堆存放在某块区域里的什么什么东西。举个例子——一堆杂乱无章的书,等等等等。

堆是干嘛用的?

対辽!这个C#中,堆的作用就是用来存放C#中的实例对象,当然,谁没事干写个项目一天传递的“东西”都是实例对象?当然也要存储数据!而且是大量数据!(不要问我这个大量是多大量,emmmm)

堆有啥特点?

我在整理资料的时候,发现我问自己最多的问题,就是这个。
从它的中文名称来思考,堆是形容杂乱无章的(如果是有序的,我更倾向于用来形容)如果这么考虑,对辽!这就是第一个特点。

  1. 存储数据或者实例对象是无序的。
  2. 既然已经没有先后顺序了,你当然可以随意存取你想要存的数据或者是实例对象啦!
  3. 还有就是可以动态分配存储空间。

那么堆有啥不好的地方呢?

有是有,就是在读取速度上比较慢和不能自动回收过期的实例对象(当然.net有GC但是C++选手就稍微惨的一……)。
因为是没有先后顺序,你在向系统发出请求,我要找某某某数据!系统就会从堆中找你所要找的东西,就像是你在书堆里找自己喜欢的漫画书一样。所以时间就会慢一点。

说了堆了,我们来说说栈这个名词。

栈相对于堆是啥样的?

简单来说,你可以把栈看成一个“乐事的薯片桶”。

那么栈有啥特点呢?

栈相对于堆来说,就显得十分智能了。
你拿起一个“乐事的薯片桶”在后部有底的情况下,你往里面放薯片是需要一片一片的拿起来再放进去,对吧?如果第一片“薯片”你放进去的是可乐味,第二片你放进去的是青柠黄瓜味,第三片是烤肉味,而且你突然想到,诶还没吃过可乐味的薯片,这时候,你想要拿出来。所以你要先把上面的两个薯片拿出来,再拿出来最下面的薯片。
特点就是!

  • 栈只能在一端进行操作!也就是我们说的“薯片桶的口”,不过我们一般叫它栈顶,储存方式就像是“乐事薯片桶”的存薯片方式一样遵循“先进后出,先出后进”的原则。
  • 栈是一种内存自我管理的结构,数据进栈自动分配内存,出栈自动清空所占内存。
  • 虽然看起来一个栈中,存取数据的过程很麻烦,但是!他在多数据中的表现效率是快过这个存储方式的。

栈方式储存数据没有什么不好的地方吗?

有啊!万事都有两面性!

  • 栈中的内存不能动态请求,就是,只能根据数据大小来分配,灵活性没有堆高。
  • 如同“乐事薯片桶”中的薯片一样,只能存储一定量的数据,因此我们在使用的时候要考虑数据因为大小带来的影响。

值类型和引用类型在栈和堆中的分配

这里其实有两个规律!

  • 在创建引用类型的时候,内存管理会分配一块空间在堆里,然后分配一块内存给栈,用来存储指堆中的内存地址!(没错就是指针。。。。。。。。。。)

  • 在创建值类型数据时,内存管理会为他分配一块内存,然后这个内存是放在值类型数据被创建的地方。
    这句话说实话我一开始也没懂,尤其是被创建的地方这句话,但是我思考了一下(万能的语文)分为以下两种情况:

  • 如果这个值类型数据是在方法内部创建的,那么他的内存地址就会跟随方法放到栈中。

  • 如果这个值类型数据是引用类型的成员变量,那么他的内存地址就是会跟随引用类型存在堆中。

值类型

啥是值类型?我也没有啥定义啥的,但是搜集的资料中明确指出:byte,short,int,long,float,double,decimal,char,bool 和 struct这几个兄弟被统称为值类型。

引用类型

C#中的引用类型有:class和string

值类型和引用类型的本质区别:

  1. 值类型分配在线程堆栈上(管理由操作系统负责),引用类型分配在托管堆上(管理由垃圾回收器GC负责)。这里的管理指的是,内存的分配和回收。
  2. 值类型继承自valueType,valueType继承自System.Object;引用类型直接继承自System.Object。
  3. 值类型在作用域内结束时,会被操作系统自释放,减少托管堆压力;引用类型则靠GC。因此值类型在性能上由优势。

值传递

用值传递看看吧!

值类型的值传递

啥也不说!直接上代码!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;

namespace Test3
{
    class Program
    {
        static int pass(int PassNum, int PassNum2)
        {
            int MaxNumber = PassNum + PassNum2;
            return MaxNumber;
        }
        static void Main(string[] args)
        {
            int Maxnumber1 = 6;
            int Maxnumber2 = 7;
            int ShowMax = pass(Maxnumber1, Maxnumber2);
            WriteLine(ShowMax);
            ReadLine();
        }
    }
}

运行结果为:在这里插入图片描述
可以看到运行结果是13。
其中的参数的传递就是值类型传递。值类型复制数据本身,形成独立的数据块。

引用类型的值传递:

继续上代码!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace Test4
{
    class Program
    {

        static void ChangeArray(int[] CArray)
        {
            CArray[0] = 888;
            CArray = new int[5] { -3, -2, -1, -3, -4 };
            WriteLine("在这个方法里面,第一个元素是,{0}", CArray[0]);

        }

        static void Main(string[] args)
        {
            int[] CArray = { 1, 2, 3, 4, 5 };
            WriteLine("在Main内部,ChangeArray方法调用前,数组的第一个元素是,{0}", CArray[0]);

            ChangeArray(CArray);
            WriteLine("在Main内部,ChangeArray方法调用后,数组的第一个元素是,{0}", CArray[0]);
            ReadLine();
        
        }
    }
}

其实我在写这个例子的时候,就很迷在调用ChangeArray这个函数后,怎么参数里的数组是{888,2,3,4,5},在我想象里,应该是{888,-2,-1, -3, -4}:new的数组并改变了传入方法中的数组指针。
同样,程序一开始,Main方法给值:Main方法给值
将引用类型值传递给方法后地址:将引用类型值传递给方法后地址
new的一个数组 更改了传入方法中的数组指针。
new的一个数组 更改了传入方法中的数组指针
调用方法后,相同地址的数组改变了首位数值,所以首位数值为888.因为从数组的地址指针能看到,调用方法后的CArray数组实际上就是一开始Main声明的CArray,而不是后面在方法中new并赋值的CArray。调用方法后,相同地址的数组改变了首位数值,所以首位数值为888.因为从数组的地址指针能看到,调用方法后的CArray数组实际上就是一开始Main声明的CArray,而不是后面在方法中new并赋值的CArray。
运行结果:运行结果
从此可以看出值传递引用类型数据时,虽然是复制但是会从新开辟地址,将一个数组实际上会变成两个地址,分别储存不同的值,用以调用。

引用传递

值类型的引用传递:

上代码!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;

namespace Test3
{
    class Program
    {
        static int pass(ref int PassNum,ref int PassNum2)
        {
            int MaxNumber = PassNum + PassNum2;
            return MaxNumber;
        }
        static void Main(string[] args)
        {
            int Maxnumber1 = 6;
            int Maxnumber2 = 7;
            int ShowMax = pass(ref Maxnumber1, ref Maxnumber2);
            WriteLine(ShowMax);
            ReadLine();
        }
    }
}

和上文相比区别在于啥?就是ref!
有了这个“前缀”,你要明白:
在这个例子中,传递的不是Maxnumber1Maxnumber2本身,而是他们的引用。在方法中的参数PassNum和PassNum2不是int类型,而是对int类型的引用

引用类型的引用传递:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace Test4
{
    class Program
    {

        static void ChangeArray(ref int[] CArray)
        {
            CArray[0] = 888;
            CArray = new int[5] { -3, -2, -1, -3, -4 };
            WriteLine("在这个方法里面,第一个元素是,{0}", CArray[0]);

        }

        static void Main(string[] args)
        {
            int[] CArray = { 1, 2, 3, 4, 5 };
            WriteLine("在Main内部,ChangeArray方法调用前,数组的第一个元素是,{0}", CArray[0]);

            ChangeArray( ref CArray);
            WriteLine("在Main内部,ChangeArray方法调用后,数组的第一个元素是,{0}", CArray[0]);
            ReadLine();
        
        }
    }
}

这倒是没有迷,直接改变了地址。
引用传递引用类型实例的main方法给值:引用传递引用类型实例的main方法给值
进入方法后,因为new的原因变了数组地址和数组的值:进入方法后,因为new的原因变了数组地址和数组的值
直到运行结束,地址都没有再改变。
运行结果如下:运行结果
可以看到,引用的方法传递引用参数后,没有复制数组新增数组地址指针,就在原本的新建的数组中进行改值,所以输出的就是-3而不是888。

用return代替引用传递:

其实这个是老师给我留的作业啦!
上代码!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Console;
namespace Test4
{
    class Program
    {

        static int[] ChangeArray( int[] CArray)
        {
            CArray[0] = 888;
            CArray = new int[5] { -3, -2, -1, -3, -4 };
            WriteLine("在这个方法里面,第一个元素是,{0}", CArray[0]);
            return CArray;
        }

        static void Main(string[] args)
        {
            int[] CArray = { 1, 2, 3, 4, 5 };
            WriteLine("在Main内部,ChangeArray方法调用前,数组的第一个元素是,{0}", CArray[0]);

             CArray = ChangeArray(CArray);
            WriteLine("在Main内部,ChangeArray方法调用后,数组的第一个元素是,{0}", CArray[0]);
            ReadLine();
        
        }
    }
}

本来是个值传递,非要获得引用传递的效果:我们就将方法中的数组用return传出,并在mian方法中创建一个变量进行接受即可,当然 变量名可以不同,以表示传出的数组是值传递后的复制的数组。
运行结果如下:
return法更改
到此结束!谢谢大家!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值