封装:
1.在类中,把数据和方法放在一起,只展示成员函数,不展示定义的数据为私有。
2.一个类型放到另一个类型里面,通过typedef成员函数调整,封装另一个全新的类型。相当于是一个包装。
继承:
struct 和 class 都可以继承。struct 默认的继承方式和访问限定符都是公有,class 的都是私有。
继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程,以前都是函数复用,继承使类设计层次的复用。也就是说,对于很多都会共有的信息放在一个大类中,然后对于各个对象而言,想创建自己的信息类,可以继承一下大类,再添加一些自己特有的信息就行了。
下图中,人这个类就是基类,学生,工人,保安就是派生类。
继承的写法如下述代码所示:再代码中,基类的name和age都是portected,因此都是受保护的可以继承,继承后不能修改,但如果是基类的私有成员,继承后派生类中不可见。
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; // 姓名int _age = 18; // 年龄
};
class Student : public Person
{
protected:int _stuid; // 学号
};
class Teacher : public Person
{
protected:int _jobid; // 工号
};
基类的public成员则派生类怎样继承就是怎样的成员。
基类的protected成员派生类public和protected继承都是protected成员,private继承就是private成员。protected成员公有继承后是在类内函数可以访问的,类外的对象不能直接调用。
基类的private成员派生类怎样继承都是不可见(不能被直接访问)成员,但如果父类有public函数可以调用父类私有成员的话,可以在派生类中调用父类中的public成员去访问父类私有成员。
基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。
在公有继承的情况下,基类的公有成员可以在派生类的类内和类外直接访问。基类的保护成员可以在类内函数访问,类外不能访问。基类的私有成员在派生类的类内和类外都不能直接访问,需要通过派生类在类内调用基类的公有函数去访问。
基类和派生类对象赋值转换
在 public 继承的情况下,每个子类都是一个特殊的父类,只需要把子类中父类具有的信息切出来然后给父类就行了。而且会进行赋值兼容。
基类不能给派生类赋值。多的可以给少的,少的不能给多的。
基类和派生类都有独立作用域,子类和父类可以定义同名成员,但子类成员会屏蔽父类同名成员的直接访问,叫做隐藏。要想访问 - - - 基类::基类成员 - - - 这种方式显示访问。
同一个作用域才会重载,父子类中的同名函数就不可能构成重载,因为父子类就不是一个作用域。只要是父子类的成员函数,只看函数名,函数名相同就是隐藏,参数可同可不同。
在派生类中,构造函数的特征是父类+自己,父类的调用父类构造函数初始化(复用),可以把父类成员当成一个整体规定调用默认构造,子类成员为内置类型和自定义类型。
就是说在子类对象的构造中中,父类就是当成子类的一个自定义类型就可以了,一般按照自定义类型处理父类成员。
对于下述代码,对于Student中对Perason的操作都是去从子类中调用父类的四大函数就行了。子类的赋值、析构也会隐藏父类。
构造是先父后子,析构得保证先子后父。
如果在子类的析构函数中显式调用父类析构函数,就绝对是先父后子的析构顺序,因此必须得自动调用,不要去自己写。子类调用结束后会自动调用父类析构。
class Person
{
public:Person(const char* name): _name(name){cout << "Person()" << endl;}Person(const Person& p): _name(p._name){cout << "Person(const Person& p)" << endl;}Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}~Person(){cout << "~Person()" << endl;}void Print(){cout << "name:" << _name << endl;}protected:string _name = "peter"; // 姓名
};
class Student : public Person
{
public:Student(int num, const char* str, const char* name):Person(name), _num(num), _str(str){cout << "Student()" << endl;}Student(const Student& s) :Person(s) //子类对象可以赋值给父类,就把子类对象自然切割就给父类了, _num(s._num), _str(s._str){}Student& operator=(const Student& s){if (this != &s){Person::operator=(s); //继承后会隐藏父类operator=,因此得显示调用_num = s._num;_str = s._str;}return *this;}~Student(){// 显示写无法先子后父//Person::~Person();cout << _name << endl;cout << "~Student()" << endl;// 注意,为了析构顺序是先子后父,子类析构函数结束后会自动调用父类析构}
protected:int _num;string _str;
};int main()
{Person p("peter");Person p1(p);Student s1(1, "xxxx", "张三");Student s2(s1);Student s3 = s2;return 0;
}
如果基类内中有静态成员,则这个静态成员属于当前类和所有继承类,也就是说只有一份静态成员。
如:下述代码中p和s只有一份_count
class Person
{
public:Person() { ++_count; }
protected:string _name; // 姓名
public:static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:int _stuNum; // 学号
};
class Graduate : public Student
{
protected:string _seminarCourse; // 研究科目
};
void TestPerson()
{Student s;Person p; // p和s只有一份count
}
菱形继承:
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant的对象中Person成员会有两份。如果Person中有sex 和 年龄等信息,只有一份,但是集成到Assistant就会产生数据冗余和二义性的问题。
菱形继承可以指定访问临时解决二义性的问题。下述代码为面对上图的情况:
void Test ()
{// 这样会有二义性无法明确知道访问的是哪一个Assistant a ;a._name = "peter"; //无法访问// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";
}
菱形继承是一个极其复杂的东西,不建议使用。
虚拟继承(virtual)可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。
假设有一个A类,B、C类虚拟继承A类,然后D类继承B、C类。每个类存放各自的成员为int a\b\c\d。
下图可以看到:也就是说,虚拟继承后存放了一个新的内容,这个地址的内容加上自己本身的地址,就指向了最早的存放A类的地址。
存放的内容就是切片时的指针偏移量。