C# 类型基础(中)

前一篇文章中我们讲到了值类型和引用类型的一些区别,那这篇我们将深入的分析一下到底有什么不一样

先总结一下两者的差别:

黄金法则:

1.引用类型总是被分配到托管堆上。

2.值类型总是分配到它声明的地方:

a.作为引用类型的成员变量分配到托管堆上

b.作为方法的局部变量时分配到栈上

这就是栈和托管堆的区别,栈是线程级别的存储,很规整的存储结构,有先进后出的规则。在每一个线程开启的时候系统默认会给线程分配大约1M左右的栈内存。而托管堆的存储是很随意的,我们对引用类型的操作都是在托管堆上的。

那么线程栈和托管堆是如何工作的呢?我们上代码,先看栈

 public int AddFive(int pValue)
{
  int result;
  result = pValue + 5;
  return result;
}

执行示意图如下:

1.方法AddFive()被压入“栈”

2.紧接着方法参数pValue被压入“栈”

3.然后是需要为result变量分配空间,这时被分配到“栈”上。

4.最后返回结果

通过将栈指针指向 AddFive()方法曾使用的可用的内存地址,所有在“栈”上的该方法所使用内存都被清空,且程序将自动回到“栈“上最初的方法调用的位置。

再看托管堆

public class MyInt
{
    public int MyValue;
}

public MyInt AddFive(int pValue)
{
    var result = new MyInt();
    result.MyValue = pValue + 5;
    return result;
}

MyInt 为引用类型,它被分配在“堆”上,并且由一个位于“栈”上的指针引用,执行完之后只剩下

一个 MyInt 类被留在“堆”上(“栈”上再也没有指向这个 MyInt 类的指针),这个时候GC就回收处理

一定要记住我们上边将的黄金法则,以后千万不要一拍脑袋告诉别人值类型是在栈上,引用类型在堆上。作为一名资深的.net开发,这样的话有点幼稚了。

前面我们一直说托管堆,什么是托管呢? 我们先看一下.net的CLR执行模型:

托管代码(Managed Code): 由公共语言运行库时(CLR)执行的代码,而不是由操作系统直接执行。托管代码也可以调用CLR的运行时服务和功能,比如GC、类型检查、安全支持等等。代码编写完毕后进行编译,此时编译器把代码编译成中间语言(IL),而不是能直接在你的电脑上运行的机器码。程序集(Assembly)的文件负责封装中间语言,程序集中包含了描述所创建的方法、类以及属性的所有元数据。

非托管代码(Unmanaged Code):直接编译成目标计算机的机器码,这些代码只能运行在编译出这些代码的计算机上,或者是其他相同处理器或者几乎一样处理器的计算机上。非托管代码不能享受公共语言运行库所提供的一些服务,例如内存管理、安全管理等。 如果非托管代码需要进行内存管理等服务,就必须显式地调用操作系统的接口,通常非托管代码调用Windows SDK所提供的API来实现内存管理。 非托管程序也可以通过调用COM接口来获取操作系统服务。

各个值类型及其基类:

结构体:struct(直接派生于System.ValueType)

数值类型:

整型:

short(System.Int16),ushort(System.UInt16)

int(System.Int32),uint(System.UInt32)

long(System.Int64),ulong(System.UInt64)

sbyte(System.SByte的别名),byte(System.Byte)

字符型:char(System.Char);

浮点型:float(System.Single),double(System.Double)

用于财务计算的高精度decimal型:decimal(System.Decimal)

bool型:bool(System.Boolean的别名)

枚举:enum(派生于System.Enum)

可空类型(派生于System.Nullable泛型结构体,语法 T? 是 System.Nullable<T> 的简

写,此处的 T 为值类型。)

值类型不能为赋值为null,而可空类型是值类型为什么可以给null值?

按照惯例,这个时候应该说一句:小二,上代码!

System.Nullable<int> number1 = null;

System.Nullable<int> number2 = new System.Nullable<int>();

System.Nullable<int> number3 = 23;

System.Nullable<int> number4 = new System.Nullable<int>(88);  

这是我们对Nullable类型的赋值操作,我们来看一下生成的IL代码是什么样的:

这下子就清楚了,实质上是编译器提供了这样的支持,从生成的中间代码可以看出:可空类型的赋值直接等效于构造实例。赋null时其实就是调用空构造函数,有值时就就把值传入带参数的构造函数。所以可空类型的null值和引用类型的null是不一样的。(可空类型的并不是引用类型的null,而是用结构的另一种表示方式来表示null)

各个引用类型及其基类:

数组:(派生于System.Array)数组的元素,不管是引用类型还是值类型,都存储在托管堆上

类:class(派生于System.Object)

接口:interface(接口不是一个“实际的类型”,所以不存在派生于何处的问题。)

委托:delegate(派生于System.Delegate)

object:(System.Object的别名)

字符串:string(System.String的别名)

问题1:Int[]是引用类型还是值类型 ?  (根据前面讲的内容,你觉得呢?)

问题2:所有的值类型都存储在栈上吗?根据前面讲的内容,你觉得呢?)

总结一下:

特殊类型 dynamic

dynamic是C#4.0 引入的新类型,声明为dynamic的类型与”静态类型”(编译时确定的类型,例如int,double类型)相比最大的特定它是”动态类型”,它会在运行时尝试调用方法,这些方法的存在与否不是编译时期检查的,而是在运行时查找,如果方法存在并且参数正确,会正确调用,否则会抛出异常.

dynamic类型只在编译时存在,运行时不存在,会转换为object

dynamic和var的区别

用var声明的局部变量只是一种简化语法,它要求编译器根据表达式推断数据的实际类型

var只能用于方法内部的局部变量,而dynamic可以用于方法内部的局部变量、字段和参数

表达式不能转型为var,但是可以转型为dynamic

必须显示初始化var声明的变量,但dynamic声明的变量无需初始化

关于dynamic还可以讲很多东西的,后面我们会专门讲解这个特殊类型

问题来了:只要继承自System.ValueType的都是值类型吗?

值类型都是System.ValueType的后代,但System.ValueType的后代不全是值类型,System.Enum就是唯一的特例,在System.ValueType的所有后代中,除了System.Enum之外其它都是值类型。

Enum的源码定义如下:

public abstract class Enum : ValueType, IComparable, IFormattable, IConvertible

我们可以看到它是抽象类,根据前面讲的class属于引用类型,所以他是引用类型。所以枚举值到底是值类型还是引用类型呢,各位看官请看:

枚举的特殊性:

1. 所有枚举类型(enum type)都是值类型。

2. System.Enum和System.ValueType本身是引用类型。

3. 枚举类型(enum type)都是隐式的直接继承自System.Enum,并且这种继承关系只能由   编译器自动展开。但System.Enum本身不是枚举类型(enum type)。

4. System.Enum是一个特例,它直接继承自System.ValueType(参见Code #03),但本身却是一个引用类型。

下面两位仁兄讲的很清楚,各位可以参考一下:

http://www.cnblogs.com/cdts_change/archive/2009/09/20/1570414.html

http://www.cnblogs.com/yank/archive/2009/02/27/1399423.html

讲了这么多引用类型和值类型的东西,我们来系统的总结一下:

值类型和引用类型的区别

a.所有继承System.Value的类型都是值类型(Enum特殊),其他类型都是引用类型

b.引用类型可以派生出新的类型,而值类型不能

c.引用类型存储在堆中,而值类型既可以存储在堆中也可以存储在栈中

d.引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型)

e.引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值

f.当比较两个值类型时,进行的是内容比较;而比较两个引用类型时,进行的是引用比较

g.值类型在内存管理方面具有更好的效率,并且不支持多态,适合用作存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为

最后再来一句:小二,上代码!

 public void ValueTypeDemo()
        {
            RefObj ref1 = new RefObj(); // 在堆上分配
            ValObj val1 = new ValObj(); //在栈上分配

            ref1.Count = 5;  //赋值并返回指针
            val1.Count = 5;  //在栈上直接修改值

            Console.WriteLine(ref1.Count); //都显示5
            Console.WriteLine(val1.Count);

            RefObj ref2 = ref1; // 只复制引用
            ValObj val2 = val1; //在栈上分配并复制成员
            ref1.Count = 8; // ref1 和 ref2 都会改
            val1.Count = 9; // ref1.Count 会改,但是ref2.Count 不会

            Console.WriteLine(ref1.Count); // 显示 8
            Console.WriteLine(ref2.Count); // 显示 8
            Console.WriteLine(val1.Count); // 显示 9
            Console.WriteLine(val2.Count); // 显示 5
        }

 public class RefObj
    {
        public int Count { get; set; }
    }

    public struct ValObj
    {
        public int Count { get; set; }
    }

  

转载的童鞋请说明出处,谢谢。

时间: 2024-08-29 11:04:32

C# 类型基础(中)的相关文章

源码-PL/SQL从入门到精通-第四章-PL/SQL控制语句(基础中的基础)

控制语句对任何一门语言.任何一个程序员而言,都是基础中的基础. 学了PL/SQL,才体会到Java语法的简洁,简直不是一个时代的东西:当然,他们的定位本来就不一样,不能以己之长比之他人之短. --第4章开始 --代码4.1 最简单的IF语句使用示例 DECLARE v_count NUMBER(10) := 0; --定义计数器变量 v_empno NUMBER(4) := 7888; --定义员工编号 BEGIN SELECT COUNT(1) --首先查询指定的员工编号是否存在 INTO v

[.net 面向对象编程基础] (7) 基础中的基础——修饰符

[.net 面向对象编程基础] (7) 基础中的基础——修饰符 在进入C#面向对象核心之前,我们需要先对修饰符有所了解,其实我们在前面说到变量和常量的时候,已经使用了修饰符,并且说明了变量和常量的修改符. 首先 修饰符是什么? 修饰符用于限定类型以及类型成员的申明 从定义可以看出按功能分为两类:访问修饰符和声明修饰符 1.访问修饰符 (5个) 访问修饰符 说明 public 公有访问.不受任何限制. private 私有访问.只限于本类成员访问,子类,实例都不能访问. protected 保护访

[.net 面向对象编程基础] (6) 基础中的基础——运算符和表达式

[.net 面向对象编程基础] (6) 基础中的基础——运算符和表达式 说起C#运算符和表达式,小伙伴们肯定以为很简单,其实要用好表达式,不是一件容易的事.一个好的表达式可以让你做事半功倍的效果,比如三元表达式,可以让你少写N多个if和case语句. 表达式 由 操作数(operand) 和 运算符(operator) 构成 1.运算符分类和优先级   2.运算符按操作数目分类   一元运算符 -x  x++  x-- 二元运算符 x+y  x+1 三元运算符 c ? x : y 3.表达式分类

[.net 面向对象编程基础] (3) 基础中的基础——数据类型

[.net 面向对象编程基础] (3) 基础中的基础——数据类型 关于数据类型,这是基础中的基础. 基础..基础..基础.基本功必须要扎实. 首先,从使用电脑开始,再到编程,电脑要存储数据,就要按类型存储,就跟我们买来衣服一样,裤子,上衣,冬天的,夏天的,总要分个类再放进衣柜中一样.越是高级的编程语言,数据类型分的越详细. 问题来了,这么多数据类型,我们是不是要一一记住,这真不是个省心的事情,不用怕,不用怕,不用怕,重要的事情说三遍. 重点:C#就15种数据类型. 准确来说,是15种预定义类型,

[.net 面向对象编程基础] (7) 基础中的基础——流程控制语句

[.net 面向对象编程基础] (7) 基础中的基础——流程控制语句 本来没有这一节的内容,后来考虑到既然是一个系列文章,那么就尽可能写的详细一些,本节参考了网上朋友所写的例子,为的是让更多小伙伴学习,提高,加薪,如有版权问题,请邮件我,我第一时间处理. 语句:是程序中的小指令,本节主要以流程控制语句为主要内容. 流程控制语句中最常用的三个是 选择语句(即条件语句).循环语句和异常处理语句 流程控制语句分类: 类别    关键字 选择语句  if.else.switch.case 循环语句  d

[.net 面向对象编程基础] (5) 基础中的基础——变量和常量

[.net面向对象编程基础]  (5) 基础中的基础——变量和常量 1.常量:在编译时其值能够确定,并且程序运行过程中值不发生变化的量. 通俗来说,就是定义一个不能改变值的量.既然不能变动值,那就必须在定义的时候初始化. 示例: 语法: const 类型名 常量名=常量表达式; 例子: const string  cat="hello cat"; 2.变量:具有变量名和值的对象 变量就是把数据暂存于某处内存中,以备调用. 示例: 声明:[访问修饰符] 类型 变量名[=初值][变量名=[

NET CLR via C#(第4版)第4章 类型基础

本章内容: 1 所有类型都从System.Object派生 2 类型转换 3 命名空间和程序集 4 运行时的相互关系 本章讲述使用类型和CLR时需掌握的基础知识.具体地说,要讨论所有类型都具有的一组基本行为. 讨论类型安全性.命名空间.程序集.以及如何将对象从一种类型转换成另一种类型. 本章最后会解释类型.对象.线程栈和托管堆在运行时的相互关系. 4.1 所有类型都从System.Object派生 CLR要求每个类型最终都从System.Object类型派生.从而确保类每个对象都具备一组最基本的

【冒号课堂】【阅读笔记】OO的多态(2)抽象类型——实中之虚

抽象类型——实中之虚 1.具体类型与抽象类型的区别是什么? 2.抽象数据类型与抽象类型的区别是什么? 3.除接口与抽象类外,还有其他抽象类型吗?它们各有何特点和意义吗? 4.抽象类型的主要作用是什么? 5.在系统中应采用何种类型作为模块之间通信的数据类型? 6.接口是为客服(JAVA C#中)抽象类不能多重继承的缺点吗? 7.接口与抽象类在语法和语义上各有什么不同? 8.标记接口有何作用?

asp.net基础中Get和Post的区别。

1.可以设定form的Method属性制定表单提交方式,Get(默认值)是通过URL传递表单值,post传递的表单只是隐藏到http报文中,url中看不到. 2.Get和Post的区别:get是通过url传递表单值,post通过url看不到表单域的值:get传递的数据量有限,如果要传递大量数据不能用get,比如type="file"上传文章,type="password"传递密码或者<textarea>发表大段文章,post没有这个限制:post会有浏览

删除string类型字符串中指定字符串段

1.实现背景 在插入list行时用邮件的MessageID给对应行命名. 在回复全部邮件时,收件人变为之前收件人中出去“自己”同时加入之前发件人,抄送人还是之前的抄送人,密送人不用管,直接不用带. 在“回复全部”按钮响应函数里面 CListUI* pList = static_cast<CListUI*>(m_PaintManager.FindControl(_T("middle_comlumn_header1")));//拿到list控件指针            int