详解C++的继承

-- TOC --

一直有些困惑,C++继承的时候,构造函数和析构函数是按什么顺序调用的,子类如何调用父类的接口。为此,我专门写了一段测试代码,完美的搞清楚了这些问题。

请看代码:

#include <cstdio>


struct aa {
    aa() {
        printf("aa constructor\n");
    }
    void init() {
        a = 123;
    }
    ~aa() {
        printf("aa destructor\n");
    }

    unsigned int a;
};


struct bb: aa {
    bb() {
        printf("bb constructor\n");
    }
    void init() {
        aa::init();
        b = 234;
    }
    ~bb() {
        printf("bb destructor\n");
    }

    unsigned int b;
};


struct cc: bb {
    cc() {
        printf("cc constructor\n");
    }
    void init() {
        bb::init();
        c = 345;
    }
    ~cc() {
        printf("cc destructor\n");
    }

    unsigned int c;
};


int main(void){
    cc c{};
    c.init();
    printf("%u %u %u\n", c.a, c.b, c.c);
    return 0;
}

代码中定义了3个类,单线继承关系,每个类包含一个自己的成员变量,每次继承,都增加一个成员变量,并增加一个init接口用来给这个变量赋值。在init接口中,调用父类的init接口,去初始化在父类中定义的成员变量。

以上代码的运行效果:

$ g++ -Wall -Wextra test.cpp -o test
$ ./test
aa constructor
bb constructor
cc constructor
123 234 345
cc destructor
bb destructor
aa destructor

各位同学可以清晰的看到,构造函数的调用,从最开始的父类开始,一层层往下。而析构函数的调用顺序刚好相反。init接口成功调用父类接口,没有异常。

cc::init也可以直接调用aa::init,这样会导致b没有初始化,打印出来是个随机值。

但,上面的测试代码,让我产生了一个新的困惑,子类一定会调用父类的构造和析构接口吗?

首先,如果父类是POD,即C语言的struct,只有数据,没有成员函数。继承这样的父类,只是继承了一推数据,父类没有任何接口可供调用。比如下面的代码:

#include <cstdio>


class aa {
public:
    unsigned int va;
};


class bb: public aa {
public:
    bb(unsigned int a) {
        va = a;
    }
};


int main(void) {
    bb b(12);
    printf("%u\n", b.va);
    return 0;
}

使用class关键词,要多写一些代码,而且默认是private,我更喜欢默认是public的struct!

如果父类存在函数接口,此时子类就肯定会调用父类的构造或析构接口,显式地或隐式地。

下面是显式地调用示例:

#include <cstdio>


class aa {
public:
    unsigned int va;
    aa(unsigned int a):
        va{a} {
    }
};


class bb: public aa {
public:
    bb(unsigned int a):
        aa{a} {
        // aa(a);  // we don't need create an aa object
    }
};


int main(void) {
    bb b(12);
    printf("%u\n", b.va);
    return 0;
}

基类aa定了一个数据va,也定义了初始化va的代码。子类bb在实例化的时候,在初始化列表中,初始化aa中定义的数据。

下面是隐式地调用示例:

#include <cstdio>


class aa {
public:
    unsigned int va;
    aa() {
        printf("aa constructor\n");
    }
    aa(unsigned int a):
        va{a} {
    }
};


class bb: public aa {
public:
    bb(unsigned int a) {
        va = a;
    }
};


int main(void) {
    bb b(12);
    printf("%u\n", b.va);
    return 0;
}

运行这段代码,会看到:

$ g++ -Wall -Wextra test.cpp -o test
$ ./test
aa constructor
12

基类aa的无参数的constructor,被隐式地调用了。

以上示例,都是省掉了destructor,因为都是简单数据类型,destructor编译自动提供了。

本文链接:https://cs.pynote.net/sf/c/cpp/202209062/

-- EOF --

-- MORE --