CH03-深入CSharp的数据类型

  1. 理解C#中的值类型和引用类型的概念
  2. 理解装箱和拆箱操作
  3. 理解值类型和引用类型作为方法参数的区别
  4. 值类型转换成引用类型传参的关键字ref和out
  5. 理解C#中的结构
  6. 理解C#中的枚举
  7. 理解C#中的可空类型
  8. 理解C#中的静态类型var和动态类型dynamic

本章内容

1、C#中的值类型和引用类型

  1. 数据类型的分类:

    数据类型按存储方式可分为两类:值类型和引用类型

  2. 值类型的概念:

    值类型:不同的变量会分配不同的存储空间,存储空间中存储的是该变量的值,改变一个变量值不会影响另一个变量值。

    案例:】张三和李四去年身高都是170cm,今年李四长到了180cm,张三没有变化,输出去年和今年两人身高。

    分析:定义两个变量保存身高,修改变量的值,输出变量的值,观察最终结果。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    int zhangSan = 170;
    int liSi = zhangSan;
    Console.WriteLine("去年张三和李四的身高分别为:{0},{1}",zhangSan,liSi);

    //今年李四长到180
    liSi = 180;
    Console.WriteLine("今年张三和李四的身高分别为:{0},{1}", zhangSan, liSi);

    Console.ReadLine();

    输出结果:

    1720925407571

    1720926022904

  3. 引用类型的概念:

    引用类型:赋值是把原对象的引用传递给另一个引用,两个引用指向同一块内存空间。

    引用相当于引用的是存储空间的内存地址

    案例:】张浩和李明去年身高与体重均为170cm和60kg,李明今年身高和体重变为180cm和70kg,张浩无变化。输出两人的身高和体重

    分析:如何保存同一个人的身高和体重呢?使用数组来保存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    int[] zhangSan = { 170,70};
    int[] liSi = zhangSan;

    Console.WriteLine("去年张三,李四的身高和体重分别为:{0},{1},{2},{3}", zhangSan[0], zhangSan[1], liSi[0], liSi[1]);
    //李四身体和体重发生变化
    liSi[0] = 180;
    liSi[1] = 80;

    Console.WriteLine("今年张三,李四的身高和体重分别为:{0},{1},{2},{3}", zhangSan[0], zhangSan[1], liSi[0], liSi[1]);

    Console.ReadLine();

    1720926707237

    1721029351305 问题:引用类型如何如何只复制数据(值)

    通过数组复制解决以上问题

    1
    2
    3
    4
    5
    6
    7
    int[ ] infoZhang = new int[ ]{170,60};
    int[ ] infoLi = new int[2];
    for(int i = 0; i < infoZhang.length; i++){
    infoLi[i] = infoZhang[i];
    }
    infoLi[0] = 180; //今年李明的身高变为180
    infoLi[1] = 70; //今年李明的体重变为70

    1721029622970

2、装箱和拆箱

  1. 装箱:

    将值类型转换成引用类型,称为装箱

    案例:】装箱操作

    1
    2
    int num=10;
    object obj=num;//将值类型转换成引用类型
  2. 拆箱:

    将引用类型转换成值类型,称为拆箱

    案例:】拆箱操作

    1
    2
    object obj=100;
    int num=(int)obj;//将引用类型转换成值类型

    注意事项:

    1、拆箱操作,变量的类型要一致。

    2、在实际的开发中,应该尽量减少不必要的装箱和拆箱,因为二者的存储方式不同,转换时性能损失较大

3、值类型和引用类型的方法参数传递

  1. 值类型作为参数传递

    值方式传递值类型参数,传递的是值的副本,在方法中修改数据,对原来的值不会有任何影响

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    static void Main(string[] args)
    {
    int num = 10;
    Console.WriteLine("调用方法前的值:{0}",num);
    //调用方法
    ChangeNum(num);
    Console.WriteLine("调用方法后的值:{0}",num);

    Console.ReadLine();
    }
    static void ChangeNum(int num)
    {
    num = 100;//在方法中修改参数的值

    Console.WriteLine("方法中修改参数后的值:"+num);
    }

    输出结果:

    1721033337099

  2. 引用类型作为参数传递

    引用类型作为参数传递,传递的引用的地址,则在方法中修改数据,则修改的是该地址指向的同一个空间,也就是说原来的值会发生变化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    static void Main(string[] args)
    {
    int[] nums = { 10, 20 };
    Console.WriteLine("调用方法前的值:{0},{1}", nums[0], nums[1]);
    ChageNums(nums);
    Console.WriteLine("调用方法后的值:{0},{1}", nums[0], nums[1]);
    Console.ReadLine();
    }
    static void ChageNums(int[] nums)
    {
    nums[0] = 1;
    nums[1] = 2;
    }

    输出结果:

    1721033645408

4、ref和out关键字的使用

  1. 概念:在C#中,ref和out关键字用于按引用传递变量,它们在变量传递、输出参数、返回值以及异常处理等方面有一些重要区别。

  2. ref关键字

    ref关键字在方法调用时创建了一个引用,该引用指向调用方法时传递的变量。在方法内部,可以通过引用修改变量的值,并且这些更改将反映在原始变量上。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    static void Main(string[] args)
    {
    int num1 = 10;//使用ref关键字,必须给参数赋初始值,不然出报错
    int num2 = 20;
    Console.WriteLine("调用方法前:num1:{0},num2:{1}", num1, num2);
    ChangeNum(ref num1, ref num2);
    Console.WriteLine("调用方法后:num1:{0},num2:{1}",num1,num2);
    Console.ReadLine();
    }

    static void ChangeNum(ref int num1,ref int num2)
    {
    num1 = 100;
    num2 = 200;
    }

    注意事项:

    1. 定义方法时,在形参前面指定ref,调用此方法时,必须在实参前加上ref;
    2. 在调用方法前,必须给指定ref的实参赋初始值;
    3. 指定ref参数,在方法中修改参数的值会保留。
  3. out关键字

    out关键字也在方法调用时创建了一个引用,但是与ref不同,out参数在方法内部不需要初始化。然而,out参数必须在方法返回之前被赋值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    static void Main(string[] args)
    {
    int num1 = 10;
    int num2 ;//使用out关键字,在调用方法前可以不给参数赋初始值
    Console.WriteLine("调用方法前:num1:{0},num2:{1}", num1, num2);
    ChangeNum(out num1, out num2);
    Console.WriteLine("调用方法后:num1:{0},num2:{1}",num1,num2);
    Console.ReadLine();
    }

    static void ChangeNum(out int num1,out int num2)
    {
    //定义out关键字,必须保证参数在方法中赋值(更改)
    num1 = 100;
    num2 = 200;
    }

    注意事项:

    1. 定义方法时,在形参前面指定out,调用此方法时,必须在实参前加上out;
    2. 在方法中,指定out的参数,必须保证都都赋值;
    3. 指定out参数,在方法中修改参数的值会保留。

5、C#中的结构

  1. 结构的概念?

    在 C# 中,结构是值类型数据结构。

    它使得一个单一变量可以存储各种数据类型的相关数据。

    struct 关键字用于创建结构。

  2. 定义结构的语法:

    1. 关键字:struct

    2. 语法:

      1
      2
      3
      4
      5
      6
      7
      8
      public struct 结构名{
      字段1;
      字段2

      方法(){

      }
      }
    3. 例如:创建一个学生的结构类型

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public struct Student
      {
      public string StudentName;//可以设置字段
      public int Age { get; set; }//可以添加属性

      public void SayHi()//可以定义方法
      {
      Console.WriteLine("大家好,我叫:{0},今年:{1}岁",StudentName,Age);
      }
      }

      static void Main(string[] args)
      {
      Student student = new Student();

      student.StudentName = "张三";
      student.Age = 20;
      student.SayHi();

      Console.ReadLine();
      }
  3. 结构的特点:

    1. 结构是值类型,则具有较快数据提取速度

    2. 结构可带有方法、字段、索引、属性、运算符方法和事件。

    3. 结构在使用时可以使用new,也可以不使用new,但是结构中有定义属性就必须使用new

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public struct Student{
      public string studentName;
      public int age;
      }

      static void Main(string[] args)
      {
      //Student student = new Student();//可以通过new关键字创建
      Student student;//也可以不使用new关键字创建

      student.studentName = "张三";
      student.age = 20;

      Console.ReadLine();
      }
    4. 如果在使用结构中,未使用new创建,则必须先给结构中的字段赋完初始值,才能使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public struct Student{
      public string studentName;
      public int age;

      public void SayHi()//可以定义方法
      {
      Console.WriteLine("大家好,我叫:{0},今年:{1}岁",studentName,age);
      }
      }

      static void Main(string[] args)
      {
      Student student;//未使用new关键字创建

      //student.studentName = "张三";
      student.age = 20;

      student.SayHi();//会报错,studentName未赋值

      Console.ReadLine();
      }
    5. 定义结构时,不能直接给结构中的字段赋初始值

      1
      2
      3
      4
      public struct Student
      {
      public string StudentName="";//报错:可以设置字段,但不能给字段赋初始值
      }
    6. 结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义默认的构造函数。默认的构造函数是自动定义的,且不能被改变。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      public struct Student
      {
      ~Student(){//报错:不能定义析构函数

      }
      //报错
      //public Student()//不能给结构创建默认构造函数
      // {

      //}
      public Student(string studentName)//正确,可以给结构定义带参数的构造函数
      {
      this.StudentName = studentName;
      }
      public string StudentName;//可以设置字段
      }
    7. 与类不同,结构不能继承其他的结构或类。

    8. 结构不能作为其他结构或类的基础结构。

    9. 结构可实现一个或多个接口。

    10. 结构成员不能指定为 abstract、virtual 或 protected。(因为结构不能被继承)

  4. 总结:结构和类的区别:

    1. 定义方式不同:结构体使用 struct 关键字定义,而类使用 class 关键字定义。
    2. 内存分配方式不同:结构体是值类型,它的实例被分配在栈上,而类是引用类型,它的实例被分配在堆上。
    3. 继承性不同:结构体不支持继承,而类可以继承其他类或抽象类。
    4. 针对默认构造函数的处理不同:结构体默认有一个无参的构造函数,且不能声明无参构造函数,而类如果没有显式定义构造函数,就会默认有一个无参的构造函数。
    5. 析构函数:结构体不支持析构函数,而类支持。
    6. 赋值方式不同:结构体赋值时是按值传递,即会复制一份,而类赋值时是按引用传递,即会复制一个引用。
    7. 性能不同:由于结构体的实例被分配在栈上,所以在一些情况下,使用结构体比使用类更高效,比如在大量创建小对象时。但是,结构体也有一些限制,比如它的大小不能超过 16KB。
  5. 怎么选择结构或类呢?

    当对象需要用较少的字段来表示时,可以选用结构结构是值类型,数据提取速度快但是频繁的赋值操作会占用较大空间, 在开发中多数情况下都定义为类!!!

6、C#中的枚举类型

  1. 什么是枚举?

    枚举是一组命名整型常量。枚举类型是使用 enum 关键字声明的。

    C# 枚举是值数据类型。换句话说,枚举包含自己的值,且不能继承或传递继承。

  2. 声明枚举的语法:

    1
    2
    3
    enum 枚举名{
    枚举的标识符列表;
    };

    例如:定义性别的枚举和定义颜色的枚举

    1
    2
    3
    4
    5
    6
    enum Gender{
    男,女
    }
    enum Color{
    red,blue,white,green
    }

    具体说明:

    1. 枚举列表中的每个符号代表一个整数值,一个比它前面的符号大的整数值。默认情况下,第一个枚举符号的值是 0 。
  3. 如何使用枚举:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //定义性别的枚举
    public enum Gender
    {
    男, 女
    }
    public class Student
    {
    public string StudentName { get; set; }
    public Gender Gender { get; set; }//声明枚举的属性

    public void SayHi()
    {
    Console.WriteLine("姓名:{0},性别:{1}",StudentName,Gender);
    }
    }

    internal class Program
    {

    static void Main(string[] args)
    {
    Student student = new Student();
    student.StudentName = "张三";
    student.Gender = Gender.男;//可以直接赋枚举值,比较方便

    student.SayHi();

    Console.ReadLine();
    }
    }
  4. 枚举的类型转换

    1. 枚举转化成字符串:直接调用ToString()方法。

      1
      string str=Gender.男.ToString();
    2. 字符串转换成枚举:

      1. 语法:

        1
        (枚举类型)Enum.Parse(typeof(枚举类型), 字符串);
      2. 案例:

        1
        2
        3
        4
        string gender = "男";

        Gender g=(Gender)Enum.Parse(typeof(Gender), gender);//将字符串转换成枚举类型
        Console.WriteLine(g);
    3. 枚举转成成int类型

      1
      int n=(int)Gender.男;
    4. int转换成枚举类型

      1
      2
      int n=0;
      Gender g=(Gender)n;

C#中的数据类型总结:

1721296442386

7、C#中的可空类型

  1. 什么是可空类型?

    C# 提供了一个特殊的数据类型,nullable 类型(可空类型),可空类型可以表示其基础值类型正常范围内的值,再加上一个 null 值。

  2. 可空类型的应用场景

    在处理数据库和其他包含可能未赋值的元素的数据类型时,将 null 赋值给数值类型或布尔型的功能特别有用。例如,数据库中的布尔型字段可以存储值 true 或 false,或者,该字段也可以未定义。

  3. 语法:

    1
    2
    3
    4
    5
    6
    7
    Nullable<data_type> 变量名=null;

    data_type? 变量名=null;
    例如:
    Nullable<int> num=null;

    int? num=null;
  4. Null 合并运算符( ?? )

    1. 说明:

      Null 合并运算符用于定义可空类型和引用类型的默认值。Null 合并运算符为类型转换定义了一个预设值,以防可空类型的值为 Null。Null 合并运算符把操作数类型隐式转换为另一个可空(或不可空)的值类型的操作数的类型。

      如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值。

    2. 例如:

      1
      2
      3
      4
      5
      6
      7
      8
      int? num1 = null;
      int? num2 = 10;

      int num3 = num1 ?? 100;//相当于:num3=num1==null?100:num1;
      Console.WriteLine(num3);//输出:100

      num3=num2??100;
      Console.WriteLine(num3);//输出:10

8、C#中的静态类型var

  1. 概念:

    var是根据所赋的值来推断变量的数据类型。你不需要显式指定数据类型,而是让编译器根据上下文来确定初始值的数据类型。

  2. 应用场景:

    var可以理解为匿名类型,我们可以认为它是一个声明变量的占位符。它主要用于在声明变量时,无法确定数据类型时使用。

  3. 语法:

    1
    2
    3
    var 变量名=值;
    例如:
    var num=10;//可以根据值,推断类型为int
  4. 使用var定义变量时有以下四个特点:

    1. 必须在定义时初始化。也就是必须是var s = “abcd”形式,而不能是如下形式: var s; s = “abcd”;
    2. 一但初始化完成,就不能再给变量赋与初始化值类型不同的值了。
    3. var要求是局部变量。
    4. 使用var定义变量和object不同,它在效率上和使用强类型方式定义变量完全一样。
  5. 注意事项:

    1. 无法使用var来定义一个全局变量,只能定义在方法的内部(因为预先不可知,所以预先不可置)
    2. 不能用来定义函数的签名,包括返回值,参数类别
    3. 在定义变量的时候,必须先给值,不能为null,也不能只定义不给值。
    4. 一旦一个变量被定义成var类型,并确定了指定的类型以后,不能再给这个变量其他类型的值。例: var a=”1”;a=1;(错误:无法将类型“XX”隐式转换成“YY”)

9、C#中的动态类型dynamic

  1. 概念:

    动态类型语言是指在运行时执行类型检查的语言。如果您不知道您将获得或需要分配的值的类型,则在此情况下,类型是在运行时定义的。

  2. 语法:

    1
    2
    3
    4
    dynamic 变量名 = 值;
    例如:
    dynamic str="abc";
    dynamic num=10;
  3. 注意事项:

    1. 动态类型是在运行时,再进行类型检测,则定义了动态类型后,那么您将无法获得任何智能提示。

    2. 动态类型可以定义在方法的返回类型或参数列表中,作为定义时的数据类型。

    3. 如果在操作过程中,类型不匹配,则在运行时会报错,编译时不会报任何错误,如下代码:

      1
      2
      3
      4
      5
      6
      7
      8
      static void Main(string[] args)
      {
      dynamic str = "abc";//定义的是字符串的动态类型
      str++;//对变量进行++操作,在此不会报错
      Console.WriteLine(str);

      Console.ReadLine();
      }

      运行后的效果如下:

      1721295783857

10、var和dynamic类型的对比:

C# var和dynamic的用法和理解:

var和dynamic的本质区别是类型判断的时间不同,前者是编译时,后者是运行时。

  1. var在声明变量方面简化语法(只能是局部变量),在编译时交给编译器推断。
  2. dynamic也是为简化语法而生的,它的类型推断是交给系统来执行的(运行时推断类型)。
  3. var不能用于字段、参数等,而dynamic则可以。
  4. var在初始化的时候就确定了类型。
  5. dynamic可以用于方法字段、参数、返回值以及泛型参数,把动态发挥的淋漓尽致。
  6. var是C# 3.0的产物,dynamic是C# 4.0的产物。

var和dynamic的区别和使用场景:

1、类型确定性

var是编译时类型推断,具有类型确定性。适用于静态类型的情况,其中类型不会改变。

dynamic是运行时类型推断,具有较低的类型确定性。适用于需要处理不同类型数据的情况,但需要小心类型错误。

2、类型安全性

var变量在编译时会进行类型检查,并且编译后根据上下文推断出准确的类型,因此具有更高的类型安全性。

dynamic变量在编译时不进行类型检查,而是编译后赋值为object类型,因此具有较低的类型安全性。

3、性能方面

从上面的情况可以看出var具有更好的性能,dynamic由于是运行时类型推断,性能较之var差一些。

4、适用场景

使用var当你能够在编译时确定变量类型,或者在需要显式类型声明的情况下。

使用dynamic当你需要处理不同类型的数据,或者需要动态类型的话可以使用它。

本章总结

  1. 理解值类型和引用类型的区别
  2. 掌握装箱和拆箱操作
  3. 掌握值类型和引用类型作为参数传递的区别
  4. ref和out关键字的如何使用
  5. C#中的几种特殊类型:
    1. 结构类型
    2. 枚举类型
    3. 可空类型
    4. 静态类型
    5. 动态类型

本章作业

  1. 更新会员积分

    1. 需求说明

      1. 使用数组存放五位会员的积分
      2. 系统升级,请将原有积分进行备份,然后赠送每位会员500积分,编写程序输出积分情况

      1722906750393

  2. 编写一个存储长方形(Rec)属性的结构

    1. 长(Length)
    2. 宽(Width)
    3. 实例化一个结构长方形,并计算面积
  3. 编写程序使用数组存储班级5名学员的成绩,实现如下功能

    1. 因为某次计分失误,需要为每位学员成绩提高5分
    2. 如果提分后学员成绩高于100分,按100分计
    3. 分别输出提分前和提分后每位学员的成绩

    1722906938302

  4. 生成设备ID

    1. 需求说明

      1. 为公司购买的3台不同型号的计算机进行编号
      2. 编号的规则是“计算机型号+4位随机号”
      3. 编号完毕后,输出每台计算机的信息
    2. 效果图:

      1722907024020

  5. 项目经理评分

    1. 需求说明

      1. 显示员工信息
      2. 实现项目经理给员工评分
    2. 实现思路

      1. 编写SE类查看评分窗体中创建公有成员变量保存员工信息

        SE对象数组

      2. 初始化员工信息并使用ListView控件显示

        编写Init()方法和UpdateView()方法

      3. PM类添加Judge(SE se)方法,实现为员工评分

      4. 选中某员工,双击打开评分窗体,实现事件处理方法

    3. 效果图:

      1722907208699