需要一些帮助来理解继承
本文关键字:继承 帮助 | 更新日期: 2023-09-27 18:21:10
有一个任务来制作一个注册动物的程序,目标是熟悉继承、多态性等。
让我感到困惑的一件事是,无论我读了多少,它似乎都没有意义。
我创建了我的主类,它是动物,其中包含一些适用于所有动物的通用字段,例如名称,年龄和物种。
到目前为止,所有动物都有此信息,但每种动物都有一个独特的领域,因此请创建我的猫作为公共类猫:动物并给猫例如田野牙齿。
现在我想创建一个新的动物,它是一只猫,我从几个列表框中获取数据,所以我需要一个接受这些参数的构造函数,这是我没有得到的,我是否必须在每个子类中声明它们?
我知道我的动物应该有来自动物类的 3 个参数加上来自猫类的另一个参数,所以新猫应该接受(名称、年龄、物种、牙齿(,但似乎我必须告诉猫类中的构造函数接受所有这些,我的问题是,动物类有什么目的?如果我仍然需要在所有子类中编写代码,为什么会有基类?可能我不明白,但我读得越多,我就越困惑。
就像谢尔盖说的,它不仅仅是关于构造函数。它使您不必一遍又一遍地初始化相同的字段。例如
没有继承
class Cat
{
float height;
float weight;
float energy;
string breed;
int somethingSpecificToCat;
public Cat()
{
//your constructor. initialize all fields
}
public Eat()
{
energy++;
weight++;
}
public Attack()
{
energy--;
weight--;
}
}
class Dog
{
float height;
float weight;
float energy;
string breed;
int somethingSpecificToDog;
public Dog()
{
//your constructor. initialize all fields
}
public Eat()
{
energy++;
weight++;
}
public Attack()
{
energy--;
weight--;
}
}
有继承
动物共有的所有东西都被移到基类中。这样,当您想要设置新动物时,无需再次键入所有内容。
abstract class Animal
{
float height;
float weight;
float energy;
string breed;
public Eat()
{
energy++;
weight++;
}
public Attack()
{
energy--;
weight--;
}
}
class Cat : Animal
{
int somethingSpecificToCat;
public Cat()
{
//your constructor. initialize all fields
}
}
class Dog : Animal
{
int somethingSpecificToDog;
public Dog()
{
//your constructor. initialize all fields
}
}
另一个优点是,如果要使用唯一 ID 标记每个动物,则无需将其包含在每个构造函数中,并保留最后一个使用的 ID 的全局变量。您可以在 Animal 构造函数中轻松执行此操作,因为每次实例化派生类时都会调用它。
例
abstract class Animal
{
static int sID = 0;
float height;
float weight;
int id;
public Animal()
{
id = ++sID;
}
}
现在当你这样做时;
Dog lassie = new Dog(); //gets ID = 1
Cat garfield = new Cat(); // gets ID = 2
如果你想要一份"农场"中所有动物的清单,
没有继承
List<Cat> cats = new List<Cat>(); //list of all cats
List<Dog> dogs = new List<Dog>(); //list of all dogs
...etc
有继承
List<Animal> animals = new List<Animal>(); //maintain a single list with all animals
animals.Add(lassie as Animal);
animals.Add(garfield as Animal);
这样,如果你想看看你是否有一种叫做Pluto
的动物,你只需要迭代一个列表(动物(而不是多个列表(猫、狗、猪等(。
编辑以回应您的评论
您不需要实例化动物。您只需创建一个您想要的任何动物的对象。事实上,由于动物永远不会是通用动物,因此您可以将Animal
创建为抽象类。
abstract class Animal
{
float height;
float weight;
float energy;
string breed;
public Eat()
{
energy++;
weight++;
}
public Attack()
{
energy--;
weight--;
}
}
class Cat : Animal
{
int somethingSpecificToCat;
public Cat()
{
//your constructor. initialize all fields
}
}
class Dog : Animal
{
int somethingSpecificToDog;
public Dog()
{
//your constructor. initialize all fields
}
}
Cat garfield = new Cat();
garfield.height = 24.5;
garfield.weight = 999; //he's a fat cat
//as you can see, you just instantiate the object garfield
//and instantly have access to all members of Animal
Animal jerry = new Animal(); //throws error
//you cannot create an object of type Animal
//since Animal is an abstract class. In this example
//the right way would be to create a class Mouse deriving from animal and then doing
Mouse jerry = new Mouse();
编辑到您的评论
如果您将其存储在动物列表中,您仍然可以访问所有字段。您只需要将其转换回其原始类型即可。
List<Animal> animals = new List<Animal>();
animals.Add(garfield as Animal);
animals.Add(lassie as Animal);
//if you do not cast, you cannot access fields that were specific to the derived class.
Console.WriteLine(animals[0].height); //this is valid. Prints Garfield's height
Console.WriteLine(animals[0].somethingSpecificToCat); //invalid since you haven't casted
Console.WriteLine((animals[0] as Cat).somethingSpecificToCat); //now it is valid
//if you want to do it in a loop
foreach(Animal animal in animals)
{
//GetType() returns the derived class that the particular animal was casted FROM earlier
if(animal is Cat)
{
//the animal is a cat
Cat garfield = animal as Cat;
garfield.height;
garfield.somethingSpecificToCat;
}
else if (animal is Dog)
{
//animal is a dog
Dog lassie = animal as Dog;
lassie.height;
lassie.somethingSpecificToDog;
}
}
您可能需要记住,您正在处理的示例非常简单。如果您需要一些复杂的方法来确定基类值之一,您不希望在多个类中编写/复制它,因为这会变得乏味并使代码维护成为一场噩梦,在这些类型的情况下,构造函数中几个参数的声明变得微不足道。
好处是你不必在每种动物中都声明年龄物种的名称。您可以为您预先制作它们。继承允许你做的另一个重要点是。假设你想拥有一系列动物。所以你键入类似的东西。数组列表 arr = 等...但这只会容纳 cat 类型的对象。因此,您可以执行类似Arraylist的操作,这将容纳所有类型的动物,猫和狗。基本上,基类的变量可以指向派生类的变量。在大多数情况下,这非常方便,因为事情变得复杂。
你需要告诉构造器接受参数(如果你不想要求它们(,但你不需要再次实现属性:
public class Animal
{
public string Name { get; set; }
public Animal(string Name)
{
Name = name;
}
}
public class Cat : Animal
{
public int Teeth { get; set; }
public Cat(string name, int teeth)
{
Name = name; //<-- got from base
Teeth = teeth; //<-- defined localy
}
//or do this
public Cat(string name, int teeth) : base(name)
{
Teeth = teeth;
}
}
您还可以执行以下操作:
Cat cat = new Cat("cat", 12);
Animal kitty = cat as Animal;
这是有道理的,例如,如果您想要像List<Animal>
这样的列表,您可以添加一个Cat
-instance:
List<Animal> animals = new List<Animal>();
animals.Add(new Animal("Coco"));
animals.Add(cat);
foreach(Animal animal in animals)
{
Console.WriteLine(String.Format("Name: {0}", animal.Name));
if(animal is Cat)
{
Console.WriteLine(String.Format("{0} is a Cat with {1} teeth.", animal.Name
(animal as Cat).Teeth));
}
Console.WriteLine("============");
}
这将输出:
Name: Coco
============
Name: cat
cat is a Cat with 12 teeth.
============
继承不仅仅是关于构造函数。例如,在基类 Animal 中,您可以声明方法 Eat(something( 或 Grow((,它们对于所有后继者都是相等的。
顺便说一句,仅使用三个参数调用默认的 Cat(( 构造函数(因此调用基本 Animal 构造函数(然后通过设置适当的字段或属性来指定牙齿没有问题。
以下信息是否对您有任何用处,但我认为作为继承的用途值得一提。继承的众多用途之一,或者更具体地说,超类是您可以将它们放在同一个集合中:
List<Animal> animals = new List<Animal>();
animals.Add(new Cat());
animals.Add(new Dog());
等等。
不要忘记您也可以将构造函数参数传递给基本构造器。您不必在每个派生类中全部初始化它们。
例如(窃取 chrfin 的代码(:
public class Animal
{
public string Name { get; set; }
}
public class Cat : Animal
{
public int Teeth { get; set; }
public Cat(string name, int teeth) : Base(name) //pass name to base constructor
{
Teeth = teeth;
}
}
你完全是想多了。
动物类的目的是什么?
你的方案真的非常简单。试着这样想:特征越常见,它应该放在层次结构中的位置就越高。为什么?只是为了避免重复和冗余。想象一下,有几个额外的类:狗,马和青蛙。由于猫、狗和马是哺乳动物,你也可以创建一个类哺乳动物来定义共同哺乳动物的特征。为什么?例如,避免为相似物种编写相同的构造函数、字段、方法。在你的情况下,试着把你的动物类看作是所有动物共有的特征库。
是的,您必须创建构造函数,但这并不会使继承毫无意义,尽管更喜欢组合而不是继承实际上是一个很好的设计实践,通常(并非总是(拥有一只 HAS-A 动物属性的猫比拥有 IS-A 动物属性的猫更好。
回到继承,当它真正得到回报时,因为有一些属性,你的所有动物都会认为可能,腿,耳朵,头,通过使用继承,你不必在你创建的每个类中声明这些属性。
同样通过继承,您可以使用多态性,假设您有此类(伪代码(
public abstract class Animal()
{
//Some atributes
//Methods
makeSound();
}
那么你有
public class Cat extends Animal
{
makeSound()
{
System.out.println("meow");
}
}
然后说你也扩展了狗的动物:
public class Dog extends Animal
{
makeSound()
{
System.out.println("woof")
}
}
现在假设你有一个像这样声明的数组:
List<Animal> animals = new ArrayList<Animal>();
animals.add(cat);
animals.add(dog);
然后假设您希望每个动物发出他的声音,那么您可以使用多态性,这将使每个实现调用其makeSound方法:
for (Animals animal : animals)
{
animal.makeSound();
}
这将为猫打印"喵">
和狗
"嘭">
继承允许您编写在类之间共享的代码,以便将公共功能/数据放在基类中,并让其他类从中派生。使用您的示例:
class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
}
class Cat : Animal
{
// Put cat-only properties here...
public Cat(string name) : base(name)
{
// Set cat-specific properties here...
}
}
你甚至不需要为每个类构造函数提供相同数量的参数 - 如果一只猫没有名字(对于人为的例子(,只需创建一个没有参数的Cat
构造函数并传入适合基构造函数的东西。这允许您控制所有动物类的设置方式。
你变得困惑,因为你只考虑构造函数。事实如 msdn 中所述,"构造函数和析构函数不是继承的"。因此,当您继承类时,基类构造函数不适用于派生类。派生类必须提到它自己的一组解释器/析构函数。要理解为什么会这样,你可以看这里:为什么构造函数不是继承的?。
现在来到你的问题,是的,你必须在 cat 类中添加一个构造函数来接受所有四个参数。但是你不必在你的 cat 类中再次实现这 3 个字段。动物类的所有公共保护字段和内部字段和方法仍然可用于 cat 类,您不必在派生类中重新实现它们。这就是基类为派生类提供服务的方式。