如何正确处理继承(方法和指针)?



我和我的朋友正在检查继承,不知道我们做错了什么

我们有继承结构A->BDC->D.

在这个简短的例子中,我们想知道

1 -为什么没有标记为A和B的行代码不能工作?

#include <iostream>
#include <string>
class A {
protected:
std::string m_name;
public:
std::string name()                  { return m_name; }
A* name (std::string str)           { m_name = str; return this; }
};
class B : public A {
protected:
int m_num;
public:
std::string name()                  { return m_name; } // <--- A
int num()                           { return m_num; }
B* name (std::string str)           { m_name = str; return this; } // <--- B
B* num (int x)                      { m_num = x; return this; }
};
int main() {
A a;
B b;
a.name("alice");
b.name("bob")->num(10);
std::cout << a.name() << " " << b.name() << " " << b.num() << std::endl;
return 0;
}

在这个较长的例子中,我们想知道为什么会弹出以下5个错误,因为它阻止了我们向目标前进。

2 -我们如何在这里继承我们需要的东西?

#include <iostream>
#include <string>
class A {
protected:
std::string m_name;
public:
A()                                 = default;
A(std::string str) : m_name(str)    { }
A(const A& a)                       = default;
std::string name()                  { return m_name; }
A* name (std::string str)           { m_name = str; return this; }
};
class B : public A {
protected:
int m_num;
public:
B()                                 = default;
B(std::string str, int x)
: m_name(str)   // 1. class 'B' does not have any field named 'm_name'
, m_num(x)                      { }
B(const B& b)                       = default;
std::string name()                  { return m_name; } // <--- A
int num()                           { return m_num; }
B* name (std::string str)           { m_name = str; return this; } // <--- B
B* num (int x)                      { m_num = x; return this; }
};
class C {
protected:
std::string m_name;                 //same as in A
unsigned long m_bigNumber;
public:
C()                                 = default;
C(std::string str, unsigned long big) : m_name(str), m_bigNumber(big) { }
C(const C& c)                       = default;
std::string name()                  { return m_name; }
unsigned long bigNumber()           { return m_bigNumber; }
C* name (std::string str)           { m_name = str; return this; }
C* bigNumber (unsigned long big)    { m_bigNumber = big; return this; }
};
class D : public B , public C {
private:
std::string m_thing;
public:
D()                                 = default;
D(std::string str1, std::string str2, int x, unsigned long big)
: m_thing(str1)
, m_name(str2)  // 2. request for member 'm_name' is ambiguous
, m_num(x)      // 3. class 'D' does not have any field named 'm_num'
, m_bigNumber(big)              { }
D(const D& d)                       = default;
std::string thing()                 { return m_thing; }
D* thing (std::string str)          { m_thing = str; }
};
int main() {
A a("alpha");
B b("bravo", 100);
C c("cookie", 1000);
D d("ddddd thing", "davis", 123, 123123);
a.name("alice");
b.name("bob")->num(10);
c.name("charlie")->bigNumber(123456789);
d.name("delta")->thing("delta thing")->num(200)->bigNumber(123456); //
// 4. request for member 'name' is ambiguous
std::cout << a.name() << " " << b.name() << " " << b.num() << std::endl;
std::cout << c.name() << " " << c.bigNumber() << std::endl;
std::cout << d.name() << " " << d.num() << " "; //
// 5. same as error 4
std::cout << d.thing() << " " << d.bigNumber() << std::endl;
return 0;
}

其他我们不理解的事情:

  1. 当我们调用b.num()时,没有问题,但是当我们调用b.name()时,我们需要用箭头和字母b标记的行,如果它继承自A并返回类型A*,为什么它不能返回B*,如果它是一个派生类?

  2. B* name (std::string str) { m_name = str; return this; }这样的事情甚至是好的做法吗?我们觉得它不是,但它确实缩短了我们实际项目中的内容,因为类成员和方法的数量非常多。也许我们可以使用B& name (std::string str) { m_name = str; return *this; }?

    来代替指向类的指针,而使用指向对象的引用。
  3. 我们如何处理更复杂的继承结构而不遇到像我们这样的冲突?例如:

class A { /* ... */ };
class B : public A { /* ... */ };
class C : public A { /* ... */ };
class D : public B, public C { /* ... */ }; // therefore has functionality of A, B, C
class E : public C { /* ... */ }; // therefore has functionality of A, C, but not B
class F : public A, public E { /* ... */ }; therefore has functionality of A, C, E, but not B

对于这些乱七八糟的代码,如果有其他的建议,我将不胜感激。

对于第一个例子,如果调用b.name("bob")将返回一个指向类a实例的指针,然后获取该实例并调用num()函数。A类没有num()函数。这是在派生类b中添加的内容。

问题1和问题2:

你提到在类B中name()是从类A继承的,它确实是,但不是你想的那样。在类A中,需要将name()函数指定为虚函数。这意味着类B可以通过重写函数来改变函数的实现。目前,类B正在创建自己的name()函数,该函数与类a中的name()函数无关。

一旦这样做了,你可以使用协变返回类型将返回类型更改为派生类,这应该可以解决1和2。

第三题:

你在这里面临的问题通常被称为钻石问题,它本身就是一个很大的话题。有很多方法可以解决这个问题。看看这篇文章和这篇文章。

其他提示:

  1. 如果你正在使用继承,确保你理解了virtual和override关键字的用法。

  2. 查看一些成员函数的const关键字。这表明函数不会改变类本身。这应该用于所有不会改变类的成员函数。代码中的例子是name()和num()的"getter"函数。这就是所谓的const correct

需要行B的原因称为协变返回类型——当重写或隐藏派生类的方法时,希望该方法返回指向派生类的引用或指针,而不是指向基类的引用或指针,这是相当常见的。在c++中你可以这样做,但是你需要显式地做。

您需要行A的原因是,一旦您在派生类中覆盖或隐藏了name函数(添加行B),它将隐藏所有基类中name的重载。因此,如果您仍然想要没有参数的重载,则需要显式地将它添加回来。如果你只想在基类中获得所有的重载,你可以在B中输入using A::name;,它将继承所有你没有显式覆盖的name方法

对于第2部分,当您希望初始化基类中的字段时,需要将其委托给基类构造函数。所以你应该输入:

B(std::string str, int x) : A(str), m_num(x)
{ }

相关内容

  • 没有找到相关文章

最新更新