虚函数 - 基类指针

3
我明白为什么需要让基类指针指向派生类对象,但是,我不理解当它本身就是一个基类对象时,为什么需要将其赋值为一个基类对象。请问有人可以解释一下吗?
#include <iostream>
using namespace std;
class base {
     public:
        virtual void vfunc() {
            cout << "This is base's vfunc().\n";
         }
};
 class derived1 : public base {
      public:
            void vfunc() {
                 cout << "This is derived1's vfunc().\n";
    }
};
int main()
{
     base *p, b;
     derived1 d1;
     // point to base
     p = &b;
     p->vfunc(); // access base's vfunc()
     // point to derived1
     p = &d1;
     p->vfunc(); // access derived1's vfunc()
     return 0;
}

3
您的意思是:为什么我们需要将其分配给一个基类对象,您能再解释清楚一点吗? - Rohit Vipin Mathews
@Rohit:在上面的例子中,调用成员函数时,为什么语句是p = &b,而不是直接使用基类指针p,比如p->func()。我知道我错了,但我需要纠正它。 - user980411
4个回答

3
因为指针本身无法执行任何操作。
只有指向有效对象时,您才能利用它。 为什么会出现上述情况? 逐步解释可能会消除您的疑虑。 步骤1:
base *p;

创建一个指针 p,用于存储类 base 对象的地址。但是它没有被初始化,指向内存中任意随机地址。

步骤 2:

p = &b;

将一个有效的base对象的地址赋值给指针p。现在,p包含这个对象的地址。

步骤三:

p->vfunc(); // access base's vfunc() 

区分指针p并调用其所指向的对象上的方法vfunc(),即:b

如果您移除步骤2:,则您的代码仅尝试取消初始化的指针,并且可能导致未定义行为和最可能崩溃。


我似乎理解了关键点。你能否再解释一下? - user980411
1
@user980411:我更新了一下,让它更加详细。希望有所帮助。但实际上你确实需要去找一本好的C++书籍 - Alok Save
谢谢您用简单的语言解释。 - user980411

1

不强制要求调用

p = &b;
p->vFunc();

你可以直接调用

b.vFunc();

两者都会给你相同的结果。

然而,你应该了解虚函数的威力。假设你想存储10个basederived1对象实例并反复调用函数,你将如何做到?或者在将其存储在数组中后,如果您想将其传递给常见函数呢?

vase *p[4];

base b1;
derived d;

p[0] = new base();

p[1] = &b1;

p[2] = new dervied1();    

p[3] = &d;

for (int i =0 ;i <4 ;i++)
{
  p[i]->vFunc();
}

谢谢。下面这行代码 p[0] = new base(); 会返回什么? - user980411
1
@user980411,它将返回基类对象的指针。 - Jeeva
指针的意思是,new base() 会创建一个类型为 base 的对象,并将其地址返回到 p[0] 中吗? - user980411

1

我不是完全确定我完全理解你的问题,但使用虚函数的一个典型情况是当我们编写一个函数并希望它与基类对象或任何派生自它的对象一起工作时:

struct base { 
    virtual void dosomething() = 0;
};

void myfunc(base *b) {
    b->dosomething();
}

当我们编写myfunc时,我们既不知道也不关心所涉及的对象的确切身份--我们只关心它知道如何按命令执行dosomething
就像你展示的那样编写代码,直接将基类或派生类对象的地址分配给指针,这更多是例外而不是规则。你非常正确,在这种情况下,我们并没有从使用指向基类的指针来引用派生对象中获得很多好处。主要的好处来自于像函数这样的东西,当我们编写代码时,派生类甚至可能还不存在,但只要派生类符合规定的接口,它就可以工作。

感谢您的真诚努力。 - user980411

-2

当你想要应用设计模式时,这种引用类型非常适用(你可能需要通过面向对象设计的高级课程,或者从阅读书籍开始:我建议先看《Head First 设计模式》)。

例如,在上述书籍中查看如何在 Java 中实现装饰器模式。

public abstract class Beverage {
String description = "Unknown Beverage";
public String getDescription() {
    return description;
}
public abstract double cost();   
}

那么,假设我们有浓缩咖啡和深度烘焙两个子类:

public class Espresso extends Beverage {

public Espresso() {
    this.description = "Espresso";
}

public double cost() {
    return 1.99;
}
}

public class DarkRoast extends Beverage {
public DarkRoast() {
    description = "Dark Roast Coffee";
}

public double cost() {
    return .99;
}
}

现在,我们要添加装饰器:

public abstract class CondimentDecorator extends Beverage {
public abstract String getDescription();
 }

并构建一些具体的装饰器:

public class Mocha extends CondimentDecorator {

Beverage beverage;

public Mocha(Beverage beverage) {
    this.beverage = beverage;
}

public String getDescription() {
    return beverage.getDescription() + ", Mocha";
}

public double cost() {

    return .20 + beverage.cost();
}
} 

还有另一个包装器:

public class Whip extends CondimentDecorator {
Beverage beverage;

public Whip(Beverage beverage) {
    this.beverage = beverage;
}

public String getDescription() {
    return beverage.getDescription() + ", Whip";
}

public double cost() {
    return .10 + beverage.cost();
}
}

最后,看看主函数中发生了什么以及我们如何利用指向父类的指针:
public static void main(String[] args) {
    Beverage beverage = new Espresso();
    System.out.println(beverage.getDescription() + " $" + beverage.cost());
    Beverage beverage2 = new DarkRoast();
    beverage2 = new Mocha(beverage2);
    beverage2 = new Mocha(beverage2);
    beverage2 = new Whip(beverage2);
    System.out.println(beverage2.getDescription() + " $" + beverage2.cost());

你能猜出输出结果吗?好的:

Espresso $1.99
Dark Roast Coffee, Mocha, Mocha, Whip $1.49

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接