目录
1、继承的概念
继承:继承就是在原有的类的基础之上进行扩展、增加一些功能。
可以这样理解:在古代的时候,皇帝驾崩了之后,就会有一个集运气于一身的幸运之子登上皇位。这位幸运之子继承了老皇帝留下来的江山,可以在这个江山已有的基础之上实施一些新政策、新措施、最终走上富国强兵……
看到这里应该不难理解继承是什么了吧,每一位新生的皇帝都是继承了上一任老皇帝的江山,颁布新政策。而类的继承,就是继承了已有的类,并可以在其基础之上进行扩展。
2、使用场景
在实际的应用中,要实现一个图书管理系统,有学生、老师、各色各样的人……,难道要一一实现每个角色的类吗?那不得烦死了。这时就可以使用继承的方式。学生、老师……都具有姓名,身份证,年龄,住址等共同信息,那就可以定义一个具有共同特征的person类。然后去定义一个学生、老师等等的类就可以来继承这个person类,并且可以在其基础之上进行扩展,增加独有的信息。
//具有共同特征的人类
class Person
{
protected:
string name;
int age;
string id;
string address;
};
//学生类继承了Person类
class student:public Person
{
protected:
//可以在原有的基础之上增加独有的信息:宿舍号
int dormitory_id;
};
int main()
{
Person p;
student st;
return 0;
}
3、继承定义
3.1、如何继承:
Person是要被继承的类,叫父类,也叫基类。student继承了Person类,是子类,也叫派生类。
3.2、继承方式
3.3、继承父类成员访问方式的变化
总结:
1、父类的private成员,不管以什么方式被继承都是private、且不可见的:父类的私有成员被继承到了子类,子类不能访问到父类的private成员,因为语法的限制所以不能去访问,但确实有被继承下来。
2、父类的成员在基类的访问方式变化其实很好记。两个访问方式进行比较取小的那个(public > protected > private ),比如父类的public成员以public继承到子类,相等就以public访问方式。如果是以protected继承,public > protected,protected比public小所以继承到子类的访问权限就会变成protected。如果是以private继承,public > private,private比public小所以继承到子类的访问权限就会变成private。
3、父类的protected、private成员:两个的权限都是一样的,在类外不能访问,但在类里面可以访问。
子类继承了父类的protected、private成员:private成员不能访问,protected可以访问。
4、一般都是使用public继承,很少会用到protected、private继承。也不提倡使用protected、private继承。
5、继承方式如果没有写,class的默认继承方式是private,struct的默认继承方式是public。最好要显示的写出继承方式。
4、基类和派生类对象的赋值转换
子类的对象可以赋值给父类的对象、指针、引用。称为切片或切割。
为什么叫切片呢?请看下图就不难理解了。
class Person
{
protected:
string name;
string _sex;
int _age;
};
class student:public Person
{
protected:
int _id;
};
int main()
{
Person p;
student st;
//会把子类中的父类成员切割出来
p = st;
Person* ptr = &st;
Person& rp = st;
return 0;
}
注:父类对象不能赋值给子类对象,只能子类的对象赋值给父类的对象、指针、引用。
5、继承中的作用域
1、在继承体系中,子类和父类都有独立的作用域。
2.、子类和父类中如果都有同名成员的话,子类将会屏蔽对父类同名成员的直接访问,这种情况叫隐藏,也叫重定义。如果想要在子类中访问父类的同名成员,可以使用 作用域限定符 :: 来显示访问。
3. 需要注意的是:只要子类中有与父类的成员同名就会构成隐藏。
4. 切记在继承体系里面最好不要定义同名的成员。
class Person
{
protected:
string _name="老王";
int _age=28;
};
class student:public Person
{
public:
void printf()
{
cout << "姓名:" << _name << endl;
cout << "没有显示访问,直接访问的是子类中的年龄:" << _age << endl; //因此这里访问到的是子类的_age成员,不会访问到父类的_age成员
cout << "有显示访问的是父类中的年龄:" << Person::_age << endl;
}
protected:
int _age=33; //子类中存在于父类同名的成员
};
int main()
{
student st;
st.printf();
return 0;
}
6、派生类的构造函数原则:
子类的构造原则:先父后子
1、先调用父类的构造函数来初始化继承的父类成员。
2、接着调用自己子类的构造函数来初始化自己的成员。
子类的析构原则:先子后父
1、先调用自己子类的析构函数来析构
2、接着再调用父类的析构函数来析构
class Person
{
public:
Person()
{
cout << "Person" << endl;
}
~Person()
{
cout << "~Person" << endl;
}
};
class Student :public Person
{
public:
Student()
{
cout << "Student" << endl;
}
~Student()
{
cout << "~Student" << endl;
}
};
int main()
{
//子类的构造先调用父类的构造函数接着再调用自己的构造函数:先父后子
//子类的析构先调用自己的析构函数接着再调用父类的析构函数:先子后父
Student st;
return 0;
}
如想显示调用子类中的父类构造函数,请看下面的代码。
class Person
{
public:
Person(string name="")
{
_name = name;
cout << "Person" << endl;
}
~Person()
{
cout << "~Person" << endl;
}
protected:
string _name;
};
class Student :public Person
{
public:
Student(string name="")
:Person(name) //显示调用父类的构造函数
{
cout << "Student" << endl;
}
~Student()
{
cout << "~Student" << endl;
}
};
int main()
{
Student st;
return 0;
}
虽然可以显示的调用父类的构造函数,但是不能显示的调用子类中的父类析构函数,这样的话会出问题:为了保证析构的顺序是先子后父,但是如果在子类的析构函数中去显示调用了父类的析构函数,就会先调用父类的析构函数,接着又析构了自己。这一系列操作完后,编译器又会自动去调用父类的析构函数,就出现多次析构同一块内存的问题了。
因此不需要去显示调用子类中的父类析构函数,等子类析构完,编译器会自动去调用父类的析构函数。
class Person
{
public:
Person(string name)
{
_name = name;
cout << "Person" << endl;
}
~Person()
{
cout << "~Person" << endl;
}
protected:
string _name;
};
class Student :public Person
{
public:
Student(string name = "")
:Person(name)
{
cout << "Student" << endl;
}
~Student()
{
//显示的去调用子类中的父类析构函数,会出现问题
Person::~Person();
cout << "~Student" << endl;
}
};
int main()
{
Student st;
return 0;
}
7、多继承与菱形继承
单继承:一个子类只有一个父类时称这个继承关系为单继承
多继承:一个子类有两个或以上父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况
有了多继承就会有菱形继承,而菱形继承会造成数据冗余和二义性。
1、什么是二义性呢?
菱形继承后,类中就会有多份同样的成员,当想要去访问的时候就会出现二义性,不知道该访问的是哪个父类的成员。虽然可以显示的去调用哪个父类的成员来解决二义性。但是数据冗余依然无法解决。
class Person
{
public:
string _name;
int _age;
};
class student:public Person
{};
class teacher:public Person
{};
class postman :public student, public teacher
{
public:
};
int main()
{
postman pm;
//pm._name = "老王"; //子类中存在多份相同的成员,访问_name的时候不知道要访问哪个父类的成员,因此会报错
//需要指定访问哪个父类的成员,方可解决二义性
pm.student::_name="小王";
pm.teacher::_name="大王";
return 0;
}
2、数据冗余?
看到二义性是什么后,想必也已经知道数据冗余是什么了吧,就是子类中有多份相同的成员,从而造成数据冗余了。
那如何解决数据冗余呢?在腰部的位置加虚继承就可以解决了。
class Person
{
public:
string _name;
int _age;
};
//腰部位置虚继承
class student: virtual public Person
{};
class teacher: virtual public Person
{};
class postman :public student, public teacher
{
public:
};
int main()
{
postman pm;
cout << sizeof(pm) << endl;
return 0;
}
因此不要设计出菱形继承。否则会很复杂。