.NET第四章

类型:

类型体系图

基本概念:
值类型:
通常分配在线程的堆栈上,并且不包含任何指向实例数据的指针,因为变量本身就包含了其实例数据。MSDN中的定义为值类型直接包含它们的数据。
引用类型:实例分配在托管堆上,变量保存了视力数据的内存引用,MSDN中的定义为引用类型存储对值的内存地址的引用,位于堆上。

主要类型

 

嵌套结构:
指在值类型中嵌套定义了引用类型,或者在引用类型变量中嵌套定义了值类型。
引用类型嵌套值类型:类的私有字段为值类型,那么它作为引用类型实例的一部分,也分配在托管堆上。

public class NestedValueinRef 
{ 
  //aInt做为引用类型的一部分将分配在托管堆上 
  private int aInt; 
   
  public NestedValueinRef() 
  { 
   //aChar则分配在该段代码的线程上 
   char achar =' a '; 
  } 
} 

值类型嵌套引用类型:该引用类型将作为值类型的成员变量,堆栈上将保存该成员的引用,而成员的实际数据还是保存在托管堆中。

public struct NestedRefinValue 
{ 
  public MyClass myClass; 
  public NestedRefinValue(MyClass mc) 
  { 
   myClass.x=1; 
   myClass.y=2; 
   myClass=mc; 
  } 
} 
结论:值类型实例总是分配在它声明的地方,声明为局部变量时其被分配在堆栈上,声明为引用类型成员时其被分配到托管堆上;引用类型实例则总是分配在托管堆上。

若执行下段代码:
AType[] myType=new AType[10];
试问:如果AType是值类型,则分配多少内存;若为引用类型,又分配多少内存?
分析:数组本身是引用类型,内存分配于托管堆,而myType为指向托管堆的引用。不同的是值类型和引用类型数组在托管堆的布局是有区别的:如果AType为值类型,则myType将保持指向托管堆中的一块大小为4*10byte的内存地址,并且将所有的元素赋值为0;若AType为引用类型,则数组由10个引用组成,并且所有的元素被设置为null值。

通用规则与比较


通用规则:
string类型是个特殊的引用类型,因此每次对string的改变都会在托管堆中产生一个新的string变量。string类型重载了==操作符,比较的是实际的字符串,而不是引用地址。

public static void Main() 
{ 
            string aString = "123"; 
            string bString = "123"; 
            Console.WriteLine((aString == bString)); //显示为true,等价于aString.Equals(bString); 
            string cString = bString; 
            cString = "456"; 
            Console.WriteLine((bString == cString)); //显示为false,等价于bString.Equals(cString); 
} 
使用Type.IsValueType来判断一个变量的类型是否为值类型。
public struct MyStructTester{} 
public static void Main() 
{ 
            MyStructTester aStruct = new MyStructTester(); //实例化值类型 
            System.Type type = aStruct.GetType(); //实例化System类型,并将值类型赋给type 
            if (type.IsValueType) 
            { 
                Console.WriteLine("{0} belongs to value type.", aStruct.ToString()); 
            } 
} 

.NET中以操作符ref和out来标识值类型按引用方式传递。ref在参数传递之前必须初始化;out在传递之前不必初始化,且在传递时必须显式赋值。
值类型与引用类型之间的转换过程称为装箱和拆箱操作。
sizeof()运算符用于获取值类型的大小,但是不适用于引用类型。
值类型都继承自System.ValueType,而System.ValueType又继承自System.Object。其主要区别是ValueType重写了Equals方法,实现对值类型按照实例值比较而不是引用地址来比较。

char a = 'c'; 
char b = 'c'; 
Console.WriteLine((a.Equals(b)));      //输出true; 

比较
值类型继承自ValueType;而引用类型继承自System.object。
值类型变量包含其实例数据,每个变量保存了其本身的数据拷贝,因此值类型的参数传递不会影响参数本身;引用类型变量保存了其数据的引用地址,;因此以按值方式进行参数传递时会影响到参数本身,因为两个变量会引
用内存中的同一块地址。
典型的值类型:struct,enum以及大量的内置值类型;而能成为类的都可以说是引用类型。
值类型是密封的,因此值类型不能作为其他任何类型的基类,但可以单继承或者多继承接口;引用类型一般都有继承性。
值类型不具有多态性;引用类型有多态性。
值类型变量不可谓null值,值类型都会自行初始化为0;引用类型变量默认情况下,创建为null值,标识没有指向任何托管堆的引用地址。对值为null的引用类型的任何操作,都会抛出NullReferenceException异常。
值类型有两种状态:装箱和未装箱,运行库提供了所有值类型的已装箱形式;引用类型通常只有一种形式:装箱。

值类型的应用场合:
数据较小的场合,最好考虑以值类型来实现系统性能的改善。
结构简单,不必在多态的情况下,值类型是较好的选择。
以存储数据为主要目的的情况下,值类型是优先的选择。
类型的选择没有子类继承的必要,优先考虑值类型。

引用类型的应用场合:
引用类型适用于结构复杂、有继承、有多态、突出行为的场合。


类型的比较通常使用:Equals(),ReferenceEquals(),==,!=这四种方法,其中Equals()为核心方法。


值类型判等:
Equals,System.ValueType重载了System.Object的Equals方法,用于实现对实例数据的判等。     //比较两个对象是否指向相同的引用地址。
ReferenceEquals,对值类型应用将永远返回false。
==,未重载的==值类型,将比较两个值是否“按位”相等。


引用类型判等:
Equals有两种方法:
public virtual bool Equals(object obj);  虚方法,引用地址比较。
public static bool Equals(object objA,object objB); 静态方法,如果objA是与objB相同的实例,或者两者均为空引用,或者如果objA.Equals(objB)返回为true,则为true,否则为false。
ReferenceEquals:静态方法,只能用于引用类型,用于比较两个实例对象是否指向同一引用地址。
==:默认为引用地址比较。和Equals方法的区别是,在多态表现上,==是被重载,而Equals是覆写。
演示:

            Console.WriteLine("类型判等---"); 
            //值类型总是返回false,经过两次装箱的myStruct不可能指向同一地址,返回false 
            Console.WriteLine(ReferenceEquals(myStruct, myStruct)); 
            //同一引用类型对象,将指向同样的内存地址,返回true 
            Console.WriteLine(ReferenceEquals(myClass, myClass)); 
            //RefenceEquals认为null等于null,因此返回true 
            Console.WriteLine(ReferenceEquals(null, null)); 
           
            //重载的值类型判等方法,成员大小不同 
            Console.WriteLine(myStruct.Equals(myStruct2)); 
            //重载的引用类型判等方法,指向引用相同 
            Console.WriteLine(myClass.Equals(myClass2)); 

类型转换


隐式转换:由低级类型向高级类型的转换过程。包括:值类型的隐式转换,主要是数值类型等基本类型的隐式转换;引用类型的隐式转换,主要是派生类向基类的转换;值类型和引用类型的隐式转换,主要指装箱和拆箱转换。


显式转换:也叫强制类型转换。但是转换过程不能保证数据的完整性,可能引起一定得精度损失或者引起不可知的异常发生。转换的格式为:(type)(变量、表达式);
例如: int a = (int) (b+2.02);


使用explicit或者inplicit进行用户自定义类型转换:主要给用户提供自定义的类型转换实现方式,以实现更有目的的转换操作,格式为:
static 访问修饰操作符 转换修饰操作符 operator 类型(参数列表);
例如:public static explicit operator VipCustom(Custom user)

参数的基础
参数按照调用方式可以分为:形参和实参。形参就是被调用方法的参数,实参就是调用方法的参数。

       public static void Main() 
        { 
            string myString = "This is your argument."; 
            //myString是实参 
            ShowString(myString); 
        } 
         
        //astr是形参 
        private static void ShowString(string astr) 
        { 
            Console.WriteLine(astr); 
        } 

参数的基本语法:
1.形参和实参必须类型、个数与顺序对应匹配;
2.参数列表可以为空,这种函数称为无参函数;
3.Main(string[]args),Main函数的参数可以为空,可以为string数组,其作用是接受命令行参数,例如在命令行下运行程序时,args提供了输入命令行参数的入口。

传递的基础:


泛型类型参数:可以是静态的,例如MyGeneric ;也可以使动态的,例如MyGeneric 中的T可以是任何类型的变量,在运行期动态替换为相应的类型参数。泛型类型参数一般以T开头来命名。


可变数目参数:使用params修饰符可以方便地提供对于参数个数不明确的情况的实现。
       params用于提示编译器实现对参数进行数组封装,将可变数目的控制由编译器来完成,形如:
       static void ShowAgeSum(string team,params int[] ages){……}
       params修饰的参数必须为一维数组,可以是任何类型,因此,如果需要接受任何类型的参数时,只要设置数组类型为object即可。params必须在参数列表的最后一个,并且只能使用一次。

传递的分类讨论:


1.值类型参数的按值传递:
值类型实例传递的是该值类型实例的一个拷贝,被调用方法操作的是属于其本身的实例拷贝,因此不影响原来调用方法中的参数值。


2.引用类型参数的按值传递:值 为对象引用,因此传递的是引用地址拷贝,会改变原来对象的引用指向。

 
3.引用类型参数的按引用传递:传递的是参数的地址。如果参数为值类型,则传递的是该值类型的地址;如果参数为引用类型,则传递的是对象引用的地址。


4.string类型的特殊性:string本身为引用类型,当它按值传送时表现出特殊的情况。

public static void RunSnippet() 
{ 
     string str = "Old String"; 
            ChangeStr(str); 
            Console.WriteLine(str); //输出Old String 
} 

static void ChangeStr(string aStr) 
        { 
            aStr = "Changing String"; 
            Console.WriteLine(aStr);//输出Changing String 
        } 

原理:执行string str = "Old String"后,在堆栈中产生str指向Old String;执行ChangeStr(str)后,在堆栈中产生astr,并将str的地址指针拷贝给astr使其指向Old String;执行aStr = "Changing String"后将astr的指向修改为新的Changing String

5.值类型和引用类型,按引用传递必须以ref或者out关键字来修饰,规则是:方法定义和方法调用必须同时显式的使用ref或者out;允许通过out或者ref参数来重载方法。
不论参数本身是值类型还是引用类型,按引用传递时,传递的是参数的地址,也就是实例的指针。

public static void RunSnippet() 
{ 
     int i = 100; 
            string str = "One"; 
            ChangeByValue(ref i); 
            ChangeByRef(ref str); 
            Console.WriteLine(i);//200 
            Console.WriteLine(str);//One more 
} 

  static void ChangeByValue(ref int iVlaue) 
        { 
            iVlaue = 200; 
     Console.WriteLine(iVlaue);//200 
        } 
        static void ChangeByRef(ref string sValue) 
        { 
            sValue = "One more."; 
            Console.WriteLine(sValue);//One more 
        } 

6.不允许通过区分ref和out来实现方法重载

        static void ShowInfo(string str) 
        { 
            Console.WriteLine(str); 
        } 
        static void ShowInfo(ref string str) 
        { 
            Console.WriteLine(str); 
        } 
        //ShowInfo不能定义仅在 ref 和 out 上有差别的重载方法 
        static void ShowInfo(out string str) 
        { 
            str = "Hello, anytao."; 
            Console.WriteLine(str); 
        } 

不同点:ref的参数必须是一个实际的对象,而不能指向NULL;而使用out的参数可以接受指向null的对象,然后在嗲用方法内部必须完成对象的实体化。

装箱与拆箱


宏观:从类型转换角度来说,装箱与拆箱是值类型与引用类型之间的桥梁,实现二者间的自由转换:装箱就是值类型数据转化为无类型的引用对象,通常这种转换主要指转换为System.Object类型或者该值类型实现的任何接口引用类型;拆箱就是引用类型转换为值类型,通常伴随着从堆中赋值对象实例的操作。


微观:装箱操作,在托管堆中分配新创建对象的内存,并将值类型的字段拷贝到该内存中,然后返回新对象的地址;拆箱操作,获取已装箱对象中来自值类型部分字段的地址。


雷区:装箱和拆箱并非完全对称的互逆操作,拆箱在执行上并不包含字段的拷贝过程。而引用类型就无所谓装箱拆箱,其本身就在“箱子”里。
            只有被装过箱的对象才能被拆箱,而并非所有的引用类型。将并非装箱而来的引用类型强制转换为值类型,将抛出InvalidCastException异常。


规则:类型一致,拆箱必须保证执行后的结果是原来未装箱时的类型,否则将抛出InvalidCastException异常。
            装箱与拆箱主要是针对值类型而言的,引用类型总是以装箱形式存在的。
            装箱和拆箱分为显式转换和隐式转换两种情况,在实际的编码中应该警惕隐式转换带来的性能与异常问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值