一.ref和out

C#中用来应对值传递时需要改变传递对象的情况(此部分可以看前面引用类型和值类型的区别),其中,ref必须在传入的时候就初始化好传入对象,传出时则不一定需要丢该传入的对象。out则相反。

二.实例化和初始化的区别:

当创建一个变量的时候,便是实例化,其中,直接声明但不赋值和声明并赋值为null,都只能称为实例化,只有声明并赋予有效值,才是初始化

三.构造函数

作用:
任何函数都自带一个无参的构造函数
如果创建了任意一个构造函数,原来默认的无参构造函数会被顶掉

1.在一个构造函数中调用其他构造函数的语法:

构造函数.tihs(参数)

2.在一个构造函数中调用其他构造函数的作用:

用来在一个构造函数中调用其他的构造函数,其中,参数可以不带,带前面构造函数传入的变量,或者直接使用常量

四.析构函数:

语法:
~类名(){函数内容}

作用:
在C#回收对应堆内存垃圾的时候会执行的函数,当对应的对象被置为null后便会在某些时间被回收并执行对应的析构函数(然而时间不确定),不过在unity中不常用

五.垃圾回收机制

作用:
所谓垃圾就是没有被任何变量,对象引用的内容

细节:
触发垃圾回收实际上就是在触发GC
每一代满的时候才会触发内存回收,其中,0代满了 ,会只对自己进行清理,会把可达内存移动到1代,并清除不可达内存,1代则会对0代 1代同时进行清理,以此类推到2代(2代满了则3者同时进行清理)。也就是老变量都会存在1代2代。越老越后面。
二代会存储比较大的变量(大于85000字节,83KB),因为较大的如果进行搬运和释放会比较消耗内存,增加耗时
总体而言,新的变量和对象大部分放在0代内存,越老越容易往后

六.手动触发垃圾回收

语法:
GC.方法名 (即GC是一种系统类,自带一堆方法)
作用:
大部分时候对于垃圾回收,是采取系统自动回收的方法,所以一般情况下,我们不会频繁调用,都是在loading过场景的时候调用的。

七.成员属性(和java的意思不一样)

属性一般使用帕斯卡命名法,get和set前也可以加访问修饰符
语法:
//访问修饰符 属性类型 属性名
//{
//get{} (get必须写return和写内容,set不一定 )
//set{} (value关键字用于表示外部传入的值)
//}

作用:
用来保护成员变量,对于成员变量进行二次加工和加密,可以实现只能获取不能修改或者只能修改不能获取的效果,解决修饰符带来的局限性,并且返回一些设定好的逻辑规则,可以实现存入和取出的值进行加密处理(如图所示)

使用的时候直接用设定好的属性值名拿去当变量一样使用即可
自动属性是属性语句块中只有get和set(不需要括号),一般用于 外部能得不能改这种情况,同时,自动属性也意味着抛弃成员变量,直接用属性替代成员变量的功能

八.索引器

作用:
索引器是一个特殊的,名叫Item,带有额外参数的++可以重载++的属性。
可以让我们以中括号的形式范围自定义类中的元素 规则自己定 访问时和数组一样
比较适用于 在类中有数组变量时使用 可以方便的访问和进行逻辑处理

语法:
//访问修饰符 返回值 this[参数列表]
//get和set语句块

注意:++结构体里面也是支持索引器++
可以形象的理解为一个带参数的属性

九.静态成员

全局性的,唯一性的东西可以设置为静态成员(但是滥用会大量占用内存,可能会频繁的发生GC,谨慎使用)
静态函数不能直接使用非静态成员(先实例化),非静态函数可以使用静态成员(主要是生命周期不同)
非静态函数里面可以有静态成员(java只有静态内部类,C#可以直接有静态类)
静态内部类是一个类,可以有静态成员、也可以有非静态成员。
成员变量在静态方法中只能实例化出来后使用(因为静态方法在程序一开始一开始就创建好了,而同一个类的变量此时还没有诞生,必须手动实例化让它诞生)

程序在开始运行时,静态成员就会被分配内存空间,所以不需要实例就能使用,静态成员和程序同生共死,所以一个静态成员就会有自己唯一的内存房间,这让静态成员有了“唯一性”,无论在哪改变调用都只影响那唯一的一个。

1.constle(常量)

可以看成一个特殊的静态,区别是必须初始化而且之后不再能修改(只能用于get),并且只能修饰变量。
同样可以用类名.常量来获得

十.静态类和静态构造函数

1.静态类:

通常被用来制作工具类,在静态类中所包含的所有成员必须都是静态成员

2.静态构造函数:

不能有参数和访问修饰符
第一次使用实例化(在普通类里面的静态构造函数)或者调用静态类的任意成员(在静态类里面的静态构造函数)的时候会调用一次,并且只会调用一次

十一.拓展方法

概念:
为现有++非静态++变量类型添加新方法(通常用来外部对于其他不想或者无法改动的普通类加方法,甚至是无法修改的系统类)
第一个参数用this修饰,是扩展目标
写在静态类内部,一定是个静态函数(可以开一个工具类专门拓展方法)

注意:
如果拓展方法和原方法重名,那就会不生效,依然动用原方法。

语法:
//访问修饰符 static 返回值 函数名(this 扩展类名 参数名,参数类型 参数名,参数类型 参数名…)

作用:
提升程序扩展性,不需要在对象中重新写方法,为别人封装的类型写额外的方法

十二.运算符重载

特点:
1.一定是一个公共的静态方法
2.返回值写在operator前面
3.只能有2个或一个(如++和–或者!等 具体的可重载的符号需要背)参数(因为即使连起来也可以统统拆成2个参数进行运算)
4.一个符号可以多个重载
5.参数中必须有一个指定为所在的类(否则你静态方法放在这个类里面没意义)
6.可以实现重载(指平时的那个重载)
7.不可以用ref和out
8.对> < >= <=这种类型的运算符,需要成对重载

语法:
public static 返回类型 operator 运算符(参数列表)

十三.内部类和分部类

1.内部类

特点:
1.用来表达一个亲密的关系,调用一个类的时候得把外部类先点出来(但是大部分时候反而会显的臃肿,其实平时不这么写)

2.分部类(比较鸡肋)

特点:
1.需要在前面加partial关键字
2.把类分成几部分来写
3.访问修饰符必须一致

3.分部方法(局限性太大,基本没软用,了解即可)

特点:
1.将方法的申明和实现分离
2.不能加修饰符,默认私有
3.只能写在分部类中
4.需要在前面加partial关键字

十四.继承

特点:
1.子类会继承父类的所有成员(即使是private也继承了,但是无法在子类的设计(方法)中调用,想要在子类中调用得用protected(子类可以访问,但是外部无法访问))
2.子类只能继承一个父类,父类可以有无数个子类

用法:
class 类名:被继承的类名

1.警告

C#允许使用子类和父类同名的成员,但是极不建议使用(甚至编辑器都觉得这种行为不合理,会报错,尽管子类看起来会直接覆盖父类的成员,但是本质是直接生成了两个同名变量,而调用那个变量取决你调用的方法,这样非常不符合面向对象和继承的概念,比如会让父类同名的成员变得无意义)

2.继承中的构造函数

特点:
1,当声明一个子类对象的时候,先执行父类的构造函数,再执行子类的构造函数(可以用来覆盖父类的一些变量)
2.子类可以通过base关键字,代表父类,调用父类构造函数
3.设计父类的构造函数的时候应该谨慎注意,防止对子类造成太多的干扰,而且最好给每个父类准备一个无参构造函数,因为只有有参构造函数的话父类的无参构造函数会被顶掉,导致只有有参的话会导致子类必须调用一次父类的有参构造函数(使用base函数(java是super),写在子类的构造函数中)
4.当父类指向子类的时候,虽然实例无法动用子类的方法和属性,但是子类的构造函数依然会执行完毕(毕竟子类已经实例化完毕了!),所以父类转化为子类才会顺利成章,不用强转
子类调用父类构造函数的情况如下图

总结调用子类构造函数的时候,是先执行父类的构造函数,再执行子类的,从老祖宗开始,依次一代一代往下执行

3.区分this和base的区别

base是调用父类的构造函数,this是调用本类中其他不同参数的构造函数,语法相同,具体可以看上图

4.密封类

使用sealed密封关键字修饰的类,让类无法再被继承(堪称断子绝孙)
用处:保证程序的规范性和安全性,在制作复杂的系统或者程序框架的时候,会加强程序设计的规范性,结构性和安全性

十五.里氏替换原则(重点)

语法表现:
用父类容器装子类对象,因为子类对象包含父类对象的所有内容
这样可以方便进行对于对象的储存和管理(即可以动用一个父类存储所有的子类对象)
父类指向子类后,相当于只能调用子类的一部分内存,即父类和子类共有的变量和方法,或者子类重写的方法,而转换回子类后,就可以调用子类独有的全部方法和变量

当我们违反了这一原则会带来有一些危害:
反直觉。期望所有子类行为是一致的,但如果不一致可能需要文档记录,或者在代码跑失败后涨此知识;
不可读。如果子类行为不一致,可能需要不同的逻辑分支来适配不同的行为,徒增代码复杂度;
不可用。可能出错的地方终将会出错

概述:
里氏替换原则,面向对象的七大原则之一,我理解为以下两种关系
1.子类型(subtype)必须能够替换掉他们的基类型(base type)
2.即任何父类出现的地方,子类都可以替代
3.子类可以扩展父类的功能,但不能改变父类原有的功能
1为里氏替换原则的本质,2为遵循了这一原则后的结果,3为遵循这个行为所需要的前提

父类引用指向子类对象,那么是向上转型
也就是说这个引用只能用父类自己的方法和自己的属性,不能用子类的。但是如果子类的方法名和属性名和父类相同的话,那么就可以用,其实是子类覆盖父类的方法和属性
通俗的来说:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,不要改变让父类原来的方法的功能被改动,即重写的对象功能必须完全和父类一模一样!当然算法可以随情况改动。

如果你父类写了一个找最大值的方法,子类直接重写成找最小值,或者甚至返回值都打算改变了(然而语法不支持你这么做),那就不符合里氏替换原则。反过来说只要你没有改变方法的功能,不管你子类方法的过程怎么写都是符合的。
具体内容请结合下面观看。

1.那么多态和重写是否违背了里氏替换原则?

并不是,多态和重写可以改变结果实现的过程,让算法变得高效,让功能更加多样,但是最终实现的基础功能应该是一样的,而不是拿来让你改变用途,如果你直接改变了用途,虽然程序不会报错,但是会对今后的维护造成巨大的影响。

当然,虽然方法的普遍性规律一样,但是实现细节可能有所不同(其实就是子类扩展了一些功能),比如一个打印机父类,拥有一个彩色打印机子类和黑白打印机子类,他们的子类打印方法虽然最终目的都是打印,但是一个是彩色照片一个是黑白照片,这样依然没有违背里氏替换原则

举个栗子来说,同样是List基类/接口,子类可以是用Array实现也可以用LinkedList实现,但都必须实现at方法(得到具体某个index的值)。你现在用List实现了一个找最小值的算法,我们假设你是一个个遍历过去找的,那么不管底下是Array实现还是LinkedList实现(子类替换),你的算法都应该是返回最小值(属性不变),只是用LinkedList的时候很蠢。而你知道at方法对于Array和LinkedList实现是不一样的,List调用at的时候根据底下具体的实现决定调用哪一个,这货就叫做多态。

2.is和as

is:判定一个对象是否是指定类对象,是返回true,否则为false
as:将一个对象转换为指定类对象,成功返回指定类型对象,否则为null

注意:
as与[]强制转换不同的是,as即使转换失败,也只会传回null,而[]强制转换失败则会抛出异常

运用场景:


如上,图一将3个相同父类的不同子类给统一包装为父类,然后存入一个父类数组
图二便将不同的子类识别出来,再从父类容器取出转化为子类容器,最后调用他们自己的功能。
这样便可以做到方便的管理和维护遍历一堆有共同父类下的子类,也是里氏替换原则的一种运用方式。

3.实战代码:

 class Program
    {
      public  class Monster
        {
            protected String name="怪物";
            public void Attack() 
            {
                Console.WriteLine(name + "进行攻击!");
            }
        }

      public  class Goblin:Monster
        {
            public Goblin() 
            {
                name = "小怪";
            }
            
}
       public class Boss : Monster
        {
           public Boss()
            {
                name = "boss";
            }
            public void  jineng() 
            {
                Console.WriteLine("Boss开大啦!");
            }

        }

        static void Main(string[] args)
        {  
    Monster[] text = new Monster[]{new Boss(),new Boss(),new Goblin() };
            for (int i = 0; i < text.Length; i++)
            {
                if (text[i] is Boss)
                {
                    (text[i] as Boss).Attack();
                    (text[i] as Boss).jineng();
                    Console.WriteLine("boss结束");
                }
                else if (text[i] is Goblin)
                {
                    (text[i] as Goblin).Attack();
                    Console.WriteLine("小怪结束");
                }

            }
            //这里动用父类的构造函数,发动了怪物攻击
            Monster textone = new Monster();
            textone.Attack();
            //这里指向了子类,所以先动用了父类的构造函数,然后动用了子类的构造函数,父类的name被覆盖了,导致发动了boss攻击(注意,这里只是构造函数里面的变量被覆盖了,如果是对方法进行覆盖,会展现多态性,情况会不一样)
            Monster texttwo = new Boss();
            texttwo.Attack();
        }
    }

最终结果:
![](https://pic.imgdb.cn/item/64a1abe71ddac507cca46106/C#核心7 .png)

十六.万物之父和装箱拆线

1.万物之父(object)

特点:
1.object是所有类型的基类,可以利用里氏替换原则,用object容器装所有对象
2.可以用来表示不确定类型,作为函数参数类型
3.用object装引用类型(包括数组),可以用as is来转换,装值类型,就必须使用强制转换,而String类型可以用tostring方法或者as来拆箱(如下图)

2.装箱拆箱

装箱:
把++值类型++用++引用类型++存储,栈内存会迁移到堆内存中
拆箱:
把++引用类型++存储的++值类型++取出来,堆内存会迁移到栈内存中

好处:不确定类型时可以方便参数的存储和传递
坏处:存在内存迁移,增加性能消耗

3.实战:


如上图,利用object就可以不受顾虑的传入任意类型的参数,随后对于传入的参数进行各种的分析和使用

注意的是,由于这会导致性能的损耗,请在保证方便的同时谨慎的使用这一特性。

十七.多态(重点)

按字面意思,就是多种状态
++让继承同一父类的子类们,在执行相同方法时有不同的表现++

当父类指向子类的时候,调用两者共有的方法,会执行父类方法,而当as为子类的时候,又会执行子类的方法,即一个相同方法不同表现。

而为了防止上面这种情况发生,可以采用vob(虚函数virtual,重写override,base调用父类)来规避(如下图)

虚函数和重写以及base的组合,可以让方法的使用固定下来,当你想用这个子类方法的时候,不用再担心父类的同名方法会造成干扰,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作,又可以用base来时刻调用父类的有用方法内容
以及还有new这个隐藏基类的用法,但是用的比较少
如果不写virtual(虚方法)的话,在派生类定义一个基类的同名方法,当实例化父对象出来后,调用同名方法,并不会调用子类的同名方法,调用的是父类自己的。使用了virtual,再利用override(覆盖)来重写,则父类的虚方法被覆盖了,当实例化父对象出来后,调用的则是子类重写的方法;如果是使用new的话,则只是隐藏了父类方法,当实例化父对象,调用的仍是父类方法,父类方法并没有被完全覆盖(此为重写)。
总结:重写会改变父类方法的功能(override),覆盖不会改变父类方法的功能(new)。

十八.为什么要使用父类指向子类这种方式,而不是直接用子类指向子类本身呢?

你针对子类写的一个函数就只能用于操作这个类的对象 你针对父类写的一个函数可以操作所有从这个父类继承的子类的对象 这是对 ++一个东西进行操作++ 和 ++对一类东西进行操作++ 的区别
而这么做的好处就是上层逻辑抽象,独立清晰, 方便以后扩展. 举个常见的例子,比如设计一个网卡驱动类,那么上层只需要考虑网卡的功能接口,具体到不同厂商只要遵循接口实现不同的子类. 这时调用方就用接口(或者父类)来调用,而不是依赖某个特定厂商A的网卡,这样即使换一种厂商B网卡也不需要重新改代码,而只要实例化一个厂商B驱动对象赋给父类指针.

十九.抽象类和抽象方法

1.抽象类

概念:
被抽象关键字abstract修饰的类

特点:
1.不能被实例化的类
2.可以包含抽象方法
3.继承抽象类必须重写其抽象方法
4.虽然不可以实例化,但是可以用父亲抽象容器装子类

2.抽象函数

概念:
又叫纯虚方法,是用abstract关键字修饰的方法

特点:
1.只能在抽象类中声明
2.没有方法体
3.不能是私有的(因为一定要被重写)
4.继承后必须实现,用override重写

3.如何选择普通类还是抽象类

不希望被实例化的对象,相对比较抽象的类可以使用抽象类
父类中的行为不太需要被实现的,只希望子类去定义具体的规则的,可以选择抽象类里面的抽象方法来实现
即,主要用于整体框架设计时,会使用抽象类去定义一个标准和要实现的方法,然后让子类一一实现

二十.接口

关键字:interface
语法:
interface 接口名
{
}
一句话记忆:接口是抽象行为的“基类”

接口命名规范:帕斯卡命名法的基础上前面加个I

结构声明的规范
1.不包含成员变量
2.只包含方法,属性,索引器,事件
3.成员不能被实现(即方法不能有方法体)
4.成员可以不写访问修饰符,不能是私有的(默认public)
5.接口不能继承类,但是可以继承另一个接口

接口的使用规范
1.类可以继承多个接口
2.类继承接口后,必须实现接口中所有成员

特点:
1.它和类的申明相似
2.接口是用来继承的,让子类实现里面的成员
3.接口不可以被实例化,但是可以作为容器存储对象(和抽象类类似)

接口可以用来表示一个有共性的属性,比如动物子类下的鸟和机械子类下的飞机,他们虽然父类不同,但是都可以共用fly和实现这个接口
因此,你可以动用接口来存储不同父类却有相同接口的子类(里氏替换原则),来实现对于一类拥有相同行为的子类的统一管理和调用

接口继承接口,不需要实现,类继承接口要去实现所有内容,善用这种关系,可以去表达不同接口和类之间的关系

1.显示实现接口

当继承的多个接口有同名方法的时候,用显示实现接口:接口名.方法名 去进行实现和区分(甚至自己也可以有一个同名方法),而执行的时候需要将子类as为接口(类似子类转父类,不需要强转)去调用对应的方法

2.和父类的区别

继承类:是对象间的继承,包括特征行为
继承接口:是行为间的继承,继承接口的行为规范,按规范去实现内容

3.细节

由于接口也遵循里氏替换原则,所以可以用接口装对象
那么就可以实现装载毫无关系却有想同类型的对象
接口继承接口相当于行为合并,待子类继承再去实现具体行为
接口可以显示实现,主要用于实现不同接口的同名函数的不同表现
实现的接口方法可以加virtual后让子类重写

二十一.密封方法

用sealed修饰的重写函数
作用:
让虚方法或者抽象方法之后不能再被重写

特点:
和override一起出现

总结:
密封方法可以让虚方法和抽象方法不能再被子类重写

二十二.命名空间

概念:
命名空间是用来组织和重用代码的

作用:
就像是个工具包,类就是一个一个的工具,都是声明在命名空间中的

基本语法:
namespace 命名空间名
{



}

细节:
不同命名空间中相互引用,需要引用命名空间(using)或指明出处(跟类一样点出来,不同命名空间中有同名类也可以这么做,类似接口的显示实现)
不同命名空间中允许有同名类
命名空间可以包裹命名空间

总结:
1.命名空间是个工具包,用来管理类的
2.不同命名空间中有可以有同名类
3.不同命名空间中相互使用,需要using引用命名空间,或指明出处
4.命名空间可以包裹命名空间

二十三.万物之父(object)中的方法

1.静态方法

1.Equals 判断两个对象是否相等(比较的是指向的内存地址,如果两个类即使相同,内存地址不同还是返回false)
最终的解释权,交给左侧对象的equals方法(因为所有对象都是object的子类,所有所有对象都有equals方法)
不管值类型引用类型都会按照左侧对象equals方法的规则来进行比较
object.equals(比较1,比较2) 和 比较1.equals(比较2) 一样

2.ReferenceEquals
比较两个对象是否是相同的引用,主要是用来比较引用类型的对象(比的还是在堆上的分配的地址是否是同一块地址,实际上和equals差不多。。)
值类型对象返回值始终是false

2.成员方法

1.普通方法:GetType
该方法在反射相关知识点中是非常重要的方法
该方法的主要作用是获取对象运行时的类型type
通过type结合反射相关知识点可以做很多关于对象的操作

2.普通方法:MemberwiseClone(是protected方法,得在类内部调用,而且返回的是object,得转换成自己的类)
该方法用于获取对象的浅拷贝对象,口语化的意思就是会返回一个新的对象,
++但是该对象中的引用变量和老对象中一致++(等于把老对象的内存地址给copy过去了。。。如果你改动新对象的引用变量老对象的引用变量也会变,所以这叫浅拷贝,指克隆的不太干净)

3.虚方法

1.equals
默认实现还是比较两者是否为同一个引用,即相当于referenceequals
但是微软还是在所有值类型的基类System.ValueType中重写了该方法,用来比较值相等。(等于如果你重写后,有可能值比较会失效)
我们也可以重新该方法,定义自己的比较相等的规则

2.GetHashCode
该方法是获取对象的哈希码
(一种通过算法算出的,表示对象的唯一编码,不同对象哈希码有可能一样,具体在根据哈希算法决定)
我们可以通过重该函数来自己定义对象的哈希码算法,正常情况下,我们使用的极少,基本不用

3.ToString
该方法用于返回当前对象代表的字符串,我们可以通过重写它定义我们自己的对象转字符串规则,
该方法非常常用,当我们调用打印方法的时候,默认使用的就是对象的tostring方法后打印出来的内容

二十四.String

1.字符串指定位置获取

字符串本质是char数组

但是由于string不允许set 所以有时候需要str.ToCharArray();来达到修改数组的目的

2.字符串拼接

String.Format{"{0}{1}",str1,str2}

3.正向查找字符位置

int index=str.IndexOf(str); (找不到会返回-1)

4.反向查找字符位置

int index=str.LastIndexOf(str); (可以用来和正向一起处理有两个相同字符的情况)

5.移除指定位置后的字符

str=str.remove(index); (截断后面的字符,但是原类不会改变,必须重新赋值一遍)
str=str.remove(1,1) (参数1 开始位置,参数2 字符个数)
注意:str的许多方法并不会改变原字符串,需要重新赋值,下面也多次出现了这种情况,不再复述,请注意是否加了str=

6.替换指定字符串

str=str.replace(str,newstr) (第一个参数为需要改变的字符串,第二个为需要替换的字符串)

7.大小写转换

str=str.Toupeer(); //小写转大写
str=str.ToLower();//大写转小写

8.字符串截取

str=str.Substring(2); (截取从指定字符开始的字符串)
str=str.Substring(2,3);(参数一 开始位置,参数二 指定个数)

9.字符串切割

String[] strs=str.Split(’,’); (指以逗号为界限切割存储到数组里)
非常有用,可以导入策划给予的配置表的时候进行切割

二十五.运用char进行置换

由于char的加减法会转化为ASCII码,可以通过这一特性来进行没有中间商的位置转换

上图为字符串反转代码,随后可以通过str=new string(chars);转换回string

二十六.StringBuilder

String是特殊的引用,每次重新赋值或拼接会分配新的内存空间
如果一个字符串经常改变会非常浪费空间,所以可以利用StringBulider来解决
StringBulider是C#提供的一个处理字符串的公共类
主要解决的问题是:
修改字符串而不创建新的对象,需要频繁修改和拼接的字符串可以使用它,提升性能。
使用前,需要引用命名空间System.Text
StringBuilder存在一个容量的问题,每次往里面增加时,会自动扩容(乘2)
str.Capacity //查看当前容量

增删改查替换

增:
str.Append(str);
str.AppendFormat("{0}{1}",str1,str2);

插入:
str.insert(index,str);

删:
str.Remove();

清空:
str.Clear();

查:
Console.witeLine(str[index]);

改:
str[index]=‘A’; (String就不允许set了)

替换:
str.Replace(“str1”,“str2”);

重新赋值:
str.clear();
str.Append(str);

判断是否相等:
Str.Equals(str);
缺点:没有indexof方法

二十七.结构体和类

区别概述:
结构体和类的最大区别是在存储空间上的,因为结构体是值,类是引用,
因此他们的存储位置一个在栈上一个在堆上
结构体和类在使用上很类似,结构体甚至可以用面向对象的思想来形容一类对象
结构体具备着面向对象思想中封装的特效,但是它不具备继承和多态的特性,因此大大减少了它的使用频率,
由于结构体不具备继承的特性,所以它不能够使用protected保护访问修饰符

细节区别:
1.结构体是值类型,类是引用类型
2.结构体存在栈中,类存在堆中
3.结构体成员不能使用protected访问修饰符,而类可以
4.结构体成员变量声明不能指定初始值,而类可以
5.结构体不能声明无参的构造函数,而类可以
6.结构体声明有参构造函数后,无参构造不会被顶掉
7.结构体不能声明析构函数,而类可以
8.结构体不能被继承,而类可以
9.结构体需要在构造函数中初始化所有成员函数,而类随意
10.结构体不能被静态static修饰(不存在静态结构体,但是可以有静态成员),而类可以
11.结构体不能在自己内部声明和自己一样的结构体变量,而类可以

结构体的特别之处:
结构体可以继承接口,因为接口是行为的抽象

如何选择结构体和类:
1.想要用继承和多态时,直接淘汰结构体,比如玩家,怪物等等
2.对象是数据集合时,优先考虑结构体,比如坐标,位置等等
3.从值类型和引用类型赋值时的区别上去考虑,比如经常被赋值传递的对象,而且改变赋值对象,原对象不想跟着改变时,就用结构体,比如坐标,向量,旋转等等

二十八.抽象类和接口的区别

相同点:
1.都可以被继承
2.都不可以直接实例化
3.都可以包括方法申明
4.子类必须实现未实现的方法
5.都遵循里氏替换原则

区别
1.抽象类中可以有构造函数,接口不能
2.抽象类只能被单一继承,接口可以被继承多个
3.抽象类可以有成员变量,接口中不能
4.抽象类可以声明成员方法,虚方法,抽象方法,静态方法,接口中只能声明没有实现的抽象方法
5.抽象类方法可以使用访问修饰符,接口建议不写,默认public

如何选择抽象类和接口:
表示对象的用抽象类,表示行为扩展的用接口
不同对象拥有的共同行为,我们往往可以使用接口来实现
举例:动物是一类对象,我们自然会选择抽象类,而飞翔是一个行为,我们自然会选择接口

面向对象七大原则

目的:高内聚低耦合
从类的角度来看,搞内聚低偶尔,减少类内部对其他类的调用
从功能块来看,高内聚低耦合,减少模块之间的交互复杂度

七大原则:
单一职责原则
开闭原则(对扩展开发,对修改关闭)
里氏替换原则
依赖倒装原则(依赖于抽象,而不要依赖于具体的实现)
迪米特法则(一个对象应当对其他对象尽可能少的了解,以降低耦合)
接口分离原则(不应该强迫别人依赖他们不需要的方法,即一个接口不应该塞太多的行为)
合成复用原则(尽量用对象组合,而不是继承来达成复用的目的,继承关系是强耦合,组合关系是低耦合)