8_面向对象程序设计之继承与封装

继承与封装

内容回顾

面向对象程序的三个特征 : 封装, 继承与多态

1_回顾继承

  • 子类从父类继承成员变量

  • 子类从父类继承成员函数

把数据成员直接暴露在外面就会出现合法但不合理的情况, 现实中设计思路就是把不该给别人看的东西藏起来.

2_实现数据隐藏

class Person
{
    public:
        int Age;
        int Sex;

        void work()
        {
            printf("Person:Work()");
        }
};

class Teacher:public Person
{
    public:
        int Level;
};

int main(int argc, char* argv[])
{
    Teacher t;
    t.Age = -1; // 合法但不合理
    t.Sex = 2;
    t.Level = 3;

    t.work();

    return 0;
}

上面那个年龄为 -1 就是合法但不合理

我们希望进行优化, 优化的思路就是, 把不希望别人看到的成员, 私有函数或者数据成员, 都给它藏起来.

private: 来藏起来


class Person
{
    private:    // 私有成员, 外面看不到
        int Age;
        int Sex;

    public:     // 像函数这种成员, 你是希望外面能够用到的, 当然放在 public 里面
        void work()
        {
            printf("Person:Work()");
        }
};

class Teacher:public Person
{
    private:
        int Level;
};

int main(int argc, char* argv[])
{
    Teacher t;
    t.Age = -1; // 合法但不合理
    t.Sex = 2;
    t.Level = 3;

    t.work();

    return 0;
}

现在我们已经把一些不希望外面直接看到的东西藏起来了


现在有了新的问题, 我们没法访问了

class Person
{
    private:    // 私有成员, 外面看不到
        int Age;
        int Sex;

    public:     // 像函数这种成员, 你是希望外面能够用到的, 当然放在 public 里面
        void work()
        {
            printf("Person:Work()");
        }

        // 对外提供两个按钮, 用来给隐藏的私有成员赋值
        void SetAge(int Age)
        {
            this->Age = Age;
        }

        void SetSex(int Sex)
        {
            this->Sex = Sex;
        }
};

class Teacher:public Person
{
    private:
        int Level;

    public:
        // Level 同理, 对外提供按钮给它赋值
        void SetLevel(int Level)
        {
            this->Level = Level;
        }
};

现在你想直接给它的数据成员是访问不了的, 你要给它赋值可以通过函数来给它赋值

现在我们可以通过在函数里面加上约束条件来控制输入的数据了 :

void SetAge(int Age)
{
    if(Age < 0)
    {
        this->Age = 0; // 如果输入进来的值小于 0, 就把年龄设置为 0, 年龄最小就是 0 嘛
    }
    else
    {
        this->Age = Age;
    }
}

你要弄清楚优化之前和优化之后这两种方式的差异. 如果是优化之前的写法, 你对于数据成员是不可控的, 不可控就会出现合法但不合理的情况

  • 为什么要隐藏数据成员 ?
    • 合法但不合理, 手机的电路板为什么没有暴露在外面 ?
    • 根本目的是可控

现在我们有一个思路就是当我们创建对象的时候, 就给它赋值

只要父类提供了构造函数, 那么子类创建对象的时候就一定会调用, 并且默认调用的是父类无参数的构造函数

当你的类继承了别人的时候, 父类一定要记得提供无参数的构造函数, 或者干脆什么构造函数都不提供

class Teacher:public Person
{
    private:
        int Level;

    public:
        Teacher()
        {

        }
        Teacher(int Age, int Sex, int Level):Person(Age,Sex)    // 告诉编译器我要使用父类的这个构造函数来进行初始化, 而不是用无参数的那一个
        {
            this->Level = Level;
        }
        void SetLevel(int Level)
        {
            this->Level = Level;
        }
};

这样的话当我创建子类对象的时候, 可以直接使用父类的构造函数, 直接给父类的私有成员赋值

int main(int argc, char* argv[])
{
    Teacher t(1, 2, 3);

    return 0;
}

现在有个问题, 为什么不直接在子类的构造函数里面调用父类的构造函数, 比如这样 :

class Teacher:public Person
{
    private:
        int Level;

    public:
        Teacher()
        {

        }
        Teacher(int Age, int Sex, int Level)
        {
            Person(Age, Sex);   // 没有意义, 仅仅是在当前构造函数里面临时创建了一个对象, 当这个函数执行完了的时候, 这个对象也就没了
            this->Level = Level;
        }
        void SetLevel(int Level)
        {
            this->Level = Level;
        }
};

我们看反汇编代码, 如果不把函数写在后面 Teacher(int Age, int Sex, int Level):Person(Age,Sex), 编译器仍然会用原来默认无参数的那个构造函数来对它进行初始化; 如果你手动在函数里面调用父类的构造函数, 编译器会在堆栈中临时给你分配一个对象, 函数执行完了这个对象就没了, 没用意义.

观察汇编代码注意对象的地址