首页 > 编程语言 >Learning hard C#学习笔记——读书笔记 07

Learning hard C#学习笔记——读书笔记 07

时间:2023-07-19 14:22:21浏览次数:38  
标签:07 读书笔记 C# addNum 传递 int 引用 类型 string


1.值类型和引用类型

1.1 什么是值类型和引用类型

  • 值类型:包括简单类型,枚举类型,结构体类型等,值类型通常被分配在线程的堆栈上,变量保存的内容就是实例数据本身
  • 引用类型:引用类型实例则被分配在托管堆上,变量保存的是实例数据的内存地址,引用类型主要包括类类型、接口类型、委托类型、字符串类型等




1.2 值类型和引用类型的区别


值类型和引用类型最主要的区别是——不同的内存分布

我们之前介绍过,值类型分配在线程的堆栈上,引用类型分配在托管堆上,不同的分配位置导致了不同的管理机制,值类型由操作系统负责管理,引用类型则由垃圾回收机制(GC)负责管理


管理的主要是内存的分配与释放

class Program {
    static void Main(string[] args) {
        // valuetype 是值类型
        int valuetype = 3;
        // reftype 是引用类型
        string reftype = "abc";
    }
}

在程序中,每个变量都有其堆栈地址,并且不同的变量,堆栈地址不同,valuetype 和 reftype 在堆栈地址占用了不同的位置,从下图可以看出,无论是值类型还是引用类型,变量本身都是存储在堆栈中,变量只是实例数据的一个引用





值类型的变量和实际数据通常会存储在线程堆栈中,而引用类型则是,变量存储在线程堆栈中,而实际数据存储在托管堆中,此时变量存储的是实际数据的地址,这个地址就像是我们实际生活中的地址,像快递员,想要送包裹给你,也是需要你的地址

注意:我们对值类型的说法是通常在线程堆栈中,而也有不在堆栈的情况


1.引用类型嵌套值类型

如果类的字段类型是值类型,它作为引用类型的一部分,会被分配到托管堆中,但是局部变量的值类型,则仍会被分配到线程栈中

public class NestedValueTypeInRef {
    // 这个和引用类型一起分配到托管堆中
    private int valueType = 3;

    public method() {
        // 方法的局部变量分配到线程栈中
        char c = 'c';
    }
}

class Program {
    static void Main(string[] agrs) {
        NestedRefTypeInValue reftype = new NestedRefTypeInValue();
    }
}


2.值类型嵌套定义引用类型


值类型嵌套定义引用类型,堆栈上将保存该引用类型的引用,而实际的数据则将保存在托管堆上

public class TestClass 
{
    public int x;
    public int y;
}

public struct NestedRefTypeValue 
{
    // 注意结构体的字段不能初始化
    private TestClass classinValueType;
  
    // 注意结构体的构造函数不能无参
    public NestedRefTypeValue(TestClass t)
    {
        classinValueType.x = 3;
        classinValueType.y = 5;
        classinValueType = t;
    }
}

class Program {
    static void Main(string[] args) {
        NestedRefTypeValue valueType = new NestedRefTypeInValue(new TestClass())
    }
}



总结:

  1. 值类型继承自ValueType,ValueType又继承自System.Object;而引用类型则直接继承自System.Object
  2. 值类型的内存不受GC控制,作用域结束,值类型会被系统自动释放,从而减少了托管堆的压力,而引用类型受到GC控制,所以与引用类型相比,值类型性能方面更占优势
  3. 值类型是密封的(sealed),你不能把值类型作为其它任何类型的基类,而引用类型一般具有继承性
  4. 值类型不能为null值,它的默认初始值为数值0,而由于类型在默认情况下为null值
  5. 由于值类型的变量包含其实际数据,因此在默认情况下,值类型之间的参数传递不会影响变量本身,而引用类型保存的数据的地址,它们作为参数传递,参数会发生变化,从而影响引用类型变量的值


1.3 两大类型转换——装箱与拆箱

由于C#存在这两种类型,自然需要对它们进行转换,类型转换指的是将数据的类型转化为另外一种类型


类型转换的方式

  1. 隐式类型转换:由低级类型向高级类型转换的过程。例如:派生类可以隐式的转换为它的父类,装箱的过程就属于指针隐式类型转换
  2. 显式类型转换:强制类型转换。这种转换可能会导致精度丢失,或出现运行异常

强制类型转换的格式

type就是你想要转换的类型

(type)(变量、或函数)
  1. 通过 isas 运算符可以进行安全的类型转换
if (myObj is MyClass)
{
    // myObj 是 MyClass 类型的实例
}

MyClass myObj = someObj as MyClass;
if (myObj != null)
{
    // someObj 成功转换为 MyClass 类型
}
  1. 通过 .NET 类库中的Convert类来完成类型转换
string str = "123";
int num = Convert.ToInt32(str);
string str = "2023-07-19";
DateTime date = Convert.ToDateTime(str);
string str = "Red";
Color color = (Color)Enum.Parse(typeof(Color), str);

1.3.1 装箱和拆箱的原理

class Program {
    static void Main(string[] args) {
        int i = 3;
        // 装箱操作
        object o = i;
        // 拆箱操作
        int y = (int) o;
    }
}

装箱(box)可以具体为三个步骤

  1. 内存分配:在托管堆中分配好内存空间存放复制的实际数据
  2. 完成数据的复制:将值类型实例的实际数据复制到新分配的内存中
  3. 地址返回:将托管对象的地址返回给由于类型变量



拆箱(unbox)操作的步骤:

  1. 检查实例:首先检查要进行拆箱操作的引用类型变量是否为null,如果为null则抛出异常,如果不为null,则继续检查变量是否和拆箱之后的类型是否是同一个类型
  2. 地址返回:返回已装箱变量的实际数据部分的地址
  3. 数据复制:将托管堆中的实际数据复制到栈中


注意

  1. 装箱和拆箱堆性能有比较大的影响,如果代码中有大量的装箱和拆箱会消耗很多运行时间
  2. 装箱和拆箱必然会产生多余的对象,进一步加重了GC的压力

应该避免多次的装箱和拆箱,最好使用泛型来编程

2. 参数传递的问题


C# 中的参数传递,我们可以分为四种情况

  1. 值类型参数按值传递
  2. 引用类型的参数按值传递
  3. 值类型参数按引用传递
  4. 引用类型的参数按引用传递

2.1 值类型的参数按值传递

参数可以分为实参和形参两种,形参指的是被调用方法中的参数,而实参指的是我们传递过去的参数

class Program {
    static void Main(string[] args) {
        int addNum = 1;
        // addNum 就是实参
        Add(addNum);
    }
    // addnum就是形参,即被调用方法中的参数
    private static void Add(int addnum) {
        addnum += 1;
        Console.WriteLine(addnum);
    }
}

值类型按值传递,其实传递的是该值类型实例的一个副本,也就是说,方法对参数的操作,并不会影响实际的参数


class Program {
    static void Main(string[] args) {
        int addNum = 1;
        // addNum 就是实参
        Add(addNum);

        Console.WriteLine("调用方法之后的实际参数的值:"+addNum);
        Console.Read();
    }
    // addnum就是形参,即被调用方法中的参数
    private static void Add(int addnum) {
        addnum += 1;
        Console.WriteLine("方法中形参的值:"+addnum);
    }
}



我们可以看到图中,并没有一根线将 addNum 与 addnum 进行绑定,addnum使用只是 addNum 的副本



2.2 引用类型按值传递


当传递的是引用类型的时,传递和操作的目标时指向对象的地址,而传递的实际内容对地址的复制,由于地址指向的是实际参数的值,当方法对地址进行操作的时候,实际上操作的地址所指向的实际值,当调用方法之后,实参也会被修改


public class RefClass
{
    public int addNum;
}

class Program {
    static void Main(string[] args) {
        Console.WriteLine("引用类型按值传递的情况");
        RefClass refClass = new RefClass();
  
        refClass.addNum = 1;
        AddRef(refClass);

        Console.WriteLine("调用方法之后,实际参数的值:"+refClass.addNum);
        Console.Read();
    }

    private static void AddRef(RefClass addnumRef) {
        addNumRef.addNum += 1;
        Console.WriteLine("方法中的addNum值:"+addNumRef.addNum);
    }

}



2.3 string引用类型参数按值传递的特殊情况


虽然string类型也是引用类型,但是它按值传递,传递的参数斌不会因为方法中的形参改变而修改

这个特殊情况是因为string具有不变性,一旦string类型被赋值之后,则它就是不可改变的,即不能通过代码修改它

2.4 值类型和引用类型的按引用传递


不管是值类型还是引用类型,都可以使用 ref 或 out 关键字来实现参数按引用传递,并且按引用进行传递的时候,方法的定义和调用都必须是显式的使用 ref 或 out 关键字,不可省略

按引用传递时,不管是值类型,还是引用类型,本质都一样是告诉编译器,方法传递的是参数地址,而非参数本身

class Program 
{
    static void Main(string[] args) 
    {
        // num 作为实际参数
        int num = 1;
        // refStr 是引用类型实参
        string refStr = "Old string";
  
        // 值类型按引用传递
        ChangeByValue(ref num);
        Console.WriteLine(num);
  
        // 引用类型按引用传递
        ChangeByRef(ref refStr);
        Console.WriteLine(refStr);

        Console.Read();
  
    }

    private static void ChangeByValue(ref int numValue) 
    {
        numValue = 10;
        Console.WriteLine(numValue);
    }

    private static void ChangeByRef(ref string numRef) 
    {
        numRef = "new string";
        Console.WriteLine(numRef);
    }
}



标签:07,读书笔记,C#,addNum,传递,int,引用,类型,string
From: https://www.cnblogs.com/trueasureyuki/p/17565457.html

相关文章

  • uniapp专栏 —— vscode报错 'uni' is not defined.
    写在前面这些内容基于通过cli搭建的uniapp项目,使用了vite4,ts4.9,vue3(组合式API,setup语法糖)。如果有版本不一致,请谨慎参考。正文uni是一个全局变量,但是eslint没有识别到。避免这个错误报错在.eslintrc.js文件中加上配置globals:{uni:true},......
  • CentOS7中安装Mysql8并配置远程连接和修改密码等
    场景使用Vmware等虚拟机软件搭建CentOS7系统,需要在其上安装Mysql8版本数据库。注:博客:https://blog.csdn.net/badao_liumang_qizhi实现1、去mysql官网手动下载rpm包并上传到服务器,或者直接通过wget进行下载wgethttps://downloads.mysql.com/archives/get/p/23/file/mysql-......
  • 【实战技能】基于硬件垂直消隐的多缓冲技术在LVGL, emWin,GUIX和TouchGFX应用,含视频教
    原贴地址:https://www.armbbs.cn/forum.php?mod=viewthread&tid=120114这两天研究了下LVGL的持单缓冲,双缓冲和配合硬件消隐的双缓冲的实现(已经分享V5,V6和V7开发板的程序模板),特别是这个整屏缓冲方案,这几款GUI的实现基本是一样的,所以专门开了一期视频做个分享。视频:https://www.b......
  • 编译安装最新的Pluto compiler,以及遇到的一些坑
    好久不见!这段时间在鼓捣一些奇奇怪怪的东西。PlutoCompiler是一款非常优秀的Polyhedral编译器。这玩意拿来优化循环和程序局部性啥的是相当好的。其安装过程涉及到整个llvm的编译过程,如果之前并没能够了解llvm的话估计会够呛,我也是基本上把坑踩了一个遍。所以干脆写篇博客给之......
  • C# 使用反射调用含 ref 或 out 参数的方法
     //程序集引用方式intint111=0;stringstr111="";boolret1=newClsITestData().GetRtf("Debug_Pas","022_0714_1654_55",outstringrtf,refint111,refstr111);stringmsg1=rtf;//COM调用strings......
  • Cisco SD-WAN (Viptela)
     CiscoSD-WAN(Viptela) 1.  思科SD-WAN融合计划viptela主要产品线有两条:1条,在国外,思科云可直接部署,独立运营团队;1条,在中国viptela不支持思科云方式部署,只支持自建三件套; phase1:   优点:维持Viptela既有产品模式和支持体系。   技术细节:平台,维持既有xEdge;管理......
  • 网络工程师的技术探索:代理IP、Socks5代理、SK5代理、网络安全
    代理IP:网络匿名化的关键代理IP是一种重要的网络工具,它允许用户隐藏真实IP地址并通过代理服务器进行网络访问。通过代理IP,用户可以绕过地理限制,提高访问速度,并增加隐私保护。我致力于研发高质量的代理IP服务,提供稳定、高速、安全的网络访问体验。Socks5代理:功能强大的代理协议Sock......
  • android transaction failed 29201/-1, size 0-0 line 3009
    解决"androidtransactionfailed29201/-1,size0-0line3009"错误引言在Android开发中,我们经常会遇到各种错误和异常。其中一个常见的错误是"androidtransactionfailed29201/-1,size0-0line3009"。这个错误通常与Fragment事务相关,并且可能会导致应用崩溃或功能异常......
  • Oracle系列---【如何查看Oracle数据库连接数?】
    如何查看数据库连接数?selecta.sid,serial#,USERNAME,a.PREV_EXEC_START,b.sql_text,a.MACHINE,a.PORT,a.status,'ALTERSYSTEMKILLSESSION'''||a.sid||','||serial#||''';'fromV$sessionaLEFTJOIN"V$SQLAREA"......
  • ClickHouse多种实时更新方法总结
    ClickHouse本身对update的执行是低效的,因为ClickHouse的MergeTree存储一旦生成一个DataPart,这个Part就不支持更改,而是需要删除旧Part,重写整个Part。所以从MergeTree存储内核层面,ClickHouse就不擅长做数据更新删除操作。本文讲述的方法包括采用系统自带的Update,采用Replacing......