我和我的朋友正在检查继承,不知道我们做错了什么
我们有继承结构A
->B
→D
和C
->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;
}
其他我们不理解的事情:
当我们调用
b.num()
时,没有问题,但是当我们调用b.name()
时,我们需要用箭头和字母b标记的行,如果它继承自A
并返回类型A*
,为什么它不能返回B*
,如果它是一个派生类?做
来代替指向类的指针,而使用指向对象的引用。B* name (std::string str) { m_name = str; return this; }
这样的事情甚至是好的做法吗?我们觉得它不是,但它确实缩短了我们实际项目中的内容,因为类成员和方法的数量非常多。也许我们可以使用B& name (std::string str) { m_name = str; return *this; }
?我们如何处理更复杂的继承结构而不遇到像我们这样的冲突?例如:
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。
第三题:
你在这里面临的问题通常被称为钻石问题,它本身就是一个很大的话题。有很多方法可以解决这个问题。看看这篇文章和这篇文章。
其他提示:
-
如果你正在使用继承,确保你理解了virtual和override关键字的用法。
-
查看一些成员函数的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)
{ }