指向超类对象的指针可以用作指向子类对象的指针。但是不能调用子类的成员函数。为什么?
Pointer to a Superclass object may serve as pointers to subclass objects. But can't call memeber functions of the subclass. why?
我注册了一个 C++ 课程,我有以下代码片段:
class Pet {
protected:
string name;
public:
Pet(string n)
{
name = n;
}
void run()
{
cout << name << ": I'm running" << endl;
}
};
class Dog : public Pet {
public:
Dog(string n) : Pet(n) {};
void make_sound()
{
cout << name << ": Woof! Woof!" << endl;
}
};
class Cat : public Pet {
public:
Cat(string n) : Pet(n) {};
void make_sound()
{
cout << name << ": Meow! Meow!" << endl;
}
};
int main()
{
Pet *a_pet1 = new Cat("Tom");
Pet *a_pet2 = new Dog("Spike");
a_pet1 -> run();
// 'a_pet1 -> make_sound();' is not allowed here!
a_pet2 -> run();
// 'a_pet2 -> make_sound();' is not allowed here!
}
我不明白为什么这是无效的。请为此推荐合适的参考文献,充分解释为什么会发生这种情况。
在 C++ 中,任何时候变量的类型和名称都是编译器允许自己知道的。
根据当前范围内的变量类型和名称检查每一行代码。
当你有一个指向基数 class 的指针时,变量的类型仍然是指向基数 class 的指针。它指向的 实际对象 可能是派生的 class,但变量仍然是指向基础 class.
的指针
Pet *a_pet1 = new Cat("Tom");
a_pet1 -> run();
// 'a_pet1 -> make_sound();' is not allowed here!
a_pet1
的类型是 Pet*
。它可能指向一个实际的 Cat
对象,但这不是 a_pet1
类型所具有的信息。
在下一行,您正在使用 a_pet1
。您只能 以对该行上的 Pet
指针有效的方式使用它。 a_pet1->make_sound()
不是对 Pet
指针的有效操作,因为 Pet
类型没有 make_sound
方法。
你可以这样做:
Cat *a_pet1 = new Cat("Tom");
a_pet1 -> run();
a_pet1 -> make_sound(); // it now works!
因为我们将 a_pet1
的类型从 Pet*
更改为 Cat*
。现在编译器允许自己知道 a_pet1
是一个 Cat
,因此允许调用 Cat
方法。
如果您不想更改 a_pet1
的类型(这是一个合理的请求),这意味着您希望在 Pet
上支持 make_sound
,您有将其添加到类型 Pet
:
class Pet {
protected:
string name;
public:
Pet(string n)
{
name = n;
}
void make_sound();
void run()
{
cout << name << ": I'm running" << endl;
}
};
现在,a_pet1->make_sound()
将被允许。它将尝试调用 Pet::make_sound
,这是 而不是 Dog::make_sound
,因为我们没有提供 定义 Pet::make_sound
,这将在 link 时导致错误。
如果您希望 Pet::make_sound
分派给它的派生方法,您必须告诉编译器这就是您想要的。如果你正确使用 virtual
关键字,C++ 将为你编写调度代码,如下所示:
class Pet {
protected:
string name;
public:
Pet(string n)
{
name = n;
}
virtual void make_sound() = 0;
void run()
{
cout << name << ": I'm running" << endl;
}
};
这里我既做了make_sound
virtual
,又做了纯虚。使其成为虚拟意味着编译器向每个 Pet
和 Pet
派生对象添加信息,因此,当它实际指向派生对象类型而不是 Pet
时,调用者可以找到正确的导出方法。
纯虚函数(=0
)只是告诉编译器基本class方法Pet::make_sound
有意没有实现,这也意味着不允许任何人创建Pet
,甚至是 Pet
派生对象实例,而没有为其实际类型提供 make_sound
实现。
最后,请注意我提到了“允许自己知道”。编译器限制它在编译的某些阶段知道的内容。您关于 a_pet1
是 Pet*
的声明告诉编译器“我不希望您假设这是一个 Cat
,即使我在其中放了一个 Cat
”。在编译的后期阶段,编译器可以记住那个事实。即使在运行时,有时也可以确定对象的实际类型(使用 RTTI)。忘记对象的类型既是有意的也是有限的。
事实证明,“强制遗忘”在许多软件工程问题中非常有用。
还有其他语言,其中对所有对象的所有方法调用都通过动态调度系统进行,除非在运行时尝试,否则您永远不知道对象是否可以接受方法调用。在这种语言中,在任何对象上调用 make_sound
都会编译,而在运行时它会失败或不失败,具体取决于对象是否实际具有 make_sound
方法。 C++ 有意不这样做。有一些方法可以获得这种能力,但它们相对深奥。
在您的示例中,a_pet1 和 a_pet2 是指向 'Pet' class 对象的指针,因此您的编译器只允许您访问其中实际可用的函数class。在这种情况下,'Pet' class 本身不包含 'make_sound' 函数。要解决此问题,您可以在基 class 中定义一个 'make_sound' 函数并将其标记为 'virtual'。这将使基指针上的函数调用始终调用继承 class.
中相应函数的执行
class Pet {
protected:
string name;
public:
Pet(string n)
{
name = n;
}
void run()
{
cout << name << ": I'm running" << endl;
}
virtual void make_sound() {}
};
class Dog : public Pet {
public:
Dog(string n) : Pet(n) {};
void make_sound() override
{
cout << name << ": Woof! Woof!" << endl;
}
};
class Cat : public Pet {
public:
Cat(string n) : Pet(n) {};
void make_sound() override
{
cout << name << ": Meow! Meow!" << endl;
}
};
int main()
{
Pet* a_pet1 = new Cat("Tom");
Pet* a_pet2 = new Dog("Spike");
a_pet1->run();
a_pet1->make_sound();
a_pet2->run();
a_pet2->make_sound();
}
我注册了一个 C++ 课程,我有以下代码片段:
class Pet {
protected:
string name;
public:
Pet(string n)
{
name = n;
}
void run()
{
cout << name << ": I'm running" << endl;
}
};
class Dog : public Pet {
public:
Dog(string n) : Pet(n) {};
void make_sound()
{
cout << name << ": Woof! Woof!" << endl;
}
};
class Cat : public Pet {
public:
Cat(string n) : Pet(n) {};
void make_sound()
{
cout << name << ": Meow! Meow!" << endl;
}
};
int main()
{
Pet *a_pet1 = new Cat("Tom");
Pet *a_pet2 = new Dog("Spike");
a_pet1 -> run();
// 'a_pet1 -> make_sound();' is not allowed here!
a_pet2 -> run();
// 'a_pet2 -> make_sound();' is not allowed here!
}
我不明白为什么这是无效的。请为此推荐合适的参考文献,充分解释为什么会发生这种情况。
在 C++ 中,任何时候变量的类型和名称都是编译器允许自己知道的。
根据当前范围内的变量类型和名称检查每一行代码。
当你有一个指向基数 class 的指针时,变量的类型仍然是指向基数 class 的指针。它指向的 实际对象 可能是派生的 class,但变量仍然是指向基础 class.
的指针Pet *a_pet1 = new Cat("Tom");
a_pet1 -> run();
// 'a_pet1 -> make_sound();' is not allowed here!
a_pet1
的类型是 Pet*
。它可能指向一个实际的 Cat
对象,但这不是 a_pet1
类型所具有的信息。
在下一行,您正在使用 a_pet1
。您只能 以对该行上的 Pet
指针有效的方式使用它。 a_pet1->make_sound()
不是对 Pet
指针的有效操作,因为 Pet
类型没有 make_sound
方法。
你可以这样做:
Cat *a_pet1 = new Cat("Tom");
a_pet1 -> run();
a_pet1 -> make_sound(); // it now works!
因为我们将 a_pet1
的类型从 Pet*
更改为 Cat*
。现在编译器允许自己知道 a_pet1
是一个 Cat
,因此允许调用 Cat
方法。
如果您不想更改 a_pet1
的类型(这是一个合理的请求),这意味着您希望在 Pet
上支持 make_sound
,您有将其添加到类型 Pet
:
class Pet {
protected:
string name;
public:
Pet(string n)
{
name = n;
}
void make_sound();
void run()
{
cout << name << ": I'm running" << endl;
}
};
现在,a_pet1->make_sound()
将被允许。它将尝试调用 Pet::make_sound
,这是 而不是 Dog::make_sound
,因为我们没有提供 定义 Pet::make_sound
,这将在 link 时导致错误。
如果您希望 Pet::make_sound
分派给它的派生方法,您必须告诉编译器这就是您想要的。如果你正确使用 virtual
关键字,C++ 将为你编写调度代码,如下所示:
class Pet {
protected:
string name;
public:
Pet(string n)
{
name = n;
}
virtual void make_sound() = 0;
void run()
{
cout << name << ": I'm running" << endl;
}
};
这里我既做了make_sound
virtual
,又做了纯虚。使其成为虚拟意味着编译器向每个 Pet
和 Pet
派生对象添加信息,因此,当它实际指向派生对象类型而不是 Pet
时,调用者可以找到正确的导出方法。
纯虚函数(=0
)只是告诉编译器基本class方法Pet::make_sound
有意没有实现,这也意味着不允许任何人创建Pet
,甚至是 Pet
派生对象实例,而没有为其实际类型提供 make_sound
实现。
最后,请注意我提到了“允许自己知道”。编译器限制它在编译的某些阶段知道的内容。您关于 a_pet1
是 Pet*
的声明告诉编译器“我不希望您假设这是一个 Cat
,即使我在其中放了一个 Cat
”。在编译的后期阶段,编译器可以记住那个事实。即使在运行时,有时也可以确定对象的实际类型(使用 RTTI)。忘记对象的类型既是有意的也是有限的。
事实证明,“强制遗忘”在许多软件工程问题中非常有用。
还有其他语言,其中对所有对象的所有方法调用都通过动态调度系统进行,除非在运行时尝试,否则您永远不知道对象是否可以接受方法调用。在这种语言中,在任何对象上调用 make_sound
都会编译,而在运行时它会失败或不失败,具体取决于对象是否实际具有 make_sound
方法。 C++ 有意不这样做。有一些方法可以获得这种能力,但它们相对深奥。
在您的示例中,a_pet1 和 a_pet2 是指向 'Pet' class 对象的指针,因此您的编译器只允许您访问其中实际可用的函数class。在这种情况下,'Pet' class 本身不包含 'make_sound' 函数。要解决此问题,您可以在基 class 中定义一个 'make_sound' 函数并将其标记为 'virtual'。这将使基指针上的函数调用始终调用继承 class.
中相应函数的执行class Pet {
protected:
string name;
public:
Pet(string n)
{
name = n;
}
void run()
{
cout << name << ": I'm running" << endl;
}
virtual void make_sound() {}
};
class Dog : public Pet {
public:
Dog(string n) : Pet(n) {};
void make_sound() override
{
cout << name << ": Woof! Woof!" << endl;
}
};
class Cat : public Pet {
public:
Cat(string n) : Pet(n) {};
void make_sound() override
{
cout << name << ": Meow! Meow!" << endl;
}
};
int main()
{
Pet* a_pet1 = new Cat("Tom");
Pet* a_pet2 = new Dog("Spike");
a_pet1->run();
a_pet1->make_sound();
a_pet2->run();
a_pet2->make_sound();
}