静态函数 抽象类 覆盖-重写-重载 委托-事件-匿名函数 密封 Lambda表达式

来源:互联网 发布:淘宝吊带碎花连衣裙 编辑:程序博客网 时间:2024/04/28 04:34

下面叙述以C#语言为例,其它语言也有参考价值

1、静态函数

   在类中定义静态函数,就不用实例化类了,直接用类名就可以访问了。在定义类的方法时,如果该方法与类的成员变量没有任何关系(也就是根本不会使用到类的成员变量),不对该类的成员变量进行读写,只是根据方法的参数进行操作,那么就应该将该方法声明为static方法,以节省资源(我自己不赞同)和提高执行速度(这点很关键)。比如常用的系统定义好的Math类中的方法一般都是静态的,因为它们都是对传入的参数进行操作然后返回一个运算结果,定义为静态的可以显著提高运行速度。Main方法也定义为静态的也是为了提高运行速度。静态的方法仅可以访问类中的静态变量不可以访问类中非静态的变量,但可以在方法内部临时定义变量并使用,也就是说static方法不可以访问方法外部定义好的变量(或对象),仅可以访问方法内部定义的变量或对象。

2、抽象类

  类中含有纯虚函数时,这个类就叫抽象类,抽象类不能实例化,子类只有实现了所有纯虚函数,才不是抽象类,就可以实例化了;抽象类就是为了表示抽象层次。

【C++中说带有纯虚函数的类称为抽象类,这里的纯虚函数就在java(c#)中就称为抽象方法,就是指没有实现体的方法。】

3、覆盖,重写,重载

基础概念:

方法的签名”包括返回类型、方法名、参数列表,这者共同标识了一个方法。
“声明方法”,即指出该方法的签名。“定义方法”,则是指定调用方法时执行的代码。
“同名方法”是指方法的签名相同的两个方法。
”重写”一个方法,意味着子类想继承父类对方法的声明,却想重新定义该方法。

“在C#里,在一个类中声明一个方法时,有四个和方法的继承性有关的关键字new、virtual、sealed、override
C#里,在一个类中声明一个方法时,有四个和方法的继承性有关的关键字:new、virtual、sealed、override。C#里,在一个类中声明一个方法时,有四个和方法的继承性有关的关键字:new、virtual、sealed、override。
C#里,在一个类中声明一个方法时,有四个和方法的继承性有关的关键字:new、virtual、sealed、override。C#里,在一个类中声明一个方法时,有四个和方法的继承性有关的关键字:new、virtual、sealed、override。C#里,在一个类中声明一个方法时,有四个和方法的继承性有关的关键字:new、virtual、sealed、override。C#里,在一个类中声明一个方法时,有四个和方法的继承性有关的关键字:new、virtual、sealed、override。C#里,在一个类中声明一个方法时,有四个和方法的继承性有关的关键字:new、virtual、sealed、override。

 在基类中,实现函数fun(),在子类中再次实现fun(),那么就是覆盖,但方法的签名(返回值类型及参数列表)必须相同。

 在基类中,实现函数fun(),在子类中再次实现fun,但是参数不同,这就是重载,要求方法的签名必须不同。

 基类中,实现虚函数virtual fun(),在子类中重写override实现fun(),那么就是重写, 但方法的签名(返回值类型及参数列表)必须相同。

 在c++中并没有严格区分覆盖和重写(没有专门关键字),实际上一般人都笼统的认为覆盖就是重写,方法的签名都必须一样,只不过对于覆盖或重写的基础上在使用虚方法时就可以实现动态联编。

【重载是静态联编,含有虚函数的覆盖或重写是动态联编,而不用虚函数的覆盖或重写根本就不存在联编的概念,也就是说此时编译器根本就不存在区分多个相同函数名的情况。】

 

 覆盖或重载时,基类对象只能访问基类函数,不能访问到子类的函数,即使把子类对象转换到基类对象,基类对 象访问的还是基类函数; 若是重载,那子类对象还是能访问到从基类继承下来的函数,若是覆盖,那么子类只能访问到自己的函数

 重写时,前提是基类的函数是虚函数 virtual,而子类函数用关键字override重写。这样如果把子类对象转换到基类对象,那么基类对象访问的是子类的函数,就实现了多态性。

举例:

/// <summary>
    /// 演示类的继承、多态、虚方法、抽象类、抽象方法、密封
    /// 总结:
    /// 0.父类中的方法会沿着继承链被继承,如E()
    /// 1.将子类赋值给父类,即使子类定义了同名方法,父类也会调用自己实现的方法,如F()
    /// 2.将子类赋值给父类,对于可以override的方法,父类会调用最后一次实现,如G()、H()
    /// 3.virtual方法可以被override,如G()
    /// 4.抽象类不能直接新建实例,但可以用子类赋值,如类A
    /// 5.抽象方法必须override并且只能定义在抽象类中,如A.H()
    /// 6.密封类不能被继承,如类C
    /// 7.密封方法可以被继承,如B.H()
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            B b = new B();
            A a = b;
            C c = new C();

            a.E();//A.E
            a.F();//A.F,调用类A的方法,虽然类B重新定义了F(),但同时也隐藏了继承了来自类A的F()
            a.G();//B.G, 因为方法被类B重写过
            a.H();//B.H, 因为b.H()是a.H()的最后一次实现
            b.E();//A.E, 从父类中继承的方法
            b.F();//B.F, 类B重新定义了F(),并隐藏从类A中继承了F()方法
            b.G();//B.G
            b.H();//B.H
            c.E();//A.E, 从父类中继承的方法
            c.F();//B.F
            c.G();//B.G
            c.H();//C.H, 类C中自定义的方法
            a = c;
            a.E();//A.E
            a.F();//A.F, 调用类A的方法,虽然类B重新定义了F(),但同时也隐藏了继承了来自类A的F()
            a.G();//B.G, 因为方法被类B重写过
            a.H();//B.H, 因为b.H()是a.H()的最后一次实现
            b = c;
            b.E();//A.E, 从父类中继承的方法
            b.F();//B.F, 从父类中继承的方法,虽然类B继承自类A,但类B重新定义了F()
            b.G();//B.G
            b.H();//B.H,
            Console.Read();
        }
    }

    abstract class A //abstract类不可以直接实例化,但可以将其子类赋值给它
    {
        public void E()
        {
            Console.WriteLine("A.E");
        }
        public void F()
        {
            Console.WriteLine("A.F");
        }
        public virtual void G() //virtual方法,子类可以重写
        {
            Console.WriteLine("A.G");
        }
        public abstract void H();//abstract没有实现,继承类必须自己实现
    }
    class B : A
    {
        new public void F() //new 重新定义F()并隐藏继承自类A的F(),可以用来处理子类中与父类方法同名的问题
        {
            Console.WriteLine("B.F");
        }
        public override void G() //重写父类的virtual方法
        {
            Console.WriteLine("B.G");
        }
        public sealed override void H()//实现父类定义的abstract方法并密封
        {
            Console.WriteLine("B.H");
        }
    }
    sealed class C : B
    {
        //public override void H() //被密封的方法不能重写
        //{
       
        //}
        new public void H() // new 重新定义H(),被密封的方法可以继承【语法上是继承,但可以认为没继承,子类定义了一个新方法,名字本质上都不一样了】
        {
            Console.WriteLine("C.H");
        }
    }
    //class D : C //密封类不能继承
    //{
   
    //}

【只有子类方法前加上new关键字,就表示我接下来要写的方法都具有“隐含的前缀名字”,也就不可能和父类任何方法重名,当然上面的理解并不正确却准确的反映了new关键字的功能,它就是表示和父类六亲不认,即断了继承链,完全隐藏了父类的某个方法,让子类完全看不到。以后就可以理解为用new声明的方法是方法名加了new前缀或一个匿名前缀,完全和父类同名的方法没有了任何关系(先理解为编译器级别底层两个方法名都不一样

4、委托-事件-匿名函数

 (1)委托是一种类型,使用时需要委托的实例。事件实际上就是一种委托的事例,只不过为了限制在内部使用在最前面加上了event关键字进行修饰。

下面语句定义了一个委托类型:

delegate void AnimalPlay(string name);

上面的语句和定义一个类的语句作用是一样的,都是定义了一种新的类型,只不过不像定义类那样还需要一个大括号来定义“类体”而已。

实例化委托类型语句如下:

AnimalPlay deleDogPlay=new AnimalPlay(DogPlay);     //DogPlay是一个函数名

从上面语句可以看出,实例化都要用new语句,只不过实例化类传递给构造函数的是一些参数,而实例化委托传递的是一个函数名而已。

使用委托的好处之一是可以实现以函数为参数,提高程序的通用性。

(2)匿名函数:不仅可以使用已有的函数(名)创建委托实例,而且可以直接使用匿名函数创建委托示例,如下语句

委托类型 委托事例=delegate(int , double y){................return xx; };

上面的语句没有使用new和一个函数名来实例化一个委托实例,而是直接将一个“代码块”定义成为一个委托,写法简便而且少用了一个函数名。另外,匿名函数有一个优点,即它不光可以使用代码块内定义的变量,而且可以使用代码块外定义的变量,且外层变量一旦被匿名函数捕获,则它的生命周期将延长至委托实例被销毁为止。【匿名函数就是一个内联的函数,即调用时不存在跳转,直接展开函数内部的所有语句并执行。匿名函数不需要明确定义返回值类型,系统会根据return语句自动推测返回值类型。其实也很好记忆:delegate关键字加上小括号再加上大括号,这个整体表达式就是定义了一个匿名函数,即内联函数,同时这个整体表达式返回一个引用这个函数函数的地址并可以直接赋给一个委托实例。或者就可以认为这个整体表达式就返回一个委托实例。】

(3)事件:事件是委托的实例。常用的系统事件(.Net内部帮我们定义好了的),它的类型声明(即对应的委托)如下:

public delegate void  EventHandler(object sender, EventArgs e);

比如常用的Click事件,它的实例化如下:

public event EventHandler Click=new EventHandler(某个函数名);

因为Control类中已经为我们定义好了Click事件,而所有的控件都继承于Control类,所以所有的控件都有Click事件。

另外,事件最前面使用event修饰是为了将事件的调用限制在事件所属的类的内部。比如不能在类的外部出现“对象.事件();”这样的调用语句,不过当然可以出现“对象.事件;”这样的非调用语句。比如,myButton.Click+=new System.EnentHandler(this.myButton_Click);

 

5、密封

以后再补。

 

6、Lambda表达式

Lambda表达式实际上是一个匿名函数。它包含表达式和语句,常用于创建委托或表达式目录树类型。所有Lambda表达式都是用Lambda运算符---------- =>,该运算符读为“goesto”。Lambda运算符的左边是输入参数(可能没有),右边是表达式或语句块。Lambda表达式返回右边表达式的结果。其基本格式如下:

(input paramenters)=>expression

其中,parameters是一个参数列表,在Lambda只有一个输入参数时可以不使用括号,否则括号是必须的。两个或更多输入参数由括在括号中的逗号分隔,如以下代码所示,包括两个参数x和y。

(x,y)=> x==y    这里参数由系统自动推测

通常Lambda表达式的参数都是可变类型的,由编译器自动确定它的具体类型。但有时编译器难于或无法推断输入类型,就需要为参数显示指定类型,既在参数之前添加参数类型。如下所示的Lambda表达式包括连个参数x和s。其中x是int类型,而s则是string类型。

(int x, string s) =>s.Length > x

当Lanbda表达式没有参数时,需要使用空的括号表示,如下所示。其中,“()”表示没有参数,而Amethod()是一个具体的方法,该方法的返回值就是Lambda表达式的结果。

() => AMethod()

由于Lambda表达式实际是匿名函数,它可以赋值到一个委托,而在IEnumerable<T>的方法中很多都通过函数委托来实现自定义的运算、条件等操作,所以Lambda表达式在Linq中被广泛使用。

使用范例如下:

范例中使用到了Array.FindAll方法,这个方法是Array类的静态方法。

结构:Array.FindAll(被检索的数组,检索条件)

【将数组中的元素一个个的传到检索条件中执行】若数组中所有元素都满足条件,那么返回该数组,否则返回空Array数组。其中检索条件可以是一个表达式,自定义函数名,匿名委托(函数)

 

staticvoidMain(string[] args)

{

    string[] arr = { "aaa","abc","acc"};

    string[] arr1 = Array.FindAll(arr, (s) => s.Contains("a"));

    foreach(stringsinarr1)

      {

        Console.WriteLine(s);

       }

    int[] arr2 = { 1, 2, 3, 4, 5 };

    int[] arr3 = Array.FindAll(arr2,delegate(intx) { returnx % 2 == 0; });

    Array.ForEach(arr3,delegate(intx) { Console.WriteLine(x); });

    string[] arr4 = Array.FindAll(arr1, (s) => { returns.Length > 2; });   //由于只有一个参数,所以s两边的括号都可以省略不写

    foreach(stringsinarr4)

       {

        Console.WriteLine(s);

       }

}

【从上面可以看出,Lambda表达式整体就可以看成一个匿名函数,即这个表达式整体返回一个匿名函数地址并可以直接赋给一个委托实例,而且使用起来更方便,因为它的参数类型都可以不用写,系统根据真正调用这个匿名函数并赋给实参时,自动推测出这个形参的类型,而且Lambda不用写return,它自动返回表达式的结果】