zoukankan      html  css  js  c++  java
  • C# 核心

    C# 核心

    面向对象编程概念

    面向过程编程是一种以过程为中心的编程思想,分析出解决问题所需要的步骤,然后有函数把步骤一步一步实现,使用的时候一个一个依次调用。

    面向对象是一种对现实世界理解和抽象的编程方法,把相关的数据和方法组织作为一个整体来看待。从更高的层次来进行程序开发,更贴近事物的自然运行模式。

    面向对象优势:提高代码复用率,提高开发效率,提高程序可拓展性,清晰的逻辑关系。

    如何学习面向对象?

    面向对象关键知识:类

    面向对象三大特性:继承+封装+多态

    封装:用程序语言来形容对象。

    继承:复用封装对象的代码;儿子继承父亲,复用现成代码。

    多态:同样行为的不同表现,儿子继承父亲的基因但是又不同的行为表现。

    面向对象七大原则:开闭原则、依赖倒转原则、里氏替换原则、单一职责原则、接口隔离原则、合成复用原则、迪米特法则。

    面向对象——封装

    类和对象

    类申明在命名空间内

    访问修饰符 class 类名
    {
        //特征-成员变量
        //行为-成员方法
        //保护特征-成员属性
        
        //构造函数和析构函数
        //索引器
        //运算符重载
        //静态成员
    }
    
    

    类的命名用帕斯卡命名法:每个首字母都大写。

    注意:同一个语句块/命名空间中的不同类不能重名。

    什么是(类)对象?

    类的申明和类对象(变量 )的申明是两个概念。

    类的申明类似枚举和结构体的申明,相当于申明了一个自定义的变量类型。

    而类对象是类创建出来的,相当于申明一个指定类的变量。

    类创建对象的过程一般称为实例化对象

    类对象都是引用类型的。

    实例化对象的基本语法

    //类名 变量名;
    //类名 变量名 = null;(null代表空)
    //类名 变量名 = new 类名();
    

    前两种都是在栈上分配了内存,堆上没有分配内存。

    第三种会在栈和堆都分配内存。

    练习

    1.A目前等于多少?

    GameObject A = new GameObject();
    GameObject B = A;
    B = null;
    

    答:A等于之前new的这个地址,不等于空。

    2.A和B有什么关系?

    GameObject A = new GameObject();
    GameObject B = A;
    B = new GameObject();
    

    答:A和B没有关系,B已经new了一个新的Object。

    成员变量和访问修饰符

    成员变量基本规则:

    • 1.声明在类语句块中

    • 2.用来描述对象特征

    • 3.可以是任意变量类型

    • 4.数量不做限制

    • 5.是否赋值由需求决定

    如果要在类中声明一个和自己相同类型的成员变量时,不能对它实例化。

    enum E_SexType
    { Man,Women,}
    class Person
    {
        string name;
        int age;
        Person girlfriend;//=null,但不能new Person();    
    }
    

    访问修饰符基本规则

    public 自己和别人都能访问和使用

    private 自己才能访问和使用,不写访问修饰符默认private

    protected 自己和子类才能访问和使用

    成员变量的使用和初始值

    初始值

    值类型来说 数字类型 默认值都是0 bool类型 false 引用类型 null

    看默认值的小技巧:

    Console.WriteLine(default(类型));
    

    就会输出类型默认初始值。

    使用

    Person p=new Person();
    p.age=10;
    Console.WrtieLine(p.age);
    

    成员方法

    成员方法的声明

    基本概念:成员方法(函数)用来表现对象行为。

    注意:

    1.成员方法不要加static关键字

    2.成员方法必须实例化出对象,再通过对象来使用,相当于该对象执行了某个行为。

    3.成员方法受到修饰符的影响。

    class Person
    {
        public string name;
        public int age;
        public Person[] friends;
        bool isAdult()
        {
            return age>=18;
        }
        public void Speak(String str)
        {
            isAdult();//私有函数只能在内部使用。
            Console.WriteLine("{0}说{1}",name,str);
        } 
        public void AddFriend(Person p)
        {
             if(friends == null)
             {
                 friends=new Person[] {p};
             }
            else 
            {
                 Person[] newFriends=new Person[friends.Length + 1];
                for(int i=0;i<friends.Length;i++)
                {
                    newFriends[i]=friends[i];
                }
                newFriends[newFriends.Length - 1] = p;
                friends=newFriends;
            }
        }
    }
    

    在c#里面通常把变量放在前面,方法放在后面,c++相反,但通常都可以。

    成员方法的使用

    成员方法必须实例化出对象,再通过对象来使用,相当于该对象执行了某个行为。

    class Program
    {
        static void Main(strring[] args)
        {
            Console.WriteLine("成员方法");
            Person p = new Person();
            p.name = "唐老师";
            p.age = 18;
            p.Sreak("我爱你");
            //p.isAdult不可以调用,因为是私有函数
            
            Person p2 = new Person();
            p2.name = "火山哥";
            p2.age = 19;
            p.addFriend(p2);
            for(int i = 0;i < p.friends.Lengh;i++)
            {
                Console.WriteLine(p.friends[i].name);
            }
        }
    }
    

    输出:成员方法

    唐老师说我爱你

    火山哥

    构造、析构、垃圾回收

    构造函数

    概念:在实例化对象时,会调用的用于初始化的和函数。

    写法:没有返回值、函数名和类名相同、一般为public

    class Person
    {
        public string name;
        public int age;
        //类中允许自己申明无参构造函数,结构体时不允许的
        public Person()
        {
            name = "唐老师";
            age = 18;
        }
        public Person(int age)
        {
            //this代表当前调用该函数的对象自己
            this.age = age;
        }
        public Person(string name)
        {
            this.name = name;
        }
        public Person(int age,string name)
        {
            this.age = age;
            this.name = name;
        }
    }
    

    注意:如果不实现自己无参构造函数而实现了有参构造函数,会失去默认的无参构造(会报错)。

    构造函数的特殊写法

    可以通过this 重用构造函数代码

    访问修饰符 构造函数名(参数列表):this(参数1,参数2)

    public Person(int age,string name):this(age)
    {
    }
    

    就能够先调用this括号参数那个的构造函数,能使代码复用。

    析构函数

    当对象被垃圾回收时调用的,主要是用来回收资源或者特殊处理内存的。

    unity中基本用不到

    垃圾回收机制

    • 垃圾回收的过程是在遍历堆(Heap)上动态分配的所有对象。

    • 通过识别它们是否被引用来确定哪些对象是垃圾,哪些对象仍要被使用。

    • 垃圾就需要被回收释放。

    • 注意:

    • 垃圾回收值负责堆内存的垃圾回收

    • 引用类型都是存在堆中,所以它分配和释放都通过垃圾回收机制来管理。

    • 栈(Stack)上的内存是由系统自动管理的

    • 值类型在栈中分配内存的,他们有自己的生命周期,不用对他们进行管理,会自动分配和释放。

    • C#中内存回收机制的大概原理

    0代内存 1代内存 2代内存

    代的概念:代是垃圾回收机制的一种算法(分代算法)。新分配的对象都会被配置在第0代内存中,每次分配都可能会进行垃圾回收以释放内存(0代内存满时)

    在一次内存回收过程开始时,垃圾回收器会认为队中全是垃圾,回收垃圾以下两步:

    • 1.标记对象 从根(静态字段、方法参数)开始检查引用对象,标记后为可达对象,未标记为不可达对象,不可达对象就被认为是垃圾。
    • 2.搬迁对象压缩堆(挂起执行托管代码线程)释放未标记的对象 、搬迁可达对象、修改引用地址

    大对象总被认为是第二代内存,目的是减少性能损耗,提高性能。

    不会对大对象进行搬迁压缩,85000字节(83kb)以上的对象为大对象。

    //手动触发垃圾回收的方法
    //一般情况下,我们不会频繁调用(因为会造成卡顿)
    //都是在Loading过场景时才调用
    GC.Collect();
    

    成员属性

    基本概念:用于保护成员变量,为成员属性的获取和赋值添加逻辑处理,解决3P的局限性。

    public——内外访问

    private——内部访问

    protected——内部和子类访问

    属性可以让成员变量在外部

    只能获取 不能修改 或者 只能修改 不能获取

    基本语法:

    访问修饰符 属性类型 属性名
    {
        get{}
        set{}
    }
    
    class Person
    {
        private string name;
        private int age;
        private int money;
        private bool sex;
        
        //属性命名一般用帕斯卡命名法
        public string Name
        {
             get
             {
                 //可以在返回之前添加一些逻辑规则
                 //意味着这个属性可以获取的内容
                 return name;
             }
            set
            {
                 //可以在设置之前添加一些逻辑规则
                 //value 关键字用于表示外部传入的值
                 name = value;
            }
        }
    }
    
    Person p = new Person();
    p.Name="唐老师";
    Console.WriteLine(p.name);
    

    成员属性中 get和set前可以加访问修饰符

    注意:1.默认不加,会使用属性声明是的访问权限

    2.加的访问修饰符要低于属性的访问权限

    3.不能让get和set的访问权限都低于属性的权限

    get和set可以只有一个

    只有一个就没必要加访问修饰符了

    一般情况下 只会出现只有 get 的情况(只获取不修改),基本不会出现只有 set(不会只改不能获取)

    自动属性

    作用:外部能得不能改的特征

    如果类中有一个特征只希望外部能得不能改的 又没什么特殊处理,那么可以直接使用自动属性。

    public float Height
    {
        //没有在get和set中写逻辑的需求或想法
        get;
        set;
    }
    

    不过没什么特殊需求就不要用属性了,直接写成员变量写的代码量更少一些。

    索引器

    基本概念:让对象可以像数组一样通过索引访问其中元素,是程序看起来更直观,更容易编写。

    基本语法:

    访问修饰符 返回值 this[参数类型 参数名,参数类型 参数名……]
    {
        内部的写法和规则和成员属性相同
        get{}
        set{}
    }
    
    class Person
    {
        private string name;
        private int age;
        private Person[] friends;
        private int[,] array;
        
        public int this[int i,int j]
        {
            get
            {
                return arraya[i,j];
            }
            set
            {
                array[i,j] = value;
            }
        }
        
        public string this[string str]
        {
            get
            {
               switch(str)
               {
                   case "name":
                       return this.name;
                   case "age":
                       return age.ToString;
               }
                return "";
            }
        }
        
        public Person this[int index]
        {
            get
            {
                //可以写逻辑 根据需求来处理这里的内容
                if(friends == null||friends.Length - 1 < index)
                {
                    return null;
                }
                return friends[index];
            }
            set
            {
                //value代表传入的值
                //可以写逻辑 根据需求来处理这里的内容
                if(friends == null)
                {
                    frienfds = new Person[]{value};
                }
                else if(index > friends.Length - 1)
                {
                    //自己定了一个规则,如果索引越界,就默认把最后一个朋友顶掉。
                    friends[friends.Length - 1] = value;
                }
                else
                {
                    friends[index] = value;
                }
            }
        }
    }
    
    Person p = new Person();
    p[0] = new Person();
    Console.WriteLine(p[0]);
    p[0,0] = 10;
    

    静态成员

    基本概念:用static修饰的成员变量、方法、属性等成为静态成员。

    特点:直接用类名点出使用。

    早已出现的静态成员:Console

    自定义静态成员

    class Test
    {
        //静态成员变量
        static public float PI = 3.1415926;
        //成员变量
        public int testInt = 100;
        //静态成员方法
        public static float CalcCircle(float r)
        {
            //静态函数不能使用非静态成员
            //成员变量只能将对象实例化出来后,才能点出来使用,不能无中生有
            //不能直接使用非静态成员,否则会报错
            //这样的写法才可以
            Test t = new Test();
            Console.WriteLine(t.testInt);
            return PI*r*r;        
        }
        //成员方法
        public void TestFun()
        {
            //非静态函数可以使用静态成员
            Console.WriteLine(PI);
            Console.WriteLine("123");
        }
    }
    
    

    静态成员的使用

    //静态成员
    Console.WriteLine(Test.PI);
    Console.WriteLine(Test.CalcCircle(2));
    //普通成员
    Test t = new Test();
    Console.WriteLine(Test.testInt);
    t.TestFun();
    

    静态成员大多都是public,要在外面点出来使用

    为什么可以直接点出来使用?

    记住!程序不能无中生有!我们要使用的对象,变量,函数都是要在内存中分配内存空间的,之所以要实例化对象,目的就是分配内存空间,在程序中产生有个抽象的对象。

    静态成员的特点程序开始运行时就会分配内存空间。所以我们就能直接使用。静态成员和程序共生共死。只要使用了它,直到使用了它,直到程序结束时内存空间才会被释放,所以静态成员就会有自己唯一的一个“内存小房间”,这让静态成员就有了唯一性,在任何地方使用都是用小房间的里的内容,改变了它也是改变了小房间里的内容。

    静态成员的作用:1.常用唯一变量的声明 2.方便别人的获取的对象声明

    静态方法:常用的唯一的方法声明,比如相同规则的数学计算相关函数。

    静态过多会导致频繁GC,导致卡顿。

    常量和静态变量

    const可以理解为特殊的static

    相同点:他们都可以通过类名点出来使用

    不同点:

    1.const必须初始化,不能修改,static没有这个规则

    2.const只能修饰变量,static可以修饰很多

    3.const一定时写在访问符后面的,static没有这个要求

    静态类

    概念:用static修饰的类

    特点:只能包含静态成员,不能被实例化

    作用:1.将常用的静态成员写在静态类中,方便使用

    2.静态类不能被实例化,更能体现工具类的唯一性,比如Console就是一个静态类

    static class TestStatic
    {
        //静态成员变量
        public static int testIndex = 0;
        
        public static void Testfun()
        {
            
        }
        
        public static int TesyIndex
        {
            get;
            set;
        }
    }
    

    静态构造函数

    概念:在构造函数加上static修饰

    特点:

    1.静态类和普通类都可以有

    2.不能访问修饰符

    3.不能有参数

    4.只会自动调用一次

    作用:在静态构造函数中初始化静态变量

    使用

    1.静态类中的静态构造函数

    static class StaticClass
    {
        public static int testInt1 = 100;
        public static int testInt2 = 100;
        
        static staticClass()
        {
            Console.WriteLine("静态构造函数");
        }
    }
    
    Console.WriteLine(StaticClass.testInt1);
    Console.WriteLine(StaticClass.testInt2);
    

    输出:静态构造函数

    100

    100

    2.普通类中的静态构造函数

    class Test
    {
        public static int testInt = 200;
        static Test()
        {
            Console.WriteLine("静态构造");
        }
        public Test()
        {
            Console.WriteLine("普通构造");
        }
    }
    
    Console.Write(Test.testInt);
    Test t = new Test();
    Test t2 = new Test();
    

    输出:

    静态构造

    200

    普通构造

    普通构造

    拓展方法

    概念:为现有非静态 变量类型 添加 新方法

    作用:1.提升程序拓展性

    2.不需要在对象中重新写方法

    3.不需要继承来添加方法

    4.为别人封装的类型写额外的方法

    特点:1.一定是写在静态类中

    2.一定是一个静态函数

    3.第一个参数为拓展目标

    4.第一个参数用this修饰

    //基本语法
    //访问修饰符 static 返回值 函数名(this 拓展名 参数名,参数类型 参数名,参数类型 参数名……)
    static class Tools
    {
        //为int拓展了一个成员方法
        //成员方法是需要实例化对象后才能使用的
        //value代表使用该方法的实例化对象
        public static void SpeakValue(this int value)
        {
            //拓展方法的逻辑
            Console.WriteLine("唐老师为int拓展的方法" + value);
        }
        public static void SpeakStringInfo(this string str,string str2,string str3)
        {
            Console.WriteLine("唐老师为string拓展的方法");
            Console.WriteLine("调用方法的对象" + str);
            Console.WriteLine("传的参数" + str2 + str3);
        }
    }
    
    int i = 10;
    i.SpeakValue();    //为int拓展的方法
    string str = "000";
    str.SpeakStringInfo("唐老师","111");
    

    输出:唐老师为int拓展的方法10

    唐老师为string拓展的方法

    调用方法的对象000

    传的参数唐老师111

    为自定义的类型拓展方法

    class Test
    {
        public int i = 10;
        public void Fun1()
        {
            Console.WriteLine("123");
        }
        public void Fun2()
        {
            Console.WriteLine("456");
        }
    }
    static class Tools
    {
         public static void Fun3(this Test t)
         {
            Console.WriteLine("为test拓展的方法");
         }
    } 
    
    Test t = new Test();
    t.Fun3();
    

    输出:为test拓展的方法

    如果拓展方法和成员函数重名,会调用成员函数。

    静态类是不能为静态类拓展方法的。

    可以有返回值和拓展参数。

    运算符重载

    基本概念:让自定义的类和结构体能够使用运算符。使用关键字operator。

    特点:1.一定是一个公共的静态方法

    2.返回值写在operator前

    3.逻辑处理自定义

    作用:让自定义类和结构体对象可以进行运算

    注意:1.条件运算符需要成对出现

    2.一个符号可以多个重载

    3.不能使用ref和out

    //基本语法
    //public static 返回类型 operator 运算符(参数列表)
    class Point
    {
        public int x;
        public int y;
        
        public static Point operator +(Point p1,Point p2)
        {
            Point p = new Point();
            p.x = p1.x + p2.x;
            p.y = p1.y + p2.y;
            return p;
        }
         public static Point operator +(Point p1,int value)
        {
            Point p = new Point();
            p.x = p1.x + value;
            p.y = p1.y + value;
            return p;
        }
         public static Point operator +(int value,Point p1)
        {
            Point p = new Point();
            p.x = p1.x + value;
            p.y = p1.y + value;
            return p;
        }
    }
    
    Point p1 = new Point();
    Point p2 = new Point();
    p1.x=1;p1.y=1;
    p2.x=2;p2.y=2;
    Point p3=p1+p2;
    

    可重载的运算符:算数运算符 逻辑运算符 位运算符 条件运算符

    不可重载的运算符:逻辑与或(&& ||)索引符[ ] 强转运算符 特殊运算符(点. 三目运算符 ? : 赋值符号 =)

    内部类和分布类

    内部类

    概念:在一个类中再声明一个类

    特点:使用时要包裹者点出自己

    作用:亲密关系的变现

    注意:访问修饰符作用很大

    class Person
    {
        public int age;
        public string name;
        public Body body;
        public class Body
        {
            Arm leftArm;
            Arm rightArm;
            //不写public,外部调用不了的
            class Arm
            {
                
            }
        }
    }
    
    Person p = new Person();
    Person.Body body = new Person.Body();
    

    分部类

    概念:把一个类分成几部分申明

    关键字:partial

    作用:分部描述一个类,增加程序的可拓展性

    注意:分布类可以写在多个脚本文件中

    分布类的访问修饰符要一致

    分布类中不能有重复成员

    partial class Student
    {
        public bool sex;
        public string name;
    }
    partial class Student 
    {
        public int number;
        public void Speak(string str)
        {
            
        }
    }
    

    分部方法

    概念:将方法的申明和实现分离

    特点:1.不能加访问修饰符 默认私有

    2.只能在分部类中申明

    3.返回值只能是void

    4.可以有参数但不用out关键字

    partial class Student
    {
        public bool sex;
        public string name;
        partial void Speak();
    }
    partial class Student 
    {
        public int number;
        public void Speak(string str)
        { }
        partial void Speak()
        {
            //实现逻辑
            throw new NotImplementedException();
        }
    }
    

    挺鸡肋的hhhh

    面向对象——继承

    继承的基本规则

    基本概念:一个类A继承一个类B,类A会继承类B的所有成员,A类将拥有B类的所有特征和行为

    被继承的类B称为父类、基类、超类、

    继承的类A称为子类、派生类。

    子类可以有自己的特征和行为

    特点:1.单根性:子类只能有一个父亲

    2.传递性:子类可以间接继承父类的父类

    class Teacher
    {
        public string name;
        public int number
        public void SpeakName()
        {
            Console.WriteLine(name);
        }
    }
    class TeachingTeacher : Teacher
    {
        public string subject;
        public void SpeakSubject()
        {
            Console.WriteLine(subject + "老师");
        }
    }
    class ChineseTeacher : TeachingTeacher
    {
        public void Skill()
        {
            Console.WriteLine("一行白鹭上青天");
        }
    }
    
    TeachingTeacher tt = new TeachingTeacher();
    tt.name - "唐老师";
    tt.number = 1;
    tt.SpeakName();
    
    tt.subject = "Unity";
    tt.SpeakSubject();
    
    ChineseTeacher ct = new ChineseTeacher();
    ct.name = "林老师";
    ct.number = 2;
    ct.subject = "语文";
    ct.SpeakName();
    ct.SpeakSubject();
    ct.Skill();
    

    访问修饰符的影响

    public - 公共 内外部访问

    private - 私有 内部访问

    protected - 保护 内部和子类访问

    之后讲命名空间的时候讲:internal - 内部的 只有在同一个程序集的文件中,内部类型或者是成员才可以访问

    子类和父类的同名成员

    概念:C#中允许子类存在和父类同名的成员,但是极不建议使用。

    里氏替换原则

    里氏替换原则是面向对象七大原则中最重要的原则

    概念:任何父类出现的地方,子类都可以替代

    重点:语法表现-父类容器装着子类对象,因为子类对象包含了父类的所有内容

    作用:方便进行对象的储存和管理

    class GameObject
    {}
    class PlayerAtk:GameObject
    {
        public void PlayerAtk()
        {
            Console.WriteLine(("玩家攻击"));
        }
    }
    class Monster:GameObject
    {
        public void MonsterAtk()
        {
            Console.WriteLine("怪物攻击");
        }
    }
    class Boss:GameObject
    {
        public void BossAtk()
        {
            Console.WriteLine("Boss攻击");
        }
    }
    
    //用父类容器装载子类对象
    GameObject player = new Player();
    GameObject monster = new Monster();
    GameObject boss = new Boss();
    GameObject[] objects = new GameObject[] {new Player(),new Monster(),new Boss()};
    //无法调用player.PlayerAtk()
    

    is和as

    基本概念:

    is:判断一个对象是否执行类对象

    返回值:bool 是为真 不是为假

    as:将一个对象转换为指定类对象

    返回值:指定类型对象

    成功返回执行类型对象,失败返回null

    //类对象 is 类名 该语句块 会有一个bool返回值 true和false
    //类对象 as 类名 该语句块 会有一个对象返回值 对象和null
    if(player is Player)
    {
        Player p = player as Player
        p.PlayerAtk();
        //或者可以这么写
        //(player as Player).PlayerAtk();
    }
    for(int i = 0;i < objects.Length;i++)
    {
        if(object[i] is Player)
        {
            (object[i] as Player).PlayerAtk();
        }
        else if(object[i] is Monster)
        {
            (object[i] as Monster).MonsterAtk();
        }
        else if(object[i] is Boss)
        {
            (object[i] as Boss).BossAtk();
        }
    }
    

    继承中的构造函数

    基本概念:当申明一个子类对象时,先执行父类的构造函数,在执行子类的构造函数。

    注意:1.父类的无参构造函数很重要

    2.子类可以通过base关键字 代表父类 调用父类构造

    class GameObject()
    {
        public GameObject()
        {
            Console.WriteLine("GameObject的构造函数");
        }
    }
    class Player:GameObject
    {
        public Player()
        {
            Console.WriteLine("Player的构造函数");
        }
    }
    class MainPlayer:Player
    {
        public MainPlayer()
        {
            Console.WriteLine("MainPlayer的构造函数");
        }
    }
    
    MainPlayer mp = new MainPlayer();
    

    输出:GameObject的构造函数

    Player的构造函数

    Mainplayer的构造函数

    父类的无参构造

    class Father
    {
        //子类实例化时,默认自动调用的时父类的无参构造 所以如果父类无参构造被顶掉,会报错
        //public Father()
        //{       
        //}
        public Father(int i)
        {
            Console.WriteLine("Father的构造");
        }
    }
    class Son:Father
    {
        //通过base调用指定父类构造
        public Son(int i) : base(1)
        {
            Console.WriteLine("Son的一个参数构造");
        }
        public Son(int i,string str):this(i)
        {
            Console.WriteLine("Son的两个参数构造");
        }
    }
    
    Son s = new Son(1,"123");
    

    输出:Father的构造

    Son的一个参数构造

    Son的两个参数构造

    万物之父

    万物之父

    关键字:object

    概念:object是所有类型的父类/基类,它是一个类(引用类型)

    作用:1.可以利用里氏替换原则,用object容器装所有对象

    2.可以用来表是不确定类型,作为函数参数类型

    Father f = new Son();
    if(f is son)
    {
        (f as son).Speak();
    }
    //引用类型
    object o = new Son();
    Son s = new Son();
    o = s;
    o = f;
    if(o is Son)
    {
        (o as Son).Speak();
    }
    //值类型
    object o2 = 1f;
    //用强转
    float f1 = (float)o2;
    //特殊的string类型
    object str = "123123";
    string str2 = str.ToString();
    //或者string str2 = str as string;
    object arr = new int[10];
    int[] ar = (int[])arr;
    //或者int[] ar = arr as int[];
    //看到引用类型都用as就好了
    

    拆箱装箱

    发生条件:用object存值类型(装箱),再把object转为值类型(拆箱)。

    • 装箱:把值类型用引用类型存储,栈内存会迁移到堆内存中

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

    • 好处:不确定类型时可以方便参数的储存和传递

    • 坏处:存在内存迁移。增加性能消耗

    //装箱
    object v = 3;
    //拆箱
    int intValue = (int)v;
    
    static void TestFun( params object[] array)
    {
        //里面的内容用is和as写各种操作就好了
    }
    //调用的时候就可以传任意的参数了
    TestFun(1,2,3,4f,34.5,new Son());
    

    尽量少用,因为装箱拆箱会造成性能消耗。也不是完全不能用。

    密封类(不重要)

    基本概念:密封类时使用sealed密封关键字修饰的类

    作用:让类无法再被继承 “结扎”

    sealed class Father
    {
        
    }
    //会报错
    class Son : Father
    {
        
    }
    

    作用:在面向对象程序的设计中,密封类的主要作用就是不允许最底层子类被继承。可以保证程序的规范性和安全性。

    目前来说用处不大,以后制作复杂系统或者程序框架时,便能慢慢体会到密封的作用。

    意义:加强面向对象程序设计的规范性、结构性、安全性。

    面向对象——多态

    概念:多态按字面意思就是“多种状态”,让继承同一父类的子类们,在执行相同方法时有不同的表现(状态)。

    主要目的:同一父类的对象,执行相同行为(方法)有不同的表现。

    解决的问题:

    让同一个对象有唯一行为的特征。

    class Father
    {
        public void SpeakName()
        {
            Console.WriteLine("Father的方法");
        }
    }
    class Son : Father
    {
         public new void SpeakName()
        {
            Console.WriteLine("Son的方法");
        }
    }
    
    Father f = new Son();
    f.SpeakName();
    //会输出Father的方法
    (f as Son).SpeakName();
    //会输出Son的方法
    

    这样写破坏了对象的唯一性。

    多态的实现

    我们目前学过的多态:编译时多态—— 函数重载,开始就写好的

    我们将学习的多态:运行时的多态(vob、抽象函数、接口)

    vob

    v:virtual(虚函数)

    o:override(重写)

    b:base(父类)

    class  GameObject
    {
        public string name;
        public GameObject(string name)
        {
            this.name = name;
        }
        //虚函数可以被子类重写
        public virtual void Atk()
        {
            Console.WriteLine("游戏对象进行攻击");
        }
    }
    class Player : GameObject
    {
        public Player(string name):base(name)
        {
            
        }
        //重写虚函数
        public override void Atk()
        {
            //base的作用:代表父类 可以通过base来保留父类的行为
            base.Atk();
            Console.WriteLine("玩家对象的攻击");
        }
    }
    
    GameObject p = new Player();
    p.Atk();
    //输出:游戏对象进行攻击
    //玩家对象进行攻击
    

    抽象类和抽象函数

    抽象类

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

    特点:1.不能被实例化的类

    2.可以包含抽象方法

    3.继承抽象类必须重写其抽象方法

    abstract class Thing
    {
        //抽象类中 封装函数的所有知识点都可以在其中书写
        public string name;
        //可以在抽象类中写抽象函数
        
    }
    class Water : Thing
    {
        
    }
    //不能Thing t = new Thing();
    //但是可以用里氏替换原则
    Thing t = new Water();
    

    抽象函数

    又叫纯虚方法,用abstract关键字修饰的方法

    特点:1.只能在抽象类中申明

    2.没有方法体

    3.不能是私有的

    4.继承后必须实现 用override重写

    abstract class Fruits)
    {
        public string name;
        //抽象方法必须写访问修饰符,因为默认是private,而抽象方法必须在子类重写,所以一定要写public或者protected
        protected abstract void Bad();   //不用去实现
        public virtual void Test()
        {
            //可以选择是否写逻辑
        }
    }
    class Apple : Fruits
    {
        //子类不重写的父类的抽象方法会报错
        public override void Bad()
        {
            
        }
        //子类可以不重写虚函数,不会报错
     class SuperApple:Apple
     {
         //子类的子类就不用必须重写抽象方法了
         //虚方法和抽象方法都可以被子类无线的去重写
         public override void Bad()
        {
            
        }
         public override void Test()
        {
            //可以选择是否写逻辑
        }
     }
    
    }
    

    如何选择普通类和抽象类?

    不需要被实例化的对象,相对比较抽象的类可以使用抽象类

    父类中的行为不太需要被实现的,只希望子类去定义具体的规则的,可以选择抽象类如何使用其中的抽象等等来定义规则

    作用:整体框架设计时,会使用。让父类更安全

    接口

    概念:接口是行为的抽象规范,它也是一种自定义类型。

    关键字:interface

    接口申明的规范

    1.不包含成员变量

    2.只包含方法、属性、索引器、事件

    3,成员不能被实现

    4.成员可以不用写访问修饰符,不能是私有的

    5.接口不能继承类,但可以继承另一个接口

    接口的使用规范

    1.类可以继承多个接口

    2.类继承接口后,必须实现接口中所有成员

    特点

    1.它和类的申明类似

    2.接口是用来继承的

    3.接口不能被实例化,但是可以作为容器储存对象

    接口的声明

    interface IFly
    {
        //默认public,不能是private
        void Fly();  //不需要写
        string Name
        {
            get;
            set;
        }
        int this[int index]
        {
            get;
            set;
        }
        //事件
        event Action doSomething;
    }
    

    接口是抽象行为的“基类”。

    接口命名规范:帕斯卡前面加个I

    接口的使用

    接口是用来继承的

    • 1.类可以继承一个类,n个接口

    • 2.继承了接口后,必须实现其中的内容,并且必须是public的

    • 3.实现的接口函数,可以加v再在子类重写

    class Animal
    {}
    class Person:Animal,IFly
    {
        //必须加public,不能是protected
        public virtual void Fly()
        {
            //virtual虚函数,可以再被子类继承和重写
        }
        public string Name
        {
            get;
            set;
        }
        public int this[int index]
        {
            get
            {
                return 0;
            }
            set;
        }
        //事件
        public event Action doSomething;
    }
    
    //接口不能被实例化,但是可以用里氏替换原则
    IFly f = new Person();
    

    接口可以继承接口

    接口继承接口时 不需要实现

    待类继承接口后 类自己去实现所有内容

    interface IWalk
    {
        void Walk();
    }
    interface IMove:IFly,IWalk
    {
        
    }
    //类中就得把接口全部实现
    class Test:IMove
    {
         public virtual void Fly()
        {
        }
        public string Name
        {
            get;
            set;
        }
        public int this[int index]
        {
            get
            {
                return 0;
            }
            set;
        }
        public event Action doSomething;
    }
    

    显示实现接口

    当一个类继承两个接口,但是接口中存在着同名方法时,不能写访问修饰符

    注意:显示实现接口时,不能写访问修饰符

    interface IAtk
    {
        void Atk();
    }
    interface ISuperAtk
    {
        void Atk();
    }
    class Player:IAtk,ISuperAtk
    {
        //显示实现接口 就是用 接口名.行为名
        void IAtk.Atk()
        {
            
        }
        void ISuperAtk.Atk()
        {
            
        }
        public void Atk()
        {
            
        }
    }
    
    Player p = new Player();
    (p as IAtk).Atk();
    (p as ISuperAtk).Atk();
    p.Atk();
    

    总结:

    继承类:是对象间的继承,包括特征行为等等

    继承接口:是行为间的继承,继承接口的行为规范,按照规范去实现内容

    由于接口也是遵循里氏替换原则,可以用接口容器装对象。那么就可以实现装载各种毫无关系但却有相同行为的对象

    注意:

    • 1.接口值包含成员方法、属性、索引器、事件,并且都不实现,都没有访问修饰符

    • 2.可以继承多个接口,但只能继承一个类

    • 3.接口可以继承多个接口,相当于在进行行为合并,待子类继承时再去实现具体的行为

    • 4.接口可以被显示继承,主要用于实现不同接口中的同名函数的不同实现

    • 5.实现的接口方法,可以加virtual关键字,之后子类再重写

    密封方法

    基本概念:用密封关键字sealed修饰的重写函数

    作用:让虚方法或者抽象方法之后不能再被重写

    特点:和override

    abstract class Animal
    {
        public string name;
        public acstract void Eat();
        public virtual void Speak()
        {
            Console.WriteLine("叫");
        }
    }
    class Person:Animal
    {
        //会报错,就不能写了
        //public sealed override void Eat()
        //{ }
        public override void Speak()
        {
            
        }
    }
    class WhitePerson:Person
    {
        public override void Eat()
        {
            base.Eat();
        }
        public override void Speak()
        {
            base.Speak();
        }
    }
    

    面向对象关联知识点

    命名空间

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

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

    基本语法:

    namespace 命名空间名

    {

    ​ 类

    ​ 类

    }

    namespace MyGame
    {
        class GameObject
        {
            
        }
    }
    namespace MyGame
    {
        class Player:GameObject
        {
            
        }
    }
    namespace MyGame2
    {
        class GameObject
        {
            
        }
    }
    
    using MyGame;
    using MyGame2;
    //不同命名空间中相互使用,需要引用命名空间或指明出处
    MyGame.GameObject g = new MyGame.GameObject();
    MyGame2.GameObject g2 = new MyGame2.GameObject();
    

    在不同的命名空间中可以有同名类。

    如果有两个命名空间中有同名类,那么在使用的时候需要指明出处。

    //命名空间可以包裹命名空间
    namespace MyGame
    {
        namespace UI
        {
            class Image
            {
                
            }
        }
        namespace Game
        {
            class Image
            {
                
            }
        }
    }
    
    using MyGame.UI;
    MyGame.UI.Image a = new MyGame.UI.Image;
    

    关于修饰类访问修饰符

    public——命名空间中的类 默认为public

    internal——只能在该程序集中使用

    abstract——抽象类

    sealed——密封类

    partical——分部类

    万物之父中的方法

    object中的静态方法

    //静态方法 Equals 判断两个对象是否相等
    //最终的判断权,交给左侧对象的Equals方法,
    //不管值类型引用类型都会按照左侧对象Equals方法的规则来进行比较。
    Console.WriteLine(Object.Equals(1,1));
    //True
    Test t1 = new  Test();
    Test t2 = new  Test();
    Test t3 = t1;
    Console.WriteLine(Object.Equals(t1,t2));
    //False
    Console.WriteLine(Object.Equals(t1,t3));
    //True
    
    //静态方法 ReferenceEquals
    //比较两个对象是否是相同的引用,主要是用来比较引用类型的对象。
    //值类型对象返回值是指是false
    Console.WriteLine(Object.ReferenceEquals(1,1));
    //False
    Console.WriteLine(Object.ReferenceEquals(t1,t2));
    //False
    Console.WriteLine(Object.ReferenceEquals(t1,t3));
    //True
    

    object中的成员方法

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

    object中的虚方法

    //虚方法Equals
    //默认实现还是比较两者是否为同一个引用,即相当于ReferenceEquals
    //但是微软在所有值类型的基类SysteValueType中重写了该方法,用来比较值相等。
    //我们也可以重写该方法,定义自己的比较相等的规则
    
    //虚方法GetHashCode
    //该方法是获取对象的哈希码
    // (一种通过算法算出的,表示对象的唯一编码,不同对象哈希码有可能一样,具体值根据哈希算法决定
    //我们可以通过重写该函数来自己定义对象的哈希码算法,正常情况下,我们使用的极少,基本不用。
    
    //虚方法Tostring
    //该方法用于返回当前对象代表的字符串,我们可以重写它定义我们自己的对象转字符串
    //该方法非常常用。当我们调用打印方法时,默认使用的就是对象的Tostring方法后打印
    
    

    string

    1.字符串指定位置获取

    //字符串本质是char数组
    string str = "唐老师";
    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);
    //输出13333
    

    3.正向查找字符串位置

    str = "我是唐老师!";
    int index = str.IndexOf("唐");
    Console.Writeline(index); //输出2
    //查找不到返回-1
    

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

    str = "我是唐老师唐老师!";
    int index = str.LastIndexOf("唐老师");
    Console.Writeline(index); //输出5
    

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

    str = "我是唐老师唐老师!";
    str.Remove(4);
    Console.WriteLine(str); //还是输出我是唐老师唐老师!
    str = str.Remove(4);
    Console.WriteLine(str); //输出我是唐老
    
    //执行两个参数进行移除
    //参数1 开始位置
    //参数2 字符个数
    str = str.Remove(1,1); //输出我唐老
    
    

    6.替换指定字符串

    str = "我是唐老师唐老师!";
    str = str.Replace("唐老师","老炮儿");
    Console.WriteLine(str); //输出我是老炮儿老炮儿!
    

    7.大小写转换

    str = "fghdsjsjkdre";
    str.ToUpper();
    Console.WriteLine(str); //小写
    str = str.ToUpper();
    Console.WriteLine(str); //大写
    
    str.ToLower();
    Console.WriteLine(str); //大写
    str = str.ToLower();
    Console.WriteLine(str); //小写
    

    8.字符串截取

    str = "唐老师唐老师";
    str = str.SubString(2);
    Console.WriteLine(str); //输出师唐老师
    
    //参数1 开始位置
    //参数2 字符个数
    //不会自动帮你判断是否越界,需要你自己判断越界
    str = str.SubString(2,2);
    Console.WriteLine(str); //输出老师
    

    ※9.字符串切割

    str = "1,2,3,4,5,6,7,8";
    string[] strs = str.Split(",");
    for(int i = 0;i < str.Length; i++)
    {
        Console.WriteLine(str[i]);
    }
    //输出 1 2 3 4 5 6 7 8
    

    StringBuilder

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

    StringBuilder

    C#提供的一个用于处理字符串的公共类

    主要解决的问题是

    修改字符串而不创建新的对象,需要频繁修改和拼接的字符串可以使用它,提升性能

    使用前需要引用命名空间

    //初始化
    using System.Test
    StringBuilder str = new StringBuilder("123123123");
    Console.WriteLine(str);
    
    //StringBuilder存在一个容量问题,每次往里面增加时,会自动扩容
    //获取容量
    Console.WriteLine(str.Capacity); //16
    //获取字符常量
    Console.WriteLine(str.Length); //9
    //增删查改替换
    //增
    str.Append("4444");
    Console.WriteLine(str);
    Console.WriteLine(str.Capacity); //16
    Console.WriteLine(str.Length);  //13
    
    str.AppendFormat("{0}{1}",100,999);
    Console.WriteLine(str); //1231234444100999
    Console.WriteLine(str.Capacity); //32
    Console.WriteLine(str.Length);  //19
    //插入
    str.Insert(0,"唐老狮");
    Console.WriteLine(str); //唐老狮1231231234444100999
    
    //删
    str.Remove(0,10);
    Console.WriteLine(str); //234444100999
    //清空
    //str.Clear();
    
    //改
    str[0] = 'A';
    Console.WriteLine(str); //A34444100999
    //替换
    str.Replace("1","唐");
    Console.WriteLine(str); //A34444唐00999
    
    //重新赋值StringBuilder
    str.Clear();
    str.Append("123123");
    Console.WriteLine(str); 
    //判断StringBuilder是否和某一个字符串相等
    if(str.Equals("12312"))
    {
        Console.WriteLine("相等")
    }
    

    结构体和类的区别

    区别概述:

    结构体和类最大的区别时在存储空间上的,因为结构体是值,类是引用。

    因此他们的存储位置一个在栈上,一个在堆上。

    值和引用在赋值上的区别

    结构体和类在使用上很类似,结构体甚至可以用面向对象的思想来形容一类对象。

    结构体具备着面向对象思想中封装的特性,但是它不具备继承和多态的特性,因此大大减少了它的使用频率。

    由于结构体不具备继承的特性,所以它不能使用protected保护访问修饰符

    细节区别

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

    结构体的特别之处

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

    如何选择结构体和类

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

    抽象类和接口的区别

    抽象类和抽象方法

    • abstract修饰的类和方法

    • 抽象类不能被实例化

    • 抽象方法只能在抽象类中申明 是个纯虚方法,必须再子类中实现

    接口

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

    相同点

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

    区别

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

    如何选择抽象类和接口

    • 表示对象的用抽象类,表示行为拓展的用接口
    • 不同对象拥有的共同行为,我们往往可以使用接口来实现
    • 举个例子:动物是一类对象,我们自然会选择抽象类;而飞翔是一个行为,我们自然会选择接口。
  • 相关阅读:
    【CH6801】棋盘覆盖
    【模板】二分图匹配
    【POJ3683】Priest John's Busiest Day
    【Asia Yokohama Regional Contest 2018】Arithmetic Progressions
    【POJ2230】Watchcow
    【CF263D】Cycle in Graph
    【CF131D】Subway
    【洛谷P2709】小B的询问
    【模板】2-SAT
    【模板】矩阵快速幂
  • 原文地址:https://www.cnblogs.com/tavee/p/15351275.html
Copyright © 2011-2022 走看看