whyyouhitme_
2018-05-27 16:04
采纳率: 65.7%
浏览 752
已采纳

3个C#中接口的多态性的问题。

图片说明
图片说明
图片说明
图1是关于接口多态性的讲述。有点看天书的感觉
图2是课后习题,习题4和5是关联的。其中题目5:“接受两个杯子对象中的任意一个”怎么理解?
图3是习题答案,其中参数列表中的HotDrink drink为什么是杯子对象?如果“Hotdrink drink”是两个杯子对象中的任意一个,按照题5中要求的“接受两个杯子对象中的任意一个”,那么另一个杯子对象是什么?(这个问题确实没水平,我还没get到C#的点)
另外,书中说“HotDrink drink不支持ICup 接口,但我们知道传
送给这个函数的两个cup 对象支持ICup 接口”
为什么我感觉它说的“传送给函数的两个cup对象”是HotDrink的两个派生类Cow和Chicken呢?(根据题目4的答案猜的)但是“传送给函数的两个cup对象(答案中表现为HotDrink drink)”和“派生类Cow、Chicken”有什么关系呢?HotDrink drink不是基类HotDrink的实例吗?而Cow、Chicken不是基类HotdDrink的派生类吗?这两个沾边吗?

  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

9条回答 默认 最新

  • threenewbee 2018-05-27 16:25
    已采纳
     这书编写的看似深入浅出,但是其实一点用没有,我们要知道,编程语言是一种工具,目的是为了更好地编写软件,而不是徒增概念去模拟现实世界。
    
    那么接口有什么用呢?
    
    比如说,我们要写一个通用的排序算法,注意通用两个字。比如说,我们的排序算法既要能对Room对象(给出房子长宽)的面积排序,也要能对Student对象(给出学生的姓名)按照名字排序。
    或者还是这个Student对象,我们按照成绩排序。
    我们发现,问题的关键在于,排序的依据每次都是不同的,不能写死在排序算法里,否则,这个排序算法通用不起来了。
    
    那怎么办呢,我们可以用接口。定义一个IComparer接口(实际上这个接口.net已经有了),这个接口有Compare方法。
    我们可以写出如下排序代码(比如最简单的,冒泡排序)
    void sort(object[] arr, IComparer c)
    {
    for (int i = 0; i < arr.Count -1; i++)
    for (int j = 1; j < arr.Count; j++)
    {
    if (c.Compare(arr[j] , arr[j - 1]) < 0)
    { object t = arr[j]; arr[j] = arr[j - 1]; arr[j - 1] = t; }
    }
    }
    注意看这个代码,我们调用接口去比较两个对象,而不是直接比较,那么比较的逻辑就独立于排序算法了。
    
    因此我们要排序学生的名字,我们只要传入一个实现了比较学生名字的比较对象,就可以了。
    class MyComparer : IComparer
    {
    public int Compare(object a, object b)
    {
    return (Student)a.Name.Compare((Student)b.Name);
    }
    }
    然后
    object[] arr = { stu1, stu2, stu3 };
    sort(arr, new MyComparer());
    如果要按照年纪排序呢,我们再定义一个类,或者修改上面的代码,用
    return (Student)a.Age - (Student)b.Age;
    
    而sort的代码,是不是就不用修改了?
    
    已采纳该答案
    打赏 评论
  • threenewbee 2018-05-27 16:30

    按照你的红框里的代码
    sort里不通过接口c,而是直接用具体的类型调用sort

    void sort(object[] arr, MyComparer c) //这里不用接口
    {
    for (int i = 0; i < arr.Count -1; i++)
    for (int j = 1; j < arr.Count; j++)
    {
    if (c.Compare(arr[j] , arr[j - 1]) < 0)
    { object t = arr[j]; arr[j] = arr[j - 1]; arr[j - 1] = t; }
    }
    }

    这代码就不通用了,为什么呢,因为显然要先编写出MyComparer,才能编写sort代码,
    sort代码是一个通用的(比如放在系统库里的),可以直接拿来用的函数,那么它显然是先写好的,显然是不能反过来依赖你调用者写好MyComparer它才能用。或者说不能每次排序依据修改了,就把sort重新写一次,这个能理解吧。

    打赏 评论
  • threenewbee 2018-05-27 16:34

    下面再看你的杯子任意一个

    我还是接着上面说,我再写一个年龄排序的
    class MyAgeComparer : IComparer
    {
    public int Compare(object a, object b)
    {
    return (Student)a.Age - (Student)b.Age;
    }
    }
    加上前面的
    class MyComparer : IComparer
    {
    public int Compare(object a, object b)
    {
    return (Student)a.Name.Compare((Student)b.Name);
    }
    }
    是不是有两个实现了IComparer的类(MyComparer和MyAgeComparer)

    那么显然,sort可以接受任何一个,而且你传哪个,就按照哪个定义的排序规则排序。
    比如
    object[] arr = { stu1, stu2, stu3 };
    sort(arr, new MyComparer());
    此时arr按照名字排序
    sort(arr, new MyAgeComparer());
    此时arr按照年纪排序

    打赏 评论
  • threenewbee 2018-05-27 16:38

    下面那个问题不用回答了吧,两个选一个,两个不需要同时存在。好比
    sort(arr, new MyAgeComparer());
    就行了,不关MyComparer的事

    反正你不用管什么cup、drink了,你需要明白的看我这个例子
    sort是一个通用的,系统预置的函数
    IComparer也是系统预置好的

    而MyComparer、MyAgeComparer...等等是你作为调用者现写的类
    并且将它作为参数传给sort,sort调用了其中Compare方法,实现了某个逻辑的自定义。(而整体算法可以重用)

    打赏 评论
  • 默默悟问 2018-05-27 16:43

    问题里根本没有比较接口的事,不明白怎么答题大谈比较接口。套用一个词,“差评!”
    解答:
    1. 你的理解没问题,书上那么写只是表示可以向上转型,估计是加深下印象对象也同时是改接口对象;
    2. 就是说HotDrink两个子类中的某一种子类都可以;
    3. 所以没有另一个杯子之说,2指的是class中的某一种;
    4. HotDrink和ICup是两个不相关的接口,就是说不能显式的转为HotDrink类型后调用ICup接口。对象实现两个接口不等于认为HotDrink对象可以调用ICup接口;
    5. 最后一句书里没那个说法,只是说HotDrink类型和ICup类型吧。

    打赏 评论
  • dabocaiqq 2018-05-27 17:10

    多态性(C# 编程指南)转自MSDN
    通过继承,一个类可以用作多种类型:可以用作它自己的类型、任何基类型,或者在实现接口时用作任何接口类型。这称为多态性。C# 中的每种类型都是多态的。类型可用作它们自己的类型或用作 Object 实例,因为任何类型都自动将 Object 当作基类型。

    多态性不仅对派生类很重要,对基类也很重要。任何情况下,使用基类实际上都可能是在使用已强制转换为基类类型的派生类对象。基类的设计者可以预测到其基类中可能会在派生类中发生更改的方面。例如,表示汽车的基类可能包含这样的行为:当考虑的汽车为小型货车或敞篷汽车时,这些行为将会改变。基类可以将这些类成员标记为虚拟的,从而允许表示敞篷汽车和小型货车的派生类重写该行为。

    多态性概述
    当派生类从基类继承时,它会获得基类的所有方法、字段、属性和事件。面向对象的语言使用虚方法表达多态。若要更改基类的数据和行为,您有两种选择:可以使用新的派生成员替换基成员,或者可以重写虚拟的基成员。

    使用新的派生成员替换基类的成员需要使用 new 关键字。如果基类定义了一个方法、字段或属性,则 new 关键字用于在派生类中创建该方法、字段或属性的新定义。new 关键字放置在要替换的类成员的返回类型之前。例如:

    1public class BaseClass

    2{

    3 public void DoWork() { }

    4 public int WorkField;

    5 public int WorkProperty

    6 {

    7 get { return 0; }

    8 }

    9}

    10public class DerivedClass : BaseClass

    11{

    12 public new void DoWork() { }

    13 public new int WorkField;

    14 public new int WorkProperty

    15 {

    16 get { return 0; }

    17 }

    18}

    19

    打赏 评论
  • dabocaiqq 2018-05-27 17:11

    使用 new 关键字时,调用的是新的类成员而不是已被替换的基类成员。这些基类成员称为隐藏成员。如果将派生类的实例强制转换为基类的实例,就仍然可以调用隐藏类成员。例如:

    DerivedClass B = new DerivedClass();
    B.DoWork(); // Calls the new method.

    BaseClass A = (BaseClass)B;
    A.DoWork(); // Calls the old method.
    为了使派生类的实例完全接替来自基类的类成员,基类必须将该成员声明为虚拟的。这是通过在该成员的返回类型之前添加 virtual 关键字来实现的。然后,派生类可以选择使用 override 关键字而不是 new,将基类实现替换为它自己的实现。例如:

    public class BaseClass
    {
    public virtual void DoWork() { }

    public virtual int WorkProperty
    {
    get { return 0; }
    }
    }
    public class DerivedClass : BaseClass
    {
    public override void DoWork() { }
    public override int WorkProperty
    {
    get { return 0; }
    }
    }
    字段不能是虚拟的,只有方法、属性、事件和索引器才可以是虚拟的。当派生类重写某个虚拟成员时,即使该派生类的实例被当作基类的实例访问,也会调用该成员。例如:

    DerivedClass B = new DerivedClass();
    B.DoWork(); // Calls the new method.

    BaseClass A = (BaseClass)B;
    A.DoWork(); // Also calls the new method.
    使用虚拟方法和属性可以预先计划未来的扩展。由于在调用虚拟成员时不考虑调用方正在使用的类型,所以派生类可以选择完全更改基类的外观行为。

    无论在派生类和最初声明虚拟成员的类之间已声明了多少个类,虚拟成员都将永远为虚拟成员。如果类 A 声明了一个虚拟成员,类 B 从 A 派生,类 C 从类 B 派生,则类 C 继承该虚拟成员,并且可以选择重写它,而不管类 B 是否为该成员声明了重写。例如:

    public class A
    {
    public virtual void DoWork() { }
    }
    public class B : A
    {
    public override void DoWork() { }
    }
    public class C : B
    {
    public override void DoWork() { }
    }
    派生类可以通过将重写声明为密封的来停止虚拟继承。这需要在类成员声明中将 sealed 关键字放在 override 关键字的前面。例如:

    public class C : B
    {
    public sealed override void DoWork() { }
    }
    在上面的示例中,方法 DoWork 对从 C 派生的任何类都不再是虚拟的。它对 C 的实例仍然是虚拟的 -- 即使将这些实例强制转换为类型 B 或类型 A。派生类可以通过使用 new 关键字替换密封的方法,如下面的示例所示:

    public class D : C
    {
    public new void DoWork() { }
    }
    在此情况下,如果在 D 中使用类型为 D 的变量调用 DoWork,被调用的将是新的 DoWork。如果使用类型为 C、B 或 A 的变量访问 D 的实例,对 DoWork 的调用将遵循虚拟继承的规则,即把这些调用传送到类 C 的 DoWork 实现。

    已替换或重写某个方法或属性的派生类仍然可以使用基关键字访问基类的该方法或属性。例如:

    public class A
    {
    public virtual void DoWork() { }
    }
    public class B : A
    {
    public override void DoWork() { }
    }
    public class C : B
    {
    public override void DoWork()
    {
    // Call DoWork on B to get B's behavior:
    base.DoWork();

        // DoWork behavior specific to C goes here:
        // ...
    }
    

    }

    注意
    建议虚拟成员在它们自己的实现中使用 base 来调用该成员的基类实现。允许基类行为发生使得派生类能够集中精力实现特定于派生类的行为。未调用基类实现时,由派生类负责使它们的行为与基类的行为兼容。

    打赏 评论
  • dabocaiqq 2018-05-27 17:11

    C# 多态性
    多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。

    多态性可以是静态的或动态的。在静态多态性中,函数的响应是在编译时发生的。在动态多态性中,函数的响应是在运行时发生的。

    静态多态性
    在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。C# 提供了两种技术来实现静态多态性。分别为:

    函数重载
    运算符重载
    运算符重载将在下一章节讨论,接下来我们将讨论函数重载。

    函数重载
    您可以在同一个范围内对相同的函数名有多个定义。函数的定义必须彼此不同,可以是参数列表中的参数类型不同,也可以是参数个数不同。不能重载只有返回类型不同的函数声明。

    下面的实例演示了几个相同的函数 print(),用于打印不同的数据类型:

    using System;
    namespace PolymorphismApplication
    {
    class Printdata
    {
    void print(int i)
    {
    Console.WriteLine("Printing int: {0}", i );
    }

      void print(double f)
      {
         Console.WriteLine("Printing float: {0}" , f);
      }
    
      void print(string s)
      {
         Console.WriteLine("Printing string: {0}", s);
      }
      static void Main(string[] args)
      {
         Printdata p = new Printdata();
         // 调用 print 来打印整数
         p.print(5);
         // 调用 print 来打印浮点数
         p.print(500.263);
         // 调用 print 来打印字符串
         p.print("Hello C++");
         Console.ReadKey();
      }
    

    }
    }
    当上面的代码被编译和执行时,它会产生下列结果:

    Printing int: 5
    Printing float: 500.263
    Printing string: Hello C++
    动态多态性
    C# 允许您使用关键字 abstract 创建抽象类,用于提供接口的部分类的实现。当一个派生类继承自该抽象类时,实现即完成。抽象类包含抽象方法,抽象方法可被派生类实现。派生类具有更专业的功能。

    请注意,下面是有关抽象类的一些规则:

    您不能创建一个抽象类的实例。
    您不能在一个抽象类外部声明一个抽象方法。
    通过在类定义前面放置关键字 sealed,可以将类声明为密封类。当一个类被声明为 sealed 时,它不能被继承。抽象类不能被声明为 sealed。
    下面的程序演示了一个抽象类:

    using System;
    namespace PolymorphismApplication
    {
    abstract class Shape
    {
    public abstract int area();
    }
    class Rectangle: Shape
    {
    private int length;
    private int width;
    public Rectangle( int a=0, int b=0)
    {
    length = a;
    width = b;
    }
    public override int area ()
    {
    Console.WriteLine("Rectangle 类的面积:");
    return (width * length);
    }
    }

    class RectangleTester
    {
    static void Main(string[] args)
    {
    Rectangle r = new Rectangle(10, 7);
    double a = r.area();
    Console.WriteLine("面积: {0}",a);
    Console.ReadKey();
    }
    }
    }
    当上面的代码被编译和执行时,它会产生下列结果:

    Rectangle 类的面积:
    面积: 70
    当有一个定义在类中的函数需要在继承类中实现时,可以使用虚方法。虚方法是使用关键字 virtual 声明的。虚方法可以在不同的继承类中有不同的实现。对虚方法的调用是在运行时发生的。

    动态多态性是通过 抽象类 和 虚方法 实现的。

    下面的程序演示了这点:

    using System;
    namespace PolymorphismApplication
    {
    class Shape
    {
    protected int width, height;
    public Shape( int a=0, int b=0)
    {
    width = a;
    height = b;
    }
    public virtual int area()
    {
    Console.WriteLine("父类的面积:");
    return 0;
    }
    }
    class Rectangle: Shape
    {
    public Rectangle( int a=0, int b=0): base(a, b)
    {

      }
      public override int area ()
      {
         Console.WriteLine("Rectangle 类的面积:");
         return (width * height); 
      }
    

    }
    class Triangle: Shape
    {
    public Triangle(int a = 0, int b = 0): base(a, b)
    {

      }
      public override int area()
      {
         Console.WriteLine("Triangle 类的面积:");
         return (width * height / 2); 
      }
    

    }
    class Caller
    {
    public void CallArea(Shape sh)
    {
    int a;
    a = sh.area();
    Console.WriteLine("面积: {0}", a);
    }
    }

    class Tester
    {

      static void Main(string[] args)
      {
         Caller c = new Caller();
         Rectangle r = new Rectangle(10, 7);
         Triangle t = new Triangle(10, 5);
         c.CallArea(r);
         c.CallArea(t);
         Console.ReadKey();
      }
    

    }
    }
    当上面的代码被编译和执行时,它会产生下列结果:

    Rectangle 类的面积:
    面积:70
    Triangle 类的面积:
    面积:25

    打赏 评论
  • qq_38481647 2018-05-28 03:30

    通过继承实现的不同对象调用相同的方法,表现出不同的行为,称之为多态
    代码

    public class Animal
    {
    public virtual void Eat()
    {
    Console.WriteLine("Animal eat");
    }
    }

    public class Cat : Animal
    {
        public override void Eat()
        {
            Console.WriteLine("Cat eat");
        }
    }
    
    public class Dog : Animal
    {
        public override void Eat()
        {
            Console.WriteLine("Dog eat");
        }
    }
    
    class Tester
    {
        static void Main(string[] args)
        {
            Animal[] animals = new Animal[3];
    
            animals[0] = new Animal();
            animals[1] = new Cat();
            animals[2] = new Dog();
    
            for (int i = 0; i < 3; i++)
            {
                animals[i].Eat();
            }
        }
    }
    

    复制代码

     输出如下:
    

    Animal eat...

    Cat eat...

    Dog eat...

    在上面的例子中,通过继承,使得Animal对象数组中的不同的对象,在调用Eat()方法时,表现出了不同的行为。

    多态的实现看起来很简单,要完全理解及灵活的运用c#的多态机制,也不是一件容易的事,有很多需要注意的地方。

    打赏 评论

相关推荐 更多相似问题