zoukankan      html  css  js  c++  java
  • 基础系列(2)—— 常用数据类型

    一、数据类型初探

         电脑是由什么来存储所使用的数据? 这个问题用一句话比较笼统的概括,那就是:电脑使用内存来记忆计算时所使用的数据。在现实生活中的数据各种各样,整数、小数、字符串、字符等等,它们的类型是不一样的,所以你要想在计算机中使用这些类型,就必须在内存中为它申请一块合适的空间。

         数据类型总结起来有以下几点:

      (1)C程序是一组函数和数据类型,C++程序是一组函数和类,而C#程序是一组类型声明;

      (2)类型是一种模板:模板本身不是数据结构,但它详细说明了由该模板构造的对象的特征;

      (3)C#提供了16种预定义类型:13种简单类型(数值类型:int,float,double,decimal等;非数值类型:bool,char),3种非简单类型(object,string,dynamic);

           所有的预定义类型都直接映射到底层的.NET类型。C#的类型名称其实就是.NET类型的别名,所以使用.NET的类型名称也符合C#语法,不过并不鼓励这样做。在C#程序中,应当尽量使用C#类型名称而不是.NET类型名称;

           (4)除了上面提到的16种预定义类型外,还可以创建自己的用户定义类型,一共有6种用户定义类型可以由用户自己创建,它们是:类(Class)、结构体(Struct)、数组(Array)、枚举(Enum)、委托(Delegate)和接口(Interface);

     二 数据类型详解

    (一)整型

     

    (二)浮点类型  

        float数据类型用于较小的浮点数,因为它要求的精度较低。

        double数据类型比float数据类型大,提供的精度也大一倍(15位)。

        如果在代码中没有对某个非整数值(如12.3)硬编码,则编译器一般假定该变量是double

        如果想指定该值为float,可以在其后加上字符F(或f),如:float f = 12.3F;

    (三)decimal类型

        decimal类型专门用于进行财务计算,使用decimal类型提供的28位的方式取决于用户。

        要把数字指定为decimal类型,可以在数字的后面加上字符M或(m),如:decimal d=12.30M;

      (四)bool(布尔)类型

     

      (五)char字符类型

     

         char类型的字变量是用单引号括起来的,如'A'。如果把字符把在"A"(双引号)内,编译器会把它看作是字符串,从而产生错误。

     (六)引用类型(Object类型和字符串类型)

     

    三 数据类型存储双雄:栈和堆

    (一) 栈

             (1)栈是一个内存数组,是一个LIFO(Last In First Out,后进先出)的数据结构。

      (2)栈存储几种类型的数据:某些类型变量的值(主要是值类型);程序当前的执行环境;传递给方法的参数;

      (3)栈具有几种显著的特征:数据只能从栈顶插入和删除;将数据放到栈顶叫做入栈;将数据从栈顶移除叫做出栈

    (二)堆

      (1)堆是一块内存区域,在堆里可以分配大块的内存用于存储某类型(主要是引用类型)的数据对象;与栈不同,堆里的内存能够以任意的顺序插入或移除;

           (2)堆中的数据不能显示地删除,CLR中的自动GC(Garbage Collector,垃圾收集器)会自动清除无主(判断程序代码是否将不再访问某数据项的时候)的堆内存对象。因此,我们可以骄傲地说:妈妈再也不用担心我的垃圾了。

     四、值类型和引用类型:屌丝和高富帅

      (一)值类型:只需要一段单独的内存,用于存储实际的数据;TIP:对于值类型,数据存放在栈里;(byte,int,long,float,double,struct,enum等)

     

      (二)引用类型:需要两段内存,第一段存储实际的数据,它总是位于堆中;第二段是一个引用,指向数据在堆中的存放位置;TIP:对于引用类型,实际数据存放在堆里,而引用存放在栈里。(object,string,dynamic,class,interface,delegate,array)

     

      (三)引用类型对象的数据始终存放在堆里,无论它们是值类型还是引用类型。

     

    五 、 数据类型转换

    数据类型在一定的条件下是可以相互转换的,如将int型数据转换成double型数据。C#允许使用两种转换方式:隐式转换和显式转换。

    (一)隐式转换

    隐式转换:从类型A到类型B的转换可以在所有情况下进行,执行转换的规则非常简单,可以让编译器执行转换。隐式转换不需要做任何工作,也不需要另外编写代码。如将int型数据转换成double型数据:

    int a = 10;double b = a;//隐式转换

    隐式转换规则是:任何类型A,只要其取值范围完全包含在类型B的取值范围内,就可以隐式转换为类型B。基于这个转换规则,C#的隐式转换不会导致数据丢失。需要注意的是我们最常用的简单类型bool和string没有隐式转换。

    (二)显式转换

    显式转换:从类型A到类型B的转换只能在某些情况下进行,转换规则比较复杂,应进行某种类型的额外处理。显式转换又叫强制类型转换,显式转换需要用户明确的指定转换类型。如将double类型数据转换成int类型数据:        

     double c = 10.5;
     int d = (int)c;//显示转换

    提醒:

    (1)、显式转换可能会导致错误。进行这种转换时编译器将对转换进行溢出检测。如果有溢出说明转换失败,就表明源类型不是一个合法的目标类型。无法进行类型转换。

    (2)、强制类型转换会造成数据丢失,如上面的例子中,最终得到的d值为10。

    (三)通过方法进行类型转换

    (1)、使用ToString()方法。所有类型都继承了Object基类,所以都有ToString()这个方法(转化成字符串的方法)。

    (2)、通过int.Parse()方法转换,参数类型只支持string类型。注意:使用该方法转换时string的值不能为为NULL,不然无法通过转换;另外string类型参数也只能是各种整型,不能是浮点型,不然也无法通过转换 (例如int.Parse("2.0")就无法通过转换)。

                int i;
                i = int.Parse("100");

    (3)、通过int.TryParse()方法转换,该转换方法与int.Parse()转换方法类似,不同点在于int.Parse()方法无法转换成功的情况该方法能正常执行并返回0。也就是说int.TryParse()方法比int.Parse()方法多了一个异常处理,如果出现异常则返回false,并且将输出参数返回0。          

     int i;
     string s = null;
     int.TryParse(s,out i);

    (4)、通过Convert类进行转换,Convert类中提供了很多转换的方法。使用这些方法的前提是能将需要转换的对象转换成相应的类型,如果不能转换则会报格式不对的错误。注意:使用Convert.ToInt32(double value)时,如果 value 为两个整数中间的数字,则返回二者中的偶数;即 4.5 转换为 4,而 5.5 转换为 6。

    (5)、实现自己的转换,通过继承接口IConventible或者TypeConventer类,从而实现自己的转换。

    六、值传递

      (一)定义:

         值类型在复制的时候,传递就是这个值得本身,例如:A同学的笔记复印了一份,给B同学,B同学任意修改笔记的内容,A同学都不会受到影响。

     

    (二)例子:

                int n1 = 10;
                int n2 = n1;
                n2 = 20;
                Console.WriteLine(n1);
                Console.WriteLine(n2);
                Console.ReadKey(); //  输出结果:n1=10;  n2=20;      

    分析:变量n1在栈区开辟一块空间,其地址为0x000000e4a99fe094 ,并且为其赋初值10;

              变量n2在栈区也开辟了一块空间,其地址为0x000000e4a99fe090,并n1值赋给了 n2,n2:10

              变量n2的值被重新赋值20,n2:20

    (三)内存代码:

                &n1    0x000000e4a99fe094    n1: 0
                &n1    0x000000e4a99fe094    n1: 10
                &n2    0x000000e4a99fe090    n2: 0
                &n2    0x000000e4a99fe090    n2: 10
                &n2    0x000000e4a99fe090    n2: 20

    由此可以看出:n1和n2在栈上的内存地址并不一样,所以在改变任意其中的而一个值,另一个并不受其影响

    (四) 内存示意图:

     

    七 、引用传递

    (一)定义:

          引用类型在复制的时候,传递的是对这个对象的引用

    (二)例子:         

      Person p1 = new Person();
      p1.Name = "张三";
      Person p2 = p1;
      p2.Name="李四";
      Console.WriteLine(p1.Name);
      Console.WriteLine(p2.Name);//   输出结果:李四 李四

    分析:

          变量p1在栈中开辟一块空间,地址为:0x00000095e5efdfd ,并且在堆中也有一块空间,存放的是new Person();这个对象,其内存空间地址为:0x0000000000000000,并且为p1.name赋值张三

         变量p2在栈中也开辟一块空间,地址为:0x00000095e5efdfc8  ,此时,把p1对象赋值给p2对象,即p2也指向new Person()开辟的这块空间,其内存空间地址为:0x0000000000000000,随后,我们把p2.name赋值为 李四,那么由于p1.name也指向这块空间,所以,p1.name=”李四”

    (四)内存代码:                        

     &p1  0x00000095e5efdfd   p1: 0x0000000000000000
     &p2  0x00000095e5efdfc8   p2: 0x0000000000000000

    (五)内存示意图:

     八 、特殊的引用类型

      例子:      

                string s1 = "张三";
                string s2 = s1;
                s2 = "李四";
                Console.WriteLine(s1)
                Console.WriteLine(s2);

    结果:s1=张三 s2=李四  string虽然为引用类型,但是string具有不可变性

    九 、方法的参数类型

    (一) 两类参数的定义:

    实参:具有实际意义的参数,一般在调用方法时,在方法的括号里面传递的参数为实参

    形参:在方法声明时,方法名后面括号里面的参数为形参,一般(值传递)形参是接收实参传过来的值

    (二) out参数:

    1、定义:

           在一个方法需要返回多个参数的时候,一般在参数列表里面声明out类型的参数,以便输出多个返回值。其中,out参数只负责把结果输出,不负责输入参数。一般out声明的变量需要在方法体内部赋值

    2、例子:

    class Program
        {
            static void Main(string[] args)
            {
                int[] ShuZi = { 1,2,3,4,5,6};
                int max1;
                int min1;
                int sum1;
                JiSuan(ShuZi,out max1,out min1,out sum1);
                Console.WriteLine("数组的最大值为:{0},数组的最小值为:{1},数组的和为:{2}",max1,min1,sum1);
                Console.ReadKey();
            }
            public static void JiSuan(int []number,out int max,out int  min,out int sum)
            {
                max = number[0];
                min = number[0];
                sum = 0;
                for (int i = 0; i < 6; i++)
                {
                    sum += number[i];
                    if (number[i] > max)
                    {
                        max = number[i];
                    }
                    else
                    {
                    min=number[i];
                    }
                }
            }
        }

    输出结果为:最大值:6,最小值:1,和为21 

    3 注意

       out指定的参数必须在函数定义的时候就赋初值。否则则出现错误。对比ref指定的参数则可以不在函数内部进行赋初值,在函数调用时候再赋初值也可以。

    (二)ref参数

    1、定义:

    ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。也即是说,在方法中对参数的设置和改变将会直接影响函数调用之处(代码①及②)。无论是函数的定义还是调用时均不可忽略关键字ref.可以对比代码:
    代码①:
    复制代码
     1 class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             Program pg = new Program();
     6             int x = 10;
     7             int y = 20;
     8             pg.GetValue(ref x, ref  y);
     9             Console.WriteLine("x={0},y={1}", x, y);
    10 
    11             Console.ReadLine();
    12           
    13         }
    14 
    15         public void GetValue(ref int x, ref int y)
    16         {
    17             x = 521;
    18             y = 520;
    19         }
    20     }
    复制代码
    运行结果为

     

    代码②:

    复制代码
     1 class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             Program pg = new Program();
     6             int x = 10;
     7             int y = 20;
     8             pg.GetValue(ref x, ref  y);
     9             Console.WriteLine("x={0},y={1}", x, y);
    10 
    11             Console.ReadLine();
    12           
    13         }
    14 
    15         public void GetValue(ref int x, ref int y)
    16         {
    17             x = 1000;
    18             y = 1;
    19         }
    20     }
    复制代码

    由代码① 和②的运行结果可以看出,在方法中对参数所做的任何更改都将反映在该变量中,而在main函数中对参数的赋值却没有起到作用,这是不是说明不需要进行初始化呢?来看第二点

    2 注意
    ref定义的参数在使用前必须初始化,无论在函数定义时候参数有没有赋予初值。这条正好区分out指定的参数可以不在调用函数的时候进行初始化。
    来看代码③ 以及其运行结果:
    复制代码
     1  class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             Program pg = new Program();
     6             int x;
     7             int y;        //此处x,y没有进行初始化,则编译不通过。
     8             pg.GetValue(ref x, ref  y);
     9             Console.WriteLine("x={0},y={1}", x, y);
    10 
    11             Console.ReadLine();
    12           
    13         }
    14 
    15         public void GetValue(ref int x, ref int y)
    16         {
    17             x = 1000;
    18             y = 1;
    19         }    
    20     }
    复制代码

    出现的错误为:使用了未赋值的局部变量“x”,“y”。故可以说明ref指定的参数无论在函数定义的时候有没有赋予初值,在使用的时候必须初始化。

    (四)总结

    ref和out使用时的区别是:
    ①:ref指定的参数在函数调用时候必须初始化,不能为空的引用。而out指定的参数在函数调用时候可以不初始化;
    ②:out指定的参数在进入函数时会清空自己,必须在函数内部赋初值。而ref指定的参数不需要。

    参考资料:《c#图解教程》

  • 相关阅读:
    7年.NET面试Java的尴尬历程
    服务挂后Dump日志
    并发中如何保证缓存DB双写一致性(JAVA栗子)
    如何通过Visual Studio来管理我们的数据库项目
    无需Get更多技能,快速打造一个可持久化的任务调度
    Dapper Use For Net
    2014年——新的开始,新的人生
    途牛网站无线架构变迁实践
    windows 下解决 Time_Wait 和 CLOSE_WAIT 方法
    System.Data.DbType 与其它DbType的映射关系
  • 原文地址:https://www.cnblogs.com/wyh19941210/p/7469846.html
Copyright © 2011-2022 走看看