C++ 虚函数与多态性

C++ 虚函数与多态性,c++,polymorphism,virtual,C++,Polymorphism,Virtual,假设我有这个: class A { public: virtual int hello(A a); }; class B : public A { public: int hello(B b){ bla bla }; }; 所以,它是一个抽象类 1在类B中,我定义了一个方法,其假设覆盖了类a。但参数略有不同。我对此不确定,对吗?可能是因为多态性,这是可以的,但它相当混乱。 2如果我这样做:A=新的B;,然后是a.Hellool;如果lol不是类型B,那么它将给出编

假设我有这个:

class A
{
    public:
    virtual int hello(A a);
};

class B : public A
{
   public:
   int hello(B b){ bla bla };
};
所以,它是一个抽象类

1在类B中,我定义了一个方法,其假设覆盖了类a。但参数略有不同。我对此不确定,对吗?可能是因为多态性,这是可以的,但它相当混乱。 2如果我这样做:A=新的B;,然后是a.Hellool;如果lol不是类型B,那么它将给出编译错误?,如果它是来自另一个类C的类型A:公共A,会发生什么

我对最重要的和虚拟的东西感到困惑。。我发现的所有示例都适用于没有参数的方法

任何答案,链接,或任何它的赞赏

谢谢


pd:对不起,我的英语

当你重写一个方法时,它会重新定义该方法的功能。只能覆盖已使用其参数集定义的虚拟成员。如果类型为,则将调用上的方法。如果类型为B,则即使变量类型为A但包含类型为B的实例,也会调用B上的方法


您不能更改被重写方法的参数定义,否则它将不再是重写。

当您重写方法时,它将重新定义该方法将执行的操作。只能覆盖已使用其参数集定义的虚拟成员。如果类型为,则将调用上的方法。如果类型为B,则即使变量类型为A但包含类型为B的实例,也会调用B上的方法


不能更改重写方法的参数定义,否则它将不再是重写。

首先,在代码中,A不是抽象类。它必须至少有一个纯虚函数才能抽象

不同的参数意味着完全不同的方法,即使名称相同。把它想象成一个不同的名字。这就是为什么它被称为签名。如果A将是一个抽象类,那么该代码根本不会编译

我会打电话给你。这并没有问题,参数必须是类型A,就像并没有继承一样


首先,A在代码中不是抽象类。它必须至少有一个纯虚函数才能抽象

不同的参数意味着完全不同的方法,即使名称相同。把它想象成一个不同的名字。这就是为什么它被称为签名。如果A将是一个抽象类,那么该代码根本不会编译

我会打电话给你。这并没有问题,参数必须是类型A,就像并没有继承一样


您在那里所做的是重载而不是重写,也就是说,就好像B类是:

class B
{
public:
  int hello(A a) {...}
  int hello(B b) {...}
};
您有两个名称相同但签名不同的函数,这使得它们具有不同的函数,就像标准库对absfloat和absdouble等具有不同的函数一样

如果要重写,则需要具有相同的签名,即类B的hello需要采用类型a的参数。这样,当您对类B的对象调用hello时,它将使用类B的hello而不是类a的hello


如果你真的想让类B的hello只接受类型B的对象,那么你所拥有的就没问题了,尽管你可能想让类A的hello变成非虚拟的,因为你并不真的想覆盖它-你正在用新参数和新行为定义一个新函数。

你在那里做的是重载而不是覆盖,也就是说,B类似乎是:

class B
{
public:
  int hello(A a) {...}
  int hello(B b) {...}
};
您有两个名称相同但签名不同的函数,这使得它们具有不同的函数,就像标准库对absfloat和absdouble等具有不同的函数一样

如果要重写,则需要具有相同的签名,即类B的hello需要采用类型a的参数。这样,当您对类B的对象调用hello时,它将使用类B的hello而不是类a的hello

如果您真的希望类B的hello只接受类型B的对象,那么您所拥有的就可以了,尽管您可能希望将类A的hello设置为非虚拟的,因为您并不真正想覆盖它-您正在定义一个具有新参数和新行为的新函数。

您的类B不会覆盖类型A中的成员函数,它超载了。或者无论如何,试着看看以后藏起来的地方

重写是指派生类从基类定义其自己的虚拟成员函数版本。重载是指使用相同的名称定义不同的函数

当对具有基类类型的指针或引用进行虚调用时,它只考虑派生类中的重写,而不是重载。这是必要的-对于调用方所处理的B实例,就好像它做了A所能做的一切,这是动态多态性和虚拟函数的点,它的hello函数需要能够接受任何类型A的对象。只接受对象的hello函数 B型的s比任何A型的更具限制性。它不能扮演A的hello函数的角色,因此它不是重写

如果您尝试在a和B上调用hello,传递类型为a或B的对象,您应该能够看到区别。A有一个函数接受一个你没有定义的A,所以如果你调用它,那么你的程序将无法链接,但是你可以修复它。B有一个取a的函数。它们恰好有相同的名称,当然,由于B派生自a,您可以将a B传递给取a的函数。但B的函数在虚拟调用中不起重写作用

<> P>可以在B对象上调用A的函数,但只通过引用或指针来调用.C++的一个特性是B中的hello定义隐藏了定义。如果重载是你想要的,可以通过使用A::Hello来隐藏基类函数。如果重写是您想要的,那么您必须使用相同的参数定义一个函数。例如:

#include <iostream>

class A
{
    public:
    virtual int hello(A a) {std::cout << "A\n"; }
    virtual int foo(int i) { std::cout << "A::Foo " << i << "\n"; }
};

class B : public A
{
   public:
   using A::hello;
   // here's an overload
   int hello(B b){ std::cout << "B\n"; };
   // here's an override:
   virtual int foo(int i) { std::cout << "B::Foo " << i << "\n"; }
};

int main() {
    A a;
    B b;
    a.hello(a);  // calls the function exactly as defined in A
    a.hello(b);  // B "is an" A, so this is allowed and slices the parameter
    b.hello(a);  // OK, but only because of `using`
    b.hello(b);  // calls the function exactly as defined in B
    A &ab = b;   // a reference to a B object, but as an A
    ab.hello(a); // calls the function in A
    ab.hello(b); // *also* calls the function in A, proving B has not overridden it
    a.foo(1);    // calls the function in A
    b.foo(2);    // calls the function in B
    ab.foo(3);   // calls the function in B, because it is overridden
}
如果您使用A::hello将;线路从B开始,然后呼叫B.helloa;未能编译:

error: no matching function for call to `B::hello(A&)'
note: candidates are: int B::hello(B)
类B不会重写A中的成员函数,而是重载它。或者无论如何,试着看看以后藏起来的地方

重写是指派生类从基类定义其自己的虚拟成员函数版本。重载是指使用相同的名称定义不同的函数

当对具有基类类型的指针或引用进行虚调用时,它只考虑派生类中的重写,而不是重载。这一点很重要——对于调用方所处理的B实例来说,就好像它做了A所能做的一切一样(这是动态多态性和虚拟函数的点),它的hello函数需要能够接受任何类型A的对象。只接受类型B的对象而不是任何类型A的对象的hello函数限制性更大。它不能扮演A的hello函数的角色,因此它不是重写

如果您尝试在a和B上调用hello,传递类型为a或B的对象,您应该能够看到区别。A有一个函数接受一个你没有定义的A,所以如果你调用它,那么你的程序将无法链接,但是你可以修复它。B有一个取a的函数。它们恰好有相同的名称,当然,由于B派生自a,您可以将a B传递给取a的函数。但B的函数在虚拟调用中不起重写作用

<> P>可以在B对象上调用A的函数,但只通过引用或指针来调用.C++的一个特性是B中的hello定义隐藏了定义。如果重载是你想要的,可以通过使用A::Hello来隐藏基类函数。如果重写是您想要的,那么您必须使用相同的参数定义一个函数。例如:

#include <iostream>

class A
{
    public:
    virtual int hello(A a) {std::cout << "A\n"; }
    virtual int foo(int i) { std::cout << "A::Foo " << i << "\n"; }
};

class B : public A
{
   public:
   using A::hello;
   // here's an overload
   int hello(B b){ std::cout << "B\n"; };
   // here's an override:
   virtual int foo(int i) { std::cout << "B::Foo " << i << "\n"; }
};

int main() {
    A a;
    B b;
    a.hello(a);  // calls the function exactly as defined in A
    a.hello(b);  // B "is an" A, so this is allowed and slices the parameter
    b.hello(a);  // OK, but only because of `using`
    b.hello(b);  // calls the function exactly as defined in B
    A &ab = b;   // a reference to a B object, but as an A
    ab.hello(a); // calls the function in A
    ab.hello(b); // *also* calls the function in A, proving B has not overridden it
    a.foo(1);    // calls the function in A
    b.foo(2);    // calls the function in B
    ab.foo(3);   // calls the function in B, because it is overridden
}
如果您使用A::hello将;线路从B开始,然后呼叫B.helloa;未能编译:

error: no matching function for call to `B::hello(A&)'
note: candidates are: int B::hello(B)

但我必须澄清一些事情才能得到我的最终答案

假设我有一个类,它完全是我在原始问题中定义的。我添加了另一种方法:

class A {
    ...
    int yeah();
}
然后我将B类定义为:

class B : public A {
    int hello(A a);
};
和另一个类似于B的C类

我所知道的是,因为我是程序员,B和C的hello方法显然有A类型的对象作为参数,但是是同一类的实例。 例如:

B b; 
b.hello(some_other_b_instance);

问题是,在B类和C类的每个hello函数中,我想对特定B类或C类的atributes执行特定操作。由于参数的类型为A,我无法使用它们

我需要的是一种逆多形,但它是错误的,因为根据定义,我可以将一个C实例发送到B hello类,但我知道这不会发生


我希望你能理解代码的意思。。。一个类别是抽象的,而实际的工作在特定的类别B和C中是有意义的,每个类别都以其特定的方式进行工作,以使函数工作。但是B和C需要访问他们的成员才能正确地执行hello操作。

关于答案,我需要澄清一些事情才能得到最终答案

假设我有一个类,它完全是我在原始问题中定义的。我添加了另一种方法:

class A {
    ...
    int yeah();
}
然后我将B类定义为:

class B : public A {
    int hello(A a);
};
和另一个类似于B的C类

我所知道的是,因为我是程序员,B和C的hello方法显然有A类型的对象作为参数,但是是同一类的实例。 例如:

B b; 
b.hello(some_other_b_instance);

问题是,在B类和C类的每个hello函数中,我想对特定B类或C类的atributes执行特定操作。由于参数的类型为A,我无法使用它们

我需要的是一种逆多形,但它是错误的,因为根据定义,我可以将一个C实例发送到B hello类,但我知道它不是 突然发生了一件事


我希望你能理解代码的意思。。。一个类别是抽象的,而实际的工作在特定的类别B和C中是有意义的,每个类别都以其特定的方式进行工作,以使函数工作。但是B和C需要访问他们的成员才能正确地执行hello操作。

一堆好的答案告诉你发生了什么,我想我会跳出来解释原因

有一个叫做的东西,它说子类中的函数必须在与基类相同的条件下工作。在这种情况下,函数必须能够对任何类型A的对象进行操作。请注意,由于继承关系,每个B都是-A A,但不是每个A都是-A B。因此,要替换基方法,派生类中的新函数可以削弱先决条件或增强后决条件,但不能强化先决条件或削弱后决条件

您试图覆盖的操作加强了前提条件,它接受Bs,而不是全部As

请注意,这在返回类型上是允许的。如果您的基类返回A,那么它保证返回值为-A。基类随后可以返回A B,因为每个B都是-A A


但对于输入参数,只有方差满足LSP的理论要求,输入/输出参数是不变量。在C++中,所有参数类型都是超载的不变量。

< P>一组好的答案告诉你发生了什么,我想我会跳进去的。 有一个叫做的东西,它说子类中的函数必须在与基类相同的条件下工作。在这种情况下,函数必须能够对任何类型A的对象进行操作。请注意,由于继承关系,每个B都是-A A,但不是每个A都是-A B。因此,要替换基方法,派生类中的新函数可以削弱先决条件或增强后决条件,但不能强化先决条件或削弱后决条件

您试图覆盖的操作加强了前提条件,它接受Bs,而不是全部As

请注意,这在返回类型上是允许的。如果您的基类返回A,那么它保证返回值为-A。基类随后可以返回A B,因为每个B都是-A A


但对于输入参数,只有方差满足LSP的理论要求,输入/输出参数是不变量。尤其是C++中,所有参数类型对于重载的目的都是不变量。

如果从B派生,在编译程序的眼中,hello的调用不会是模糊的吗?@扎克,不,它使用了最派生的。注意,我假设通过值传递错误是固定的,因为不能通过值传递抽象类。这不是重载,因为两个函数声明在不同的范围内。B::hello在A中隐藏hello。在您的示例中,它只是重载。如果A派生自B,那么对hello的调用在编译器看来不是很模糊吗?@Zach,不,它使用派生最多的一个。注意,我假设通过值传递错误是固定的,因为不能通过值传递抽象类。这不是重载,因为两个函数声明在不同的范围内。hello在A中隐藏hello。在您的示例中,它只是重载。我从未在类A中定义hello,因此函数是纯虚拟的,因此是一个抽象类。请注意,我在B@ritmbo中声明并定义了hello。不,这是不正确的。要声明纯虚拟函数,必须编写int-helloA a=0;。您所做的是声明一个函数,但从未定义过它。这根本不是一回事。我从未在类A中定义hello,因此函数是纯虚拟的,因此是一个抽象类。请注意,我在B@ritmbo中声明并定义了hello。不,这是不正确的。要声明纯虚拟函数,必须编写int-helloA a=0;。您所做的是声明一个函数,但从未定义过它。这根本不是一回事。注意,新操作符返回一个指针,因此a=新B;类型不正确。你想要A=B;或A*A=新的B;,虽然每个人都做不同的事情。一个很大的区别是前者是B。如果A是抽象的,则只有后者才是合法的。要拥有纯虚拟基类,至少需要定义一个函数:virtual int helloA A=0;所有从基类派生的类都必须覆盖这个函数。不能实例化纯虚拟基类的对象。请注意,新运算符返回指针,因此a=新B;类型不正确。你想要A=B;或A*A=新的B;,虽然每个人都做不同的事情。一个很大的区别是前者是B。如果A是抽象的,则只有后者才是合法的。要拥有纯虚拟基类,至少需要定义一个函数:virtual int helloA A=0;所有从基类派生的类都必须覆盖这个函数。您不能实例化纯虚拟基类的对象。对此问题的澄清应
进入问题,而不是作为答案发布。Q&a站点也是如此,而不是论坛。如果您将B::hello声明为int helloA a并传递一个B实例,则该实例将经历切片并成为a,任何特定于B的内容都将被丢弃;动态施法没用。按常量引用传递或使用指针,但最好使用常量引用来防止切片:int B::helloconst a&a。请注意,动态强制转换是危险的:没有任何东西可以阻止其他编码器将a或C传递给B::hello。打字的目的是防止这种事情发生。以这种方式使用dynamic_cast,您还可以使用非类型化语言。请密切关注本·沃格特的回答。对问题的澄清应该放在问题中,而不是作为答案发布。Q&a站点也是如此,而不是论坛。如果您将B::hello声明为int helloA a并传递一个B实例,则该实例将经历切片并成为a,任何特定于B的内容都将被丢弃;动态施法没用。按常量引用传递或使用指针,但最好使用常量引用来防止切片:int B::helloconst a&a。请注意,动态强制转换是危险的:没有任何东西可以阻止其他编码器将a或C传递给B::hello。打字的目的是防止这种事情发生。以这种方式使用dynamic_cast,您还可以使用非类型化语言。密切关注本·沃格特的回答。