• C#基础知识


    1       C#基础知识

    1.1     简介

    C#是一门纯面向对象的新语言,这门语言是专为.NET这个平台开发的。它不仅是语言,也是.NET平台不可缺少的组成部份。C#提高了安全性,同时还支持组件对象模型(COM)和基于Windows的API。C#还允许有限制的使用本机指针。

    本章主要探讨C#中的基本编程构造和基本数据类型,简单说说装箱和取消装箱的概念,最后讲下怎样编写和编译简单的C#程序。

    1.2     C#程序的基本流程

    请研究下面的示例1,这是一个使用C#编写的“Hello World”程序,运行后将会在屏幕上显示出“Hello World”。现在我们来分析这个程序。

    示例1:

    /*这是我的第一个C#程序*/

    using System;

    class myFirst

    {

    public static void Main()

    {

    Console.WriteLine(“Hello World”);

    }

    }

    第一行:此行为注释,注释以“/*”开头,以“*/”结尾。注释可以包含多个行。

    第二行:using System;些行与C/C++中的#include语句非常相似。在这里using是导入指令,System是命名空间。我们可以将命名空间视为一组类,System类是所有类的基类,包含大多数应用程序与操作系统进行交第操作时所需要用到的类。请注意C#每条语句后都有一个“;”号。C#中的所有代码行都必须以分号结尾。

    第三行:class myFirst中的class是关键字,代表我们定义了一个类,这个类的名字叫做myFirst。基本语法class<name>{},“{”与“}”之间为类的作用域,为了养成良好的编程习惯,在定义类以后就跟上{},以免忘记。

    第五行:public static void Main()函数是C#程序的入口点,我们也称为主函数。即,程序开始执行后首先调用的函数是Main()函数。Public访问修饰符代表此函数声明为公共作用域,因此可以从程序中的任何位置对它进行访问。static为静态成员(稍后章节我们会对它进行详细的讨论),void代表此函数不返回任何值。特别注意C#是严格区分大小写。在此行代码中所有关键字都使用小写字母,只有Main()函数的首字母大写。例如:using与Using是完全不同的。

    第七行:这一行为主函数的内容,我们也用{}把它们包含在这个主函数的作用域中,Console.WriteLine(“Hello World”);调用了Console类中的WriteLine方法,并将文本“Hello World”作为参数传递。WriteLine函数将文本显示在控制台或DOS窗口中。请注意:Console是属于System命名空间。如果我们没有导入命名空间,即没有写using System;那么我们将以下列方式实现文本打印。

    System.Console.WriteLine(“Hello Word”);

    那么我们为什么要预先导入命名空间呢?因为使用完全限定名称表示对象可能很乏味,而且易引发错误。为了减轻这一负担,C#提供了using指令。一个程序中可以导入多个命名空间,但必须在文件头部指定。

    以上程序只用于帮助理解C#中的程序执行的基本流程。本章后面的部份将更细的讨论C#。

    1.3     C#中的变量声明

    C#中的变量声明格式:

    AccessModifer DataType VariableName;

    AccessModifer:访问修饰符

    DataTyep:变量类型

    AriableName:变量名

    各修饰符的访问级别如表1.1

    表1.1:C#的访问修饰符

    访问修饰符

    描述

    Public

    使成员可以从任何位置访问

    Protected

    使成员可以从声明它的类及其派生类内部访问

    private

    使成员仅可以从声明它的类内部访问

    internal

    使成员仅可以从声明它的程序集内部访问

    表1.2:C#数据类型

    C#数据类型

    描述

    Int

    声明存储整数值的变量

    String

    声明存储字符串值的变量。

    float

    声明存储实数值(包括整数和小数部份)的变量

    变量命名

    示例2:

    using System;

    class Test

    {

    static void Main()

    {

    string @string()

    @string=“此例中string是一个关键字,但在本例中作用一个变量名”;

    Console.WriteLine(@string);

    }

    }

    注意:上面的示例中声明了一个string类型的变量,因为string是关键字,所以我们在变量前加上前缀@以表示这是一个变量名。

    与C#中的变量相关的另一功能是,创建静态变量、类实例变量和数组元素时,会自动为其指定一个默认值。请研究下列示例:

    示例3:

    using System;

    class Test

    {

    static void Main()

    {

    int[] array1 = new int[5];

    Console.WriteLine (10 * array1[2]);

    }

    }

    以上示例输出为0

    表1.3:数据类型及其默认值

    类型

    默认值

    Numeric(int、float、short)

    0

    bool

    False

    char

    ‘\0’

    enum

    0

    引用

    null

    对于所有其它变量,编译器将引发错误。因此,在编程的时候尽量在使用变量前就为其赋值。

    1.4     C#中的基本输入与输出

    在执行C#的基本输入和输出的操作时,需要使用System命名空间的Console类的方法。

    两个最常用的方法如下所示。

    Console.WriteLine()

    Console.ReadLine()

    示例1中已经使用了Console.WriteLine。但是Console.WriteLine的功能十分强大,可以用它在显示文本之前设置文本的格式。它还带有附加参数、占位符,指定显示方式。

    请仔细研究下面的示例4。

    示例4:

    using System;

    class Teest

    {

    static void Main()

    {

    int number,result;

    number=5;

    result=100 * number;

    Console.WriteLine(“数字100乘上{0}时,结果为{1}”,number , result);

    }

    }

    示例输出为:数字100乘上5时,结果为500

    示例5使用Console.ReadLine()

    示例5:

    using System

    class InputStringTest

    {

    static void Main()

    {

    string input;

    Console.WriteLine(“请输入数字:”);

    input=Console.ReadLine();

    Console.WriteLine(“{0}”,input);

    }

    }

    示例5中的ReadLine()方法可读取回车符之前的所有字符。输入将以字符串的形式返回。

    1.5     C#中的判断语句

    C#中的条件检查与C中相同,可以使用if结构执行条件分支。请记住,C#语法中,条件永远都是boolean值语法如下:

    If(expreession)

    {

    //表达式的结果为true时执行的语句

    }

    else

    {

    //表达式的结果为false时执行的语句

    }

    代码段1:

    string str=”条件换算”;

    if(str)

    {

    System.Console.WriteLine(“判断值为True”);

    }

    if(str = = “条件换算”)

    {

    System.Console.WriteLine(“判断值为 True”);

    }

    else

    {

    System.Console.WriteLine(“判断值为 False”);

    }

    当编译遇到第一个if语句时,将生成下列错误:

    错误CS0029:无法将类型“string” 隐式转换为 “bool”

    这个错误的原因是第一个if语句的表达式str的计算结果不是Boolean值。

    swich结构的语法如下:

    switch(weekday)

    {

    case 1:

    Console.WriteLine(“星期一”);

    break;

    case 2:

    Console.WriteLine(“星期二”);

    break;

    case 3:

    Console.WriteLine(“星期三”);

    break;

    default:

    Console.WriteLine(“默认星期日”);

    break;

    }

    以上语法与C基本想同,只有一个增强功能,它允许将switch结构与字符串一起使用。

    1.6     C#中的循环结构(也称迭代结构)

    C#提供了下列循环结构类型。

    while循环

    do循环

    for循环

    foreach循环

    除foreach循环外,其它的所有类型都与C中的相应类型相似。以下为各种循环结构的解释。

    while循环

    while循环执行一组语句,直至指定的条件为false为止。循环条件要求boolean条件。

    语法如下:

    while(condition)

    {

    //语句

    }

    可在while循环中指定break语句跳出循环。continue语句可用于跳过当前这次循环进入下一次循环。

    do循环

    do循环与while循环非常相似,不同之处是在while循环中,首先计算条件,然后执行语句,而在do循环中,条件是在循环第一次结束时计算的。在do循环中,至少要执行一次循环才会检查条件。语法如下:

    do

    {

    //语句

    }while(condition)

    break和continue语句也可以用在do循环中。

    for循环

    for循环与其它的循环区别不大。循环变量可以作为for语句的一部份进行声明。

    语法如下:

    for(intialization;condition;increment/decrement)

    {

    //语句

    }

    例子:

    for( int i ; i <=1;i++)

    {

    Console.WriteLine(i);

    }

    变量必须是整型

    foreach循环

    foreach循环是C#中嫁接自VB的概念,该结构常用于能通过集合或数组来循环。

    语法如下:

    foreach(Type Identifier in expression)

    {

    //语句

    }

    请仔细研究下面的示例6:

    示例6:

    using System;

    public class ForEachLoop

    {

    static void Main (String [] args)

    {

    int index;

    String [] array1=new String [3];

    For (index=0;index<3;index++)

    {

    array1[index]=args[index];

    }

    foreach (String strName in array1)

    {

    Console.WriteLine (strName);

    }

    }

    }

    输入参数one two ABC得到one two ABC

    for循环通过数组args循环,并将其中包含的值指定给数组array1。然后foreach循环通过数组array1循环,并将其中所包含的值一次一个的指定给变量strName。

    1.7     C#的构造函数

    构造函数是特殊的方法,与包含它们的类同名。创建类实例时,首先执行构造函数。下面是语法。

    class MyConstructorEx

    {

    public MyConstructorEx()

    {

    //MyConstructorEx构造函数

    }

    }

    C#中的析构函数

    C#中的析构函数与构造函数的编写方式相同,它们与类同名,不同之外是有一个颚化(~)符号作为前缀。那么什么时候会执行析构函数呢?在类的实例被删除或超出程序的作用域时,将执行析构函数。

    代码段4:

    class MyConstructorEx

    {

    public MyConstructorEx()

    {

    //MyConstructorEx构造函数

    }

    public ~MyConstructorEx()

    {

    //~MyConstructorEx析构函数

    }

    }

    注意:C#中的析构函数虽然与C++中的定义相同,但是行为不同,C#中它们由垃圾回收器调用。垃圾回收器是CLR(.NET运行库)的一种服务。

    1.8     C#中数据类型的分类

    C#中的数据类型分为两个基本类别,即值类型和引用类型。值类型表示实际数据,存储在堆栈中,而引用类型则表示指向该数据的指针或引用,存储在堆上。Int,char,结构等都是值类型。类、接口、数组和字符串都是引用类型。

    Using System;

    Class DataTypeTest

    {

    static void Main()

    {

    int variableVal=100;

    funcTest(variableVal);

    Console.WriteLine(variableVal);

    }

    static void funcTest(int variableVal)

    {

    int tempVar=10;

    variableVal=tempVar * 20;

    }

    }

    输出结果为:100

    因为变量variableVal的值在函数funcTest()中被修改后,没有反应到Main()函数。出现这种情况是因为int为值类型,因此当传递至函数funcTest()时,仅传递一个变量的值的副本。

    示例8:

    using System;

    class DataTypeTest

    {

    public int variableVal;

    }

    class DataTypeTestRef

    {

    static void Main()

    {

    DataTypeTest dataTest=new DataTypeTest();

    dataTest.variableVal=100;

    funcDataTypeTest(dataTest);

    Console.WriteLine(dataTest.variableVal);

    }

    static void funcDataTypeTest(DataTypeTest dataTest)

    {

    int tempVar=10;

    dataTest.variableVal=tempVar * 20;

    }

    }

    输出结果:200

    这是因为这一次传递至函数的参数是一个对象。这个对象属于引用类型,因此传递该对象时,传弟的是对象的引用(也称地址),而不是其值的副本。所在以函数内所做的修改会反应到Main()函数。

    表 3.4 数值数据类型和引用数据类型的常见特征

    值类型

    引用类型

    变量存储

    实际值

    引用

    保存位置

    内联(椎栈)

    默认值

    Null

    传递给函数的参数

    值的副本

    引用的副本

    从3.4表中可以看出:

    值类型的变量保存实际值,而引用类型的变量则保存对象的地址。

    值类型内存分配在堆栈上,而引用类型的则分配在堆上。

    前面部分中曾讲到C#会为变量分配一个默认值。值类型的默认值为零,而引用类型的默认值为null引用。

    对值类型执行“=”运算会将值复制至目标变量,而引用类型执行同一运算则会将对像的引用复制至目标。

    1.8.1  装箱和取消装箱

    简单的说,装箱就是从值类型到引用类型的转换。同样,取消装箱便是从引用类型到值类型的转换。这是C#非常强大的一个功能。可以让你像简单的操作值类型一样操作复杂的引用类型。这对于实现多态性非常重要。

    我们来研究下概念,看看代码段5。

    代码段5:

    class BoxEx

    {

    int objsTaker(object objectX)

    {

    //objsTaker接收一个对象

    //并在此处对其进行处理

    }

    object objsConverter()

    {

    //objsConverter执行处理过程

    //并返回对象

    }

    }

    //代码实现

    int variable1;

    variable1=5;

    boxVar.objsTaker(variable1);//第1行

    int convertVar=(int)boxVar.objsConverter();//第2行

    上面定义了一个包含两个方法的类。第一个方法objsTaker()将一个对象作为其参数。第二个方法variable1的int型值类型变量,并赋值勤5,创建了一个类BoxEx的新对象boxVar。在指定为“第一行”的行中,调用了objsTaker()方法并将variable1作为参数传递。请注意,objsTaker方法使用的一个对象作为其参数。对象是引用类型,而程序中是将值类型的variable1作为参数传弟。通常情况下这样会产生错误,但在C#中,值类型variable1将被隐式转换为引用类型。这个值类型转换为引用类型的过程(无论是隐式还是显式)即称为“装箱”。在接下来的标记为“第2行”的行中,创建了一个新的名为convertVar的int型变量,然后对该变量与objsConverter方法执行相等运算。ObjsConverter()不带参数,但返回一个对象。由于将方法int类型的值类型变量进行了赋值运算,因此必须将此对象显式地转换为int类型(转换是通过将所需的数据类型括到圆括号中来完成的)。这个将引用类型转换为值类型的过程我们称为“取消装箱”。

    特别需要注意的是,取消装箱需要进行显式的转换。运行库将执行检查,以确保指定值类型与引用类型中包含的类型匹配。如果此检查失败,将引发异常,而如果该异常没有得到妥善处理,应用程序将终止。

    1.9     数据类型处理

    C#提供了一个“统一类型系统”。也就是说,C#中的所有数据类型(引用类型或值类型)都是从一个类(object类)派生而来的。这意味着,C#中的一切全都是对象(因为所有类型都是object类的派生物)。

    下面我们来阐释这一点。

    示例9:

    using System;

    class ObjectProff

    {

    static void Main()

    {

    string objectVal;

    objectVal=7.ToString();

    Console.WriteLine(“该值现在是”+objectVal);

    }

    }

    此示例输出结果是:该值现在是7

    请注意粗体显示部份,7是一个数字,如果是一个字符串,那么我们应该用双引号把它引起来。这里没引,说明一个整型。为何数字可以拥有方法。在C#,一切都是对象,即便是数字7也被认为是对象。因此,它可以拥有方法,而ToString()就是方法之一。此方法是将数字7转换为字符串值。

    C#的另一个激动人心的功能:由于所有类型都是由共同的一个类,object 类派生而来,因此他们具有一些相同的特征。

    1.10        C#中的静态成员

    某此情况下,需要在类中定义这样的成员:这些成员不与任何其他特定对象相关联(类本身除外)。也就是说,无论有多少该类的对象,只会有此方法/字段的一个实例。在C#中可以使用关键字static定义静态成员,如下代码所示:

    代码段6:

    static int staticMem;

    static int instanceCount()

    {

            //instanceCount实现

    }

    1.11        数组

    数组是一组具有类似数据类型的值。这些值存储在相邻的内存位置,因此访问和操纵这些值更为简便。数组下标由零开始,C#中的数组属于引用类型,因此存储在堆中。语法如下:

            DataTyep[number of elements] variableName;

    下面的代码段显示了一个数组声明示例。

    代码段7:

    int[6] array1

    此示例声明了一个名为array1的整型类型数组,该数组包含6个元素。

    代码段8:

    string[] array2;

    array2 = new string[5];

    该示例表明,数组中元素的个数可在程序中稍后部份指定。

    代码段9:

    string[] array3 = {“top”, “down”,“left”,“right” };

    该示例表明,可以在声明阶段进行数组初始化。

    1.12        结构

    可以使用类来实现对象。但某些情况下,可能需要使对象具有内置数据类型的行为,以加快分配,避免过多重载和引用开销。这个问题可以通过使用结构来解决。结构与类很相似,都表示可以包含数据成员和函数成员的数据结构。与类不同的是,结构是值类型并且不需要堆分配。结构类型的变量直接包含结构结构的数据,而类类型的变量包含对数据的引用(该变量称为对象)。C#的结构内可以定义方法,并且还可以有一个构造函数。

    类与结构有很多相似之处:结构可以实现接口,并且可以具有与类相同的成员类型。然而,结构在几个重要方面不同于类:结构为值类型而不是引用类型,并且结构不支持继承。结构的值存储在“在堆栈上”或“内联”。

    下面我们来研究一下下面的代码段:

    代码段10:

    struct structEx

    {

            public int structDataMember;

            public void structEx()

            {

                    //构造函数实现

    }

    public void structMethod1()

    {

            //structMethod1实现

    }

    }

    上面代码段所示,名为structEr的结构中有一个构造函数、一个方法和一个数据成员。结构也有自己的局限性,它们不能像类那样实现继承。更为重要的是,结构是值类型,而类则是引有类型。在C#中,值类型实际是属于System命名空间的结构。例如:long类型是System.Int64结构的别名。也就是说,使用简单类型时,实际上是在使用基类中的结构。

    例如,将 Point 定义为结构而不是类在运行时可以节省很多内存空间。下面的程序创建并初始化一个 100 点的数组。对于作为类实现的 Point,出现了 101 个实例对象,因为数组需要一个,它的 100 个元素每个都需要一个。

    class Point

    {

       public int x, y;

       public Point(int x, int y) 

    {

          this.x = x;

          this.y = y;

       }

    }

    class Test

    {

       static void Main() 

    {

          Point[] points = new Point[100];

          for (int i = 0; i < 100; i++)

             points = new Point(i, i*i);

       }

    }

    如果将 Point 改为作为结构实现,如

    struct Point

    {

       public int x, y;

       public Point(int x, int y) 

    {

          this.x = x;

          this.y = y;

       }

    }

    则只出现一个实例对象(用于数组的对象)。Point 实例在数组中内联分配。此优化可能会被误用。使用结构而不是类还会使应用程序运行得更慢或占用更多的内存,因为将结构实例作为值参数传递会导致创建结构的副本。

    1.13        枚举类型

    枚举类型是声明一组命名常数的独特类型。当程序中某些数值可以具有一组特定值时,可以使用枚举类型。假设需要使程序仅接受五个值,例如:Monday、Tuesday、Wednesday、Thursday和Friday作为Weekdays的值。要强制实现此要求,仅需要指定包含指定值的枚举类型Weekdays,并编写一个仅接受此枚举数作为参数的方法。下段的代码段对此进行了说明。

    代码段11:

    public class Holiday

    {

            public enum WeekDays

            {

    Monday,

    Tuesday,

    Wednesday,

    Thursday,

    Friday

    }

    public void GetWeekDays (String EmpName,WeekDays DayOff)

    {

            //处理WeekDays

            …

    }

    static void Main()

    {

            Holiday myHoliday = new Holiday();

            MyHoliday.GetWeekDays(“Richie”,Holiday.WeekDays.Wednesday);

    }

    }

    与C一样,C#中的枚举数拥有与值关联的数字。默认情况下,向枚举数的第一个元素赋值0,而其后各个元素的值较前一个元素的值都递增1。不过这些值可以重写,并且可以在初始化阶段指定其他的值。

    代码段12:

    public enum WeekDays

    {

            Monday=1,

            Tuesday=2,

            Wednesday=3,

            Thursday=4,

            Friday=5

    }

    1.14        编译和执行C#程序

    首先要运行C#程序必须要有C#运行的环境。即安装Microsoft .NET Framework SDK v1.1或以上版本。

    csc.exe是.net用来编译.cs文件的,但必须要在安装目录下使用。 所以我们要设置一下环境变量。 

    C#环境变量设置: 

    1、在桌面右击[我的电脑]->[属性]->[高级]->[环境变量] 

    2、在下面的系统变量栏点击“新建” 

    3、变量名输入“csc” 

    4、变量值输入:“C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\” (2000是C:\WINNT\Microsoft.NET\Framework\v1.1.4322\)

    5、然后在系统变量列表框中双击“Path” 

    6、在变量名文本框的最后面加入 ; “%csc%” 

    恩,现在可以在任意目录下调试.cs文件了。

    下面给出命令行示例

    编译 File.cs 以产生 File.exe: 

    csc File.cs 

    编译 File.cs 以产生 File.dll: 

    csc /target:library File.cs 

    编译 File.cs 并创建 My.exe: 

    csc /out:My.exe File.cs 

    通过使用优化和定义 DEBUG 符号,编译当前目录中所有的 C# 文件。输出为 File2.exe: 

    csc /define:DEBUG /optimize /out:File2.exe *.cs 

    编译当前目录中所有的 C# 文件,以产生 File2.dll 的调试版本。不显示任何徽标和警告: 

    csc /target:library /out:File2.dll /warn:0 /nologo /debug *.cs 

    将当前目录中所有的 C# 文件编译为 Something.xyz(一个 DLL): 

    csc /target:library /out:Something.xyz *.cs 

    编译 File.cs 以产生 File.dll: csc /target:library File.cs这个就是我们使用最多的一个命令,其实可以简单的写成csc /t:library 

    File.cs,另外的一个写法是 

    csc /out:mycodebehind.dll /t:library mycodebehind.cs,这个可以自己指定输出的文件名。 

    csc /out:mycodebehind.dll /t:library mycodebehind.cs mycodebehind2.cs,这个的作用是把两个cs文件装到一个.dll文件里,很有用哦。

    1.15        小结

    C#变量的声明方式如下:

    AccessModifier DataType VariableName;

    C#中,通过添加前缀@符号,可以将关键字用作变量名称。此@符号不是标识符的一部份。

    在C#,静态变量、类实例变量和数组元素在创建时自动赋值。

    选择语句可用于根据表达式的值执行各种操作。

    C#中的swhitch语句要求为每个case块都使用一个break语句。

    C# 允许将switch结构与字符串一起使用。

    C# 提供了下列循环结构类型:

    while循环

    do循环

    for循环

    foreach循环

    在C#中,数据类型分为两种基本类型,即值类型和引用类型。

    在C#中,多数基本数据类型(如 int、char),包括结构在内,为值类型。引用类型包括类、接口、数组和字符串。

    装箱是指从值类型到引用类型的转换,而取消装箱是指从引用类型到值类型的转换。

    C#中的所有数据类型都是从一个类即object类派生而来的。

    C#结构内部可以定义方法,也可以拥有构造函数。

    枚举类型是声明一组命名常数的独特类型。

    1.16        练习

    1. C#中的标识符可以是保留关建了。

    a.对 b.错

    2. ___________方法用于接受用户输入。

    a.Console.Read b.Console.RealdLine

    c.Console.WriteLine

    3. 在C#中,if结构始终要求条件的计算结果为boolean类型。

    a.对 b.错

    4. 值类型存储于____________________。

    a.堆栈 b.堆

    c.队列

    5. 与C/C++不同,C#允许结构拥有____________。

    a.仅构造函数 b.方法和构造函数

    1.17        作业

    编写C#程序,实现下列目的:

    1. 显示一条消息,要求用户输入自己的姓名、雇员编号和部门。接受输入的值并显示在屏幕上。(提示:使用多个变量存储这些值。)

    2. 修改上面的程序,以下列方式显示这些值。如果输入的值为Samuel、7576和HR,则输出应为:HR部门的Samuel的雇员编号为7576。

    3. 在上述程序中,接受用户输入的值作为命令行参数。对命令行参数编号并将其显示在屏幕上。

    假如,假设用户在<Filename>中输入:Samuel 7576 HR

    则输出应为

    1:Samuel

    2:7576

    3:HR

    4. 在屏幕上显示下面的项列表:

    1. 加

    2. 减

    3. 乘

    4. 除

    接受用户提供的三个integer值。前两个integer值为数字,按照用户的选择对其进行数学运算,第三个integer值为用户要执行的数学运算。此值不能小于1且不能大于4。根据用户的选择(用户提供的第三个interger值)执行下列运算。

    如果此值为 则执行

    加 将前两个integer值相加,并显示结果

    减 从第一个integer值减去第二个integer值,并显示结果

    乘 将前两个integer值相乘,并显示结果

    除 用第二个integer除第一个integer,并显示结果

    提示:使用switch结构或多个if 语句

    5. 编写一个结构,以华氏和摄氏的形式存储温度。编写一个函数,转换温度表示形式,如从华氏温度转换为摄氏温度。

     

  • 相关阅读:
    2019.1.4函数的相关内容
    2019.1.3 序列常见的BIF
    2019.1.2字符串格式化的内容
    2018.12.29字符串的相关内容
    2018.12.28字符串的相关内容
    2018.12.27上午学习内容
    下午的学习内容
    今天上午学习的内容
    四、自动装配
    lombok
  • 原文地址:https://www.cnblogs.com/drgraph/p/2364607.html
Copyright © 2020-2023  润新知