在学唐老狮的课程 顺便ab自己记录一下
C#是面向对象语言,也具备三大特征,ab会从这里开始。
类的声明 和 类对象 声明是两个概念。
类的声明: 相当于自定义一个变量类型。
类对象:是从类里创建出来的,而创建对象的过程称之为实例化对象。
成员变量:要声明在类中,用来描述对象的特征,可以是任意变量类型(可以在一个类中声明别的类),同时成员变量的数量不做限制,而是否要赋值根据需求决定。
成员变量的默认值: 值类型 数字类型都是0,bool类型为false,引用类型为null。
如果要在类中声明一个和自己相同类型的成员变量时,不能对它进行实例化。
访问修饰符:
public —公共的:自己(内部)和别人(外部)都能访问和使用。‘
private —私有的: 自己(内部)才能访问和使用;不写的时候默认为private。
protected —保护的: 自己(内部)和子类才能访问和使用。
成员方法(函数)用来表现对象行为,并且受到访问修饰符的影响,返回值参数不做限定,成员方法的数量也不做限制。
成员方法不能加Static关键字,使用时必须实例化出对象,再通过对象使用(可以想成某个对象执行了某个行为)。
定义一个老师类和学生类,再定义一个实物类,有名字,并让他们联系起来
如:老师吃了什么什么,学生吃了什么什么。
在实例化对象(new对象)时,会调用用于初始化的函数,就叫做构造函数。在不写构造函数的时候,会有一个默认的无参构造函数。
构造函数的写法:没有返回值,函数名必须和类名相同,没有特殊需求时都是public,并且构造函数可以被重载。
如果自己没有写无参构造函数,却写了有参构造函数,默认的无参构造函数会被顶掉。
是用于保护成员变量的,为成员变量的获取和赋值添加逻辑处理,也可以解决public,private,protected的局限性。属性可以让成员变量在外部(可读不可写),(可读只有本类可写)等等.
上面就是一个属性,相当于把小写的name包裹了一层。
get和set可以只写一个
1. 当get set前面什么都不写时,会使用声明时的访问权限。
2.加的访问修饰符要低于属性的访问权限。(get和set的访问权限不能比声明变量时的大)
3.不能让get和set的权限都低于属性的权限。(两个都写就让声明的访问修饰符没用了)
用Static修饰的成员变量,方法,属性等,都叫做静态成员。
静态成员可以直接通过类名点出来使用
静态成员属于这个类!并不属于这个类的实例对象。
静态成员在程序运行后就会存在。静态函数中不能使用非静态成员(因为成员变量要将对象实例化出来后,才能点出来使用,不能无中生有),想要使用可以在静态函数中实例化一个。
报错
没报错
说的更细一点就是 静态成员方法在程序一运行就有了,但是你还没有声明别的变量,所以此时不能使用非静态变量(无中生有了就是), 但是如果你在静态成员方法中实例化了过了,那就能用。
常量和静态变量的区别:
相同点:他们都可以通过类名点出来使用。
不同点:1.const必须初始化 不能修改,而static没有这个限制。
2.const只能修饰变量,static可以修饰很多。
3.const一定是卸载访问修饰符后的,static没有这个要求。
静态类:
就是class前面加了static,静态类只能包含静态成员,不能被实例化。
作用:工具类,拓展方法用。
静态构造函数:
就是在构造函数上加static。可以在静态构造函数中初始化静态变量。
1.静态类和非静态类都可以有。
2.不能使用访问修饰符。
3.不能有参数
4.只会自动调用一次。(静态构造函数只会在靠前次使用这个类时调用一次)
作用:初始化静态成员。
为现有 非静态 变量类型 添加新方法。
为非静态变量类型 添加方法,也就是说要从实例点出来用。
1.一定是写在静态类中。
2.一定是个静态函数
3.靠前个参数为拓展目标(类)
4.靠前个参数用this修饰
为整形拓展一个求平方的方法:
内部类:
在类的内部声明的类,使用时要用包裹的类点出自己,访问修饰符的作用很大。
如:
分布类(partial):
把一个类分成几部分声明,增加拓展性。
分布类可以写在多个脚本中,并且他们的访问修饰符要一致,最后不能有重复的成员。
一个类A继承一个类B,类A将会继承类B的所有成员,A类将拥有B类的所有特征行为。
被继承的类:叫做父类或基类。
继承的类:叫做子类或派生类。
子类将拥有父类所有特征和行为,并且子类可以有自己的特征和行为,也受到访问修饰符的影响。
public—公共,内外部访问。
private—私有,内部访问。
protected—保护,内部和子类访问。
internal—内部,只有在同一个程序集的文件中,内部类型或者是成员才能访问。
特点:
1.单根性:子类只能由一个父类。
2.传递性:子类可以间接继承父类的父类。
里氏替换原则是面向对象七大原则中最重要的原则。
即:任何父类出现的地方,子类都可以代替(因为子类对象包含了父类所有内容),父类容器装子类对象。
这里用父类装载了子类(Player)那他可以用Player类的成员方法吗?
显然是不行的,方法是在子类里的,而你声明的是父类对象。
父类 对象名 = new 子类();
这时就要用到 ↓ 下面的知识点 ↓
is:用于判断,判断一个对象是否是指定类对象。 返回值:bool,是为true,不是false。
as:用于转换,将一个对象转换为指定类对象。 返回值:指定类型对象(转换成功时),失败则返回null。
写一个Monster类,派生出Boss和Goblin两个类,Boss有技能;小怪有攻击;随机生成十个怪,装载到数组中,最后遍历这个数组调用他们的攻击方法。
当声明一个子类对象时,会先执行父类的构造函数,再执行子类的构造函数。
父类的父类的构造函数—→ 父类构造函数—→子类构造函数。(执行顺序)
如图:
这个类还是Grandpa但是 new的是son所以会调用上面的构造函数。
通过base可以调用父类的构造函数(类型要一样吗,如下图)
输出结果会打印出ab
1.父类的无参构造函数很重要(被顶掉的话)
2.子类可以通过base代表父类 调用父类构造函数。
有一个打工人基类,有工种、工作内容两个特征,一个工作方法,
程序员、策划、美术分别继承打工人,用继承中的构造函数知识点
实例化三个对象,分别是程序员、策划、美术。
可以想象成 子类的无参构造函数 调用了父类的有参构造函数 而传递的参数就是自己后面定的。
万物之父object:
关键字是object,它是所有类型的基类,是引用类型。
可以利用里氏替换原则,用object容器装所有对象。也可以用来表示不确定类型,作为函数参数类型。
当用object储存值类型时:(用强转)
当用object储存引用类型时:(用is和as来判断和转换)
装箱和拆箱:
发生条件:
装箱:用object存值类型
把值类型用引用类型存储,栈内存会迁移到堆内存中。
拆箱:再把object转为值类型
把引用类型存储的值取出来,堆内存会迁移到栈内存中。
好处:不确定类型时可以方便参数的存储和传递。
坏处:存在内存迁移,层架性能消耗。
密封类:使用关键字sealed修饰的类,会让该类无法再被继承。(可以理解为让类 结扎)
在面向对象程序设计中,密封类主要作用就是不允许最底层子类被继承,可以保证程序的规范性,安全性。
字面意思就是“多种状态”。
让继承同一个父类的子类们,在执行相同方法的时有不同的表现。
(同一父类的对象 执行相同行为(方法) 有不同的表现)
让同一个对象有较早行为的特征。
多态的实现:
运行时多态(vob,抽象函数,接口)。
vob中:
v:virtual(虚函数):用来给子类重写的。
o:override(重写):跟virtual配套出现的,用来重写方法。
b:base(父类):代表父类,通过base来保留父类行为。
创建一个图形类,有求周长和求面积两个方法。
创建矩形类和圆形类继承图形类。
实例化矩形,圆形对象求面积周长。
抽象类:
被关键字abstract修饰的类,就叫抽象类。
不能被实例化、可以包含抽象方法、继承抽象必须重写其抽象方法。
抽象类中,封装所有的知识都可以写在其中(包括里氏替换原则)!!
抽象函数:
抽象方法又叫纯虚方法,即用abstract关键字 修饰的方法。
只能在抽象类中声明、没有方法体、不能是私有的、继承后必须用override重写。
抽象方法(abstra):
1.不能有方法体,而且必须被子类重写。
2.只能声明在抽象类中。
虚方法(virtual):
1.虚方法是有方法体的,也可以调用,可以被子类选择性重写。
2.虚方法可以在任何非密封类中声明。
共同点:
1.都可以无限被子类重写。
2.都可以base重用。
如何选择使用普通类还是抽象类呢?
不希望被实例化的对象,相对于比较抽象的类可以用抽象类。
父类中的行为不太需要被实现的,只希望子类定义具体规则的 可以选择使用抽象类。
(用于整体框架设计 会使用)
接口是行为的抽象规范!!!!
是一种自定义类型,关键字为:interface。
接口声明规范:
1.不包含成员变量。
2.只包含方法、属性、索引器、事件。
3.成员不能被实现。(不能写方法体)
4.成员可以不用写访问修饰符,不能是私有的。(此处不写默认是public)
5.接口不能继承类,但是可以继承另一个接口。
接口使用规范:
1.类可以继承多个接口。(类只能继承一个类)
2.类继承接口后,必须实现接口中所有成员。
特点:
1.和类的声明类似。
2.接口是用来继承的。
3.接口不能被实例化,但是可以作为容器存储对象。
通过接口 飞机 跟鸟类建立起了联系(让不同种类的子类有联系),就可以用里氏转换原则,用一个接口父类,存储不同类型的对象。
IFly f = new (飞机); or IFly f = new (鸟);
里氏转换原则 用IFly来存储不同种类 又有相同行为的对象。
当接口继承接口时:
接口继承接口时 不需要实现。
等待 类继承接口后 类自己去实现所有内容。
接口总结:
用密封关键字sealed修饰的重写函数,让虚方法(virtual)或者抽象方法(abstract)之后不能再被重写。(会和override一起出现)
命名空间是用来组织和重用代码的,用来包裹类的。就像是一个工具包,类就像是一件一件的工具,都是声明在命名空间中的。
语法:
namespace 命名空间名
{
class 类名
class 类名
}
1.命名空间是可以分开声明的(和分布类一样),但是里面的类名不能有重复的。
2.不同命名空间中相互使用 需要引用命名空间或指明出处(即使是写在同一个脚本中)
3.不同命名空间中 允许有同名类。
4.命名空间可以包裹命名空间。(using 包裹的命名空间名.被包裹的)
万物之父 object。是所有类型的基类(引用类型)
可以利用里氏替换原则装载一切对象。存在装箱拆箱。
1.object中的静态方法:
Equals:判断两个对象是否相等。 返回bool
最终判断权交给左侧对象的Equals方法,不管值类型or引用类型都会按照左侧对象的Equals方法的规则来进行比较。(引用类型判断相等,是看有没有指向同一个内存地址,并不是字面上的同一个类)。
ReferenceEquals:比较两个对象是否是相同的引用。
主要用来比较引用类型的对象。值类型对象的返回值始终是false。
Equals是比值类型的,ReferenceEquals是比引用类型的。
2.objcet中的成员方法:
GetType:用于获取对象运行时的类型Type,通过Type结合反射相关知识可以做很多关于对象的操作。
MemberwiseClone:用于获取对象的浅拷贝对象。(理解为克隆的不干净,克隆了一个引用类型对象和老的一样,值类型是可以改的)
说白了就是会返回一个新的对象,但是新的对象中的引用类型会和老对象中一致(也就是指向同一个内存地址)。
不能通过 对象.出来用 是因为他是保护(propted)类型。
3.object中的虚方法:
虚方法Equals:
我们可以重写该方法,定义自己比较相等的规则。
虚方法GetHashCode:
该方法是获取对象的哈希码。通过重写该函数来自己定义对象的哈希码算法。
虚方法ToString:
用于返回当前对象代表的字符串。通过重写该函数来自己定义对象转字符的规则。
在打印方法时,默认使用的就是对象的Tostring方法打印出来的内容。
一个Monster的类的引用对象a,Monster类有攻击力,防御力,技能名,血量等属性。
我想复制一个和a对象一模一样的b对象,并且改变b的属性a不会受到影响怎么办?
结构体和类的最大区别是在存储空间上的,因为结构体是值类型,类是引用类型。因此他们的存储位置一个是在栈上,一个是在堆上。
两者在使用上很相似,结构体甚至可以用面向对象的思想来形容一类对象。
但是 结构体只具备封装的特性,并不具备继承和多态的特性,因此大大减少了它的使用频率。
由于结构体不具备继承和多态,所以他不能够使用protected访问修饰符。
细节区分:
1.结构体是值类型所以在栈中。类是引用类型所以在堆中。
2.结构体不具备继承和多态的特性,不能用protected访问修饰符。类则全具备三大特征。
3.结构体成员变量声明不能指定初始值。类可以。
4.结构体不能声明无参的构造函数。类可以。
5.结构体声明有参构造函数后,无参构造函数不会被顶掉。而类的会被顶掉。
6.结构体需要在构造函数中初始化所有变量成员,而类随意。
7.结构体不能被继承。类可以。
8.结构体不能声明析构函数。类可以。
9.结构体不能被Static修饰。类可以。
10.结构体不能在自己内部声明和自己一样的结构体变量。类可以。
注意:结构体可以继承接口。
在什么时候使用结构体和类呢?
1.想要用继承和多态时,直接淘汰结构体 用类,比如玩家,怪物等等。
2.对象是数据***时 优先考虑结构体,比如位置、坐标等等。
3.从值类型和引用类型赋值时的区别上去考虑,比如经常被赋值传递的对象,并且改变赋值对象,原对象不想跟着变化时,就用结构体,比如坐标、向量、旋转等等。
抽象类(abstract):
abstract修饰的类和方法。
抽象类不能被实例化。
抽象方法只能在抽象类中声明 是个纯虚方法,必须在子类中实现。
接口(interface):
interface 自定义类型。
是行为的抽象。
不包含成员变量,仅包含方法,属性,索引器,事件。成员都不能实现,不写访问修饰符时默认为public。
抽象类和接口的相同点:
1.都可以被继承。
2.都不能直接实例化。
3.都可以包含方法声明。
4.子类必须实现未实现的方法。
5.都遵循里氏替换原则。
抽象类和接口的不同点:
1.抽象类中可以有构造函数;接口则不能。
2.抽象类只能被单一继承,接口则可以被继承多个。
3.抽象类中可以有成员变量;接口则不能。
4.抽象类方法可以使用访问修饰符;接口则建议不写 ,因为默认public。
5.抽象类中可以声明成员方法、虚方法、抽象方法 静态方法;接口则只能声明没有实现的抽象方法。
什么时候用抽象类:表示对象的用抽象类。
什么时候用接口:表示行为拓展的用接口。不同对象拥有的共同行为,可以使用接口来实现。
比如:动物是一类对象,可以选择抽象类;而飞翔是一个行为,并不是所有动物都会飞翔,就可以用接口。
使用一些UML可视化软件,不用写代码,通过一些图标相关内容就可以直接生成代码,在其基础上进行开发。(最终目的就是能通过图形就把业务逻辑就完成了)
但是本文中的UML类图是uml其中很小的一部分,学习它的目的是为了帮助我们在进行面向对象程序开发时,更好的理清对象关系,养成面向对象编程的习惯。
就用VISIO。
(设置方法)
如:
一:单一职责原则(SRP) Single Responsibility Principle
类被修改的几率很大,因此应该专注于单一功能。如果把多个功能放在同一个类里,功能之间就形成了关联,改变其中一个功能时有可能会终止另一个功能。
如:策划、程序、美术是三个类,他们应该各司其职,在程序语言的世界中只做自己应该做的。
二:开闭原则(OOP)Open Closed Principle
对拓展开发,对修改关闭。
拓展开发:模块的行为可以被拓展从而满足新的需求。
修改关闭:不允许修改模块的源代码(或尽量使修改最小化)。
如:继承就是最经典的开闭原则的体现,可以通过添加新的子类和重写父类的方法来实现。
三:里氏替换原则(LSP)Liskov Substitution Principle
任何父类出现的地方,子类都可以代替(用父类容器装载子类对象)
四:依赖倒转原则(DIP)Dependence Inversion Principle
要依赖于抽象,不要依赖于具体的实现。
如:
人要开枪,是依赖于枪械,但是我并没有直接去依赖这些枪械,利用里氏替换原则,倒转去依赖了这个开枪接口。
五:迪米特原则(LoP)Law of Demeter
又称最少知识原则,一个对象应该对其他对象尽可能的少了解。
如:一个对象中的成员,要尽可能少的直接和其它类建立关系,目的是降低耦合性。
六:接口分离原则(LSP) Interface Segregation Principle
不应该强迫别人依赖他们不需要使用的方法。
一个接口不需要提供太多的行为,应该尽量只提供一个对外的功能,让别人去选择需要实现什么样的行为,而不是把所有的行为都封装到一个接口中。
如:飞机接口、走路接口、跑步接口等等虽然他们都是移动的行为,但是我们应该把他们分为一个一个单独的接口,让别人去选择使用。
七:合成复用原则(CRP)Composite Reuse Principle
尽量使用对象组合,而不是继承来达到复用的目的,继承关系是强耦合,组合关系是低耦合。
如:脸应该是嘴巴、鼻子、眼睛、耳朵的组合,而不是依次继承。角色和装备也应该是组合,而不是继承。
注意:不能盲目使用合成复用原则,要在遵循迪米特原则的前提下。
okk本文到这里可能(我也不确定?)就结束了,后面还会有C#进阶的课程我再接着做笔记。
TAG:csharp