■:重中之重 ■:重要 ■:有点重要
倾斜:概念
一、类和对象
1.面向对象概念回顾
万物皆对象
用程序来抽象(形容)对象
用面向对象的思想来编程
2.什么是类
基本概念
具有相同特征
具有相同形为
一类事物的抽象
类是对象的模板
可以通过类创建出对象
类的关键词:class
3.类申明在哪里
类一般申明在 namespace 语句块(命名空间)中
4.类申明的语法
基本语法
class 类名{封装内容}
类名中英文都支持(最好用英文)
class 类名 { // 包含的内容 // 特征 -- 成员变量 // 行为 -- 成员方法 // 保护特征 -- 成员属性 // 构造函数和析构函数 // 索引器 // 运算符重载 // 静态成员 }
5.类申明实例
命名规范采用帕斯卡命名法
帕斯卡命名法:每个单词的首字母大写
同一个命名空间语句块中的不同类不能重名
该类用来形容人类
class Person { // 包含的内容 // 特征 -- 成员变量 // 行为 -- 成员方法 // 保护特征 -- 成员属性 // 构造函数和析构函数 // 索引器 // 运算符重载 // 静态成员 }
该类用来形容机器
class Machine { // 包含的内容 // 特征 -- 成员变量 // 行为 -- 成员方法 // 保护特征 -- 成员属性 // 构造函数和析构函数 // 索引器 // 运算符重载 // 静态成员 }
6.什么是(类)对象
基本概念
类的申明 和 类对象(变量)申明 是两个概念
类的申明 类似枚举和结构体的申明 类的申明相当于 申明了一个自定义变量类型
而对象是类创建出来的
相当于申明一个指定类的变量
类创建对象的过程 一般称为实例化对象
类对象都是引用类型的
7.实例化对象基本语法
(1)类名 变量名;
(2)类名 变量名 = null;
(3)类名 变量名 = new 类名();null 代表空
8.实例化对象
Person p; Person p2 = null; // 一个人 Person p3 = new Person(); // 另一个人 Person p4 = new Person();
null 代表空 不分配堆内存空间
注意
虽然他们是来自一个类的实例化对象
但是他们的特征 行为等等信息 都是他们独有的
两个人 你是你 我是我 彼此没有关系
一切的对象 都是由我们来控制的
总结
类的申明和类对象的申明 是两个概念
类的申明是申明对象的模板 用来抽象(形容)显示事物的
类对象的申明 是用来表示现实中的 对象个体
类是一个自定义的变量类型申明在 namespace(命名空间) 语句块中
命名用帕斯卡命名法(在类申明实例中有详解)
实例化一个类对象 是在申明变量null 代表空 只分配栈内存的空地址 不分配堆内存
二、成员变量和访问修饰符
1.成员变量
基本规则
1.申明在类语句块中
2.用来描述对象的特征
3.可以是任意变量类型(14种基本变量 枚举 类 结构体 数组......)注:也可以申明自己类的变量
4.数量不做限制
5.是否赋值根据需求来定// 性别枚举 enum E_SexType { Man, Woman, } // 位置结构体 struct Position { } // 宠物类 class Pat { } // 人类的类 class Person { // 特征 -- 成员变量 // 姓名 public string name = "Unity打怪升级"; // 年龄 public int age; // 性别 public E_SexType sex; // 手机 // 如果要在类中 申明一个和自己相同类型的成员 // 不能对它进行实例化 public Person phone; // 朋友 public Person[] boyFriend; // 位置 public Position pos; // 宠物 private Pat pat = new Pat(); }
2.访问修饰符
public -- 公共的 自己(内部)和别人(外部) 都能访问和使用
private -- 私有的 自己(内部)才能访问和使用 不写 默认为 private
protected -- 保护的 自己(内部)和子类 才能访问和使用
3.成员变量的使用和初始值
值类型:整型变量 默认值:0 bool 类型:false
引用类型:null
教给大家一个看默认值的小技巧 default(变量类型) 就能得到默认值Person p = new Person(); Console.WriteLine(default(Person)); p.age = 10; Console.WriteLine(p.age);
总结
成员变量
描述特征
类中申明
赋值随意
默认值都不同
任意类型
任意数量
访问修饰符
3P
public 公共 内外
private 私有的 内
protected 保护的 内和子类
三、成员方法
1.成员方法申明
基本概念
成员方法(函数) 用来表现对象行为基本规则
1.申明在类语句块中
2.是用来描述对象的行为的
3.规则和函数申明规则相同
4.受到访问修饰符规则影响
5.返回值参数不做限制
6.方法数量不做限制
注意:
1.成员方法不要加 static 关键字
2.成员方法 必须实例化出对象 再通过对象来使用 相当于该对象执行了某个行为
3.成员方法 受到访问修饰符影响class Person { /// <summary> /// 判断是否成年 /// </summary> /// <returns></returns> public bool IsAdult() { return age >= 18; } /// <summary> /// 说话的行为 /// </summary> /// <param name="str">说话的内容</param> public void Speak(string str) { Console.WriteLine("{0}说{1}", name, str); } /// <summary> /// 添加朋友的方法 /// </summary> /// <param name="p">传入新朋友</param> public void AddFriend(Person p) { if (friends == null) { friends = new Person[] { p }; } else { // 新建一个房子数组 Person[] newFriends = new Person[friends.Length + 1]; // 搬家 for (int i = 0; i < friends.Length; i++) { newFriends[i] = friends[i]; } // 把新加的朋友放到最后一个 newFriends[newFriends.Length - 1] = p; // 地址重定向 friends = newFriends; } } public string name; public int age; // 朋友们 public Person[] friends; }
2.成员方法的使用
成员方法 必须实例化出对象 再通过对象来使用 相当于该对象执行了某个行为
// 实例化对象 Person p = new Person(); p.name = "Unity打怪升级"; p.age = 18; p.Speak("孤勇者"); if (p.IsAdult()) { p.Speak("我有好朋友"); } Person p2 = new Person(); p2.name = "火山哥"; p2.age = 16; p.AddFriend(p2); for (int i = 0; i < p.friends.Length; i++) { Console.WriteLine(p.friends[i].name); }
总结
成员方法
描述行为
类中申明
任意数量
返回值和参数根据需求决定
四、构造函数和析构函数
1.构造函数
基本概念
在实例化对象时 会调用的用于初始化的函数
如果不写 默认存在一个无参构造函数
构造函数写法规则
1.没有返回值
2.函数名和类名必须相同
3.没有特殊需求时 一般都是 public 的
4.构造函数可以被重载
5.this 代表当前调用该函数的对象自己
注意:
如果不自己实现无参构造函数 而实现了由参构造函数
会失去默认的无参构造class Person { public string name; public int age; // 类中是允许自己申明无参构造函数的 // 结构体是不允许的 public Person() { name = "Unity打怪升级"; age = 18; } public Person(int age) { // this 代表当前调用该函数的对象自己 this.age = age; } public Person(string namee) { this.name = name; } }
2.构造函数特殊写法
可以通过 this 重用构造函数代码
访问修饰符 构造函数名(参数列表):this(参数1,参数2......)class Person { public string name; public int age; public Person(int age) { // this 代表当前调用该函数的对象自己 this.age = age; } public Person(int age,string name):this(age + 10) { Console.WriteLine("Person两个参数构造函数调用"); ; } }
3.析构函数
基本概念
当引用类型的堆内存被回收时 会调用该函数
析构函数是当垃圾 真正被回收的时候才会调用的
对于需要手动管理内存的语言(比如 C++)需要在析构函数中 做一些内存回收处理
但是 C# 中存在自动垃圾回收机制 GC
所以我们几乎不会怎么使用析构函数除非你想在某一个对象被垃圾回收时做一 些特殊处理
注意:
在 Unity 开发中析构函数几乎不会使用 所以该知识点只做了解即可
基本语法
~类名()
{
}class Person { ~Person() { } }
4.垃圾回收机制
垃圾回收 英文简写 GC(Garbage Collector)
垃圾回收的过程 是在遍历堆(Heap)上动态分配的所有对象
通过识别他们是否被引用 来确定哪些对象是垃圾 哪些对象仍要被使用
所谓的垃圾 就是没有被任何变量 对象引用的内容
垃圾就需要被回收释放
垃圾回收有很多种算法 比如
引用计数(Reference Counting)
标记清除(Mark Sweep)
标记整理(Mark Compact)
复制集合(Copy Collection)
注意:
GC 只负责堆(Heap)内存的垃圾回收
引用类型都是存在堆(Heap)中的 所以它的分配和释放 都通过垃圾回收机制来管理
栈(Stack)上的内存是由系统自动管理的
值类型在栈(Stack)中分配内存的 他们有自己的生命周期 不用对他们进行管理会自动和分配和释放
C# 中内存回收机制的大概原理
0代内存 1代内存 2代内存
代的概念:
代是垃圾回收机制使用的一种算法(分代算法)
新分配的对象都会被配置在第0代内存中
每次分配都可能会进行垃圾回收以释放内存(0代内存满时)
在一次内存回收过程开始时 垃圾回收器会认为堆中全是垃圾 会进行以下两步
1.标记对象 从根(静态字段、方法参数)开始检查引用对象 标记后为可达对象未标记为不可达对象
不可达对象就认为是垃圾
2.搬迁对象压缩堆(挂起执行托管代码线程)释放未标记的对象 搬迁可达对象修改引用地址
大对象总被认为是第二代内存 目的是减少性能损耗 提高性能
不会对大对象进行搬迁压缩 85000字节(83kb)以上的对象为大对象
Person p = new Person(18,"Unity打怪升级");
Console.WriteLine(p.age);
p = null;
// 手动触发垃圾回收的方法
// 一般情况下我们不会频繁使用
// 都是在 Loading 过场景时才调用
GC.Collect();
总结
构造函数
实例化对象时 调用的函数
主要是用来初始化成员变量的
基本语法
不写返回值
函数名和类名相同
访问修饰符根据需求而定
一般为 public
注意
可以重载构造函数
可以用 this 语法重用代码
可以在函数中用 this 区分同名参数和成员变量
有参构造会顶掉默认的无参构造
析构函数
当对象被垃圾回收时调用的 主要是用来回收资源或者特殊处理
基本语法
不写返回值
不写修饰符
不能有参数
函数名和类名相同
前面加~
五、成员属性
1.成员属性的基本概念
基本概念
1.用于保护成员变量
2.为成员属性的获取和赋值添加逻辑处理
3.解决 3P 的局限性
public -- 内外访问
private -- 内部访问
protected -- 内部和子类访问
属性可以让成员变量在外部
只能获取 不能修改 或者 只能修改 不能获取
2.成员属性的基本语法
2.1.语法及申明
属性的命名 一般是用帕斯卡命名法
访问修饰符 属性类型 属性名
{
get{}
set{}}
class Person { private string name; private int age; private int money; private bool sex; public string Name { get { // 可以再返回之前添加一些逻辑规则 // 意味着这个属性可以获取内容 return name; } set { // 可以再设置之前添加一些逻辑规则 // value 关键字 用于表示外部传入的值 name = value; } } public int Money { get { // 解密处理 return money - 5; } set { // 加密处理 money = value + 5; } } }
2.2.成员属性中 get 和 set 前可以加访问修饰符
注意
1.默认不加 会使用属性申明时的访问权限
2.加的访问修饰符 要低于属性的访问权限
3.不能让 get 和 ret 的访问权限都低于属性的权限
2.3.get 和 set 可以只有一个
注意:
只有一个时 没必要在前面加访问修饰符
一般情况下 只会出现只有 get 的情况 基本不会出现只有 setclass Person { public bool Sex { get { return sex; } // set // { // sex = value; // } } }
2.4.自动属性
作用:外部能得不能改的特征
如果类中有一个特征 是只希望外部能得不能改的 又没什么特殊处理
那么可以直接使用自动属性class Person { public float Height { // 没有在 get 和 set 中写逻辑的需求或想法 get; private set; } }
3.成员属性的使用
Person p = new Person();
p.Name = "Unity打怪升级";
Console.WriteLine(p.Name);
p.Money = 21000;
Console.WriteLine(p.Money);
// Console.WriteLine(p.Sex);
// p.Sex = true;
总结
1.成员属性概念:一般是用来保护成员变量的
2.成员属性的使用和变量一样 外部用对象点出
3.get 中需要 return 内容;set 中用 value 表示传入的内容
4.get 和 set 语句块中可以加逻辑处理
5.get 和 set 可以加访问修饰符 但是要按照一定的规则进行添加
6.get 和 set 可以只有一个
7.自动属性是属性语句块只有 get 和 set 一般用于外部能得不能改这种情况
六、索引器
1.索引器基本概念
基本概念
让对象可以像数组一样 通过索引访问其中元素 使程序看起来更直观更容易编写
2.索引器语法
2.1.语法
访问修饰符 返回值 this[参数类型 参数名,参数类型 参数名......]
{
内部的写法和规则和索引器相同
get{}
set{}
}
2.2.索引器可以重载
重载的概念是 -- 函数名相同 参数类型 数量 顺序不同
class Person { private string name; private int age; private Person[] friends; private int[,] array; public int this[int i, int j] { get { return array[i, j]; } set { array[i, j] = value; } } public string this[string str] { get { switch (str) { case "name": return this.name; case "age": return age.ToString(); } return ""; } } }
2.3.索引器中可以写逻辑
可以写逻辑的 根据需求来处理这里面的内容
class Person { private string name; private int age; private Person[] friends; private int[,] array; public Person this[int index] { get { // 可以写逻辑的 根据需求来处理这里面的内容 if (friends == null || friends.Length - 1 < index) { return null; } return friends[index]; } set { // value 代表传入的值 // 可以写逻辑的 根据需求来处理这里面的内容 if (friends == null) { friends = new Person[] { value }; } else if (index > friends.Length - 1) { // 自己定了一个规则 如果索引越界 就默认把最后一个朋友顶掉 friends[friends.Length - 1] = value; } friends[index] = value; } } }
3.索引器的使用
Person p=new Person();
p[0]=new Person();
Console.WriteLine(p[0]);
p[0, 0] = 10;
总结
索引器对于我们来说的主要作用
可以让我们以中括号的形式范围自定义类中的元素 规则自己定 访问时和数组一样
比较适用于在类中有数组变量时使用 可以方便的访问和进行逻辑处理
固定语法
访问修饰符 返回值 this[参数列表]
get 和 set 语句块
可以重载
注意:结构体里面也是支持索引器
七、静态成员
1.静态成员基本概念
静态关键字 static
用 static 修饰的成员变量 方法 属性等
称为静态成员
静态成员的特点是:直接用类名点出使用
2.早已出现的静态成员
Console 等等都是系统配置好的静态成员
3.自定义静态成员
3.1.静态函数中不能使用非静态成员
成员变量只能将对象实例化出来后 才能点出来使用 不能无中生有
不能直接使用 非静态成员 否则会报错class Test { public const float G = 9.8f; // 静态成员变量 static public float PI = 3.1415926f; // 成员变量 public int testInt = 100; // 静态成员方法 public static float CalcCircle(float r) { // Console.WriteLine(testInt); Test test = new Test(); Console.WriteLine(test.testInt); // πr² return PI * r * r; } }
3.2.非静态函数可以使用静态成员
class Test
{
public const float G = 9.8f;
// 静态成员变量
static public float PI = 3.1415926f;
// 成员变量
public int testInt = 100;
// 成员方法
public void TestFun()
{
Console.WriteLine("123");
Console.WriteLine(PI);
Console.WriteLine(CalcCircle(2));
}
}
4.静态成员的使用
Console.WriteLine(Test.PI);
Console.WriteLine(Test.G);
Console.WriteLine(Test.CalcCircle(2));
Test t = new Test();
Console.WriteLine(t.testInt);
t.TestFun();
5.为什么可以直接点出来使用
记住!
程序中是不能无中生有的
我们要使用的对象 变量 函数都是要在内存中 分配内存空间的
之所以要实例化对象 目的就是分配内存空间 在程序中产生一个抽象的对象
静态成员的特点
程序开始运行时 就会分配内存空间 所以我们就能直接使用
静态成员和程序同生共死
只要使用了它 直到程序结束时 内存空间才会被释放
所以一个静态成员就会有自己唯一的一个"内存小房间"
这让静态成员就有了唯一性
在任何地方使用 都是用的小房间里的内容 改变了他也是改变小房间里的内容
6.静态成员对于我们的作用
静态变量
1.常用唯一变量的申明
2.方便别人获取的对象申明
静态方法:
常用的唯一的方法申明 比如 相同规则的数学计算相关函数
7.常量和静态变量
const(常量)可以理解为特殊的 static(静态)
相同点
他们都可以通过类名点出使用
不同点
1.const 必须初始化 不能修改 static 没有这个规则
2.const 只能修饰变量 static 可以修饰很多
3.const 一定是写在访问修饰符后面的 static 没有这个要求
总结
概念:用 static 修饰的成员变量 成员方法 成员属性等 就称为静态成员
特点:直接用类名点出来使用(全局性)
生命周期:和程序同生共死
程序运行后就会一直存在内存中 直到程序结束后才会释放 因此静态成员具有唯一性
注意:
1.静态函数中不能直接使用非静态成员
2.非静态函数中可以直接使用静态成员
常量和静态成员
常量是特殊的静态成员
相同点
他们都可以通过类名点出来使用
不同点
1.const 必须初始化 不能被修改 static 没有这个规则
2.const 只能修饰变量 static 可以修饰很多
3.const 不能是写在访问修饰符前面 一定是写在变量申明前面 static 没有这个规则
八、静态类和静态构造函数
1.静态类
概念
用 static 修饰的类
特点
只能包含静态成员
不能被实例化
作用
1.将常用的静态成员写在静态类中方便使用
2.静态类不能被实例化 更能体现工具类的唯一性
比如 Console 就是一个静态类static class Tools { // 静态成员变量 public static int testIndex = 0; public static void TestFun() { } public static int TestIndex { get; set; } }
2.静态构造函数
概念
在构造函数加上 static 修饰
特点
1.静态类和普通类都可以有
2.不能使用访问修饰符
3.不能有参数
4.只会自动调用一次
作用
在静态构造函数中初始化静态变量
使用
1.静态类中的静态构造函数static class StaticClass { public static int testInt = 100; public static int testInt2 = 100; static StaticClass() { Console.WriteLine("静态构造函数"); testInt = 200; testInt2 = 300; } }
2.普通类中的静态构造函数
class Test { public static int testInt = 200; static Test() { Console.WriteLine("静态构造"); } public Test() { Console.WriteLine("普通构造"); } }
Console.WriteLine(StaticClass.testInt);
Console.WriteLine(StaticClass.testInt2);
Console.WriteLine(StaticClass.testInt);
Console.WriteLine(Test.testInt);
Test t= new Test();
Test t2 = new Test();
总结
静态类
用 static 修饰的类
特点
只能包含静态成员
不能被实例化
作用
工具类
拓展方法
静态构造函数
用 static 修饰的构造函数
特点
静态类和普通类都可以有静态构造函数
不能使用访问修饰符
不能有参数
只会自动调用一次
作用
初始化静态成员
九、拓展方法
1.拓展方法基本概念
概念
为现有非静态变量类型 添加新方法
作用
1.提升程序拓展性
2.不需要在对象中重新写方法
3.不需要继承来添加方法
4.为别人封装的类型写额外的方法
特点
1.一定是写在静态类中
2.一定是个静态函数
3.第一个参数为拓展目标
4.第一个参数用 this 修饰
2.基本语法
访问修饰符 static 返回值 函数名
(this 拓展类名 参数名,参数类型 参数名,参数类型 参数名......)
3.实例
static class Tools
{
// 为 int 拓展了一个成员方法
// 成员方法是需要实例化对象后 才能使用的
// value 代表使用该方法的实例化对象
public static void SpeakValue(this int value)
{
// 拓展的方法的逻辑
Console.WriteLine("唐老狮为int拓展的方法" + value);
}
public static void SpeakStringInfo(this string str,string str2,string str3)
{
Console.WriteLine("唐老狮为string拓展的方法");
Console.WriteLine("调用方法的对象" + str);
Console.WriteLine("传的参数" + str2 + str3);
}
public static void Fun3(this Test t)
{
Console.WriteLine("为test拓展的方法");
}
}
4.使用
int i = 10;
i.SpeakValue();
string str = "000";
str.SpeakStringInfo("Unity打怪升级", "111");
Test t= new Test();
t.Fun2();
5.为自定义的类型拓展方法
class Test
{
public int i = 10;
public void Fun1()
{
Console.WriteLine("123");
}
public void Fun2()
{
Console.WriteLine("456");
}
}
总结:
概念:为现有的非静态变量类型 添加方法
作用:
提升程序拓展性
不需要在对象中重新写方法
不需要继承来添加方法
为别人封装的类型写额外的方法
特点:
静态类中的静态方法
第一个参数代表拓展的目标
第一个参数前面一定要加 this
注意:
可以有返回值和 n 个参数
根据需求而定
十、运算符重载
1.基本概念
概念
让自定义类和结构体
能够使用运算符
使用关键字
operator
特点
1.一定是一个公共的静态方法
2.返回值写在 operator 前
3.逻辑处理自定义
作用
让自定义类和结构体对象可以进行运算
注意
1.条件运算符需要成对实现
2.一个符号可以多个重载
3.不能使用 ref 和 out
2.基本语法
public static 返回类型 operator 运算符(参数列表)
3.实例
3.1.申明
class Point
{
public int x;
public int y;
public static Point operator +(Point p1, Point p2)
{
Point p = new Point();
p.x = p1.x + p2.x;
p.y = p1.y + p2.y;
return p;
}
public static Point operator +(Point p1, int value)
{
Point p = new Point();
p.x = p1.x + value;
p.y = p1.y + value;
return p;
}
public static Point operator +(int value, Point p1)
{
Point p = new Point();
p.x = p1.x + value;
p.y = p1.y + value;
return p;
}
}
3.2.可重载的运算符
(1)算术运算符
// 注意 符号需要两个参数还是一个参数
public static Point operator -(Point p1, Point P2)
{
return null;
}
public static Point operator *(Point p1, Point P2)
{
return null;
}
public static Point operator /(Point p1, Point P2)
{
return null;
}
public static Point operator %(Point p1, Point P2)
{
return null;
}
public static Point operator ++(Point p1)
{
return null;
}
public static Point operator --(Point p1)
{
return null;
}
(2)逻辑运算符
// 注意 符号需要两个参数还是一个参数
public static bool operator !(Point p1)
{
return false;
}
(3)位运算符
// 注意 符号需要两个参数还是一个参数
public static Point operator |(Point p1, Point p2)
{
return null;
}
public static Point operator &(Point p1, Point p2)
{
return null;
}
public static Point operator ^(Point p1, Point p2)
{
return null;
}
public static Point operator ~(Point p1)
{
return null;
}
public static Point operator <<(Point p1, int num)
{
return null;
}
public static Point operator >>(Point p1, int num)
{
return null;
}
(4)条件运算符
// 1.返回值一般是 bool 值 也可以是其他的
// 2.相关符号必须配对使用
public static bool operator >(Point p1, Point p2)
{
return false;
}
public static bool operator <(Point p1, Point p2)
{
return false;
}
public static bool operator >=(Point p1, Point p2)
{
return false;
}
public static bool operator <=(Point p1, Point p2)
{
return false;
}
public static bool operator ==(Point p1, Point p2)
{
return false;
}
public static bool operator !=(Point p1, Point p2)
{
return false;
}
3.3.不可重载的运算符
逻辑与(&&) 逻辑或(||)
索引符 []
强转运算符 ()
特殊运算符
点 . 三目运算符 ? : 赋值符号 =
4.使用
Point p = new Point();
p.x = 1;
p.y = 1;
Point p2 = new Point();
p2.x = 2;
p2.y = 2;
Point p3 = p + p2;
Point p4 = p3 + 2;
Point p5 = 2 + p3;
总结
关键
operator
固定语法:
public static 返回值 operator 运算符(参数列表)
作用:
让自定义类和结构体对象进行计算
注意:
1.参数的数量
2.条件运算符的配对实现
3.一个符号可以多个重载
4.不能用 ref 和 out
十一、内部类和分部类
1.内部类
概念
在一个类中再申明一个类
特点
使用时要用包裹者点出自己
作用
亲密关系的表现
注意
访问修饰符作用很大class Person { public int age; public string name; public Body body; public class Body { Arm leftArm; Arm rightArm; class Arm { } } }
2.分部类
概念
把一个类分成几部分申明
关键字
partial
作用
分部描述一个类
增加程序的拓展性
注意
分部类可以写在多个脚本文件中
分部类的访问修饰符要一致
分部类中不能有重复成员partial class Student { public bool sex; public string name; partial void Speak(); } partial class Student { public int number; partial void Speak() { // 实现逻辑 } public void Speak(string str) { } }
3.分部方法
概念
将方法的申明和实现分离
特点
1.不能加访问修饰符 默认私有
2.只能在分部类中申明
3.返回值只能是 void
4.可以有参数 但不用 out 关键字
局限性大 了解即可