第03部分 C#核心——面向对象
前言
0.1 面向过程(POP)
-
“面向过程”(Procedure Oriented)是一种以过程为中心的编程思想。这些都是以什么正在发生为主要目标进行编程,不同于面向对象的是谁在受影响。与面向对象明显的不同就是封装、继承、类。简写为POP。
-
C语言:面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
0.2 面向数据(DOP)
-
面向数据主要是指面向数据解析(DOP,Data-oriented parsing),也称为面向数据编程(data-oriented processing),是一种形式主义概率语法的计算语言学。
-
面向数据是更多的是在分析、设计、以及实现过程中,以数据为中中心,跟踪数据流向,从而保证数据流守恒。例如,由某个模块分别流向某些模块。而上述一切的操作都基于维护这些数据的完整性、一致性和有效性。同时在设计类的时候,面向数据的设计,为了体现数据的重要性,一般是在类的开始就定义数据。
0.3 面向对象(OOP)
-
面向对象(Object Oriented)是软件开发方法,一种编程范式。面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库方法、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
-
面向对象是相对于面向过程来讲的,面向对象方法,把相关的数据和方法组织为一个整体来看待,从更高的层次来进行系统建模,更贴近事物的自然运行模式。
第一部分:封装性
1> 面向对象
-
前导:功能与数据分离,使用的建模概念不能直接映射问题域中的对象。不符合人们对现实世界的认知和思维方式。自顶向上的设计方法限制了软件模式的可复用性,降低了开发的效率,当系统的需求变化时,维护和扩展都会变得非常困难。
-
概念:面向对象工程是一种新兴的程序设计方法,或者是一种新的程序设计规范,其基本思想是使用对象,类,封装,继承,消息等概念来进行程序设计。
-
面向对象是一种对现实世界的理解和抽象的编程方法,把相关性的数据和方法组织为一个整体来看待,从更高的层次来进行程序开发,更贴近事物的自然运行模式。
-
优点:
- 从现实世界中客观存在的事物或对象出发来构造软件系统,并且在系统构造尽可能运用人类的自然思维方式。
- 可提高代码的复用率,提高开发效率,提高程序的可拓展性,清晰的逻辑关系。
- 对于描述的对象而言,属性是描述对象的静态特征,操作是描述对象的动态特征。
-
特征:
- 封装性:把对象的属性和操作结合成一个独立的相同单位,并尽可能隐藏对象的内部细节(用程序语言来描述对象)。
- 继承性:在泛化关系中特殊类可自动拥有一般类的属性和操作,这叫做继承,而特殊类可以定义自己的属性和操作;对于一般类的功能进行补充(子类复用封装对象的代码)。
- 继承具有传递性,派生可享受各级基类所提供的服务,从而实现高度的可复用性,当基类的某项功能发生变化时,对它的修改会自动反映到各个派生类,提高软件的可维护性。
- 多态性:指同一事物在不同条件下可以表现不同的形态。
2> 类和对象
-
面向对象中类的作用,类是面向对象技术中最重要的结构,支持信息隐藏和封装,支持对抽象数据类型的实现,信息隐藏(对象私有信息)不能由外界直接访问,而只能通过该对象公开的操作来间接访问,有助于提高程序的可靠性和安全性。
-
基本概念:具有相同的特征,具有相同行为,是一类事物的抽象,可以通过类创建出对象。
-
语法:
<访问修饰符><修饰符(限定)><class<T>><类名称>:继承列表 where T:约束条件{ 方法体 } -
成员:
- 特征——成员变量
- 行为——成员方法
- 保护特征——属性
- 类型——构造函数和析构函数,索引器,运算符重载,静态成员
3>成员变量
- 基本规则:声明在类语句块中,用来描述对象的特征,可以是任意变量类型,数量不作限制,且是否赋值根据需求来。类声明作为class成员时,尽量不new()声明,防止栈溢出。
4>成员方法
- 基本规则:声明在类语句中,用来描述对象的行为的,受到访问修饰符影响。成员方法不作static修饰,必须实例化对象才能访问,相当于该对象执行了该行为。
5>构造和析构
构造函数
- 构造函数用于对象分配空间,完成初始化工作。
- 特征:
- 名称与类名相同,可以重载,任意数目参数,但无返回的概念,主要用于初始化成员变量,new创建类实例的同时分配初始化成员初始值。
- 系统会默认生成一个无参构造,无特殊需求,一般都是public修饰。
- 但未声明无参构造时声明了有参构造,会隐藏掉默认的无参构造。
特殊用法:复用构造
- 当一个构造的函数签名后有复用构造时,会先调用this(参),然后再调用本体构造:
class A
{
public int x;
public A()
{
Console.Write("6666");
}
public A(int x):this()//复用构造
{
this.x = x;
}
static void Main(string[] args)
{
var a = new A(1);
Console.Write(a.x);
}
}//output:6666 1;
析构函数
-
但对象脱离其作用域时,系统会默认生成一个无参析构函数(但引用类型的堆内存被回收时,会调用析构函数)
-
基本语法:
~ClassA(){
<可选方法体> }//只有在垃圾回收时,会调用析构函数
6> 成员属性
- 基本概念:用于保护成员变量,为成员属性的获取和赋值添加逻辑处理解决public,protected,private的局限性,让成员在外部只能获取不能修改,或只写不可读。作用是保护,限定,加密,赋值等作用。
7> 索引器
-
基本概念:让对象可以像数组一样可以通过索引的方式访问类的成员,是程序更加直观更容易编写。
-
语法:
访问修饰符 返回值 this[参数列表] //<可以重载>
{
get;
set;
}
- 索引器配合switch和泛型类型返回,比较适用于在类中有数组变量时使用,可以方便的访问和逻辑处理。
8> 静态成员
-
静态成员巨有全局性和唯一性。
-
基本概念:用static声明的成员叫静态成员,所属于类,为类中所有实例共享,只能从类名点出而不是类实例。
-
非实例调用静态的特征:
- 实例成员在程序中给并不是无中生有的,之所以使用对象,变量和函数,都是要在内存中分配内存空间的,实例化成员目的就是分配空间,在程序中生成一个抽象的对象。
- 而静态成员在程序开始时,就会分配空间,所以我们可以直接使用,且静态成员同程序同生共死,只要使用了静态成员,只有在程序结束时内存空间才会释放。
- 每一个静态成员都有自己唯一的开辟空间,这使得静态成员具有了唯一性,不可使用实例成员或this调用,且静态方法中不能调用非静态的方法和引用非静态的成员变量。
-
作用:常用唯一变量的声明,方便别人获取的对象声明。常用的唯一方法声明。
-
常量与静态变量:const可以被认为是特殊的静态,都是通过类名点出使用,不同在于const必须初始化,不可修改,静态变量可修改。
9> 静态类
-
用static修饰的类,只能包含静态成员且不能实例化和继承。一般认为是工具类(Math类,Console类),体现出工具的唯一性,一般为public修饰。
-
静态构造函数:静态类和普通类都可以有,不能使用访问修饰符,不能有参数,且在调用类的时候优先调用一次静态构造,并且只会调用一次。<自动调用>
-
静态类可用于拓展方法。
10> 拓展方法
-
基本概念:为现有的非静态变量类型添加新方法。
-
作用:提高程序的拓展性,不需要在对象中重新写方法。不需要继承来添加方法,为别人封装的类型写额外的方法。
-
特点:一定是写在静态类中,且是个静态函数。第一个参数为拓展目标,第一个参数用this修饰。
-
语法:访问修饰符 static 返回值 函数名(this 拓展类名 参数名,参数列表){ 方法体 }
可以声明在任意静态类里。且静态类必须声明在顶级语言内,不可以是嵌套静态类类。
static class B
{
public static void Func(this int value,int a)
//为int添加了一个拓展方法名为Func的方法
{
int Sum = value + a;
Console.WriteLine(Sum );
}
}
//在其他类中调用时:
class A
{
int a = 15;
a.Func(a:15);//output:30
}
- 为int拓展了方法,需要一个对象int实例去调用。可以为任何数据类型和自定义类型拓展方法
- 但拓展方法名与类中实例方法重名时,拓展方法会被隐藏且只会调用自身方法,当方法签名不一致时,也不会形成重载,不可使用拓展方法。
11> 运算符重载
-
表示让自定义类和结构体能够使用运算符。(operator)
-
特征:一定是一个公共静态方法,返回值在operator前面,逻辑处理自定义。但是,条件运算符需要成对实现,一个符号可以多个重载,但是不能使用参数修饰符。
-
基本语法:
public static A operator + (A a1,A a2)
{
A a = new A();
a = a1 + a2;
return a;
}
一元运算符只能有一个参数,二元运算符必须有两个参数,相对应的复合运算符会自动实现重载。
12> 内部和分部类
-
内部类是嵌套类,点出体现亲密关系,访问修饰符作用很大。
-
分部类(partial)分部声明,增加程序拓展性,分部类可以写在多个脚本文件中,分部类的访问修饰符要一致,且不能有重复成员。
-
分部方法和分部类规则类似,且要求声明签名必须一致,不可重复声明局部变量。
第二部分:继承性
1> 继承的基本规则
-
一个类B继承类A,类B会将类A的所有成员,因此类B会有A的所有特征和行为。
-
特征:单一继承性,传递性马克间接继承父类的父类。子类可与父类同名,成员同名,但是不建议,用new可以覆盖掉父类的同名方法。
2> 里氏替换原则
- 表示任何父类出现的地方,子类都可以替代;语法上表现在父类容器装子类对象,子类对象包含了父类的所有内容。作用在于,方便进行对象的存储和管理。、
3> 继承中的构造函数
-
当声明一个子类对象时,先调用父类的构造函数,再执行子类的构造函数,子类继承父类成员,初始化子类对象的同时,会先将父类对象的成员初始化。
-
父类无参构造很重要,子类可以通过base代表父类,调用父类构造,在子类的构造函数声明中,可以用base显式指定父类的构造调用。
class A
{
public A()
{
Console.WriteLine("这是A的无参构造");
}
public A(int a, int b)
{
Console.WriteLine("这是A的参构造函数");
}
}
class B : A
{
public B():base(1,2)//指定调用父类的构造函数
{
Console.WriteLine("这是B的无参构造");
}
static void Main(string[] args)
{
B b = new B();
}
}//output:这是A的参构造函数
// 这是B的无参构造
4> 万物之父和装箱拆箱
-
万物之父:object是所有类型的基类,它是一个类,利用里氏替换原则,用object容器装所有对象,也可以用来表示不确定对象,作为方法的参数类型。
-
使用:
object obj = value;
if(obj is T){
obj as T};
//T1 = obj as T1;
装箱拆箱:装值类型和拆值类型
- 装:栈类型迁移到堆内存。
- 拆:堆类型迁移到栈内存。
当不确定类型时可以方便参数的存储和传递,但会增加性能消耗,存在内存迁移。因此要减少太多的拆装箱操作,减少性能的消耗。
5> 密封类
-
表示使用sealed密封,使之无法被继承。
-
作用:在面向对象程序设计中,密封类的主要作用就是不允许最底层子类被继承,可以保护程序的规范性,安全性。在框架中的作用很大。
第三部分:多态性
- 继承同一个父类的多个子类调用相同方法有不同的表现(让同一对象有唯一行为的特征)
1> 多态的实现
- 编译时多态——函数重载。
运行时多态——vob(virtual override base),抽象方法,接口
- 利用重写功能使父类子类对象行为统一。
2> vob模式的优势
-
当继承的子类拥有和父类相同名称的成员时,用new声明可以覆盖隐藏掉父类的同名成员,这时破坏了里氏替换原则,子类无法正常调用父类的完整成员,当访问父类方法时只能通过父类本身的对象去调用。
-
vob原则:重写父类中继承的虚方法,使得里氏替换原则生效,使同一个对象有唯一行为的特征。
3> 抽象类和抽象方法(abstract)
-
抽象类:不能被实例化的类,可以包含抽象方法,继承其的子类必须能够重写其抽象方法。
-
抽象方法:抽象方法只能有签名而没有具体的逻辑处理块,且不能用private修饰。
4> 接口(实现多态的第三种方式)
-
接口是行为的抽象规范,自定义类型,在7.3#接口中不能包含成员变量,只包含方法,属性,索引器,事件。且成员不能有访问修饰符修饰,接口也可以继承接口。
-
在8.0#开始,接口可以有具体方法,也可以有访问修饰符,但是仍然不可以声明实例字段,静态成员字段可以声明,并在静态构造函数中进行初始化。
-
接口与抽象方法相同,不可以实例化,但是可以作为容器装子类对象。
5> 密封方法(多态)
- 使用sealed修饰重写方法,说明此方法标记已完整。使得虚方法,抽象方法不再被重写。
sealed virtual / abstract
第四部分: 面向对象关联内容
1> 命名空间
-
基本概念:命名空间用来组织和重用代码的,像是一个工具包,类就像一件一件的工具,都是声明在命名空间里的。
-
不同的命名空间中相互作用使用,需要using引用命名空间或出处(完全限定名称点出)
2> StringBuilder
- C#内置了一个用于处理字符串的公共类,主要用于解决的问题使修改字符串而不是创建新的字符串对象,需要频繁修改和拼接字符串可以使用StringBuilder可以提升性能,//Using System.Text;
StringBuilder str = new StringBuilder(String.Empty);
//StringBulider存在一个容量的概念,每次增加内容会自动扩容。
str.Capacity;//容量
str.Length;//当前长度。
<增>: str.Append(string str);
str.AppendFormat("{0}",0);
<插>: str.Insert(index,string str);
<删>: str.Remove(index,Length/count);
str.Clear();
<查>: str[index]//索引查找
<改>: str[index]='char';
<替换>: str.Replace(str,str2);//用str2将str替换
<比较>: str.Equals("str");
- 优点:摆脱了字符串只读的局限性。
3> 结构体和类的区别
-
最大的区别是存储的空间不同,因为结构是值类型,类是引用类型,结构体存在栈中,类存在堆中。
-
结构值类型,类引用传递。
-
结构体和类在使用上相似,结构体甚至可以用面向对象的思想来形容一类对象。
-
结构体具备有面向对象思想中封装的特性,但是它不具备继承和多态的特征,因此大大减少了它的使用频率。
-
由于结构不具备继承的特点,因此不可以用protected保护修饰。
-
(细节区别)结构体声明变量不能指定初始值,不能声明无参构造,构造函数必须实现所有成员初始化任务,自带默认无参构造,不能声明析构函数,不能被继承,不能static修饰,成员可以,内部不能声明同名变量,同名用于构造函数。
-
(特殊)结构体可以继承接口,接口是行为的抽象。
-
(如何选择结构体或类)当想要继承多态性质可以使用类(玩家,怪物等),当对象是数据的集合时,优先考虑结构体(坐标,怪物重生点,位置等),从值或引用类型赋值时的区别上去考虑,比如经常被赋值的对象,并且改变赋值对象,原对象不能跟着变化时,就用结构体,如坐标,向量旋转。
4> 抽象类和接口的区别
-
相同点:都可以被继承,都不能直接实例化,都可以包含方法声明,子类必须为其提供实现,都遵循里氏替换原则。
-
区别:接口中没有构造函数,且类可继承多个接口,类可以有若干个构造函数且只能单一继承,抽象类中还可以有成员变量和声明成员方法,虚方法和静态成员,抽象方法。在7.3#,接口只能声明没有实现的抽象方法,抽象类可以添加访问修饰符,接口中默认为public。
-
如何选择:表示对象用抽象类,表示行为的拓展用接口。不同的对象拥有共同的行为,我们往往可以使用接口来实现。
转载:https://blog.csdn.net/weixin_52566131/article/details/116425011