(02)基础强化:面向对象,变量作用域,封装,继承,虚方法,可访问性

    
一、面向对象概念复习


    1、什么是面向对象?OOP(Object-Oriented Programming)
        一种分析问题的方式,增强了程序的可扩展性。
        OOP面向对象编程
        OOA面向对象分析
        OOAD面向对象分析与设计(Object Orient Analysis Design)面向对象的分析和设计
        
        面向对象技术的优点:为能够构建与现实世界相对应的问题模型,并保持他们的结果,
            关系和行为模式。
        
        
    2、面向对象的三大特性:
        封闭,继承,多态。
        
        
        面向对象起源于生活,把生活中相同或相似的进行归类,总结,抽象出相同的特征,
        这样就形成了类,是人类分析问题和解决问题的一种方法。因此是先有实例后有类
        别。
        也即:先有对象后有类。
        
        因此解决问题时,也是先根据实际问题,分析异同,是否扩展,创建一个类,来解
            决问题中的实际问题(对象)

        
        创建了类和对象,会使得相关的数据、功能集中,它集合了字段属性方法。使得分
            析问题脉络清晰,同时还增强了问题的可扩展性。
    
    
        类是模具,创建对象的模具,抽象的。
            类是一种数据类型,用户自定义的数据类型。
            类的组成:字段,属性,方法,构造函数等
        
        
        对象是具体的,是类的具体实例。
            对象具有属性(特征),和方法(行为)
            
            
        类中包含了数据(用字段表示)和行为(用方法,函数,功能)来表示。
            方法为一块具有名称的代码。
    
    技巧:
        添加类文件(.cs)的方式:
        可以直接在主程序Program.cs中进行添加类,但不推荐,容易混乱,推荐单独建立一
        个类文件(.cs).
        
        vs2022的几种方式:
        1)右击当前项目->添加->新建项,选择“类”,填入名称即可创建。也可以在右上侧
            搜索中填入“类”,或窗体的左侧中选择“代码”,更快地过滤出类文件,便于
            选择。
        2)右击当前项目->添加->现有项,然后选择自己已经建立好的类文件。
        3)右击当前项目->添加->类,直接添加。
        
    
    技巧:关于类的代码片段(写上关键字后按Tab)
        class  加上Tab,创建类的代码(默认internal)
        ctor   类中创建构造函数
        prop   类中创建自动属性
        propfull  类中创建字段与属性的代码
        indexer   类中创建索引器
        
        
    3、构造函数:
        类中默认有一个隐形的无参构造函数。
        当手动创建一个构造函数后,原隐形无参函数会被覆盖而消失。
        
        构造函数一般为public否则不能创建对象,也有特殊情况时用private,通过曲线
        方式来创建对象。(比如类中方法可以调用私有的构造函数)
        
    练习:

        学生类的输入(结合上面的代码片段)

internal class Student//输入class后按Tab自动创建类,修改类名后按回车,光标进入类中
{
    public string Name { get; set; }//输入prop按Tab自动创建
    public int Age { get; set; }
    public string Gender { get; set; }
    public string SId { get; set; }

    public Student(string name, int age, string gender, string sid)//输入ctor按Tab,自动创建构造函数
    {
        this.Name = name;
        this.Age = age;
        this.Gender = gender;
        this.SId = sid;
    }

    public void SayHi()//输入"SayHi("后,按Shift+Enter,自动添加花括号并进入代码内
    {
        Console.WriteLine($"{Name}{SId}");//输入cw后按Tab自动输出命令
    }
}


    
    
    
    4、自动属性与手动字段+对应属性的区别
        两者基本相同。只不过自动属性的字段(隐形)由编译器自动生成,例如:
            private string <Name>k__BackingField;(用.Net Reflector反编译查看)
        每次编译器生成的字段可能不一致。
        
        因此,一般情况下两者可互换,但在序列化时,由于自动属性的字段由编译器自动
            生成,每次不一样,有可能产生问题。
所以推荐传统老式写法。
            
    
    5、Convert.ToInt32()与int.Parse(),int.TryParse()的区别
        对Convert.ToInt32()按F12查看反编译,可以里面有int.Parse():
        public static int ToInt32(string value)
        {
            if (value == null)
            {
                return 0;
            }

            return int.Parse(value, CultureInfo.CurrentCulture);
        }    
        
        说明Convert.ToInt32()内部调用了int.Parse(),因此比int.Parse()更完善。
        
            Convert.ToInt32()参数为null时,返回0;
            int.Parse()参数为null时,抛出异常。
            
            若参数为""时,上面两个都抛出异常。
            Convert.ToInt32()参数可以是字串、字节、字符等类型转多。
            int.Parse()只能转换数字类型的字符串。
            
            简单地说Convert.ToInt32()适应性更强。
            
            由于int.Parse()抛出异常时费资源,因此进行了改进为int.TryParse()
            这样就不会抛出异常。
            
            int.TryParse(s,out int result)成功返回true,结果在result中
                                          失败返回false,结果在result为0
                注意:result是一个"外部"变量,在语句外生效。

            string s = "123";
            bool n = int.TryParse(s, out int result);
            Console.WriteLine(Convert.ToInt32(n));//true,1


    
            例:

                int.TryParse("",out int result)//false,result=0
                int.TryParse(null,out int result)//false,result=0
                Convert.ToInt32("")//异常
                Convert.ToInt32(null)//0


                
            (int)属于cast转换,只能将其它数字转为int,比如double,float等,而不能
                转换字符串。如:

            int n = (int)2.4;   //正确
            int n1 = (int)"231";//错误,无法将string转为int


                
                
    6、bool转为整形是多少?

        Convert.ToInt32(true);//为1
        Convert.ToInt32(false);//为0


    
    


二、案例:计算器


    上课的计算器用的是winform,本次用控制台。无论哪一种,都会重写一遍代码。
    
    如果能够在winform与控制台,都能用。尽可能把功能进行封装,实现代码重用。
        因此使用类来进行封装计算器。
    
    创建一个计算类。先只添加一个+

        internal class Calculator
        {
            public Calculator(double n1, double n2)
            {
                this._number1 = n1;
                this._number2 = n2;
            }

            private double _number1;
            private double _number2;

            public double Number2
            {
                get { return _number2; }
                set { _number2 = value; }
            }

            public double Number1
            {
                get { return _number1; }
                set { _number1 = value; }
            }

            public double Add()
            {
                return _number1 + _number2;
            }
        }


    
        然后在winForm中可以调用这个对象的实例,用Add加法实现:

        private void button1_Click(object sender, EventArgs e)
        {

            double n1 = Convert.ToDouble(textBox1.Text);
            double n2 = Convert.ToDouble(textBox2.Text);
            Calculator c = new Calculator(n1, n2);
            switch (comboBox1.Text)
            {
                case "+":
                    label1.Text = c.Add().ToString();
                    break;
                default:
                    break;
            }

        }


    
        说明:
            每次添加一个运算,比如再添加一个减法,就会再次打开Calculator进行方法
        更新,再生成可执行文件。这样,每次操作源代码繁冗,是不可取的。
        
            一般,会将运算生成dll,更新时只须要更新dll,这样不会全部源代码打开进
        行重新生成可执行exe,即方便了更新升级程序,也方便功能的拆分,使得分工更
        为恰当。
    


三、案例:猜拳游戏


    问题:人和电脑分别出剪刀石头布,进行比较胜败。
    
    1、分析
        对猜拳进行数字化处理,设置布1,剪刀2,石头3
        两者相差的数进行归类总结:
            败:1-2=-1,2-3=-1,3-1=2,有两种-1和2为败.
            平:0为平
            胜:其余为败
            
    2、OOP分析:
        创建三个类。玩家,有拳(字串数据)和出拳(动作,返回int)
                    电脑,有拳(字串和整形)和出拳。
                    裁判,用一个静态方法进行判断
            细化:玩家与电脑类有构造函数,电脑构造时添加随机出拳。
                    对于属性只取Get,所以不用写set
                    
        界面:(5个label,3个button)
          label1:玩家,label2:玩家拳名,label3电脑,label4电脑拳,label5胜负平
        button1玩家布,button2玩剪刀,button3玩家石头
        
        玩家类:

        internal class Player
        {
            private string _fist;

            public string Fist
            {
                get { return _fist; }
            }

            public Player(string fist)
            {
                this._fist = fist;
            }

            public int ShowFist()
            {
                switch (_fist)
                {
                    case "布":
                        return 1;

                    case "剪刀":
                        return 2;

                    default:
                        return 3;//石头
                }
            }
        }


        
        电脑类:

        internal class Computer
        {
            private string _fist;
            private int _n;

            public string Fist
            {
                get { return _fist; }
            }

            public Computer()
            {
                Random r = new Random();
                _n = r.Next(1, 4);//随机出拳
                switch (_n)
                {
                    case 1:
                        _fist = "布";
                        break;

                    case 2:
                        _fist = "剪刀";
                        break;

                    default:
                        _fist = "石头";
                        break;
                }
            }

            public int ShowFist()
            {
                return _n;
            }
        }


        
        裁判类(判断胜负,静态)

        internal class Judgment
        {
            public static string Judge(Player player, Computer computer)
            {
                if (player.ShowFist() == computer.ShowFist())
                {
                    return "平";
                }
                else if (player.ShowFist() - computer.ShowFist() == -1 || player.ShowFist() - computer.ShowFist() == 2)
                {
                    return "败";
                }
                else
                {
                    return "胜";
                }
            }
        }


    
        事件,主要是三个按钮,由于代码类似,写成一个方法:

        private void button1_Click(object sender, EventArgs e)
        {
            GetJudge("布");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            GetJudge("剪刀");
        }

        private void button3_Click(object sender, EventArgs e)
        {
            GetJudge("石头");
        }

        private void GetJudge(string v)
        {
            Player p = new Player(v);
            Computer c = new Computer();

            label2.Text = v;//玩家拳
            label4.Text = c.Fist;//电脑拳

            label5.Text = Judgment.Judge(p, c);//结果
        }

    3、补充说明        
        
        1)sender与e是什么意思?
        
            很多控件的事件中都带有这两个参数,它们是什么意思?例如:
                private void button2_Click(object sender, EventArgs e)
                
        object sender 表示触发事件的对象(事件的激发控件),由什么控件被激发
                比如点击button1,button1监测到被点击了,它就触发点击事件,这个
                sender就是button1
                
                可以理解为委托:
                Delegate void Event(object o,EventArgs e)
                
                为什么事件类型用Object?
                因为Object是所有类型的基类。事件激发可能是Button类,可能是Label类
                可能是Picture类等,甚至还有可能是一个变量(后面委托监测激发),因此
                类型多样,所以用Object来代替.
                
        eventArge e 表示触发事件的相关信息,对象中的数据(包含事件参数)。比如:
                如果是单击事件,那么这个事件是左击还是右击,都包含在e中.
                如果是鼠标移动事件,那么鼠标移动的坐标包含在e中.
                如果是键盘按下事件,那么按的是哪个键包含在e中.
        
        2)DRY原则:(don't repeat yourself)不要重复你自己。
        
            如果有代码老是用Ctr+C和Ctr+V,说明代码重复量比较大,说明软件设计有问
        题。因为应该进行封装,或者再写一个方法传入参数,来减少代码的重复量。
        
        
        3)根据上面1)2),还可以优化一下:
        
            将三个button的事件关联到一起。
            选择button2,按F4调出属性,切换到事件,选择Click事件,在其右侧的下拉
            菜单中选择button1。同理,button3的点击也关联到button1中.
            
            这样三个键的事件就可以由button1一键激活。
            
            但事件得判断object sender是谁激活。因此改写button1的事件:

        private void button1_Click(object sender, EventArgs e)
        {
            Button b = (Button)sender;//里氏转换,父转子以以便后面取Text
            GetJudge(b.Text);
        }


            同时,屏蔽(注释)掉原来button2与button3的点击事件。
            
            这样button2与的button3的事件由于关联到button1中,当点击button2时,它
        会自动去激活button1的Click事件,其中的sender就是激发的控件(button2),通
        过转换,取得真实的控件b,从而将其属性Text转给方法。
        
        问题引申:
            不通过设置,而通过代码将button2与button3的点击事件关联到button1的点击
            事件?
        
        技巧:
            将重复的代码封装成一个方法:
            选中这些代码,右击选择->快速操作和重构(或者Alt+Shift+F10),然后回车
            修改方法名即可。
        
        
 


四、命名空间-添加引用-变量作用域


    1、命名空间
    
        添加引用(前提):添加程序集
        
        
        导入命名空间:    namespace(快捷键Alt+Shift+F10)
            
            
        为什么在另一个项目中建的类,添加引用后还是不能使用?
            类的访问修饰符默认:internal,改成public。
        
        
        参数与返回值
            参数的个数、类型与返回值没有任何半毛线关系。
            
            
        控制台应用程序中不要新建Form
        
        
        再次说明this的使用,通过this访问类的属性。this.Fist
            表示类中当前的这个对象。
    
    2、变量作用域
    
        变量作用域:离声明该变量最近的那对包含声明语句的大括号内部
        
        
        成员变量--直接属于某个类,作用域在该类内部。
            成员变量:在类中直接声明的变量叫做类的成员变量,类的成员变量声明以后
            可以不赋初值,因为会有默认值
            
            成员变量使用前如果不赋值,默认会有一个初始值,
            string->null,
            int->0,
            bool->false
            
            局部变量:在方法/函数体(如主函数)、或语句块(如For)中声明的变量。
            局部变量必须声明并赋值后,才能使用,否则报错。

            private static void Main(string[] args)
            {
                int x;
                x++;  //错误 x=x+1,使用了x
                x=10+1;//正确, x=11,没有使用x,只是赋值
                for (int i = 0; i < 10; i++)
                {
                    int n;
                    n++;//错误
                }
            }
            
            public void Show()
            {
                int n = 10;
                if (n > 5)
                {
                    int n =100;//已经声明n,不能重复声明
                }
            }


            
            
        类成员变量与局部变量是可以重名的,而且局变量优先成员变量。
            但局部变量不能重名如上例。
        
        判断下面是否错误:
        
        
        1)

        public int n;
        public static int m;

        private static void Main(string[] args)
        {
            Console.WriteLine(n);//错误,
        }


        非静态字段,必须赋初值。
        由于静态成员只能调用本范围外的静态成员。本范围内的不限于静态成员和局部
        变量。静态Main中的n,对其外是非静态成员,对其内是局部变量(未声明赋值)
        调用时会出错。
        
        详细如下:

        internal class Program
        {
            private static void Main(string[] args)
            {
                Person p = new Person();
                p.Show();//0
                Console.ReadKey();
            }
        }

        internal class Person
        {
            public int n;

            public void Show()
            {
                Console.WriteLine(n);
            }
        }


        
        
        
        2)

        public int n;
        public static int m;

        private static void Main(string[] args)
        {
            Console.WriteLine(m);//0
        }


        正确,静态字段int默认为0
        
        
        3)

        public int n;
        public static int m;

        private static void Main(string[] args)
        {
            int m = 3;
            Console.WriteLine(m);//3 局部变量优先
        }


        正确,局部变量优先成员变量,为3.
        
        
        4)

        public int n;
        public static int m;

        private static void Main(string[] args)
        {
            Console.WriteLine(m);
            int m = 3;
            Console.WriteLine(m);//3 局部变量优先
        }


        错误,因为声明了局部变量,则花括号内应是局部变量的范围,但在局部变量声明前
        又使用了m,所以报错。
    


五、面向对象(封装)


    1、什么是封装?
    
        遥控器刚出来的时候很神奇,点个按钮就能换台、改变音量、关电视。而我们使用
        遥控器的人不需要知道他是怎么实现的(只须知道每个按钮的功能即可)
        
        和遥控器类似,面向对象的封装就是把事件的状态和行为封装在类中。
        
        使用类的人不需要知道类内部是怎么实现的,只要调用其中的属性和方法实现功能
        就行。就如同使用遥控器,不需知道怎么控制,只要知道按钮能换台即可。
        
        
    2、为什么要封装?
            方便用户使用,代码重用,增加安全性,可扩展性。
        
    
    3、类和对象本身就是封装的体现。
    
        1)属性封装了字段(清洗数据);
        2)方法的多个参数封装成了一个对象;
        3)将一堆代码封闭到了一个方法中;
        4)将一些功能封装到了几个类中;
        5)将一些具有相同功能的代码封装到了一个程序集中(dll,exe),并且对外提供
            统一的访问接口(属性名,方法名等)。
    
        Person person = new Person("张三", 23) { Name = "曹操", Age = 40 };//对象的初始化器
        在调用初始化器时,会先调用构造函数,然后才执行初始化器。故Name最终为曹操
        


六、面向对象 继承


    1、继承是指类与类之间的关系。具体的继承于更抽象的。
        例如:车Vehicle
                    卡车Truck
                        轻型卡车
                        重型卡车
                    轿车Car
                        小轿车
                        面包车
    
    
    2、如何判断一个继承关系是否合理?
            子类 is a 父类
        
        卡车和轿车都是车,都有轮子、发动机。但卡车又能拉货,轿车能拉人
        
            
    3、Base Class 基类
        Parent Class 父类
        Derived Class 派生类
        Child Class   子类
        父类越向上越抽象,子类越向下越具体。
        
        所有的类都直接或间接的继承自object,查看IL代码(.Net Reflector)。
        
        任何类未显式写明继承时,都继承于Object。当A显式写明父类B时,则继承于当前
        的父类B,A不再直接继承于B。但是,当前的父类B又没显示写明继承,同理它也继承
        于object,所以实际上A再次继承了object的一些成员。object的显隐。
        
        类的单根继承性、传递性。
        
        
    
    4、为什么要继承?继承带给我们的好处?
        1)代码重用;
        2)多态
            子类继承父类的属性和方法,使创建子类变得很简单,实现了代码重用以及多态。
        
        3)LSP里氏替换原则:  子类可以赋值给父类。
                            已经存储子类A的父类,可以强制转为子类A。
                            (但已经存储子类A的父类,不可以强制转为子类B)
        例如:Student继承Person,Teacher继承Person.

        Person ps=new Studendt();
        Person pt=new Teacher();
        Student s1=(Student)ps;//正确
        Student s2=(Student)pt;//错误。存储同子类(student)的父类才能强制转换到student
        Person p=new Person();
        Student s3=(Student)p; //错误,此时p实际存储的是父类,而不是子类,这种情况
                                        不能父转子。


        
        里氏替换原则主要为了多态。    多态就是为了增加程序的可扩展性、灵活性。
        
        
        判断一个对象(实例)属于某个类:object is Class

        Student s=new Student()
        if(s is Student){...}


        
        
        代码重用代码冗余的区别:
            冗余是功能相同,把代码再次进行复制粘贴的过程,叫代码冗余。多了不好!
            重用是功能相同,不须复制粘贴直接再次用别人的代码,例如,老师继承于
        学生类,这样学生类中的姓名、年龄、ID,不必在老师类再进入输入,直接由继承
        实现代码重用。精简了很好!
        
        
        方法的重写override
        虚方法原理:虚方法表
        
        
    5、继承时构造函数的问题。
            构造函数不能被继承。所以有下面情况:
            
            子类调用本身构造函数时,一般情况下会默认先调用父类的无参构造函数。若
            父此时没有无参构造函数,则报错。解决方法:
                1)父类添加无参数构造函数
                2)子类构造函数指定父类的某一有参构造函数base.
                
            特殊情况两种:1)用this,表示调用本类其它构造函数。
                          2)用base,表示明确指明调用父类存在的构造函数。

        internal class Studend : Person
        {
            public string SId { get; set; }

            public Studend(string name, int age, string sid) : base(name, age)
            {
                this.SId = sid;
            }
        }


        上面Student构造函数中参数可以乱序,但在base(name,age)中参数必须严格按照
        父类指明的构造函数中的参数顺序来写。
        
        Student构造函数后面无base时,会先调用父类Person的无参构造函数(若无此
        函数则报错),添加base后,会先调用指定的父类Person对应的构造函数。
            所以,一般情况下子类是先调用了父类的base(),即无参构造函数。

        internal class Studend : Person
        {
            public string SId { get; set; }

            public Studend(string name, int age, string sid) : base()
            {//父类有无参构造函数时,也可以“刻意”调用base(),不会报错
                this.SId = sid;//Name与Age没有赋值,不会报错
            }
        }


        
        
        
    6、关键字
    
        1)protected
        
            只能在当前类的内部,以及所有子类的内部中使用;
            可以在类内部以及所有子类中 (内部) 访问。
            
            五种修饰符:internal,public,private,protected,internal protected
            
            internal 程序集,只要在同一个程序集(同一项目,同一exe,同一dll)中都
                可以访问,与namespace没有关系。
                同一程序集(项目)中可能有不同的命名空间,只要引用,就可使用访问
            到成员。
                但是,在不同的程序集(项目)中,既使引用了另一个项目,若其中
            的成员用的是interanl,那么引用后仍然无法使用到该成员。
            
            类中成员默认为private,同样结构内部成员默认也为private.
                接口内的成员默认是public,且不能添加任何访问修饰符。
            
            类本身不写修饰符则默认为internal,同样结构与接口也默认为internal.
                命名空间下的成员修饰符只能是internal或public,比如类。
                注意微软自己可以用private,与我们程序员无关,可查看IL。
            
            
        2)this
        
            1.作为当前类的对象,可以调用类中的成员。this.成员(调用成员,自己)
            2.调用本类的其他构造函数。this()(调用构造函数,自己) reflector查看

        internal class Studend : Person
        {
            public string SId { get; set; }

            public Studend(string name, int age, string sid) : base(name, age)///3
            {
                this.SId = sid;
            }

            public Studend(string name) : this(name, 22, "1111")//2
            {
            }

            public Studend() : this("秦始皇")//1
            {
            }
        }


            this调用本类的构造函数时,应比当前的构造函数更为全面。例如上面2处比1
        更全面,所以this调用的是2处。同理3比2更全面,所以this调用的是3处。
            因此上面若调用:
                Studend s = new Studend();
            那么先执行1处,再跳到2处执行,但2处又要调用3处,3处又要调用父类构造
        这样,回来时从父类->3->2->1,完成了上面的过程。所以Age的值是22。
            结论:
                用this调用本类构造函数,应是更全面的构造函数。
                但是,不是把所有字段参数全部写完,可以写部分,如2处。
            
            
        3)base
        
            1.调用父类中的成员(在子类重写父类成员,或者子类使用new关键字隐藏了父
                类成员时,调用父类成员) ,base点不出子类独有成员。
            2.调用父类中的构造函数(调用构造函数,父类)
                当调用从父类中继承过来的成员的时候,如果子类没有重写则:
                    this.成员 与 base.成员:  两者没有区别。
                如果子类重写了父类成员,则:
                    this.成员:调用的是子类重写以后的。
                    base.成员;调用的依然是父类的成员。
                    

        internal class Program
        {
            private static void Main(string[] args)
            {
                Student s = new Student("宋江", 52);//6
                s.Show();//7
                Console.ReadKey();
            }
        }

        internal class Person
        {
            public string Name { get; set; }
            public int Age { get; set; }

            public Person()//1
            {
                this.Name = "李世明";
            }
        }

        internal class Student : Person
        {
            public string SId { get; set; }

            public Student(string name, int age)//2
            {
                this.Name = name;//3
                this.Age = age;
            }

            public void Show()
            {
                Console.WriteLine(this.Name);//4
                Console.WriteLine(base.Name);//5
            }
        }


        
        说明:主函数6处创建对象时,调用Student中2处构造函数,2处会先调用父类Person
            中1处的构造函数,给Name赋值“李世明”,由于继承,此时父类的base.Name与子
            类Student中的this.Name都是“李世明”。
                父类构造函数调用完后,回到子类Student中的构造函数2处,执行方法体内
            3处的赋值,即this.Name="宋江",同时这个this.Name与base.Name是相同的。所
            以,子类与父类的Name再次赋值为宋江。所以调用主函数中7处时,其对应Student
            类中方法Show()内,4处与5处显示宋江。
            
            实际上,子类内部部分根据父类模板,在子类内部创建了父类的成员(继承):
          

 


            因此主函数6处实际上只是创建了一个对象(不是两个),其中子类调用的是复制
            到子类内部的父类成员,所有这些都是在子类内部。
                因此this是所有子类的成员,包括上部分的父类模板创建的成员。
                而base则只能是由父类模板在子类内部创建的成员,上部分黑色部分。
                
                此时,原父类中的成员用this与base都是指向同一个。因此两者相等。
                
            但是,如果子类中有意用new隐藏父类中成员,使得原this指向上部分base的转向
            改为指向子类“自己”的下部分中同名成员,这时base与this的指向就变得不一样
            了。如图:
          

 


 

        internal class Program
        {
            private static void Main(string[] args)
            {
                Student s = new Student("宋江", 52);//6
                s.Show();//7
                Console.ReadKey();
            }
        }

        internal class Person
        {
            public string Name { get; set; }
            public int Age { get; set; }

            public Person()//1
            {
                this.Name = "李世明";
            }
        }

        internal class Student : Person
        {
            public string SId { get; set; }
            public new string Name { get; set; }//8

            public Student(string name, int age)//2
            {
                this.Name = name;//3
                this.Age = age;
            }

            public void Show()
            {
                Console.WriteLine(this.Name);//4
                Console.WriteLine(base.Name);//5
            }
        }


            主函数执行到6处时转到2处,又会调用父类1处,因此base.Name就成了李世明
        本来此时子类中this.Name也应该是李世明,但因为New string Name刻意隐藏父类
        成员,也即this.Name原来指向base.Name,却改指向了New string Name了。
            由1处返回到子类3处执行,这时的Name就New Name即宋江了。
            可以看到,一旦用New进行改写有意隐藏父类同名成员,那么对于原来父类模板
        创建的成员this与base指向了不同。
            因此:this.Name是宋江,base.Name是李世明。
            
            
        4)子类构造函数必须指明调用父类哪个构造函数    
            
            
    7、练习: 
        1)定义父亲类Father(姓lastName,财产property,血型bloodType),儿子Son类(玩
        游戏PlayGame方法),女儿Daughter类(跳舞Dance方法),调用父类构造函数(:base()
        )给子类字段赋值

        internal class Program
        {
            private static void Main(string[] args)
            {
                Son s = new Son("李", 2345678.2, "A");
                s.PlayGame();
                Daughter d = new Daughter("张", 2345511, "O");
                d.Dance();
                Console.ReadKey();
            }
        }

        internal class Father
        {
            public string LastName { get; set; }
            public double Property { get; set; }
            public string BloodType { get; set; }

            public Father(string lastname, double property, string bloodtype)
            {
                this.LastName = lastname;
                this.Property = property;
                this.BloodType = bloodtype;
            }
        }

        internal class Son : Father
        {
            public Son(string lastname, double property, string bloodtype) : base(lastname, property, bloodtype)
            {
            }

            public void PlayGame()
            {
                Console.WriteLine("game");
            }
        }

        internal class Daughter : Father
        {
            public Daughter(string lastname, double property, string bloodtype) : base(lastname, property, bloodtype)
            {
            }

            public void Dance()
            {
                Console.WriteLine("dance");
            }
        }


        
        
        2)定义汽车类Vehicle属性 (brand(品牌),color(颜色) ) 方法run,子类卡车
        (Truck) 属性weight载重 方法拉货,轿车(Car)属性passenger载客数量 方法载
        客

        internal class Vehicle
        {
            public string Brand { get; set; }
            public string Color { get; set; }

            public void Run()
            {
                Console.WriteLine("running");
            }
        }

        internal class Truck : Vehicle
        {
            public double Weight { get; set; }

            public void LaHuo()
            {
                Console.WriteLine("拉货");
            }
        }

        internal class Car : Vehicle
        {
            public int Passenger { get; set; }

            public void ZhaiKe()
            {
                Console.WriteLine("载客");
            }
        }


        
        
        
        3)升级猜拳游戏(加入父类[继承] )
        在玩家与电脑出拳上进行归纳总结。总结到父类有拳名(fistname,剪刀石头布)和
        拳的数据化(intfist)。字段只需protected,属性只须Get。
        因此从玩家与电脑中提炼父类,其它的都不变化。

        internal class Fist
        {
            protected int _intfist;
            protected string _fistname;

            public int IntFist
            {
                get { return _intfist; }
            }

            public string FistName
            {
                get { return _fistname; }
            }
        }

        internal class Player : Fist
        {
            public Player(string fistname)
            {
                switch (fistname)
                {
                    case "布":
                        _intfist = 1;
                        break;

                    case "剪刀":
                        _intfist = 2;
                        break;

                    default:
                        _intfist = 3;
                        break;
                }
                _fistname = fistname;
            }

            public int ShowFist()
            {
                return _intfist;
            }
        }

        internal class Computer : Fist
        {
            public Computer()
            {
                Random r = new Random();
                _intfist = r.Next(1, 4);
                switch (_intfist)
                {
                    case 1:
                        _fistname = "布";
                        break;

                    case 2:
                        _fistname = "剪刀";
                        break;

                    default:
                        _fistname = "石头";
                        break;
                }
            }

            public int ShowFist()
            {
                return _intfist;
            }
        }


        
        
        注意: 
            当this与base作为调用构造函数的语法的时候,参数的传递可能会有的疑惑。
            :base(参数,参数)对应指定的父类构造函数时的参数须类型、顺序一致。
            
            

 
七、可访问性不一致问题(修改成员的访问修饰符)


    1、访问级别约束
    
        1)子类的访问级别不能比父类的高。 (会暴露父类的成员)
 

        internal class Fist
        {
        }

        public class Player : Fist//错误,
        {
        }


        上面发生错误:可访问性不一致:基类Fist的可访问性低于子类Player.
        原因:父类原意限制自己成员仅在同一程序集才能访问,子类Player继承父类这些
            成员后,因其为public,所以在任意程序集都可“使用展现”原来父类的成员,
            导致父类成员“暴露”在另一程序集中。
            
            
        2)类中属性或字段的访问级别不能比所对应的类型访问级别高。
 

        internal class Person
        {
            public string Name { get; set; }
        }

        public class Myclass
        {
            public void SayHi(Person person) //错误
            {
                Console.WriteLine(person.Name);
            }
        }


        提示:可访问性不一致,参数类型Person的可访问性低于(public)SayHi().
        原因:(public)SayHi()可以在任意程序集中进行访问,但是它的参数person是
            internal也即只能在程序集内进行访问,两者访问性不一致,person拖了这
            个方法的“后脚”,这样导致,在程序集外访问时可能因person而发生错误。
            
            方法的访问修饰符需要与,方法的参数类型的访问修饰符一致。
            
        注意:即使上面方法public void SayHi(Person person)把public改protected一样
            会报错。这和1)的原因一样,protected原意思只想类及子类中用,但参数person
            的public,有可能使得原来protected中的“数据”,通过public而泄露。
            
            因此,必须保持一致!是一模一样!!!
            
        对比下面:

        internal class Person
        {
            public string Name { get; set; }
        }

        internal class Myclass
        {
            public void SayHi(Person person) 
            {
                Console.WriteLine(person.Name);
            }
        }


        提示:这里person是internal,其方法前面是public,两者类型不一致,似乎应该是
            错误的。但是,因为SayHI()方法的类是internal,也即SayHI虽写public,但
            因类internal的限制,始终跳不出如来佛的手心internal,public实际不起作用,
            相当于是internal,与参数person的internal是一致的,所以正确!!!
        
        
        3)方法的访问级别不能比方法的参数和返回值的访问级别高。
 

        internal class Person
        {
        }

        public class Myclass
        {
            public Person GetPerson()   //error
            {
                return new Person();
            }
        }


        提示:可访问性不一致,返回类型Person的可访问性低于方法(Public)GetPerson()
        原因:同2)原因一样,返回参数Person是Internal拖了方法GetPerson中public的后
                脚,导致在程序集外该public的方法却不能执行。
            

        internal class Person
        {
        }

        public class Myclass
        {
            public Person Friend { get; set; }
        }


        
        因为在编译器最终属性也是编译成方法,因此上面实际上也是public方法却返回了一
        个internal的返回值。因此也是错误的。
        
        结论:
            方法的参数与方法的返回值都必须得和方法保持一致的访问修饰符。门当户对!!
        
    2、用于解决“可访问性不一致”的错误
        修改访问修饰符,保持两者一致。门当户对即可。
    


八、实现多态的手段


    1、通过虚方法virtual,实现方法重写-多态
        
        多态:同样的方法,因里面实际对象的不同而调用不同的方法出现不同结果。
        Person-Chinese-American-Korean,每个国家的人都有一个说出自己国籍的方法。
        当有一个Person[]的时候,循环现实每个国家的人。

        internal class Program
        {
            private static void Main(string[] args)
            {
                Person[] p = new Person[] { new Chinese(), new American(), new Japanese() };
                for (int i = 0; i < p.Length; i++)
                {
                    p[i].SayNationality();
                }
                Console.ReadKey();
            }
        }

        internal class Person
        {
            public string Name { get; set; }

            public virtual void SayNationality()
            {
                Console.WriteLine("person");
            }
        }

        internal class Chinese : Person
        {
            public override void SayNationality()
            {
                Console.WriteLine("CN");
            }
        }

        internal class American : Person
        {
            public override void SayNationality()
            {
                Console.WriteLine("USA");
            }
        }

        internal class Japanese : Person
        {
            public override void SayNationality()
            {
                Console.WriteLine("JP");
            }
        }


    
        创建一个对象,其实对象的内部有很多内容:同步索引块,方法表,元(元数据)
        虚方法大概情况:
      

 



        
        所有类,都有字段与方法,创建实例的内存中的方法指向一个方法表,表中对应
        着每一个应执行的方法。
            当虚方法继承后,子类要进行重写。子类同样对应方法表,除了父类的还有
        子类本身的。对于已经重写的虚方法,后面会再嵌入一个Method Slot Table方法
        槽表。
            原本指向SayNationality(父类的方法)因为重写,会在后面槽表中记录重
        写后的方法。因此,它不会按棕色箭头执行,再是按红色箭头执行方法槽表中的
        方法。
            没有重写的,则按前面的方法表对应进行执行。
            因此,重写后由父类继承来的方法,若在子类用base指定方法(父类的)与用
        this指定的(已经重写,此时this发生改变指向子类重写的),两者不再一样。
        
    
        属性也可以重写(因为属性也方法set,get),故可以用虚属性。
        
        虚方法可以给父类中的方法一个实现,比如ToString()方法
        
        虚方法必须有实现部分,哪怕是空实现
    
    2、练习
        
        1)员工类、部门经理类(部门经理也是员工,所以要继承自员工类)。员工有上班
            打卡的方法。用类来模拟。

        internal class Program
        {
            private static void Main(string[] args)
            {
                Employee emp = new Manager() { Name = "黄勃" };
                emp.Daka();
                Console.ReadKey();
            }
        }

        internal class Employee
        {
            public string Name { get; set; }

            public virtual void Daka()
            {
                Console.WriteLine("9点打卡");
            }
        }

        internal class Manager : Employee
        {
            public override void Daka()
            {
                Console.WriteLine("不用打卡");
            }
        }


            
    
        2)把Person类中的SayHello改为虚方法让其默认为学生的,增加老师、司机类。

        internal class Program
        {
            private static void Main(string[] args)
            {
                Person[] p = new Person[] { new Teacher(), new Driver() };
                p[0].SayHello();
                p[1].SayHello();
                Console.ReadKey();
            }
        }

        internal class Person
        {
            public virtual void SayHello()
            {
                Console.WriteLine("人类");
            }
        }

        internal class Teacher : Person
        {
            public override void SayHello()
            {
                Console.WriteLine("教师");
            }
        }

        internal class Driver : Person
        {
            public override void SayHello()
            {
                Console.WriteLine("司机");
            }
        }


        
        
    3、虚方法和抽象方法的区别。
    
        虚方法必须有实现,抽象方法必须没有实现;
        抽象方法必须在抽象类中声明,虚方法可以出现在抽象类中;
        抽象方法必须在子类中重写,虚方法可以被重写。
    
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/7308.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis管道(pipeline)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言1、管道(pipeline)的基本概念2、管道实操3、小总结前言 在正式讲解Redis管道之前&#xff0c;先引入一个面试题&#xff1a; 如何优化频繁命令往返造成的性能瓶…

【Hello Linux】线程控制

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;简单介绍linux中的线程控制 线程控制线程创建线程等待线程终止线程分离线程id和进程地址空间布局线程创建 我们可以通过下面pthread_c…

蓝桥杯基础14:BASIC-1试题 闰年判断

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 给定一个年份&#xff0c;判断这一年是不是闰年。 当以下情况之一满足时&#xff0c;这一年是闰年&#xff1a; 1. 年份…

Java面向对象 - 封装、继承和多态的综合练习(答案+知识点总结)第1关:封装、继承和多态进阶(一)+ 第2关:封装、继承和多态进阶(二)

目录 第1关&#xff1a;封装、继承和多态进阶&#xff08;一&#xff09; 报错总结 & 注意事项&#xff1a; 第2关&#xff1a;封装、继承和多态进阶&#xff08;二&#xff09; 源码&#xff1a; 报错总结 & 注意事项&#xff1a; 思维导图免费制作网站&#xf…

软考软件设计师下午试题一

数据流图基本图形元素 外部实体 外部系统是当前系统之外的系统 数据存储 在里面存储数据后还能取出来用 跟实体没有关系&#xff0c;他负责存储加工的数据或者提供数据给加工 加工 灰洞的解释比如输入需要两个才能得到输出的&#xff0c;但是他只输入了一个就是灰洞 数…

Matlab傅里叶级数展开(附结果图)

Matlab傅里叶级数展开&#xff08;附结果图&#xff09; 代码下载链接 代码下载链接 代码下载链接 如下图所示&#xff1a;

“唯一靶点”的华堂宁会成控糖爆品吗?

一上市&#xff0c;两次“断货”的货华堂宁有爆品那味儿了。 2022年10月28日华领医药-B&#xff08;02552.HK&#xff09;公告华堂宁&#xff08;多格列艾汀&#xff09;正式进入商业化&#xff0c;一周后各个渠道便进入到了断货和限售的状态。 对于一个不在传统九大降糖药品…

元宇宙与网络安全

元宇宙是一种虚拟现实空间&#xff0c;用户可以在计算机生成的环境中进行互动。元宇宙的应用范围很广&#xff0c;比如房地产&#xff0c;医疗&#xff0c;教育&#xff0c;军事&#xff0c;游戏等等。它提供了更具沉浸感的体验&#xff0c;更好地现实生活整合&#xff0c;以及…

组件、套件、 中间件、插件

组件、套件、 中间件、插件 组件 位于框架最底层&#xff0c;是由重复的代码提取出来合并而成。组件的本质&#xff0c;是一件产品&#xff0c;独立性很强&#xff0c;组件的核心&#xff0c;是复用&#xff0c;与其它功能又有强依赖关系。 模块 在中台产品和非中台产品中&…

C语言程序环境和预处理

文章目录程序的翻译环境和执行环境详解编译和链接翻译环境编译本身也分为几个阶段预处理编译汇编链接段表符号表的合并预处理详解预定义符号#define#define 定义标识符#define定义宏#define替换规则#和#### 的作用带副作用的宏参数宏和参数的对比宏和函数的一个对比命名约定#un…

FastestDet:比yolov-fastest更快!更强!全新设计的超实时Anchor-free目标检测算法

本篇文章转自于知乎——qiuqiuqiu,主要设计了一个新颖的轻量级网络! 代码地址:https://github.com/dog-qiuqiu/FastestDet 1 概述 FastestDet是设计用来接替yolo-fastest系列算法,相比于业界已有的轻量级目标检测算法如yolov5n, yolox-nano, nanoDet, pp-yolo-tiny, Fast…

CSS基础知识,必须掌握!!!

CSS基础知识Background&#xff08;背景&#xff09;CSS文本格式文本颜色文本对齐格式文本修饰文本缩进CSS中的字体字体样式字体大小CSS链接&#xff08;link&#xff09;CSS列表不同列表标项CSS列表项用图片作为标记CSS列表标记项位置CSS中表格&#xff08;table&#xff09;表…

Shell脚本之嵌套循环与中断跳出

1、双重循环 1.1 格式 #!/bin/bash for ((i9;i>1;i--)) do for ((j9;j>$i;j--)) do echo -n -e "$j$i$[$i*$j]\t" done echo done1.2 实例操作 2.1 格式 #!/bin/bash for ((a1;a<9;a)) dofor ((b9;b>a;b--))doecho -n " "donefor((c1;c<…

系统信息:uname,sysinfo,gethostname,sysconf

且欲近寻彭泽宰&#xff0c;陶然共醉菊花怀。 文章目录系统信息系统标识 unamesysinfo 函数gethostname 函数sysconf()函数系统信息 系统标识 uname 系统调用 uname()用于获取有关当前操作系统内核的名称和信息&#xff0c;函数原型如下所示&#xff08;可通过"man 2 un…

面向对象编程(基础)7:再谈方法(重载)

目录 7.1 方法的重载&#xff08;overload&#xff09; 7.1.1 概念及特点 7.1.2 示例 举例1&#xff1a; 举例2&#xff1a; 举例3&#xff1a;方法的重载和返回值类型无关 7.1.3 练习 **练习1&#xff1a;** 练习2&#xff1a;编写程序&#xff0c;定义三个重载方法并…

如何大批量扫描的发票进行ocr识别导出Excel表格和WPS表格

OCR技术&#xff1a;OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;是将数字图像中的文字识别成字符代码的技术&#xff0c;在发票识别中应用广泛。通过OCR技术&#xff0c;可以将图片发票上的信息识别出来&#xff0c;并导出到Excel表格中…

3年测试越来越迷茫... 技术跟不上接下来是不是要被淘汰了?

这两天和朋友聊到了软件测试的发展&#xff1a;这一行的变化确实蛮大&#xff0c;从开始最基础的功能测试&#xff0c;到现在自动化、性能、安全乃至于以后可能出现的大数据测试、AI测试岗位需求逐渐增多。我也在软件测试这行摸爬滚打有些日子了&#xff0c;正好有朋友问我&…

晶振01——晶振分类和无源晶振的设计

晶振 晶振相当于人的心脏&#xff0c;能跳动&#xff0c;整个系统才是“活的”。 晶振常见有有源晶振、无源晶振。 有源晶振比较贵&#xff0c;但是需要外围电路少&#xff0c;供个电就能工作。 无源晶振价格便宜&#xff0c;匹配电路复杂些。 以无源晶振进行分析&#xff0c…

WCF手麻系统源码,手术室麻醉临床系统源代码,商业源码 有演示

手麻系统源码 手术麻醉系统源码 手术室麻醉临床信息系统源码 商业级源码&#xff0c;有演示&#xff0c;三甲医院临床应用多年&#xff0c;系统稳定。 文末获取联系&#xff01; 技术架构&#xff1a;C# .net 桌面软件 C/S版&#xff0c;前后端分离&#xff0c;仓储模式 开发语…

2.5.3 乘法

这段话告诉我们&#xff0c;在程序中有一条乘法运算语句。这个程序会让计算机帮助我们完成一个简单的数学问题&#xff1a;计算6乘以2。和我们平常做数学题一样&#xff0c;程序使用*号表示乘法运算。语句 “feet 6 * fathoms;” 可以这样理解&#xff1a;它会找到之前我们定义…