Bootstrap

Unity基础之C#核心篇笔记5:面向对象关联知识点

命名空间

1.命名空间基本概念

//概念
//命名空间是用来组织和重用代码的
//作用
//就像是一个工具包,类就像是一件一件的工具,都是申明在命名空间中的

2.命名空间的使用

//基本语法
//namespace 命名空间名
//{
//  类
//  类
//}
namespace MyGame
{
    class GameObject
    {

    }
}

namespace MyGame
{
    class Player:GameObject
    {

    }
}

3.不同命名空间中相互使用 需要引用命名空间或指明出处

            Image img0 = new Image();
            MyGame.UI.Image img = new MyGame.UI.Image();
            MyGame.Game.Image img2 = new MyGame.Game.Image();

4.不同命名空间中允许有同名类

namespace MyGame2
{
    //在不同的命名空间中 是可以有同名类的
    class GameObject
    {

    }
}

5.命名空间可以包裹命名空间

namespace MyGame
{
    namespace UI
    {
        class Image
        {

        }
    }

    namespace Game
    {
        class Image
        {

        }
    }
}

6.关于修饰类的访问修饰符

//public   —— 命名空间中的类 默认为public
//internal —— 只能在该程序集中使用
//abstract —— 抽象类
//sealed   —— 密封类
//partial  —— 分部类

万物之父中的方法

1.知识回顾

            //万物之父 object
            //所有类型的基类 是一个引用类型
            //可以利用里氏替换原则装载一切对象
            //存在装箱拆箱

2.object中的静态方法

  class Test
    {
        public int i = 1;
        public Test2 t2 = new Test2();

        public Test Clone()
        {
            return MemberwiseClone() as Test;
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override string ToString()
        {
            return "唐老狮申明的Test类";
        }
    }
    class Test2
    {
        public int i = 2;
    }
    //静态方法 Equals 判断两个对象是否相等
            //最终的判断权,交给左侧对象的Equals方法,
            //不管值类型引用类型都会按照左侧对象Equals方法的规则来进行比较
            Console.WriteLine(Object.Equals(1, 1));
            //Test t = new Test();
            //Test t2 = new Test();
            //Console.WriteLine(Object.Equals(t, t2));
            //静态方法ReferenceEquals
            //比较两个对象是否是相同的引用,主要是用来比较引用类型的对象。
            //值类型对象返回值始终是false。
            //Console.WriteLine(Object.ReferenceEquals(t, t2));

3.object中的成员方法

//普通方法GetType
            //该方法在反射相关知识点中是非常重要的方法,之后我们会具体的讲解这里返回的Type类型。
            //该方法的主要作用就是获取对象运行时的类型Type,
            //通过Type结合反射相关知识点可以做很多关于对象的操作。
            Test t = new Test();
            Type type = t.GetType();
            //普通方法MemberwiseClone
            //该方法用于获取对象的浅拷贝对象,口语化的意思就是会返回一个新的对象,
            //但是新对象中的引用变量会和老对象中一致。
            Test t2 = t.Clone();
            Console.WriteLine("克隆对象后");
            Console.WriteLine("t.i = " + t.i);
            Console.WriteLine("t.t2.i = " + t.t2.i);
            Console.WriteLine("t2.i = " + t2.i);
            Console.WriteLine("t2.t2.i = " + t2.t2.i);

            t2.i = 20;
            t2.t2.i = 21;
            Console.WriteLine("改变克隆体信息后");
            Console.WriteLine("t.i = " + t.i);
            Console.WriteLine("t.t2.i = " + t.t2.i);
            Console.WriteLine("t2.i = " + t2.i);
            Console.WriteLine("t2.t2.i = " + t2.t2.i);

可以理解为用MemberwiseClone方法克隆对象后,克隆体中开辟了与原对象一样的内存空间。
克隆体中引用变量的内存单元存储的值和原对象一样(克隆了原对象的引用变量的地址),也就是两者的引用地址指向同一堆内存单元中,修改时,原对象的引用变量也会改变。
而克隆体开辟的值类型的内存,存储的值与原来对象值类型值一致,两者没有联系,修改时原对象值类型值不变

4.object中的虚方法

            //虚方法Equals
            //默认实现还是比较两者是否为同一个引用,即相当于ReferenceEquals。
            //但是微软在所有值类型的基类System.ValueType中重写了该方法,用来比较值相等。
            //我们也可以重写该方法,定义自己的比较相等的规则

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

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

5.总结

1.虚方法 toString 自定字符串转换规则
2.成员方法 GetType 反射相关
3.成员方法 MemberwiseClone 浅拷贝
4.虚方法 Equals 自定义判断相等的规则

6.练习题

1.练习题1

    //有一个玩家类,有姓名,血量,攻击力,防御力,闪避率等特征
    //请在控制台打印出“玩家XX,血量XX,攻击力XX,防御力XX”XX为具体内容

    class Player
    {
        private string name;
        private int hp;
        private int atk;
        private int def;
        private int miss;

        public Player(string name, int hp, int atk, int def, int miss)
        {
            this.name = name;
            this.hp = hp;
            this.atk = atk;
            this.def = def;
            this.miss = miss;
        }

        public override string ToString()
        {
            return string.Format("玩家{0},血量{1},攻击力{2},防御力{3},闪避{4}", name, hp, atk, def, miss);
        }
    }

2.练习题2

   //一个Monster类的引用对象A,Monster类有 攻击力、防御力、血量、技能ID等属性。
    //我想复制一个和A对象一模一样的B对象。并且改变了B的属性,A不会受到影响。请问如何实现?

    class Monster
    {
        public Monster(int atk, int def, int hp, int skillID)
        {
            Atk = atk;
            Def = def;
            Hp = hp;
            SkillID = skillID;
        }

        public int Atk
        {
            get;
            set;
        }

        public int Def
        {
            get;
            set;
        }
        public int Hp
        {
            get;
            set;
        }
        public int SkillID
        {
            get;
            set;
        }

        public Monster Clone()
        {
            return MemberwiseClone() as Monster;
        }

        public override string ToString()
        {
            return string.Format("攻击里{0},防御力{1},血量{2},技能{3}", Atk, Def, Hp, SkillID);
        }
    }
        class Program
    {
        static void Main(string[] args)
        {
            Monster m = new Monster(10, 20, 100, 1);
            Monster m2 = m.Clone();
            m2.Atk = 30;
            m2.SkillID = 100;
            Console.WriteLine(m);
            Console.WriteLine(m2);
        }
    }

String

1.字符串指定位置获取

            //字符串本质是char数组
            string str = "123";
            Console.WriteLine(str[0]);
            //转为char数组
            char[] chars = str.ToCharArray();
            Console.WriteLine(chars[1]);

            for (int i = 0; i < str.Length; i++)
            {
                Console.WriteLine(str[i]);
            }

2. 字符串拼接

			str = string.Format("{0}{1}", 1, 3333);
            Console.WriteLine(str);

3. 正向查找字符位置

            str = "123!";
            int index = str.IndexOf("1");
            Console.WriteLine(index);

            index = str.IndexOf("4");
            Console.WriteLine(index);

4. 反向查找指定字符串位置

           str = "123456456";

            index = str.LastIndexOf("456");
            Console.WriteLine(index);

            index = str.LastIndexOf("456");
            Console.WriteLine(index);

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

            str = "123456456";
            str.Remove(4);
            Console.WriteLine(str);
            str = str.Remove(4);
            Console.WriteLine(str);
            
            //执行两个参数进行移除
            //参数1 开始位置
            //参数2 字符个数
            str = str.Remove(1, 1);
            Console.WriteLine(str);

6. 替换指定字符串

            str = "123456456";
            str.Replace("123", "789");
            Console.WriteLine(str);
            str = str.Replace("123", "789");
            Console.WriteLine(str);

7. 大小写转换

            str = "ksdfasdfasfasdfsasdfasdf";
            str.ToUpper();
            Console.WriteLine(str);
            str = str.ToUpper();
            Console.WriteLine(str);

            str.ToLower();
            Console.WriteLine(str);
            str = str.ToLower();
            Console.WriteLine(str);

8. 字符串截取

            str = "456456";
            //截取从指定位置开始之后的字符串
            str.Substring(2);
            Console.WriteLine(str);
            str = str.Substring(2);
            Console.WriteLine(str);

            //参数一 开始位置
            //参数二 指定个数
            //不会自动的帮助你判断是否越界 你需要自己去判断
            str = str.Substring(2, 2);
            Console.WriteLine(str);

9. 字符串切割

            str = "1_1|2_2|3_3|5_1|6_1|7_2|8_3";
            string[] strs = str.Split('|');
            for (int i = 0; i < strs.Length; i++)
            {
                Console.WriteLine(strs[i]);
            }

10. 练习题

1.练习题1

            //请写出string中提供的截取和替换对应的函数名
            //string str = "123123";
            //str = str.Substring(1);
            //str = str.Substring(1, 1);

            //str = "123123";
            //str = str.Replace("123", "456");

2.练习题2

            //请将字符串1 | 2 | 3 | 4 | 5 | 6 | 7
            //变为     2 | 3 | 4 | 5 | 6 | 7 | 8
            //并输出
            //(使用字符串切割的方法)
            str = "1 | 2 | 3 | 4 | 5 | 6 | 7";
            string[] strs = str.Split('|');
            str = "";
            for (int i = 0; i < strs.Length; i++)
            {
                str += int.Parse(strs[i]) + 1;
                if (i != strs.Length - 1)
                {
                    str += "|";
                }
            }
            Console.WriteLine(str);

3.练习题3

//String和string、Int32和int、Int16和short、Int64和long他们的区别是什么?
//别名,本质是一家

4.练习题4

            //string str = null;
            //str = "123";
            //string str2 = str;
            //str2 = "321";
            //str2 += "123";
            //请问,上面这段代码,分配了多少个新的堆空间
            //3个 “123”,“321”,“321123”

5.练习题5

            //编写一个函数,将输入的字符串反转。不要使用中间商,你必须原地修改输入数组。交换过程中不使用额外空间
            //比如:输入{‘h’,‘e’,‘l’,‘l’,‘o’}
            //输出      {‘o’,‘l’,‘l’,‘e’,‘h’}
                        Console.WriteLine("请输入内容");
            string str3 = Console.ReadLine();
            char[] chars = str3.ToCharArray();
            for (int i = 0; i < chars.Length / 2; i++)
            {
                //交换
                //char temp = chars[i];
                //chars[i] = chars[chars.Length - 1 - i];
                //chars[chars.Length - 1 - i] = temp;
                chars[i] = (char)(chars[i] + chars[chars.Length - 1 - i]);
                chars[chars.Length - 1 - i] = (char)(chars[i] - chars[chars.Length - 1 - i]);
                chars[i] = (char)(chars[i] - chars[chars.Length - 1 - i]);
            }

            for (int i = 0; i < chars.Length; i++)
            {
                Console.Write(chars[i]);
            }
            Console.WriteLine();
            str3 = new string(chars);
            Console.WriteLine(str3);

StringBuilder

1.知识回顾

        string是特殊的引用
        每次重新赋值或者拼接时会分配新的内存空间
        如果一个字符串经常改变会非常浪费空间

2.StringBuilder

            //C#提供的一个用于处理字符串的公共类 
            //主要解决的问题是:
            //修改字符串而不创建新的对象,需要频繁修改和拼接的字符串可以使用它,可以提升性能
            //使用前 需要引用命名空间

            #region 初始化 直接指明内容
            StringBuilder str = new StringBuilder("123123123");
            Console.WriteLine(str);
            #endregion

            #region 容量
            //StringBuilder存在一个容量的问题,每次往里面增加时 会自动扩容
            //获得容量
            Console.WriteLine(str.Capacity);
            //获得字符长度
            Console.WriteLine(str.Length);
            #endregion

            #region 增删查改替换
            //增
            str.Append("4444");
            Console.WriteLine(str);
            Console.WriteLine(str.Length);
            Console.WriteLine(str.Capacity);

            str.AppendFormat("{0}{1}", 100, 999);
            Console.WriteLine(str);
            Console.WriteLine(str.Length);
            Console.WriteLine(str.Capacity);
            //插入
            str.Insert(0, "唐老狮");
            Console.WriteLine(str);
            //删
            str.Remove(0, 10);
            Console.WriteLine(str);
            //清空
            //str.Clear();
            //Console.WriteLine(str);
            //查
            Console.WriteLine(str[1]);
            //改
            str[0] = 'A';
            Console.WriteLine(str);
            //替换
            str.Replace("1", "唐");
            Console.WriteLine(str);

            //重新赋值 StringBuilder
            str.Clear();
            str.Append("123123");
            Console.WriteLine(str);
            //判断StringBuilder是否和某一个字符串相等
            if( str.Equals("12312") )
            {
                Console.WriteLine("相等");
            }

3.练习题

1.练习题1

            #region 练习题一 请描述string和stringbuilder的区别
            //1.string相对stringbuilder 更容易产生垃圾 每次修改拼接都会产生垃圾
            //2.string相对stringbuilder 更加灵活 因为它提供了更多的方法供使用
            //如何选择他们两
            //需要频繁修改拼接的字符串可以使用stringbuilder
            //需要使用string独特的一些方法来处理一些特殊逻辑时可以使用string
            #endregion

2.练习题2

            #region 练习题二 如何优化内存
            //内存优化 从两个方面去解答 
            //1.如何节约内存
            //2.如何尽量少的GC(垃圾回收)

            //少new对象 少产生垃圾
            //合理使用static 
            //合理使用string和stringbuilder
            #endregion

结构体和类的区别

1.区别概述

        结构体和类最大的区别是在存储空间上的,因为结构体是值,类是引用,
        因此他们的存储位置一个在栈上,一个在堆上,
        通过之前知识点的学习,我相信你能够从此处看出他们在使用的区别——值和引用对象在赋值时的区别。

        结构体和类在使用上很类似,结构体甚至可以用面向对象的思想来形容一类对象。
        结构体具备着面向对象思想中封装的特性,但是它不具备继承和多态的特性,因此大大减少了它的使用频率。
        由于结构体不具备继承的特性,所以它不能够使用protected保护访问修饰符。

2.细节区别

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

3.结构体的特别之处

结构体可以继承 接口 因为接口是行为的抽象

4.如何选择结构体和类

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

抽象类和接口的区别

1.知识回顾

        抽象类和抽象方法
        abstract修饰的类和方法
        抽象类 不能实例化
        抽象方法只能在抽象类中申明 是个纯虚方法 必须在子类中实现

        接口
        interface 自定义类型
        是行为的抽象
        不包含成员变量
        仅包含方法、属性、索引器、事件,成员都不能实现,建议不写访问修饰符,默认public

2.相同点

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

3.区别

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

4.如何选择抽象类和接口

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

面向对象七大原则

1.单一职责原则

SRP(Single Responsibility Principle)
类被修改的几率很大,因此应该专注于单一的功能。
如果把多个功能放在同一个类中,功能之间就形成了关联,改变其中一个功能,有可能中止另一个功能。
举例:假设程序、策划、美术三个工种是三个类,他们应该各司其职,在程序世界中只应该做自己应该做的事情。

2.开闭原则

OCP(Open-Closed Principle)对拓展开发,对修改关闭
拓展开放:模块的行为可以被拓展从而满足新的需求
修改关闭:不允许修改模块的源代码(或者尽量使修改最小化)
举例:继承就是最典型的开闭原则的体现,可以通过添加新的子类和重写父类的方法来实现

3.里氏替换原则

LSP(Liskov Substitution Principle)任何父类出现的地方,子类都可以替代
举例:用父类容器装载子类对象,因为子类对象包含了父类的所有内容

4.依赖倒转原则

DIP(Dependence Inversion Principle)要依赖于抽象,不要依赖于具体的实现

在这里插入图片描述

5.迪米特原则

LoP(Law of Demeter)又称最少知识原则
一个对象应当对其它对象尽可能少的了解(不要和陌生人说话)
举例:一个对象中的成员,要尽可能少的直接和其它类建立关系。目的是降低耦合性

6.接口分离原则

ISP(Interface Segregation Principle)不应该强迫别人依赖他们不需要使用的方法
一个接口不需要提供太多的行为,一个接口应该尽量只提供一个对外的功能,让别人去选择需要实现什么样的行为,而不是把所有的行为都封装到一个接口当中
举例:飞行接口、走路接口、跑步接口等等虽然都是移动的行为但是我们应该把他们分为一个一个单独的接口,让别人去选择使用

7.合成复用原则

CRP(Composite Reuse Principle)
尽量使用对象组合,而不是继承来达到复用的目的继承关系是强耦合,组合关系是低耦合
举例:脸应该是眼镜、鼻子、嘴巴、耳朵的组合,而不是依次的继承角色和装备也应该是组合,而不是继承
注意:不能盲目的使用合成复用原则,要在遵循迪米特原则的前提下

8.总结

单一职责原则:一个类只处理自己应该处理的内容,不应该啥都写在一起
开闭原则:对拓展开放,对修改封闭。新加功能尽量是加处理而不是改代码
里氏替换原则:任何地方子类都能替代父类,父类容器装子类
依赖倒转原则:不要依赖具体的实现,要依赖抽象(接口)
迪米特法则:一个类要尽量减少对别的类的了解,尽量少用别的类和自己关联
接口隔离原则:一个接口一个行为,不要一个接口n个行为
合成复用原则:除非设计上需要继承,否则尽量用组合复用的形式

9.如何使用这些原则

在开始做项目之前,整理UML类图时先按自己的想法把需要的类整理出来
再把七大原则截图放在旁边,基于七大原则去优化整理自己的设计
整体目标就是:高内聚,低耦合
;