目录 Table of Contents
继承与封装
内容回顾
面向对象程序的三个特征 : 封装, 继承与多态
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)
, 编译器仍然会用原来默认无参数的那个构造函数来对它进行初始化; 如果你手动在函数里面调用父类的构造函数, 编译器会在堆栈中临时给你分配一个对象, 函数执行完了这个对象就没了, 没用意义.
观察汇编代码注意对象的地址