什么是面向对象?

面向对象:就是把现实中的事物都抽象为“对象”。每个对象是唯一的,且都可以拥有它的属性与行为。我们就可以通过调用这些对象的方法、属性去解决问题。

什么是对象?

对象是面向对象编程中的一个概念,它是类的一个实例化(即具体化)的结果。对象是具体的实际存在的,可以在程序中被创建操作销毁

什么是类?

每个对象都属于某个类,类是对象的抽象,它定义了对象所具有的属性方法结构和行为。对象通过实例化类来创建,并且可以根据类的定义进行属性和方法的访问和调用。

为什么非要创建对象才能使用呢?

类只是一种程序内的“设计图纸”或者摸具,需要基于图纸或摸具生产实体(对象),才能正常工作这种套路,称之为:面向对象编程,类就相当于是闹钟的设计图纸,而对象就相当于按照闹钟的设计图纸所生产出来的闹钟

一、类和对象的创建(封装)

(1)对象的创建

      类名  对象名 =  new 类名()

       对象名.成员名称=值
       new的时候{成员名称=值}

 (2)获取成员的值
       对象名.成员名称

(3)方法(对象的行为)调用
       对象名.方法名()

internal class Program
{
    
    static void Main(string[] args)
    { 
        //类只是提供一个类型,凡是由类构建出来对象都属于同一种类型

        //对象创建 需要使用构造函数进行创建
        People zs = new People();
        zs.id = "002";//访问公共的属性

        People ls = new People();
        ls.id = "001";

        //调用成员方法
        //调用非静态的方法 (对象方法)
        zs.F1();
        ls.F1();

        //调用静态的方法(类方法) 类名去调用  
        People.F2(10);
    }

}
//类的创建位置:在namespace范围内.
//创建语法:
//修饰符:public 公共的 ,private私有的 protected受保护的,internal内部访问,类一般使用public 或者internal
//class 关键字
//类名:大驼峰命名法则

//类里面包含成员变量:字段 属性 方法
public class People
{
    //字段又叫成员变量
    //定义成员变量的语法
    //修饰符 类型 变量名;
    string name;//字段
    int age;//字段
    bool sex;//字段 默认情况下字段是私有的
    public string id;//字段
    private int _height;//
  
  //成员方法: 修饰符  static/非static  void/数据类型 方法名(参数列表){}
    public void F1()
    {        
        Console.WriteLine("非静态无返回值方法");
    }
    public static int F2(int a)
    {         
        return 100000;

    }
}

二、字段和属性

1.字段和属性区别

 字段和属性区别:

(1)用途不同:字段是内部使用;属性是外部使用。
(2)权限不同:字段一般是私有的;但属性一般是公开的。
(3)结构不同:字段是存数据的变量;属性带有 get/set 访问器,本身不存数据,只是对字段的访问包装。
(4)安全性不同:字段直接暴露数据,不安全、不可控;属性包装字段,可以加验证、逻辑,安全、可控

(1)私有字段:_ 开头 + 小驼峰     private int  _age;

(2)公开属性:大驼峰        public int Age { get; set; }

(3)赋值 = 自动跑 set

(4)读取 = 自动跑 get

(5)set 里可以写逻辑、调用方法等,赋值时自动执行

(1)同个类内,方法中直接写属性名,就能访问当前对象的属性,编译器会默认加上 this.

(2)this 代表当前实例对象this.属性名 ,this可以省略。

什么时候必须写 this. ?

局部变量 / 参数 和 属性重名时,必须用 this. 区分,否则编译器会优先找局部变量;

  • 无命名冲突:属性名 / this.属性名 都可以,随意写;

  • 有命名冲突:必须加 this. 区分属性和局部变量 / 参数;

 internal class Program
 {
     static void Main(string[] args)
     {
          //创建对象
          People p = new People();   
          p.Name = "T"; //在给属性赋值的时候触发set访问器
          Console.WriteLine(p.Name);//取值的话触发get访问器                      
          
         People p3 = new People();
         p3.Name = "Y";
        
        // p3.Msg = "S"; //只能读取 不能修改
         Console.WriteLine(p3.Name+":"+p3.Id);      
     }
 }

 public class People 
 {
     private int _age;// 私有的字段 只能在内部访问

     //最早的属性定义方式
     //先添加一个私有的字段
     //再添加一个公共的属性,可以添加get set访问器
     private string _name;//字段
     public string Name //属性
     {
         get{
             Console.WriteLine("get访问器触发了");
             return _name; 
         } //在外部获取属性所关联字段值 触发get访问器 ,需要添加return 关联字段

         set{
             Console.WriteLine("set访问器触发了"+value);
             if (value.Length==0)
             {
                 throw new Exception("不能赋空字符串");
             }
             _name = value; 
         } //在给属性赋值的时候触发set访问器  把一个value赋值给字段, 
     }

     //2 优化上面属性的写法
     private int _a1;
     public  int A1 { get => _a1; set => _a1 = value; }

     //3如果不想加属性拦截 get set不想写任何东西
     //属性语法糖写法
     public string Id { get; set; } = "001";

     // 可以保留一个get 使属性变成只读的属性
     public string Msg { get; }
 }

2.属性拦截例子

namespace _8属性拦截例子
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Dog d1 = new Dog();
            d1.Age = 19;
            Console.WriteLine(d1.Age);

            d1.Color = '白';
            Console.WriteLine(d1.Color);
        }
    }
   
}
namespace _8属性拦截例子
{
    internal class Dog
    {
        public int a=100;

        private int _age;
        public int Age
        {
            get
            {
                _age *= 10;
                return _age; 
            } //get访问器   对象.属性
            set
            {
                if (value < 0||value>20)
                {
                    throw new Exception("年龄不在合适的范围内");//
                }
                _age = value; //年龄在0-20之间
 
            }//set访问 对象.属性=10
        }
        private char[] colors = { '黄', '白', '黑' };

        private char _color;
        public char Color
        {
            get { return _color; } 
            set 
            {
                if (!colors.Contains(value))
                {
                    throw new Exception("颜色不合适");
                }
                _color = value;
            
            }
        }
    }
}

三、构造函数

          在 C# 中,构造函数的名称必须与类名一致,并且它不能具有返回类型(如void或int)。它在类中的地位比较特殊,不需要我们主动调用,当创建一个类的对象时自动调用类中的构造函数

默认地,所有类都有构造函数:如果你不自己创建类构造函数,C# 会为你创建一个。但是,那么你将无法为字段设置初始值。

 注意: 类中默认有一个无参构造函数,但是如果手动添加了有参构造函数后,就会把无参的构造函数覆盖掉

namespace _9构造函数
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //构造函数:People() 和类的名称是一样的.作用创建对象和初始化属性的值, 可以有非静态的构造函数,也可以有静态构造函数
            //创建一个类默认提供一个不带参数构造函数,也可以定义有参数的构造函数.
            People p1 = new People();
            Console.WriteLine(p1.Name);//张三

            People p2 = new People();
            Console.WriteLine(p2.Name);//张三

            People p3 = new People("李四",20);
          
            Console.WriteLine( p3.Name);

            People p4 = new People("王五", 20);
            Console.WriteLine(p4.Name);

        }
    }
    public class People
    {
        public  string Name { get; set; }
        public int Age { get; set; }

        //不带参数的构造函数
        public People()
        {
            Name = "张三";
            this.Age = 10; // this当前类的对象,不是具体的对象 ,
        }

    public People(string n, int a)
   {
            this.Name = n;  //把参数赋值给属性
            this.Age = a;
            Console.WriteLine(this.Name + "---------");
    }

    }
}

四、析构函数

(1)析构函数:是一个特殊的成员函数,当一个对象被释放的时候执行
 (2)被释放:C#有垃圾回收机制(GC) 当一个数据没有被任何变量所引用的时候,垃圾回收机制就会把这个对象当作垃圾回收掉
(3) 析构函数:当类的某个对象被当作垃圾回收的时候,这个函数就会自动调用

 (4)析构函数的作用是释放对象的资源,但析构函数是由垃圾回收器控制的,无法做到显式调用,所以使用析构函数不是最佳方案(很少使用)

namespace _10析构函数
{
    internal class Program
    {
        static void Main(string[] args)
        {
            People p = new People();
            p = null;
            GC.Collect();//GC强制回收,对象不再被引用的时候 进行回收
            Console.ReadKey();
            
        }
    }
    public class People
    {
      
        //当一个对象不再被其他变量引用的时候,可以通过GC垃圾回收机制强制回收,这个时候会触发析构函数
         ~People() 
        {
            Console.WriteLine("对象被GC回收了");
        }
    }
}

五、静态成员(属性、方法)

1.静态与非静态区别

(1)归属不同 静态成员属于;非静态成员属于实例对象

(2)调用方式不同: 静态成员通过 类名.成员 调用,无需实例化;非静态成员必须先 new 创建对象,再通过 对象名.成员 调用。

(3)内存特点不同: 程序运行时只分配一次内存,全局唯一,所有对象共享同一份数据;非静态成员每创建一个对象,就单独分配一份内存。   

(4)访问规则不同 静态方法只能使用静态的属性和静态方法(若要访问实例成员,必须先实例化对象);非静态方法不仅能使用静态属性、静态方法,还能直接使用实例属性、实例方法

简洁版:

(静态属于类,非静态属于对象)

(静态不用 new,非静态必须 new)

( 静态全局只有一份,非静态每个对象独立一份)

 (静态只能使用静态,非静态全都能用)

为什么是这样?

  • 静态:属于,程序一运行就存在
  • 非静态:属于对象,必须 new 才存在

静态方法运行时,对象还没创建呢,所以根本用不了非静态!

什么时候用?

  • 静态:工具方法、共用数据、全局唯一
  • 非静态:每个对象不一样的属性 / 方法(姓名、年龄、成绩等)
internal class Program
{
    static void Main(string[] args)
    { 
        //非静态找对象去调用
        People p1 = new People();
        p1.F1();
        p1.Id = 1;

        //静态的方法  类名.属性/方法
        People.Age = 10;
        People.F2();           
    }
}
 //静态属性定义在类空间里面,每一个实例都拥有同一个静态数据
 //非静态属性 每个实例拥有对立一份数据
 public class People 
 { 
     //非静态成员变量和非静态成员方法

     private int id;
     public int Id { 
         get
         {
             return id;
         }

         set
         {
             Console.WriteLine("ssssssssss");
             id = value;
             //Id = value; // Id是属性   属性=10执行set访问器 切记不要写属性赋值,造成死循环
         }    
      }

     public void F1()
     {
         Console.WriteLine("非staticF1");
         //非静态的方法可以使用静态属性和静态方法
        // Age = 10;
        // F2();

     }

     //静态成员变量和静态成员方法     
     public static  int Age {    get; set; }
     public static void F2()
     {  
         //如果直接调用非静态的方法 不允许的,但是如果使用new 创建一个新的 ,这个新的对象可以调用
         //People p  = new People();
         //p.F1();
         //p.Id = 1;

         //静态方法只能使用静态的属性和静态方法 
         Console.WriteLine("StaticF2");
     }
 
 }

2.静态方法测试

internal class Program
{
    static void Main(string[] args)
    {
        People p1 = new People();
        p1.AddNum();
     
        People p2 = new People();
        p2.AddNum();
       
        //Num属性的静态 每个实例都拥有同一个数据
        //Num属性的非静态 每个实例都有一个独立的一份数据
        Console.WriteLine(p1.GetNum());//  如果静态num值是2,非静态num值1
        Console.WriteLine(p2.GetNum());//  如果静态num值是2,非静态num值1
      
    }
}
public class People
{
    public int Age {  get; set; }
    public  int Num { get; set; }

    public void AddNum()
    {
        Num++;
    }
    public int GetNum() 
    {
       return Num;
    }
}

六、常量和只读变量

readonly :表示只读的一个字段,只能在构造函数里面进行修改;

public static readonly:静态的只读变量,只能在静态构造函数里面进行修改;

静态构造函数只能被调用一次:1.new的时候调用,2.访问静态属性时候调用;

const :给字段加const 关键字 ,表示这个字段为常量,不能修改;

internal class Program
{
    static void Main(string[] args)
    {
        People p1 = new People();
        //p1.a = 30; //不能其他进行修改
        // Console.WriteLine(p1.a);//可以访问

        Console.WriteLine(People.b);
        Console.WriteLine(People.c);
    }
}
public class People
{ 
    //readonly 表示只读的一个字段,只能在构造函数里面进行修改,
    public readonly int a = 10;
    public static readonly int b = 20;//静态的只读变量 只能在静态构造函数;里面进行修改

    public const int c = 30;//常量,自动编译成一个静态的常量

    public People()
    {
       // b = 30; 静态只读不能在非静态的构造函数进行修改
        a =20;
        
        Console.WriteLine(a+"++++++++++++");//20
    }

    // 静态的构造函数不能使用修饰符
    //静态构造函数只能被调用一次 1:new的时候调用了,2访问静态属性时候调用
    static People()
    {
        b = 40;
        Console.WriteLine(b+"----------------");
        Console.WriteLine("静态构造函数只能被调用一次 1:new的时候调用了,2访问静态属性时候调用 ");
    }

}

七、继承

  • 作用:继承主要是为了代码复用,把公共的属性和方法放到父类,子类直接使用,不用重复写。
  • 叫法:A 类继承 B 类,
    • B 叫 父类 / 基类
    • A 叫 子类 / 派生类
  • 语法:C# 用 冒号 : 实现继承。
  • 单继承:C# 是单继承
    • 可以:A → B → C(链式继承)
    • 不可以:A 同时继承 B 和 C
  • 默认继承:如果不写冒号,类默认继承 object
  • object:是所有类的最终基类(祖宗类)。
internal class Program
{
    static void Main(string[] args)
    {
        Student s1 = new Student();
        s1.Name = "Test";
        s1.Address = "s";

        s1.F1();

        People p1 = new People();
    }
}   
//定义父类
public class People
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    private int Age = 1000; //私有的不能被继承
    protected int CC { get; set; }//受保护的属性 People的实例不能访问,子类可以使用 

    public void F1()
    {
        Console.WriteLine( "父类的F1方法");
    }
}
//学生类 继承于People
public class Student:People
{
    public void F2()
    {
        Console.WriteLine(Name+":"+Id+":"+CC);
    }
}

1.子类构造和父类构造

  • 在子类继承父类时,创建子类对象(不管无参 / 有参)会先调用父类的无参构造函数,再调用子类自己的构造函数;
  • 如果想主动调用父类的有参构造,必须在子类构造函数后面加  : base (参数);
  • 一旦加了 : base (参数),父类无参构造就不会再执行了。
  • 父类的构造函数,一次只会执行一个! 要么执行无参,要么执行有参不可能两个都执行
 internal class Program
 {
     static void Main(string[] args)
     {
        // People p1 = new People();
        // People p2 = new People("福瑞");
//在子类继承父类的时候,创建子类的对象(无参或有参),会先调用父类的无参构造函数,在调用子类自己
// 的构造函数。
        
       // Student s1 = new Student();  
           //1 父类的无参数构造函数
           //3子类的无参数构造函数

//即使子类调用带参数构造函数,默认情况下还是先调用父类的无参数构造函数
//如果想调用父类有参构造函数 在子类有参数构造函数后面添加base("11"),调用父类有参数构造 。
       
        Student s2 = new Student(10,"张三");
          //2 父类带参数的构造函数
          //4子类带参数的构造函数
     }
 }
 public class People
 {
     public string Name {  get; set; }
     public People()
     {
         Console.WriteLine("1 父类的无参数构造函数");
     }

     public People(string name)
     {
         Console.WriteLine("2 父类带参数的构造函数");
     }
 }

 public class Student:People
 {
     public int Age { get; set; }    
     public Student():base() //base指的是父类的对象, :base()调用父;类的构造函数
     {
         Console.WriteLine("3子类的无参数构造函数");
     }

     public Student(int age, string n) : base(n) //:base(n) 调用父类的有参数构造函数
     {
         
         Console.WriteLine("4子类带参数的构造函数"+Name);
     }
 }

2.访问修饰符

访问修饰符/范围 当前类 子类 实例对象 引用当前项目的项目子类 引用当前项目实例对象
public
private × × × ×
internal × ×
protected × ×
protected internal ×

八、多态

 多态的分类

  • 编译时多态(静态多态):通过方法重载(Method Overloading)和运算符重载(Operator Overloading)来实现。

  • 运行时多态(动态多态):通过继承和接口实现,以及方法重写(Method Overriding)来实现。

静态多态:在编译过程中,通过方法重载和运算符重载来实现,也称之为静态绑定和早期绑定

动态多态:在运行过程中,通过方法重写,隐藏方法来实现,也称之为动态绑定或者后期绑定

    一、函数重载

    函数重载满足条件:
    (1)函数名必须一样;
    (2)参数的个数 或者 类型不一样;
    (3)仅仅只有返回值类型不一样,不是重。

    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine();     
        }
    
        public static void F1()
        {
            Console.WriteLine("F1不带参数");
        }
        public static void F1(int a)
        {
            Console.WriteLine("F1带一个参数");
        }
        public static void F1(string a)
        {
            Console.WriteLine("F1带一个参数");
        }
        public static void F1(string a,int b)
        {
            Console.WriteLine("F1带俩个参数");
        }
        public static void F1(int b,string a)
        {
            Console.WriteLine("F1带俩个参数");
        }
        public static int F1(bool b)
        {
            Console.WriteLine("F1带1个参数");
            return 10;
        }
    }

    二、运算符重载

      符号重载:对运算符号或者比较符号等进行功能的重写 例如可以让俩个对象能够实现相加

    核心规则:

    1. 必须用 public static

    2. 必须用 operator 关键字

    3. 必须返回值(不能是 void)

    4. 二元运算符(+、-、*、/)必须带两个参数

    5. 固定写法:public static 返回值  operator 运算符(参数列表)

    运算符类别 可重载运算符 关键备注
    算术运算符 +-*/% 二元运算符,必须带 2 个参数
    一元算术运算符 ++--+-~ 一元运算符,仅需 1 个参数
    比较运算符 ==!=><>=<= 必须成对重载(如重载==必须同时重载!=
    位运算符 &、` ^<<>>` 支持按位运算自定义逻辑
    特殊运算符 truefalse 用于自定义类型的布尔判断
    运算符类别 不能重载的运算符
    赋值运算符 =
    逻辑与 &&
    逻辑或 ||
    三元运算符 ?:
    类型判断 is
    类型转换 as
    获取大小 sizeof
    获取类型 typeof
     internal class Program
     {
         static void Main(string[] args)
         {    
             //符号重载:对运算符号或者比较符号等进行功能的重写 例如可以让俩个对象能够实现相加.
             Box b1 = new Box(10,20,30);
             Box b2 = new Box(30,40,50);
    
             Box  b3 = b1 + b2;
             Console.WriteLine(b3.Length+":"+b3.Width+":"+b3.Height);
            
             int b4 = b1 -b2;
             Console.WriteLine(b4);
             
             Box b5 = b1++;
             Console.WriteLine(b1.Length);//11
             Console.WriteLine(b5.Length);//10
    
             Console.WriteLine(b1>b2);//false
    //执行<符号 执行重载函数,重载函数比较了b1和b2的体积, b1.v<b2.v成立,重载结果就为true
             Console.WriteLine( b1<b2); //true
         }
     }
    public class Box
    {
        public int Length {  get; set; } //长
        public int Width { get; set; } // 宽
        public int Height { get; set; }//高
    
        public Box() 
        {    
        }
    
        //体积的属性
        public int Volume 
        { 
            get 
            { 
                return Length*Width*Height;
            }
        }
    
        public Box(int l, int w,int h)
        {
            Length = l;
            Width = w;
            Height = h;
        }
    
        //重载加+ 实现俩个对象相加 返回一个对象,结果对象三个属性分别是俩个对象属性相加和
        public static Box operator+(Box b1,Box b2)
        {
             
            //return b1.Length*b1.Width*b1.Height + b2.Length * b2.Width * b2.Height; 
            return new Box()
            {
                Length = b1.Length + b2.Length,
                Width = b1.Width + b2.Width,
                Height = b1.Height + b2.Height
            };
        }
    
        //重载- ,俩个对象相减等到俩个体积相减的差值
        public static  int operator-(Box b1,Box b2)
        {
            return b1.Length * b1.Width * b1.Height - b2.Length * b2.Width * b2.Height;
        }
    
        //重载++ 让对象的长宽高在基础之上加一 并且返回一个对象
        public static Box operator++(Box b1)
        {
             return new Box()
            {
                Length = b1.Length + 1,
                Width = b1.Width + 1,
                Height = b1.Height + 1,
            };
        }
        //重载大于号
        public static bool operator>(Box b1,Box b2)
        {
            return b1.Volume > b2.Volume;
        }
    
        //重载小于符号
        public static bool operator <(Box b1, Box b2)
        {
            return b1.Volume < b2.Volume;
        }
      
    }

    三、抽象类(abstract)

    抽象方法和普通方法区别:

    (1)抽象方法只能定义在抽象类里面,普通方法可以定义在抽象类,也可以定义普通类中;
    (2)抽象方法没有方法体,普通方法需要有方法体;
    (3)抽象方法 / 属性必须在子类重写(override)实现,普通方法则不必须。

    抽象类和普通类区别:

    (1)抽象类:必须加 abstract;    普通类:不需要关键字;

    (2)抽象类:不能 new 对象;       普通类:可以直接 new 对象;

    (2)抽象类:可以有抽象方法 + 抽象属性;      普通类:绝对不能有任何抽象成员

    (3)抽象类:普通子类继承后,必须重写(override)所有抽象成员;     普通类:子类继承后,不需要重写任何东西,直接用;

    (5)抽象类:普通属性、普通方法、抽象属性、抽象方法;    普通类:只能写普通属性、普通方法。

    internal class Program
    {
        static void Main(string[] args)
        {
            //抽象类:
            //定义时候使用abstract关键字,不能实例化
            //抽象类存在的意义就是为了被继承的, 所以一般是公共的类
            //可以定义普通属性和方法 也可以定义抽象属性和抽象方法,需要在子类把抽象的属性和方法进行实现
            //抽象类可以继承自抽象类,子类如果不是抽象类,就必须重写抽象类中全部的抽象方法
    
            // 抽象方法和普通方法区别
            // 抽象方法只能定义在抽象类里面,普通方法可以定义在抽象类 也可以定义普通的类中
            // 抽象方法没有方法体,普通方法需要有方法体
            // 抽象方法在子类实现. 需要通过override重新抽象方法
        }
    
    }
    //定义抽象类
     abstract class People
    {
        public int Age { get; set; }//普通的属性
        public void F2()  // 普通方法
        { 
            Console.WriteLine("F2非抽象方法");
        }
    
        public abstract string Name { get; set; } // 定义抽象属性
        public abstract void F1(); //抽象方法 不要加{}
    }
    
    //定义子类继承抽象类 必须实现抽象属性和抽象方法
    class Student : People
    {
        public override string Name { get; set; } = "ssss";
    
        public override void F1()
        {
            Console.WriteLine("子类的F1" );
        }
    }
    

    四、虚方法(virtual)

    抽象方法与虚方法区别:

    (1)抽象方法:abstract,虚方法:virtual;

    (2)抽象方法只能定义在抽象类中,虚方法普通类、抽象类都可以定义;

    (3)抽象方法没方法体,子类必须重写;虚方法有方法体,子类可选重写。

    (4)虚方法和new 或者override进行联合使用

    internal class Program
    {
        static void Main(string[] args)
        {     
            China  c = new China();
         
            c.SayHellow();
        }
    }
     public class People
     {
         public string Name { get; set; }
         public virtual void SayHellow()
         {
             Console.WriteLine("People打招呼");
         }
     }
     public class China :People
     {
         //父类的虚方法 需要在子类通过override进行重写,覆盖父类的方法
         // 也可以不用重写,调用的父类的方法
         public override void SayHellow()
         {
             Console.WriteLine("吃了吗,抽个烟");
         }
     
     }
     public class Japan : People
     {
         //父类的虚方法 需要在子类通过override进行重写,覆盖父类的方法
         // 也可以不用重写,调用的父类的方法
         public override void SayHellow()
         {
             Console.WriteLine("汪汪");
         }
     }
    
     public class HanGuo : People
     {
         //父类的虚方法 需要在子类通过override进行重写,覆盖父类的方法
         // 也可以不用重写,调用的父类的方法
         public override void SayHellow()
         {
             Console.WriteLine("啊你赛有");
         }
     }

    五、new 和 override

    在重写虚方法的时候 可以使用new 或者override
            new 或者override 区别

            new:创建一个新的方法,父类和子类都保留了一份,他们是相互独立,调用new重写方法时候,就看变量的类型:变量类型如果是父类,调用父类的方法,如果是子类,调用子类自己的方法。

            override:相当于把父类的方法覆盖掉了,不管变量类型是子类类型还是父类类型,调用的是子类的方法

    1. new(隐藏 / 独立)

    • 父类、子类各存一份方法,互不干扰
    • 调用谁,看变量类型 :变量是父类 → 调父类, 变量是子类 → 调子类

    2. override(重写 / 覆盖)

    • 父类方法被覆盖
    • 调用谁,看真实对象是谁 :不管变量怎么写,永远调用最终子类的方法
    internal class Program
    {
        static void Main(string[] args)
        {
            //  创建的对象如果使用自己类型的变量去接收, 调用的时候调用自己类的方法
            //People p =  new People();
            // p.Test1();
            // p.Test2();
            // p.Test3();  
    
            People p1 = new Student(); // 找父类类型的变量 接收子类的对象
            Student s1 = new Student();  //找自己类的类型变量接收对象
    
            p1.Test1();//People的Test1
            s1.Test1();//Student的Test1
    
            p1.Test2();//People的Test2
            s1.Test2();//Student的Test2
    
            p1.Test3();//Student的Test3
            s1.Test3();//Student的Test3    
        }
    }
     public class People
     {
         public virtual void Test1()
         {
             Console.WriteLine("People的Test1");
         }
         public virtual void Test2()
         {
             Console.WriteLine("People的Test2");
    
         }
         public virtual void Test3()
         {
             Console.WriteLine("People的Test3");
         }
     }
    
     public class Student : People
     {
         public new void Test1()
         {
             Console.WriteLine("Student的Test1");
         }
         public new void Test2()
         {
             Console.WriteLine("Student的Test2");
    
         }
         public override void Test3()
         {
             Console.WriteLine("Student的Test3");
         }
     }

    六、密封类和静态类

        internal class Program
        {
            static void Main(string[] args)
            {
                //Test t1 = new Test();
                // Test1 t2 = new Test1();
                //Test2 t3 = new Test2();     
            }
        }
        //抽象类型   不能实例化,用来被继承的
        abstract class Test
        {
    
        }
        //密封类 不能被继承  能够被实例化
        sealed class Test1
        {
    
        }
        //静态类要求成员都是静态的, 不能被实例化
        static class  Test2
        {
          public static  int Age { get; set; }
             
        }
    
        //部分类:把一个分成多个类文件进行定义,编译器在编译时候会把多个文件自动合并一个类.
        // C# 部分类partial class 是 C# 中的部分类,核心作用是:将一个类的代码拆分到多个文件中,编译时编译器会自动把这些文件合并成一个完整的类。
        //它的关键字是 partial,必须和 class 配合使用。
        // 每个文件中的类都必须用 partial 修饰
        // 类名、命名空间必须完全一致
        // 最终编译后,所有部分会合并为一个类
        partial class Test3
        {
    
        }
    }

    七、重载和重写区别

    特点 重载 (Overload) 重写 (Override)
    位置 同一个类 父子类(继承)
    方法名 相同 相同
    参数 必须不同 必须相同
    返回值 无关 必须相同
    关键字 virtual + override

    更多推荐