C#的进阶

C#的进阶

转自擅码网并修改

文章目录

  • C#的进阶
    • 1. 字符串
      • 1.1 常用函数(方法)
      • 1.2 字符串的特点
    • 2. 字符串之StringBuilder
      • 2.1 简介
      • 2.2 StringBuilder的工作方式
      • 2.3 StringBuilder的默认容量
      • 2.5 StringBuilder 效率测试
    • 3. 枚举
      • 3.1 什么是枚举?
      • 3.2 语法
      • 3.3 在游戏开发中的使用
    • 4. 结构体
      • 4.1 结构体简介
      • 4.2 结构体语法
    • 5. 面向对象基础之类与对象
      • 5.1 类
      • 5.2 对象
      • 5.3 字段
      • 5.4 对象的使用
    • 6. 面向对象基础之字段属性
      • 6.1 访问修饰符
      • 6.2 属性
      • 6.3 命名空间
    • 7. 面向对象基础之三种方法
      • 7.1.普通方法
      • 7.2 构造方法
      • 7.3 析构方法
    • 8. 面向对象基础之堆栈关系
      • 8.1 对象的赋值
      • 8.2 对象的实例化
      • 8.3 面向对象编程
    • 9. 继承之原理分析
      • 9.1 继承简介
      • 9.2 继承的语法格式
    • 10. 继承之构造方法
    • 11. 继承之成员继承
      • 11.1 继承的效果
      • 11.2. 子类所继承的成员
    • 12. 多态之成员继承
      • 12.1 多态简介
      • 12.2 虚方法语法
      • 12.3 虚方法使用细节
    • 13. 多态之里氏转换原则
      • 13.1 面向对象六大原则
      • 13.2 里氏转换原则
    • 14. 多态之抽象类语法
      • 14.1 抽象方法
      • 14.2 抽象类
    • 15. 多态之抽象类案例
    • 16. 多态之接口语法
      • 16.1 接口语法
      • 16.2 使用场景
    • 17. 多态之接口案例
      • 17.1 常规属性
      • 17.2 自动属性
    • 18. 多态之虚方法抽象类接口对比
      • 18.0 使用场景对比
    • 19. 封装之五种访问修饰符
      • 19.1 五种访问修饰符
      • 19.2 使用场合
    • 20. 静态之字段与属性
      • 20.1 static 关键字
      • 20.2 静态字段
      • 20.3 静态属性
    • 21. 静态之方法与类
      • 21.1 静态普通方法
      • 21.2 静态构造方法
      • 21.3 静态类
    • 22. 静态之单例设计模式
      • 22.1 设计模式
      • 22.2 单例设计模式
      • 22.3 单例代码步骤
    • 23. 嵌套类匿名类与密封类
      • 23.1 嵌套类
      • 23.2 匿名类
      • 23.3 密封类
    • 24. 装箱与拆箱
      • 24.1 Object 类
      • 24.2 装箱与拆箱
    • 25. 预编译指令与对象初始化器
      • 25.1 预编译指令
      • 25.2 对象初始化器

1. 字符串

1.1 常用函数(方法)

(黄色为最重要)

1.ToUpper()
作用:将字符转换成大写形式,仅对字母有效。返回值是转换后的字符串。
使用:字符串变量或常量.ToUpper();
例如:"hh".ToUpper();

2.ToLower()
作用:将字符转换成小写形式,仅对字母有效。返回值是转换后的字符串。
使用:字符串变量或常量.ToLower();
例如:"hh".ToLower();

3.Equals() ※
作用:比较两个字符串是否相同。相同返回真,不相同返回假。
使用:字符串变量或常量.Equals(要比较的字符串变量或常量);
例如:"hh".Equals(name2);

4.Split() ※
作用:分割字符串。返回字符串类型的数组
使用:字符串变量或常量.Split (用于分割的字符数组或字符常量);
例如:

string[] Txt2 = "憨憨|是|他|不是|我".Split('|','他');
//再次声明个字符串数组用于保存Split函数返回的字符串数组。
char[] Fuhao = new char[] { '|','是','|','他' };
string[] Txt1 = Txt.Split(Fuhao);

5.Substring() ※
作用: 截取字符串。返回截取后的子串。
使用:

  1. 字符串变量或常量.Substring(开始截取的位置);
  2. 字符串变量或常量.Substring(开始截取的位置,截取多长);

**注意:**角标范围为 0~(字符串长度-1)
例如:
name1.Substring(2); //从角标为2 的位置,截取到最后;
name2.Substring(2,2); //从角标为2 的位置,截取2 个字符;

6.IndexOf()
作用: 查找某个字符串在字符串中第一次出现的位置。返回该字符串第一个字符所在的索引位置值。如果没有找到,返回-1。
使用: 字符串变量.IndexOf(子字符串变量或常量);
例如:name.IndexOf(“on”);

7.LastIndexOf()
作用:查找某个字符串在字符串中最后一次出现的位置。返回该字符串第一个字符所在的索引位置值。如果没有找到,返回-1。
使用:字符串变量或常量.LastIndexOf(子字符串变量或常量);
例如:name.IndexOf(“on”);

8.StartsWith()
作用:判断是否以…字符串开始。如果是,返回 true;如果不是,返回 false。
使用:字符串变量或常量.StartsWith(子字符串变量或常量);
演示:name.StartsWith(“Mo”);

9.EndsWith()
作用:判断是否以…字符串结束。如果是,返回真;如果不是,返回假。
使用:字符串变量.EndsWith(子字符串变量或常量);
演示:name.EndsWith(“key”);

10.Replace() ※
作用:将字符串中的旧字符串替换成一个新字符串,只要是旧字符串就全都替换。返回新的字符串。
使用:字符串变量或常量.Replace(旧字符串,新字符串);
演示:

string Txt = "憨憨|是|憨憨|不是|我";
Console.WriteLine(Txt.Replace("憨","皮"));
//输出皮皮|是|皮皮|不是|我

11.Contains() ※
作用:判断某个字符串中是否包含指定的字符串。如果包含返回真,否则返回假。
使用:字符串变量或常量.Contains(子字符串变量或常量);
例如:name.Contains(“key”);

12.Trim() ※
作用:只能去掉字符串中前后空格。返回处理后的字符串。
使用:字符串变量或常量.Trim();
例如:

string Txt = "  8    0   8     ";
Console.WriteLine("|" + Txt.Trim() + "|");
//结果是输出字符串"|8    0   8|"
//所以空格只能去掉字符串中除了' '以外任意字符前后的空格。
//若字符串由全空格组成,则会去掉全部字符串

13.TrimEnd()
作用:去掉字符串结束后的空格。返回处理后的字符串。若字符串由全空格组成,则会去掉全部字符串
使用:字符串变量或常量.TrimEnd();
例如:address.TrimEnd();

14.TrimStart()
作用:去掉字符串开始前的空格。返回处理后的字符串。若字符串由全空格组成,则会去掉全部字符串
使用:字符串变量或常量.TrimStart();
例如:address.TrimStart();

15.IsNullOrEmpty()
作用:判断一个字符串是否为 null 或者。如果为 null 或者空,返回真;否则返回假。
**区别:**null 是不占内存空间的,而空字符串(“”)是占内存空间的。
使用:string.IsNullOrEmpty(字符串变量或常量);
例如:

string Txt1 = null;
string Txt2 = "";
Console.WriteLine("0}\n{1}",string.IsNullOrEmpty(Txt1),string.IsNullOrEmpty(Txt2));

1.2 字符串的特点

1.字符串是引用类型
字符串的数据是存储在堆空间,在栈空间中则用字符串变量的变量名存储了该数据的引用地址。

2.字符串是不可变的——原数据不变
当你给一个字符串变量重新赋值时,旧值并没有销毁——原数据不变,而是重新开辟一块空间来存储新值,然后字符串变量的变量名储存了新值在堆中的地址。此时该string变量指向了新值。

3.字符串可以看做是是自读的字符数组
可以使用 字符串变量[下标] 的形式读取字符串中指定的字符。也可以使用 for 循环变量依次输出字符。

字符串变量或常量.Length;可以取得字符串字符的个数。

拓展:

  1. string 是类,有个成员用于储存字符串长度
  2. 与C不同,比C++更加高级的语言的字符串都不用这种用0结尾的字符数组
  3. 至于C++ 则是:std::string: 标准中未规定需要\0作为字符串结尾。编译器在实现时既可以在结尾加\0,也可以不加。(因编译器不同)
  4. 在新的C++标准 C++11 里面规定std::string一定是以’\0’结尾

2. 字符串之StringBuilder

2.1 简介

  1. string变量的缺点:
    当需要对一个字符串变量进行多次重新赋值时,在内存中会产生大量的垃圾数据信息。因为旧值并没有销毁——原数据还在原空间且内存没有被释放
    所以当多次重新赋值的频率很高时,执行的效率就会 降低

  2. StringBuilder 简介
    StringBuilder 是一个,是引用类型,动态对象。在创建了一个 StringBuilder 类型的实例后,可以通过追加、移除、替换或插入字符来修改它。
    StringBuilder 类型的变量名会一直储存同一个地址。所以不会改变字符串变量名所指向的那一块内存,也不会产生垃圾数据,所以执行效率远远高于 string 类型的字符串变量。
    C#的进阶_第1张图片

2.2 StringBuilder的工作方式

StringBuilder.Length 属性指示 StringBuilder 对象当前包含的字符数。 如果向 StringBuilder 对象添加字符,则其长度将增加。 如果添加的字符数导致 StringBuilder 对象的长度超过其当前容量,则在原内存的基础上分配新内存,此时该对象的内存翻倍,此时 Capacity 属性的值翻倍,新字符将添加到 StringBuilder 对象,并调整其 Length 属性的大小。

string k = "kkkkkkkkkkkkkkkk";//16个k
StringBuilder Txt = new StringBuilder();
Txt.Append(k);//此时Length为16,Capacity为16。
Txt.Append(k);//此时Length为32,Capacity为32,跟16比翻了2倍,内存是之前的2倍
Txt.Append(k);//此时Length为48,Capacity为64,跟32比翻了2倍,内存是之前的2倍
Console.WriteLine(Txt.Capacity);//结果为64,初始容量为16,同时内存也翻了4倍

StringBuilder 对象的额外内存会动态分配,直到达到 StringBuilder.MaxCapacity 属性定义的值。达到最大容量时,不能为 StringBuilder 对象分配更多的内存,此时尝试添加字符或将其扩展到超出其最大容量时,会引发 ArgumentOutOfRangeException 或 OutOfMemoryException 异常。

2.3 StringBuilder的默认容量

StringBuilder 对象的默认容量是16个字符,其默认最大容量为 Int32.MaxValue。 如果调用 StringBuilder() 和 StringBuilder(String) 构造函数,则使用这些默认值。
可以通过以下方式显式定义 StringBuilder 对象的初始容量:

  • 通过在创建对象时调用包括 Capacity 参数的任何 StringBuilder 构造函数。
  • 通过将新值显式分配给 StringBuilder.Capacity 属性来展开现有 StringBuilder 对象。 请注意,如果新容量小于现有容量或大于 StringBuilder 对象的最大容量,则属性引发异常。
  • 通过使用新容量调用 StringBuilder.EnsureCapacity 方法。 新容量不得大于 StringBuilder 对象的最大容量。 但是,与对 Capacity 属性的赋值不同,如果所需的新容量小于现有容量,则 EnsureCapacity 不会引发异常;在这种情况下,方法调用不起作用。

StringBuilder 对象默认容量是16个字符,其默认容量最大值为 Int32.MaxValue。如果分配给构造函数调用中的 StringBuilder 对象的字符串长度超过默认容量或指定容量,则 Capacity 属性设置为用 value 参数指定的字符串长度。

string k = "kkkkkkkkkkkkkkkk";//16个k
StringBuilder Txt = new StringBuilder(k,17);//此时默认容量为17

具体实例化方法和修改默认容量 Capacity 的方法如下

构造函数 字符串值 默认容量 最大默认容量
StringBuilder() String.Empty 16 Int32.MaxValue
StringBuilder(Int32) String.Empty 由 capacity 参数定义 Int32.MaxValue
StringBuilder(Int32, Int32) String.Empty 由 capacity 参数定义 由 maxCapacity 参数定义
StringBuilder(String) 由 value 参数定义 16或 value。 Length,以较大者为准 Int32.MaxValue
StringBuilder(String, Int32) 由 value 参数定义 由 capacity 参数或 value定义的。 Length,以较大者为准。 Int32.MaxValue
StringBuilder(String, Int32, Int32, Int32) 由 value 定义。 Substring(startIndex, length) 由 capacity 参数或 value定义的。 Length,以较大者为准。 由 maxCapacity 参数定义
  1. 创建 StringBuilder 类型的变量
StringBuilder Txt = new StringBuilder(); //创建一个对象。

注意:StringBuilder 依赖 System.Text 命名空间。

  1. 往 Txt 中追加数据
    Txt.Append(i); //追加数据,此处的 i 可以为多种类型的变量或常量。有点类似 + 运算符在起 “连接” 的作用。
    Txt.ToString(); //将 Txt 转成字符串形式。

  2. 清空 Txt 中的数据
    Txt.Clear(); //将 Txt 清空。

  3. Txt 中的单个字符
    可以采用输出 string 中单个字符一样的方法,StringBuilder 类型支持通过数组的形式使用。

2.5 StringBuilder 效率测试

  1. Stopwatch 类
    Stopwatch,秒表计时器,用来记录程序运行的时间。

注意:Stopwatch 依赖System.Diagnostics 命名空间。

  1. 创建Stopwatch 类型对象
Stopwatch sw = new Stopwatch();//声明一个Stopwatch类的变量
sw.Start(); //计时器开始。
sw.Stop(); //计时器结束。
sw.Elapsed; //开始到结束之间的时长。
  1. 效率测试
    使用 for 循环分别往 string 和 StringBuilder 中追加9万个信息,统计时长。

  2. 思维扩展
    使用“秒表计时”来判断代码在时间上的执行效率。在后期需要测试执行效率的时候,都可以使用这个方法。

3. 枚举

3.1 什么是枚举?

枚举类型是由基础整数数值类型一组 命名常量 定义的值类型。

默认情况下,枚举成员的关联常量值类型为 int ; 它们默认从开始,并按照定义文本顺序增加一。 您可以显式指定任何其他整数类型作为枚举类型的基础类型。您还可以显式指定关联的常数值,如以下示例所示:

enum ErrorCode : ushort
{
    None,//关联的常量值为0
    Unknown = 1,//此处可以不用指定为1,因为编译器会自动将前一个枚举成员的关联常量值增加1,结果正好为1
    ConnectionLost = 100,
    txt,//此处txt关联的常量值为101
    OutlierReading = 200
}

3.2 语法

1. 定义枚举类型
枚举类型定义在什么地方?枚举定义在 namespace 下,这样在当前的命名空间下,所有的类(class)都可以使用该枚举。

语法:

  1. 枚举类型的定义
public enum 枚举名
{1,2,
	值N
}
  1. 定义了枚举后的变量声明和初始化
season a = season.春天;
  1. 可以通过强制转换将枚举类型转换成整型数值
int b = (int)a;

2. 使用枚举

public enum season
{
	春天,//0
	夏天,//1
	秋天,//2
	冬天 //3
}
class Program
{
	static void Main(string[] args)
	{
		season a = season.春天;
		int b = (int)a;//结果为0
		Console.ReadKey();
	}

3.3 在游戏开发中的使用

一般用于游戏开发的游戏状态、RPG游戏的职业等多种方面。

4. 结构体

4.1 结构体简介

1.什么是结构体?
struct 结构体是一种值类型,通常用于封装一些小型变量数据。

结构体和“对象”有很多类似和一样的地方,可以把结构体当成一个迷你版的对象来使用。

结构还可以包含构造函数、常量、字段、方法、属性、索引器、运算符、事件和嵌套类型,但如果同时需要上述几种成员,则应当考虑改为使用类作为类型。

2.Untiy3D 中的结构体
在Unity3D 中提供了很多结构体类型的数据,这些数据储存的量都小。比如:Vector3(三维向量),Color(颜色),Quaternion(四元数),Ray(射线)等等

4.2 结构体语法

1.声明结构体

public struct 结构体名称
{
public 数据类型  变量名;
public 数据类型  变量名;
public 数据类型  变量名;
}

2.使用结构体

  1. 创建结构体类型的变量:
    格式: 结构体类型 结构体变量名= new 结构体类型();
Person monkey = new Person();
  1. 给结构体赋值:
    格式: 结构体变量名.变量名 = 值;

  2. 结构体取值:
    格式: 结构体变量名.变量名;

5. 面向对象基础之类与对象

5.1 类

  1. 什么是类?
    在我们的程序中,描述某具体的物体的特征,就是 类(class)
    类是一种数据结构,它可以包含数据成员(常量和字段)、函数成员(方法、属性、事件、索引器、运算符、实例构造函数、析构函数和静态构造函数)和嵌套类型
  2. 类的语法
[访问修饰符] class 类名
{
类的描述信息;
}

访问修饰符: 用于描述这个类的访问权限,可以不写,因为有默认值;
class: 关键字,用于声明一个类,和之前枚举的enum,结构体的 struct 作
用是一样的。
类的描述信息: 描述这类事物的特征(字段,属性,方法);

至于修饰符的默认值,这个则要看类的位置
如果类,结构,代理,枚举是外置的,那修饰符只有:public 和 internal,默认是 internal——内部 。至于 private 和 protected 是用来修饰类的成员。在外置class前面加private,protected不会通过编译。
如果是内置的,就是作为另一个类型的成员,也称内部类型(inner type),这样的话,修饰符可以是全部可用修饰符,默认是private。

5.2 对象

  1. 什么是对象?
    一个具体的个体,就是一个对象。

  2. 对象的语法

类名 对象名 = new 类名()

类名: 描述这个对象的类,可以是我们自己定义的,也可以是系统提供的;
**对象名:**我们要通过这个类创建出来的一个具体的个体的名称;
new: 关键字,实例化的意思,new 类名() 就是实例化一个类的对象,通过
这个类创建出一个具体的对象出来。

  1. 类与对象的关系
    类用于描述一类事物的信息;
    对象是这个类中的一个具体的个体。
    就像 人类与一个具体的人

5.3 字段

  1. 什么是字段?
    字段就是我们之前一直在用的变量,只是位置不同。当变量在面向对象的类中就叫做字段
    而类是一个抽象的,而字段也是抽象的,变量就不同了就是具体到一个实体。
    所以变量实际上包含字段。
  2. 字段的作用
    字段的作用和变量的是一样的。
    都是用于存储一些具体的信息。
  3. 字段的语法
public 数据类型 字段名;

public: 访问修饰符;
数据类型: 就是数据类型,比如int,string,float,double等;
字段名: 就是变量名,在面向对象编程的类中,变量就叫字段。

5.4 对象的使用

  1. 实例化对象
类名 对象名 = new 类名()
  1. 字段的赋值与取值
    赋值:对象名.字段名 = 值;
    取值:对象名.字段名

6. 面向对象基础之字段属性

6.1 访问修饰符

  1. 什么是访问修饰符?
    所有类型和类型成员都具有可访问性级别,该级别可以控制 是否可以从你的程序集或其他程序集中的其他代码中使用它们。
    而访问修饰符,又称权限修饰符。作用是在进行声明时指定类型或成员的可访问性。

  2. public 与private
    public:公开的。通过对象名.xxx 的形式可以直接访问到。
    private:私有的。通过对象名.xxx 的形式访问不到。
    演示:定义一个Person 类,用于描述人的基本信息。
    字段包含:姓名,年龄,性别。
    特别演示:对字段数据的恶意赋值。

6.2 属性

  1. 什么是属性?
    字段是我们对象的核心数据,如果直接 public 公开的话,容易被赋值了不符合要求的数据。就像对一个人的身高赋值11米,是不符合要求的。
    所以,字段通常使用 private 修饰,这样通过对象名.xxx 的形式就访问不到。
    —————————————————————————————————
    但是我们又需要通过对象名.xxx 的形式对字段存储的数据进行操作。这个时候就出现了一个新的东西“属性”。借助属性,开发人员能够编写出准确表达其设计意图的代码。
    属性的作用:对字段进行保护,或者说是处理输入的数据,使字段得到合理的赋值。
    我们将属性设为 public 这样子可以直接访问,然后将属性保护的字段设置成 private,我们通过属性间接的操作字段。
  2. 属性的语法
    可只写 set 和 get 其中一个,或两个都写
public 数据类型 属性名
{
get{return 字段名;}
set{字段名= value;}//可以拓展,比如用if-else语句判断value是否符合要求
//get 和 set 的{}里为用户自定义的代码段。

//可只写其中一种
public 数据类型 属性名
{
	get{return 字段名;}
}
public 数据类型 属性名
{
	set{字段名= value;}
}
}

数据类型: 和要保护的字段的数据类型一样
属性名: 和字段名一样,只不过首字母要大写
get: 当通过属性名取值的时候,会自动调用 get 中的代码;
set: 当通过属性名给字段赋值的时候,会自动调用 set 中的代码;
value: 也是系统关键字,代表赋给属性的值;

6.3 命名空间

  1. 什么是命名空间?
    命名空间,也叫名称空间,英文名是namespace。

  2. 命名空间的作用
    对不同的代码文件进行分类管理。
    命名空间的作用类似于操作系统中的目录和文件的关系,由于文件(类)很多,不便管理,而且容易重名,于是人们设立若干子目录(命名空间),把文件分别放到不同的子目录中,不同子目录(命名空间)中的文件(类)可以同名。调用文件时应指出文件路径——调用类和函数时应 using 命名空间。

  3. 命名空间的语法
    定义命名空间:namespace 空间名称{ 类 }
    引入命名空间:using 空间名称

7. 面向对象基础之三种方法

7.1.普通方法

  1. 什么是方法?
    方法就是函数,存在面向对象编程的类中的函数称之为方法,方法的使用语法与函数完全一样。
    说到底,这俩一个玩意。

  2. 什么是普通方法?
    普通方法,就是普通函数。
    在类中的作用是对对象的“行为”进行描述。
    行为:这个对象能干什么。

  3. 普通方法语法

访问修饰符 返回值 方法名(参数列表)
{
方法的具体功能代码;
}

例如:

class Jobs
{
private string name;
private Gender gender;
private Profession profession;
private BornAddress bornAddress;

public void DisplayInformation()
{
Console.WriteLine("我叫{0},性别{1},职业是{2},出生地是{3}", name, gender, profession, bornAddress);
}

public 修饰的普通方法可以使用:对象名.方法名()的形式调用;
private 修饰的普通方法无法通过:对象名.方法名()的形式调用。

7.2 构造方法

  1. 什么是构造方法?
    首先它是一个方法,然后它具有“构造”的作用,所有称之为构造方法。
    简单点说,构造方法可以对实例化出来的对象进行初始化。

  2. 构造方法的语法要求

public Jobs( string name, Gender gender, Profession profession, BornAddress bornAddress )
{
	this.name = name;
	this.gender = gender;
	this.profession = profession;
	this.bornAddress = bornAddress;
}

①构造方法要用 public 修饰,不能用 private 修饰;
②构造方法没有返回值,且连void 也不能写
③构造方法的方法名字必须跟类名一样
④构造方法是可以有重载的,可以再创造一个参数与函数代码为空的重载方法,这样允许开发者多情况操作。

  1. 构造方法什么时候被调用?
    当我们使用 new 关键字实例化一个对象的时候,会首先执行构造方法。

  2. this 关键字
    this 代表当前类的对象。

  3. 注意事项
    当我们的类中不写构造方法的时候,在代码编译后,系统会自动给它添加一个空构造方法。但是如果我们写了一个构造方法,系统就不会自动添加默认的“空构造方法”。
    此时若想要有“空构造方法”就必须再写一个“空构造方法”,形成重载方法。

7.3 析构方法

  1. 什么是析构方法?
    和构造方法相反,构造方法用于初始化一个对象,析构方法常用清理一个对
    象,往往干的是“善后”的事情。
    当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。(例如在建立对象时用 new 实例化对象的时候开辟了一片内存空间,delete会自动调用析构函数后释放内存)。

  2. 析构方法的语法

~类名 ()
{
析构方法代码体;
}

①析构方法不能有任何参数
②析构方法无返回值也无访问修饰符
③析构方法由系统自动调用
④析构方法可以不写,编译系统会自动生成一个缺省(系统默认)的析构函数。它也不进行任何操作。
⑤析构方法只能有一个,不能重载

8. 面向对象基础之堆栈关系

8.1 对象的赋值

对象这种“数据”也是引用类型

对象对对象的赋值,这个的过程是传递的堆空间中的引用地址,所以两个对象都 指向同一个堆空间地址 ,所以通过两个对象中的任意一个 修改了对象中的数据,两个对象都会同时发生改变的。

例如,加设有个名为 Jobs的类:

Jobs a = new Jobs();
Jobs b;
b = a;//此时传递的是地址,数据在堆空间的地址

8.2 对象的实例化

一般情况下,开发者会先写一个默认的构造方法,也就是无参、无赋值的构造方法,再根据需求写其它有参构造方法。

而 new 运算符在实例化对象的过程中起到以下作用:

①在内存(堆空间)中开辟了一块空间;
②在开辟出来的这个空间中创建对象数据;
③调用对象的构造方法进行对象的初始化。

8.3 面向对象编程

在开发游戏时,游戏中的一切都是对象。主角是对象,npc 是对象。
为了创建这些对象,我们需要实现针对不同的对象写出相应的类。在类中规划好
这个对象的基本信息,和对象的功能作用。
然后逐一实例化对象,实现对象与对象之间进行相应的交互。

9. 继承之原理分析

9.1 继承简介

  1. 什么是继承?
    继承 是 面向对象开发的三大特性之一。
    通过继承,可以创建重用、扩展和修改在其他类中定义的行为的新类。 其成员被继承的类称为 “基类” ,继承这些成员的类称为 “派生类”
    每一个派生类只能有一个直接基类。
    但是 继承是可传递的 。 如果 ClassC 派生自 ClassB,并且 ClassB 派生自 ClassA,则 ClassC 会 同时继承 在 ClassB 和 ClassA 中声明的成员。
  2. 继承的好处
    ①优化代码结构,使代码简介,让类与类之间产生关系。
    ②提高代码复用性,同时增强可读性。
    为“多态”提供前提。

9.2 继承的语法格式

  1. 职业选择界面分析
    将几个职业的公共数据提取,抽象构成一个 Profession 类,然后这几个职业都继承这个 Profession 类,然后分别编写各自特有的类成员(比如技能)。
  2. 语法格式
    子类继承父类,使用 “:” 冒号关键字符。
class 子类名:父类名
{
//特有的类成员,也就是在父类的基础数拓展的类成员,比如字段、方法和属性;
//可写可不写。
}
  1. 类视图
    方法:当前项目上右键–>查看–>查看类图
    可以看到类的继承关系图,以及类中的成员概况。
    展开后可以看到字段,属性,方法各自特有的图标

10. 继承之构造方法

  1. 编写父类的构造方法
    先在 Profession 类中创建构造方法,用于初始化父类中的字段。

  2. 编写子类的构造方法
    演示:在各个子类中编写各自的构造方法,再使用 base 关键字将公共值传给父类。
    格式如下:

class xiuxian :Jobs//继承名为Jobs的父类,此类已编写了无参与有参的构造方法
{
	private string taskInfo;

	public TaskNPC(): base(){	}//无参构造方法的继承
	public TaskNPC(string taskInfo, string name, NPCType type)
		: base(name, type)
	{
		this.taskInfo = taskInfo;//子类特有的要用this关键字赋值
	}
}

关键字 base,代表父类,将公共值传给父类的构造方法进行初始化。

  1. 实例化子类对象
    格式如下
子类名 对象名 = new 子类名();

备注:在实际开发中,一般情况下只会实例化子类的对象,因为子类才是具体的事物,父类是子类之间的公共数据的抽象化。

11. 继承之成员继承

11.1 继承的效果

子类继承父类的全部成员(字段、属性和方法),意味着在子类中可以访问到父类中部分成员。

11.2. 子类所继承的成员

①每种成员都可以使用 privatepublic 修饰符进行修饰。
②private 修饰的成员,我们在子类中访问不到,public 修饰的字成员,我们在子类中可以访问到。
③实例化对象后就无需 base 关键字,直接 “对象名.成员” 就可以访问。

成员 常用修饰符 在子类中的访问格式
字段 private base.字段名
属性 public base.属性名
方法 public base.方法名
构造方法 public public 子类名(数据类型 参数名):base.(参数名){}

例如:

//在子类中的定义
public xiuxian(string name, Gender gender, Profession profession, BornAddress bornAddress)
	:base( name,  gender,  profession,  bornAddress)
{//不用写
}

12. 多态之成员继承

12.1 多态简介

  1. 面向对象开发有三大特性之一。
  2. 继承关系前提下,实例化出不同的对象,这些对象调用相同的方法,但是却表现出不同的行为,这就叫做多态。
  3. 在C#语言中体现多态有三种方式:虚方法,抽象类,接口

12.2 虚方法语法

  1. 什么是虚方法?
    父类中使用 virtual 关键字修饰的方法,就是虚方法。
    子类中可以使用 override 关键字对该虚方法进行重写。
  2. 虚方法语法

方法名相同

父类:

public virtual 返回值 类方法名()
{
功能代码段;
}

子类进行重写:

public override 返回值 类方法名()
{
功能代码段;
}

12.3 虚方法使用细节

  1. 将父类的方法标记为虚方法,就是在父类方法的返回值前加 virtual 关键字,表示这个方法可以被子类重写。
  2. 子类重写父类方法,在子类的方法的返回值前加 override 关键字。
  3. 父类中的虚方法,子类可以重写,也可以不重写。不重写调用的就是父类中的虚方法。
  4. 父类中用 virtual 修饰的方法,可以用于实现该方法共有的功能(比如初始化该方法)。然后在子类重写该方法时,也可以使用 base 关键字调用父类中的该方法。
  5. 要带上 { }

例如:

public override void Cry()
{
	base.Cry();//使用 base 关键字 调用父类中的该方法
	Console.WriteLine("咆哮");
}

13. 多态之里氏转换原则

13.1 面向对象六大原则

六大原则在 面向对象编程 中是作为编程的 “指导思想”和“行动指南” 存在的。

> 六大原则如下:

①单一职责原则;②开闭原则;③里氏转换原则;
④依赖倒置原则;⑤接口隔离原则;⑥迪米特原则(最少知道原则);

13.2 里氏转换原则

  1. 里氏转换原则是什么?
    所有引用父类的地方必须能透明地使用其子类的对象,也就是说子类可以扩展父类的功能,但不能改变父类原有的功能。
  2. 里氏转换原则的具体内容
    子类的对象可以直接赋值给父类(包括接口)的对象
    用 new 关键字创建一片子类的空间并将地址赋值给父类的对象,
    相当于父类 变量 = new 子类();
    当子类C继承了IUSB接口的时候,且有一个A类(不一定是该子类的父类)内定义了一个IUSB接口类型的B字段,则该子类的对象C1可以赋值给A类的对象A1内的B字段,等于A1.B = C1;
    ②子类对象可以调用父类中的成员,但是父类对象永远只能调用自己的成员;
    ③如果父类对象中装的是子类对象,可以将这个父类对象强制转换为子类对象;
    ④如果在子类中对父类的虚方法进行重写,不能该改变该虚方法的返回值、访问修饰符和参数,只能改变功能代码段、并将virtual改为override。
  3. is 和 as
    is 用于检查对象是否与给定类型兼容(只考虑引用转换、装箱转换和拆箱转换),而在引用转换中就是检查该对象是否为目标类或该类的父类。
    as 与也是检查对象是否与目标类型兼容,但是 as 不会做的转换操作,当需要转化对象的类型属于转换目标类型或者转换目标类型的派生类型时,那么此转换操作才能成功,而且并不产生新的对象。

is 和 as 两个关键字都可以进行类型转换

对象 is 目标类;
对象 as 目标类;

is:如果转换成功,返回true,失败返回false;
as:如果转换成功,返回对应的对象,失败返回null。

14. 多态之抽象类语法

14.1 抽象方法

  1. 虚方法->抽象方法
    父类里面用 virtual 关键字修饰的方法叫做虚方法,子类可以使用 override 重写该虚方法,也可以不重写。
    虚方法还是有功能代码段的,当我们父类中的这个方法已经无法确定功能代码段的时候,就可以使用另外一种形式来表现,而这种形式叫抽象方法
  2. 抽象方法语法
    例如:
public abstract void Speak();//不能写{},要带上;

①抽象方法的返回值类型前用关键字 abstract 修饰,且无功能代码段。
②其中抽象方法必须存在于抽象类中。
③不能写 {} ,要带上 ;
子类继承了抽象类后,一定得对父类的所有抽象方法进行重写。 否则编译器会报错。

14.2 抽象类

  1. 抽象类语法
    在定义类的关键字 class 前面加 abstract 修饰的类就是抽象类
    子类继承抽象类,使用 override 关键字重写父类中所有的抽象方法。
  2. 注意事项
    <1> 抽象类中不一定要有抽象方法,可以编写不是抽象方法的方法,但是抽象方法必须存在于抽象类中
    <2> 抽象类不能被实例化,因为抽象类中有抽象方法,如果真能实例化抽象类的话,调用这些无功能代码段的方法是没有任何意义的,所以无法实例化。
  3. 使用场景
    <1>当父类中的方法不知道如何去实现的时候,可以考虑将父类写成抽象类,将方法写成抽象方法。
    <2>如果父类中的方法有默认实现,并且父类需要被实例化,这时可以考虑将父类定义成一个普通类,用虚方法实现多态。
    <3>如果父类中的方法没有默认实现,父类也不需要被实例化,则可以将该类定义为抽象类。

15. 多态之抽象类案例

  1. 前置回顾
    <1>面向对象的三个特性:封装,继承,多态。都是我们后期规划代码结构的基本思想。
    <2>使用 Unity3D 开发一款游戏,大点的项目可能会有几百个独立的脚本文件,这么多的脚本文件,如果没有一个代码结构框架来管理的话,项目是会开发失败的。

  2. 使用抽象类结构实现NPC模块
    在游戏中会出现很多种不同用途的 NPC ,这些 NPC 有各自的存在的价值和作用,同时又具备一些共性的东西。在开发 NPC 系统的时候,常常是需要提取共性,抽象成一个父类,然后子类继承该父类,进而实现不同作用的 NPC 子类。

例如:
任务NPC,商贩NPC,铁匠NPC,三种 NPC 的种类。
共有属性:npc 的名字,npc 的类型;
共有方法:都能和玩家交互(交谈任务或闲聊);

16. 多态之接口语法

16.1 接口语法

  1. 抽象类->接口
    前提:当 抽象类中所有的方法都是抽象方法 的时候
    这个时候可以把这个抽象类用另外一种形式来表现,这种形式叫接口

  2. 语法格式和规范

抽象类名的命名规范:
接口名一般为“IXxxx”,I + 接口名(帕斯卡命名法)
类和接口的名称区别:为且仅为前缀"I"

格式:

返回值 方法名(参数列表);

注意事项:
①接口使用 interface 关键字定义,没有 class 关键字
②接口中不能包含字段,但是可以包含自动属性,不支持常规属性。
③接口中定义的方法全是抽象方法,但不需要用 abstract 修饰
④接口中的成员不允许添加访问修饰符,默认都是public

  1. 接口注意事项
    ①接口中所有的方法都是抽象方法,所以接口不能被实例化。
    一个类可以 实现 多个接口,被实现的多个接口之间用逗号 “,” 分隔。
    一个接口可以 继承 多个接口,接口之间也要用逗号 “,” 分隔。

16.2 使用场景

当我们对现在已经存在的类的 继承关系 进行 功能扩展 的时候,就可以使用接口来完成相应的工作。

17. 多态之接口案例

17.1 常规属性

先定义一个私有的字段,然后在为这个私有字段封装一个公开的属性,在属性中实现get 和set 两个方法,这种方式叫做常规属性。
当我们使用常规属性的时候,可以在 get 和 set 方法中,编写逻辑代码以便对 取值 和 赋值 进行逻辑性的检查。

17.2 自动属性

前提:当属性的 get 和 set 只是完成字段的取值和赋值操作,而不包含
任何附加的逻辑代码的时候。

格式如下:

public 数据类型 属性名 { get; set; }//允许赋值和获取
public 数据类型 属性名 { get; }//只允许获取
public 数据类型 属性名 { set; }//只允许赋值

等同于

public 数据类型 属性名
{
	get{return 字段名;}
	set{字段名= value;}
}
public 数据类型 属性名
{
	get{return 字段名;}
}
public 数据类型 属性名
{
	set{字段名= value;}
}

当我们使用自动属性的时候,就不需要再写对应的字段了,C#编译器会自动给
我们的自动属性提供一个对应的字段。这提高了代码的简洁性。

注意:
在接口中使用属性,就要用不加上访问修饰符的自动属性,因为接口的方法和属性都是公共的,没有必要写“public”。
而且接口中没有字段,无法编写常规属性。

数据类型 属性名 { get; set; }//允许赋值和获取
数据类型 属性名 { get; }//只允许获取
数据类型 属性名 { set; }//只允许赋值
//在接口中的属性格式

18. 多态之虚方法抽象类接口对比

18.0 使用场景对比

虚方法: 父类中的个别方法用虚方法实现,然后允许子类在有需要的情况下重写这些虚方法。

抽象类: 父类定义一系列的规范,子类去把父类里面定义的这些规范全部实现。

接口: 是一种功能的扩展,是在原有的类的继承关系以外的新功能的扩展。

19. 封装之五种访问修饰符

19.1 五种访问修饰符

  1. public [公开访问]
    公开的访问权限。
    当前类,子类,以它们的实例对象,都可以访问到。
  2. private [私有访问]
    私有的访问权限。
    只能在 当前类 内部进行访问使用;子类,实例对象,都访问不到。
  3. protected [保护访问]
    受保护的访问权限。
    只能在 当前类 的内部,以及 该类的子类 中访问;实例对象访问不到。
  4. internal [内部访问]
    只能在 当前程序集(项目) 中访问;
    在同一个项目中 internal 和 public 的访问权限是一样的。
  5. protected internal [内部保护访问]
    protected + internal 的访问权限。

19.2 使用场合

  1. 修饰
    能够修饰类的访问修饰符只有两个,public 和 internal;
    类的默认访问修饰符是 internal。
  2. 修饰类的成员
    五种访问修饰符都可以修饰类中的成员;
    类中的成员默认访问修饰符是 private。

20. 静态之字段与属性

20.1 static 关键字

static 关键字,用于修饰类,字段,属性,方法,构造方法等。
被static 修饰的类称之为“静态类”;
被static 修饰的成员称之为“静态成员”。

20.2 静态字段

  1. 概念
    被static 关键字修饰的字段,叫做“静态字段”。
    静态字段不属于任何对象,只属于类,必须要用类名.静态字段名进行访问,通过对象名.静态字段名的方式是访问不到静态字段的。
  2. 注意事项
    静态字段是可以重新赋值的,类名.静态字段名= 新的值;
    静态字段存储的数据在内存中只有一份
    实例(对象)字段在内存中会有 N 份,有多少对象就会有多少实例字段;

20.3 静态属性

  1. 概念
    被 static 关键字修饰的属性,叫做“静态属性”。
    静态属性用于对静态字段进行封装,并保证静态字段值的合法性;
    静态属性使用类名.静态属性名进行访问;
  2. 注意事项
    静态属性不能用于封装非静态字段。
    因为,在静态变量是在编译过程中就已经分配空间的,而非静态成员需要等到类实例化对象后才会分配内存,静态的类成员是先于非静态的类成员存在的。

21. 静态之方法与类

21.1 静态普通方法

  1. 概念
    被 static 修饰的方法,叫做静态方法。
    直接使用类名.静态方法名进行访问。
  2. 注意事项
    ①控制台程序的 Program 入口类中的 Main 函数就是一个静态方法;
    ②在静态方法内中不能调用非静态方法,只能调用静态方法。如下:
public void H1()
{}
public static void N1()
{
	H1();//失败
}

21.2 静态构造方法

  1. 概念
    ①静态构造方法的作用是用于初始化静态成员。
    一个类只能有一个静态构造方法,该静态方法没有任何访问修饰符,也没有参数。
    ③可以定义在静态类中,也可以定义在非静态类中。
  2. 执行的时间
    静态构造方法会 在程序创建该类第一个实例对象或引用任何静态成员 之前,完成此类中 静态成员的初始化

21.3 静态类

  1. 概念
    当类中的成员全部是静态成员的时候,可以把这个类声明为静态类。
    声明静态来需要在 class 关键字前加静态关键字static。
  2. 注意事项
    ①静态类中不能存在非静态(实例)成员;
    ②静态类不能实例化对象。

22. 静态之单例设计模式

22.1 设计模式

<1>在程序开发过程中经常会遇到一些典型的问题,在对这些问题进行处理解决
的过程中,慢慢的整理出来了一套系统的解决方案,这套方案称之为“设计模式”。
<2>在游戏中有很多副本,很多Boss 需要击杀和通关,网上就出现了很多针
对性的副本,Boss 的攻略,通过这些攻略可以很方便的通过这些副本和Boss。
<3>在我们的开发过程中,也会遇到很多针对性的问题,我们就可以学习前人总
结好的各种各样的设计模式,来解决这些编程开发中的问题。
<4>大多数情况下,我们只需要把前人总结好的设计模式学会即可。并不需要自
己去研究去创造新的模式。代表性的有《GoF23 种设计模式》。

22.2 单例设计模式

  1. 何为单例?
    设计一个类的时候,需要保证整个程序在运行期间只存在一个实例对象。
    解决这个问题,我们就需要用到“单例(模式)”。
  2. 注意事项
    单例设计模式是用于非静态类中的,在静态类中写单例无意义。

22.3 单例代码步骤

<1>在类定义内声明一个静态且私有的这么一个当前类的类型的字段;
private static ClassName instance;

<2>创建私有无参构造方法,保证外部无法实例化这个类;
private ClassName() { }

<3>创建一个静态方法,用于创建此类的唯一对象。
public static ClassName Instance()
{
if (instance == null)
{
instance = new ClassName();//没有则创建一个
}
return instance;//返回地址
}

最后实例化的方法为:

ClassName m1 = ClassName.Instance();
ClassName m2 = ClassName.Instance();

最后结果是m1和m2都存有相同的地址,都指向同一块空间

23. 嵌套类匿名类与密封类

23.1 嵌套类

  1. 概念
    在C#中可以将一个类定义在另一个类的内部;
    外面的类叫“外部类”,内部的类叫“嵌套类”;嵌套类和普通类相似.
  2. 注意事项
    如果想实例化嵌套类的话,需要使用外部类名.嵌套类名的方式访问到内部类,然后外部类名.嵌套类名 对象名 = new 外部类名.嵌套类名();

23.2 匿名类

  1. 概念
    如果某个类的实例对象只会使用到一次,可以使用匿名类的方式创建这个对象。不需要定义类,我们就可以创建一个对象。
    这样的类一般用于存储一组只读属性
  2. 代码格式
    使用一个没定义的类名比如var,然后操作如下
var p = new { Name = "Monkey", Age = 100 };//定义并初始化这个类的对象内两个string的字段

23.3 密封类

  1. 概念
    sealed 关键字修饰过的类不可以被继承,也就是说不能有子类;这样的类,通常被称之为“密封类”。

24. 装箱与拆箱

24.1 Object 类

  1. 概念
    在C#语言中,Object 类所有类的父类,在C#中所有的类(内置的,用户自己创建的)都直接或者间接继承自Object 类。

Object 是类,object 是类型。(类与系统关键字的语法颜色区别)

  1. Object类
    可以在调用任意类的对象中的方法时,找到几个类内没定义过的方法,比如ToString,这些都是继承自Object类的方法。
    输入Object Txt;鼠标左键移到 Object 双击,按下F12,就可以查看到 Object类
    C#的进阶_第2张图片
  2. 重写 ToString 方法
    我们经常会在自己的类中重写 ToString 方法,将类中的信息打印输出。因为原 ToString 方法返回的是该类的命名空间和类名。
    目的是:辅助调试和开发。

24.2 装箱与拆箱

  1. 概念
    装箱(自动的):值类型–>引用类型,eg:int的a赋值给object的b
    拆箱(强制的):引用类型–>值类型,eg:object的b赋值给int的a

前提:两种类型只有存在继承关系的时候,才可能出现装箱或拆箱操作。

  1. 注意事项
    装箱和拆箱本质上是数据存储在栈空间与堆空间之间变更,因此频繁的装箱或拆
    箱会降低代码的运行速度,所以代码中尽量少用装箱或拆箱操作。

25. 预编译指令与对象初始化器

25.1 预编译指令

  1. 什么是预编译指令?
    预编译指令也叫预处理指令,都是在程序正式编译之前执行。
    这些指令不会转化为可执行代码中的命令,但是会影响编译过程的各个方面。
  2. 区域指令
    区域指令是预编译指令的一种。

指令格式:

#region 定义的名
......
#endregion

作用:
①优化代码结构,当我们一个脚本文件有500 行+,1000 行+的时候,一个脚本
文件中会出现大量的字段,属性,方法,各种各样的功能的代码。
②如果想要快速的定位某个功能的代码,是很不方便的。这个时候我们可以使用区域指令,进行代码折叠,并显示 定义的名

25.2 对象初始化器

  1. 概念
    在一个类中,我们通常使用构造方法来对属性进行赋值,完成对象的初始化。
    但是当一个类中的属性很多很多的时候,不可能为各种情况都定义或重载构造方法,这个时候可以使用 “对象初始化器” 来完成属性的初始化。
  2. 注意事项
    使用 对象初始化器” 就不能给该类定义 有参的构造方法
  3. 语法格式:
类名 对象名 = new 类名(){属性名 =;属性名 =};

你可能感兴趣的:(面向对象编程,c#)