继承的定义
通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。
简单的来说,继承就是新的类可以继承一个已经存在的类,同时新创建的类拥有所继承的类的成员,但要注意的是,我们在继承的时候也会有继承的权限,并不是我们继承了旧类的成员,就可以访问,同是,继承的成员我们虽然权限不够,不能进行访问,但是旧类的成员就是依旧会被继承,只是我们无法访问到。
对于继承的理解我们可以根据父子间的关系来理解,儿子一定会继承父亲的财产,但是可能因为儿子让父亲不满意,父亲就委托别人去管理要继承给你的财产,这些财产依旧是继承给了儿子,但是儿子却不能自己去支配这个财产。
继承的定义格式
class Person
{
public:
void Print()
{
cout << "name:" << _name << endl;
cout << "age:" << _age << endl;
}
protected:
string _name = "peter"; // 姓名
int _age = 18; // 年龄
};
// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。
//这里体现出了Student和Teacher复用了Person的成员。
首先我们来看着一段代码,我们先定义了一个person类,这个类中有一个打印函数,有两个成员变量,一个用来记录姓名,一个记录年龄。
紧接着我们想要定义一个学生类,首先学生也是人,所以我们可以继承人这个类。学生同样拥有姓名和年龄,但是学生有自己独有的学号。
// 派生类 : 继承方式 基类
class Student : public Person
{
protected:
int _stuid = 100100100; // 学号
};
然后我们在main函数中创建一个学生类。
int main()
{
Student s;
s.Print();
return 0;
}
由于之前的姓名和年龄已经给了默认值,所以当我们使用无参构造时,学生类中的成员变量也将使用默认值。再我们调用学生类的print函数时,将打印出姓名和年龄,由此我们可以证明student类继承了person类中的所有成员。
继承的不同方式
类成员/继承方式 | public继承 | protected继承 | private继承 |
---|---|---|---|
基类的public成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类中不可见 | 在派生类中不可见 | 在派生类中不可见 |
总结:
- 基类private成员在派生类中无论以什么方式继承都是不可见的。即使不可见,但依然会继承在派生类中
- 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
- 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
- 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。
基类和派生类对象赋值转换
- 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切 割。寓意把派生类中父类那部分切来赋值过去。
其实这个操作我们也很好理解,在派生类中有一部分成员在基类中是存在的,所以当我们将派生类赋值给基类时,就会自动匹配在派生类中基类的部分,并将里面值赋给基类的成员。
- 基类对象不能赋值给派生类对象
有了上面派生类向基类赋值的理解,可能有些人会问派生类对象中存在基类的成员,那为什么基类向派生类赋值时不能自动匹配,首先派生类的成员包含基类的成员,同时又拥有自己的成员,派生类向基类赋值时,派生类的成员数据就会冗余,在赋值时对于不匹配,无法赋值的,可以将这份数据进行抛,但是基类向派生类赋值是,基类的成员是无法满足派生类的成员,导致操作系统不知道将这些数据该分配给谁。
- 基类的指针可以通过强制类型转换赋值给派生类的指针。但是必须是基类的指针是指向派生类对象时才是安全的。
class Person
{
protected :
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
public :
int _No ; // 学号
};
void Test ()
{
Student sobj ;
// 基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj
Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
ps1->_No = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
ps2->_No = 10;
}
继承中的作用域
- 在继承体系中基类和派生类都有独立的作用域。
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
- 注意在实际中在继承体系里面最好不要定义同名的成员
继承的友元与静态成员
- 友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
- 基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。