c++语言三大特点:继承,封装,多态。
先简单介绍一下封装:通过类的设计将数据和方法放在一起,可以对外访问的设为public,不可以的设为protected或者private,同时,还可以在一个类当中封装另一个类,通过typedef进行细节调成,成为全新的类型。
继承的引入和概念
下面我们来详细介绍一下继承。通过现实生活中的例子我们可以看出很多事物会有一些共同的特点,区别只是一部分,例如Student类和Teacher类,这两个类中我们可以发现name,age和address这些属性是共同的,所以我们可以抽象出一个父类Person。
#include<iostream>
using namespace std;
class Person
{
public:
string name;
protected:
int age;
private:
string address;
}
class Student:public Person//这里的public是继承方式
{
public:
int id;
protected:
int grade;
string major;
}
class Teacher:public Person
{
public:
string major;
private:
int id;
}
父类的成员继承到子类之后的访问权限不仅要看在父类的权限,还要观察继承方式,可以简单概括,那个权限可以访问的范围小,就是那个权限。基类private成员无论是以什么方式继承在派生类当中都是不可以访问的。class的默认继承方式是private,struct的默认继承是public。
父类和子类的关系可以看成是每一个子类的对象都是一个特殊的父类对象。可以用当用子类对象来初始化父类对象时,可以通过切片的方式理解,将子类中属于父类的部分切片赋给父类对象。但是父类对象不能给子类对象赋值。
void Test ()
{
Student sobj ;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj ;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
sobj = pobj;
// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj
Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
ps1->id = 10;
pp = &pobj;
Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
ps2->id = 10;
}
基类和派生类有着不同的作用域,如果子类和父类中有同名成员,当子类对象去调用此成员时,会对子类成员进行访问而不是父类,这种叫做隐藏,只需要函数名相同就够成立。注意,父类当中的友元关系并不是子类的友元关系。
派生类的默认成员函数
派生类的成员函数基本都要调用父类的默认成员函数来构造或者拷贝或者清理基类的成员,再调用派生类的函数。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
在构造的时候,先调用父类的构造函数,再调用子类的构造函数。析构相反,先调用子类的,在调用父类的。
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例
菱形继承
由于c++允许多继承的出现,所以会出现菱形继承。
如上图所示,Assistant继承了Studnet和Teacher两个类,两个类当中都有Person父类的name,age,address。此时会造成两个问题:
1.数据冗余,Person类的信息存储了两边
2.二义性:在Assistant的对象中对name进行访问时无法明确访问的说哪一个name,那么运行会报错。
解决方法是在菱形继承的中间部分的两个类前面加上virtual,在此处就是Student和Teacher类。
此时在内存当中,只会存储一份Person的信息,内存的信息如下图所示
在Student和Teacher的内存当中会有=存一个偏移量指向基类Person,这样就不会产生二义性。