c#类的定义、类的继承以及不同模块之间的相互类引用、new关键字作用、所有类的汇总

425 阅读10分钟

一.类的定义:

类的定义是以关键字 class 开始,后跟类的名称,包括方法、属性、字段、构造函数等等,定义类我们可以更好的定义自己想要的类型,类似于结构体,但是比结构体更高级。

     class Animal
    {
        //构造函数
        //初始化对象 当我们new创建一个对象的时候,回执行构造函数进行初始化

        public Animal(string name, int age, int num1)
        {
            //this指向当前创建的对象
            this.name = name;
            this.age = age;
            this.num1 = num1;
        }

        //字段
        public string name;
        public int age;
        public int num1 = 1;
        //属性
        public int num
        {
            //get set 代码块  功能:权限控制功能、拦截代码功能
            //注意:当存在get代码块,不存在set代码块的时候,只允许读取,不允许修改,相反也是这样
            //在get,set代码块中的时候我们可以进行拦截,实现对应的业务
            get
            {
                Console.WriteLine("只要属性被读取了,我就会被访问");
                return num1; //表示属性被访问的时候会执行该代码,同时会返回一个值
            }
            set
            {
                num1 = value;
                Console.WriteLine("新的值为{0}",value); //响应式数据
                //set表示当设置num的值的时候会执行,value表示新的值

            }
        }
        //方法
        public void Show()
        {
            Console.WriteLine("我叫{0}会打篮球,今年{1}岁", name, age);

        }

    }

初始化类的字段,new一个动物类实例,可以使用构造方法进行赋值也可以使用对象.字段名进行赋值,相对于赋值代码量而言,使用构造方法大大简化了代码量

    //1.有参构造函数进行对字段的赋值
            Animal dog = new Animal("旺财",18,123456);
            Animal cat = new Animal("猫咪",16,12345678);

            //2.对象.字段名进行赋值
           /* dog.name = "蔡徐坤";
            dog.age = 10;*/

            dog.Show();
            cat.Show();

在类中,有属性,get set代码块,暂时在项目中还不明白用意何在

            //get set 代码块  功能:权限控制功能、拦截代码功能
            //注意:当存在get代码块,不存在set代码块的时候,只允许读取,不允许修改,存在set代码块,不存在get代码块的时候,只允许修改,不允许访问
            //getset代码块中的时候我们可以进行拦截,实现对应的业务
            
            
            dog.num = 10; //修改属性,会执行set代码块
            Console.WriteLine(dog.num); //读取属性,会执行get代码块

二.类的继承 继承是面向对象程序设计中最重要的概念之一。继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得更容易。同时也有利于重用代码和节省开发时间。 当创建一个类时,程序员不需要完全重新编写新的数据成员和成员函数,只需要设计一个新的类,继承了已有的类的成员即可。这个已有的类被称为的基类,这个新的类被称为派生类。 继承的思想实现了 属于(IS-A)  关系。例如,哺乳动物 属于(IS-A)  动物,狗 属于(IS-A)  哺乳动物,因此狗 属于(IS-A)  动物。

    //继承:
    //创建一个基类:(父类)
    public class Enemy
    {
        public Enemy()
        {
            Console.WriteLine("父类的构造方法");

        }
        public int life;
        public virtual void move()  //规定虚方法
        {
            Console.WriteLine("父类的move方法");
        }

    }
    //派生类(子类)

    class Boss:Enemy
    {
        public Boss()
        {
            Console.WriteLine("子类的构造方法");

        }
        //虚方法 重写父类的方法
        public override void move()
        {
            Console.WriteLine("子类的move方法");
        }
    }


        Enemy enemy = new Enemy();
            enemy.move();
            Console.WriteLine("----------------------------");
            //若父类重写子类的方法
            Enemy enemy1 = new Boss();
            enemy1.move();
            Console.WriteLine("----------------------------");
            Boss boss = new Boss();
            boss.move();

image.png

注意:C# 中的虚方法

多态的实现方法之一(虚方法,抽象类,接口)

如果父类中的方法有默认的实现,并且父类需要被实例化,这时可以考虑将父类定义成一个普通的类,用虚方法来实现多态。

定义方法: 将父类的方法标记为虚方法,使用关键字virtual,标记后这个函数就可以被子类重新写一遍,在子类方法前加上override即可

三.不同模块之间的相互类引用

我们在解决方案中定义不同模块,模块中有不同的类;当我们模块中的类要引用另一个模块中的类的时候,我们需要进行引用。 1.首先右击需要被引用模块的类-添加-项目引用

image.png

2.填写对应的类的命名空间

image.png

注意点:在引用其他模块的类的时候,类必须使用public修饰,否则会出现访问权限不够,无法访问到类。

image.png

4.编写MyMath类,计算圆的周长、面积、球的体积

image.png

     public class MyMath
    {
       /* public MyMath(double PI)
        {
             this.PI = PI;
        }*/
        public const double PI = 3.1415926;

        //求圆的周长
       public static void getZhouChang(int r)
        {
            double zhouc = r * PI;
            Console.WriteLine("圆的周长为{0}", zhouc);
        }
        //求圆的面积
        public static void getArea(int r)
        {
            double area = r * r * PI;
            Console.WriteLine("圆的面积为{0}",area);
        }
        //求圆的体积
        public  void gettiji(int r)
        {
            double tiji = r * r *r * 0.75 * PI ;
            Console.WriteLine("球的体积为{0}", tiji);
        }
    }
    
            //使用静态方法,直接类名.访问
            MyMath.getZhouChang(n);
            MyMath.getArea(n);
            //调用非静态变量要使用实例化对象来调用
            MyMath my = new MyMath();
            my.gettiji(n);

四.里氏转换

当我们在定义一个Person类的时候,同时Teacher类继承Person的类,当我们new Teacher的实例的时候,想调用person独有的方法的话,这时候需要将new Teacher的实例转化为person的实例,可以类似于强制类型转化,采用(需要转化的类型)的方法进行转化

    public class Person
    {
        public void saidhello()
        {
            Console.WriteLine("你好");
        }
    }
    public class Teacher:Person
    {
        public void talk()
        {
            Console.WriteLine("上课上课");
        }
    }
    
    Person p = new Teacher();
            p.saidhello();
            Teacher t = new Teacher();
            t.talk();
            Person p2 = (Person)t;
            t.saidhello(); //里氏转换

结果如下:

image.png

五.new关键字的用法:

    在 C# 中,new 关键字可用作运算符、修饰符或约束。
            1new 运算符:用于创建对象和调用构造函数。
            2new 修饰符:在用作修饰符时,new 关键字可以显式隐藏从基类继承的成员。
            隐藏的后果就是子类调用不到父类的成员(根据遵循继承,子类继承父类的所有方法和属性,当子类对象调用new修饰的方法
            和对象的时候,会发现调用不到父类的成员方法,在父类和子类又相同名称方法的时候,子类调用的时候会自动隐藏父类的同名方法
            并且你不给子类的同名方法加new关键字的话,会发生警告)
            3new 约束:用于在泛型声明中约束可能用作类型参数的参数的类型。
    namespace new关键字
{
    internal class Program
    {
        public class Father
        {
            public string name;
            public int age;

            public void show()
            {
                Console.WriteLine("我是父类show");
            }
            public  void sayhello()
            {
                Console.WriteLine("我在父类sayhello");
            }
        }

        public class Son : Father
        {
            public new  string name;
            public new int age;

            public new void show()
            {
                Console.WriteLine("我是子类show");
            }
            public new void sayhello()
            {
                Console.WriteLine("我在子类sayhello");
            }
        }
        static void Main(string[] args)
        {
            Father f = new Father();
            f.sayhello();
            Son s = new Son();
            s.sayhello();
            Father f2 = new Son();
            f2.sayhello(); 
        }
    }
}

六.继承、重写方法、virtual、new、overrid汇总

    namespace demo1017
{
    internal class Program
    {
            /*
             public 公有访问。不受任何限制。
             private 私有访问。只限于本类成员访问,子类,实例都不能访问。
             protected 保护访问。只限于本类和子类访问,实例不能访问。
             internal 内部访问。只限于本项目内访问,其他不能访问。
             protected internal 内部保护访问。只限于本项目或是子类访问,其他不能访问
             */
        public class M
        {
            public M(int num)
            {
                this.num = num;
            }
            public int num; //没有初始化的时候,也有默认值
            static public int num2;

            public string str
            {
                get;set; //相对于没有函数体的函数 相对于字段
            }

           static public void getNum()
            {
                /*Console.WriteLine(num);*/
                Console.WriteLine(num2);
            }
            public virtual void show()
            {
                Console.WriteLine("1111父类");
            }
            //new 隐藏父类中的方法
            public new void heihei()
            {
                Console.WriteLine("heihei父类");
                Console.WriteLine("访问父类的{0}", num);
            }
            public void sayhello()
            {
                Console.WriteLine("我在父类说hello");
            }
            public  void lala()
            {
                Console.WriteLine("父类lala");
            }
            //静态类中只能有静态成员,非静态类中可以有静态成员或非静态成员
            //静态类要使用类名.方法名or属性名访问,非静态类要通过实例化对象进行访问

            
        }
        //创建一个子类(派生类)
        class N : M
        {
            public N(int num,string str) : base(num){
                this.str = str;
            }
            public string str;
            //重写父类方法的前提,必须是virtual、abstract修饰的才可以被重写
            public override void show()
            {
                /*base.show();*/ //base可以理解为就是父类
                Console.WriteLine("我重写了111");
            }
            public  void heihei()
            {
                
                Console.WriteLine("heihei子类");
            }
            public  void lala()
            {
                Console.WriteLine("子类lala");
            }
            public void enen()
            {
                Console.WriteLine("我在子类说嗯嗯");
            }
        }

        static void Main(string[] args)
        {
            //M是父类,N是子类
            M m1 = new M(10);
            m1.show();
            m1.heihei();
            Console.WriteLine("-------------------");

            M m2 = new N(10,"哈哈");
            m2.show(); //调用的是子类的方法,new的是子类
            m2.heihei(); //heihei父类

            //如果父类和子类都有单独的方法,这时候需要单独调用的时候,
            //这个根据前面声明的是什么类型的,变量类型调用对应父类和子类的方法
            //比如M m2中m2这个变量类型只包含sayhello()函数,没有enen()方法,enen在子类中
            //如果子类重写了父类的方法,这时候虽然是父类的变量,但是new的是子类,所以调用的还是子类的方法
            //比如子类N重写了父类M的show方法,在new N 的时候调用的是子类的show方法
            m2.sayhello();
            m2.lala();

            Console.WriteLine("-------------------");
            N n1 = new N(18, "嘿嘿");
            Console.WriteLine(n1.str);
            n1.enen();

            n1.show();
            n1.heihei();

        }
    }
}

七、里氏转化练习

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class test : MonoBehaviour
{
    // Start:由于Unity的特殊机制,Start会首先执行
    void Start()
    {
        // Animal animal = new Animal("某个动画", "我也不知道动物是咋叫的"); // 不允许实例化一个抽象类
        KejiDog daHuang = new KejiDog("大黄", "汪汪汪", "柯基");
        Cat xiaoBai = new Cat("小白", "喵喵喵");

        DoCry(xiaoBai);// 隐式转换,子类xiaoBai对象是Cat类型转父类Animal类型
        KejiMove(daHuang);  // 传递参数时依然是子类转父类
    }

    public void DoCry(Animal animal)
    {
        Debug.Log("开始叫!");
        animal.Cry();//形参虽然是父类,但是实参进来的子类,实际上父类的形参对象实际上调用的是子类的方法
    }

    public void KejiMove(Animal animal) // 此处约定传递过来的参数要么是Animal,要么是Animal的子类通过隐式转换传递过来
    {
        // 强制转换,会报错
        KejiDog keji = (KejiDog)animal; // 显式转换,父类转子类,有风险,要确保类型兼容
        KejiDog keji2 = animal as KejiDog; // 显式转换,父类转子类,有风险,要确保类型兼容
        keji.Move();

        //注意:因为animal对象中包含KejiDog的对象daHuang,这时候将父类Animal转化为子类Cat会报错,可能是因为中间
        //Cat cat = (Cat)animal;      // 类型不兼容会立刻报错
        //cat.Move();

        // as转换,不会报错
        //Cat keji2 =  animal as Cat; // 类型不兼容,不会立刻报错,但是返回的是null,也就是空数据
        //keji2.Move(); // 空数据是没有Move函数的,所以报错!
    }
}

// 父类/基类
// 抽象类
public abstract class Animal
{
    public string Name;
    public string CryString;

    public Animal(string name, string cryString)
    {
        Name = name;
        CryString = cryString;
    }

    // 虚函数
    // 1.提供默认逻辑,因为有一些子类不想要重写可以不重写
    // 2.提供可选的逻辑
    public virtual void Cry()
    {
        // 下列逻辑是可选的
        // 可选的:就是子类想要就要,不想要也可以不要
        Debug.Log($"{Name}张嘴了!");
    }

    // 抽象函数
    // 业务上存在飞翔、跑步、游泳等各种情况,我们并不知道具体的子类如何做实现
    // 没有函数体
    public abstract void Move();
}

// 子类/派生类
// 第二层,Dog也是抽象类
// 抽象类不需要实现其基类中的抽象函数(我也不知道我下面的子类要干嘛,我怎么给你实现?)
public abstract class Dog : Animal // 获的Animal的所有成员
{
    public string dogType;

    public Dog(string name, string cryString, string dogType) : base(name, cryString)
    {
        this.dogType = dogType;
        //注意:子类继承父类,并且子类和父类都有构造方法的时候,new 子类的时候也会调用父类的构造方法
        Debug.Log($"名字:{name},品种:{dogType}诞生了!"); 

    }

    // 重写
    // 重载:是同名函数,但是不同参数
    // 重写:是覆盖基类中的函数
    public override void Cry()
    {
        Debug.Log($"{Name}:开始蓄力张嘴!");
        base.Cry(); // 调用基类中的Cry函数,这个函数是没有被重写的
        Debug.Log($"{Name}:汪汪汪");
        Debug.Log($"小狗叫完还要跳一跳!");
    }

    // 重写的是基类中的抽象函数
    public override void Move()
    {
        Debug.Log("小狗使用四肢奔跑!");
    }
}

public class KejiDog : Dog
{
    // 他的构造函数约束是自身基类的
    public KejiDog(string name, string cryString, string dogType) : base(name, cryString, dogType)
    {
        Debug.Log("调完父类Dog才会执行子类KejiDog");
    }
    public override void Cry()
    {
        base.Cry();
        Debug.Log("抖了抖小短腿!");
    }
    public override void Move()
    {
        base.Move(); // 因为基类做了基本实现,所以基类中Move函数对我们来说相当于是一个虚函数了,而不是抽象函数
        Debug.Log("抖了抖科技翘臀");
    }
}


public class Cat : Animal
{
    public Cat(string name, string cryString) : base(name, cryString)
    {
    }

    public override void Cry()
    {
        //base.Cry(); // 调用基类中的Cry函数,这个函数是没有被重写的
        Debug.Log($"{Name}:喵喵喵");
        Debug.Log($"小猫叫完还要跑跑跑!");
        Debug.Log($"小猫叫完还要跑跑跑!");
        Debug.Log($"小猫叫完还要跑跑跑!");
    }

    // 重写的是基类中的抽象函数
    public override void Move()
    {
        Debug.Log("小猫上树了!");
    }


}

image.png