2

动机

最近一直在使用C++写win32程序,用了一些库,里面提供的类和demo各种是virtual这个关键字,一直不是很明白到底是啥用,于是查看了一些文档,写小程序来实验它的作用。

结论

virtual这个关键字的发挥作用是在子类去继承父类的时候。比如:

class Person
{
public:
    void foo1()
    {
        // do ...
    }
    
    virtual void foo2() 
    {
        // do ...
    }
};

像上面的代码,如果类Person就一直被实例化使用,但是没有类去继承它的话,那么这个virtual实际上并没有什么卵用。foo2()方法和foo1()是一样的。

当它被继承的时候,有两种情况,覆写(override)这个foo2()方法,或者不覆写它。比如这样:

class Student : public Person
{
public:
    void foo2() { // do something.. }
};

class Teacher : public Person
{
public:
    // no override
};

然后我们使用的时候,如果是子类的实例,调用foo2()方法,理所当然是执行子类中所定义的foo2()方法体。但是当将这个子类的实例强制转型成父类的实例(指针),再去执行foo2()方法的时候,对应的两种情况:子类实现了父类中virtual方法的,调用子类的方法;子类中没有override的,仍然是调用父类中的实现(这不是废话么……)

列个表格大概是这样:

// 大前提是父类中有个`virtual`方法`foo2()`
        是否override foo2()    调用子类实例的foo2()    强转成父类后调用foo2()
子类1          是                执行子类1的foo2()        执行子类1的foo2()
子类2          否                执行父类的foo2()         执行父类的foo2()

// 另一种情况
// 大前提是父类中有个方法`foo2()`,但是没有virtual关键字修饰
        是否override foo2()    调用子类实例的foo2()    强转成父类后调用foo2()
子类1          是                执行子类1的foo2()        执行父类的foo2()
子类2          否                执行父类的foo2()         执行父类的foo2()

与Java的对比

我的感觉好像Java自带这个多态的特性,不需要用什么关键字修饰,某个实例转换成父类后调用方法,默认就会调用子类的实现(如果有的话)。写了个小demo实验了一下,果然如此。

public class Main {

    public static void main(String[] args) {
        Person p = new Person();
        p.foo(); // output: Person foo
        
        Student s = new Student();
        s.foo(); // output: Student foo
        
        Person ps = s;
        ps.foo(); // output: Student foo

    }
    
    static class Person {
        
        public void foo() {
            System.out.println("Person foo");
        }
    }
    
    static class Student extends Person {
        
        public void foo() {
            System.out.println("Student foo");
        }
    }

}

在《Effective Java 2e》中,作者也说了,尽可能的在申明,传参,返回值的时候使用父类和接口,而不要使用实现类。

大概是这样:

ArrayList<String> strList = new ArrayList<String>();    //这样是耿直的写法
List<String> strList = new ArrayList<String>();    //这样更好,因为你可以换后面这个new

// 返回值和参数也是一样,一般能使用接口就尽量使用接口,而不要写死成实现类,这样带来更大的灵活性
public List<String> buildStrList(List<String> raw, AnyInterface interf) {
    // do xxxx
}

总结

  • virtual关键字修饰的方法在子类继承实现后,就可以达到多态的目的(使用父类的指针依然可以调用到子类的实现)。

  • Java中不需要这个关键字来达到多态,覆写方法自带这个功能。

参考


krosshj
152 声望16 粉丝

Developer, Gamer, Artist