一、封装
1.什么是封装
封装是将数据与方法进行结合,隐藏对象的部分属性和实现细节,对外开放一些接口,通过这些接口约束,类外可以合理的访问类内的属性
2.封装的作用
封装可以让数据隐藏
让类外合理访问类内的数据
3.为什么需要封装
将一个对象的属性和行为结合在一起更符合人们对事务的认知,通过访问限定符将部分功能开放出来域其他对象进行交互,外部用户是不需要知道具体的实现细节的,即使知道了,也只会增加使用和维护的难度,让事情变得复杂
4.举例
乘火车
- 售票系统:负责售票,用户凭票进入,对号入座
- 工作人员:售票、咨询、安检、保全、卫生等
- 火车:带用户到目的地
例如我们坐火车买票,我们只需要知道票在哪买的,去哪里可以乘车,不需要去了解火车的构造,购票系统的内部操作,但是火车站不能不管理,它需要“封”起来,只提供部分通道供乘客进入,不能就直接暴露出来,如果从任何地方都能上车,那整个流程就乱套了
二、继承
1.什么是继承
继承机制是面向对象程序设计代码可以复用的重要手段,它允许程序在保持原有类特性的基础上进行扩展,增加功能,产生的新类称为派生类
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "limengru";//姓名
int _age = 21; //年龄
};
//学生类公有继承了Person类,person的成员函数和成员变量都会变成子类的一部分
class Student : public Person
{
protected:
int _stuid;//学号
};
1.1 继承定义格式
1.2继承关系和访问限定符
1.3.继承基类成员访问方式的变化
类成员/继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
2.基类和派生类对象的赋值转换
- 1.派生类对象可以赋值给基类对象/基类指针/基类的引用(切片或切割,指把派生类中父类的那部分切割下来,赋值过去)
- 2.基类对象不能赋值给派生类的对象
- 3.基类的指针可以通过强制类型转换赋值给派生类的指针
3.继承中的作用域
- 1.在继承体系中基类和子类都有独立的作用域
- 2.子类和父类拥有同名成员 ,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义
- 3.注意:如果是成员函数的隐藏,只需要函数名相同就构成隐藏
- 4.所以实际中在继承体系里最好不要定义同名的成员
举例1
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "limengru";//姓名
int _age = 21; //年龄
};
//学生类公有继承了Person类,person的成员函数和成员变量都会变成子类的一部分
class Student : public Person
{
protected:
int _stuid;//学号
string _name = "dameinv";
};
可以看到上面的代码虽然没有错误,但是基类和子类的
_name
构成了隐藏关系,非常容易混淆
举例2
class A
{
public:
void fun()
{
cout << "fun()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
A::fun();
cout << "fun(int i)" << endl;
}
};
上面这个例子,A和B中的
fun
函数不构成重载,因为不在同一个作用域
但是这两个fun
构成隐藏,因为成员函数满足函数名相同就构成隐藏
4.派生类的默认成员函数
在之前的学习中,我们知道类会有六个默认的成员函数,即使我们不写,编译器也会为我们自动生成一个,那么派生类中这几个“特殊”的函数是怎么生成的呢?
- 1.派生类的构造函数必须调用基类的构造函数初始化基类那一部分的成员,如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用
- 2.派生类的拷贝构造函数必须调用基类的拷贝构造函数完成基类的拷贝和初始化
- 3.派生类的
operator=
必须调用基类的operatr=
完成基类的赋值 - 4.派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员,因为这样才能保证派生类对象先清理派生类成员,再清理基类成员函数的顺序
- 5.派生类对象初始化先调用基类的构造函数,再调用派生类的构造函数
- 6.派生类的对象的清理先调用派生类的析构函数,再调用基类的析构函数
5.继承和友元
友元关系是不能继承的,基类的友元函数不能访问子类的私有和保护成员
#include <iostream>
#include <string>
using namespace std;
class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name;
};
class Student : public Person
{
protected:
int _stuNo;
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNo << endl;
}
正如上面的代码,在编译后会给我们报出如下图的错误,代表即使是基类的友元,也不能访问子类的私有和保护成员
6.继承和静态成员
基类定义了static
静态成员,则整个继承体系里面只有一个这样的成员,无论派生出多少个子类,都只有一个static
成员实例
1.举例
#include <iostream>
#include <string>
using namespace std;
class Student;
class Person
{
public:
Person() {
++_count; }
protected:
string _name;
public:
static int _count;
};
int Person::_count = 0;//设置_count的值为0
class Student : public Person
{
protected:
int _stuNo;
};
class Graduate : public Student
{
protected