🧸🧸🧸各位大佬大家好,我是猪皮兄弟🧸🧸🧸
文章目录
- 一、继承概述
- 二、继承方式与访问限定符
- 三、继承中的作用域
- 四、基类和派生类对象赋值兼容转换
- 五、继承当中默认成员函数的问题
- 1.构造函数
- 2.拷贝构造和赋值运算符重载
- 3.析构函数
- 六、继承与友元
- 七、继承与和静态
一、继承概述
继承(inheritance)机制是面向对象程序设计使代码可以复用的重要手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认识过程,以前我们接触的复用都是函数复用,继承是类设计层次的复用
继承说白了就是类的复用
函数间相似可以提取出来写成函数库,比如algorithm算法库
那么类之间的相似,就要体现到继承
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;//学号
};
这就是一个简单的继承
这个公共的类Person就叫做基类/父类
这个继承的类Student就叫做派生类/子类
二、继承方式与访问限定符
访问限定符*继承方式 = 9 种
C++的继承设计过分复杂,99%都是使用的共有继承
其实就是在基类当中的private成员继承之后不可见之外,其他的按最低权限来更改访问限定符即可,另外,不可见的意思是我是复制了的,但是语法上规定我不能去访问(又被称作隐身)
因为基类的private是不可见的,所以如果你想在基类中不想被类外访问,但是需要在派生类中能访问到(不隐式),那么原来的那个成员就只能是protected,可以看出保护成员限定符是因为继承才出现的
三、继承中的作用域
class Person
{
protected:
string _name = "小李子";//声明+ 缺省参数peter
int _num = 111;
};
class Student:public Person
{
public :
void Print()
{
cout << "姓名:" << _name << endl;//小李子
cout << "学号:" << _num << endl;//999
// cout<<"号码"<<Person::_num<<endl;//111
}
protected:
int _num=999;//学号
};
void main()
{
Student s1;
s1.Print();
}
1.在继承体系当中,基类和子类有独立的作用域
2.子类和父类具有同名成员,子类对象将屏蔽对父类同名成员的直接访问,这种情况被称为隐藏,也叫重定义(只能显示的去访问父类成员)
3.值得注意的是,在隐藏/重定义中,只要成员函数名相同,就构成隐藏,因为在不同的作用域,所以不构成函数重载
class A
{
public :
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
cout << "func(int i)->" << i << endl;
}
};
1.两个函数是不构成函数重载的,因为在不同的作用域
2.两个函数构成隐藏。
int main()
{
B b;
b.fun(10);
//b.fun();//无法这样调用,编译报错,因为已经构成隐藏,需要显示访问父类
b.A::fun();
}
四、基类和派生类对象赋值兼容转换
1.派生类对象可以赋值给基类对象/基类的指针/基类的引用,有个形象的说法叫做切片或者切割,寓意就是把派生类中父类的那部分切来赋值给基类的xx
class Person
{
protected:
string _name;
string _sex;
int _age;
};
class Student:public Person
{
public:
int _No;//学号
};
int main()
{
Student sobj;
Person pobj=sobj;//赋值兼容转换
Person *pp = &sobj;
Person& rp= sobj;
}
这是继承天然支持的,称作切割或者切片(赋值兼容转换)
特殊的原因就是他们是继承关系
而且, Person*pp = &sobj,其实pp还是指向的子类对象,只不过它只能看到子类当中父类的那一部分,引用也是一样的道理,引用只是变成了子类对象父类那一部分的别名。这不是隐式类型转换,所以也不会有具有常性的临时变量
基类的指针或者引用可以通过强转的方式赋值给派生类的指针或者引用,但是基类指针只有指向派生类对象才是安全的。
五、继承当中默认成员函数的问题
对下面的小总结:要不是C++规定,继承的东西要通过父类的成员函数来进行操作的话,理论上来说,子类是可以直接操作的,因为已经拷贝给我了,就是我的
1.构造函数
如下代码所示:
继承的父类的成员,必须调用父类的构造函数初始化
子类只初始化自己的,父类的通过调用它的构造函数进行初始化
class Person
{
public:
Person(const char*name = "peter")
protected:
string _name;
};
class Student:public Person
{
public:
Student(const char*name,int num)
//:_name(name)报错
:Person(name)
,_num(num)
{}
protected:
int _num;//学号
};
2.拷贝构造和赋值运算符重载
和构造函数一样,父类的成员只能通过调用父类的拷贝构造/赋值运算符重载进行处理
3.析构函数
析构函数是这些默认构造函数中最特殊的一个
子类的析构函数和父类的析构函数是构成隐藏的/重定义,是因为由于后面多态的需要,析构函数的名字会被同一处理成destructor(),也就是说,写的是~Person ~Student,编译器看到的是destructor()
//错误代码
~Student()
{
~Person();
}
结果:
为什么Person析构的次数翻倍了????
---->
其实不去显示的调用父类的析构才是正确的
1.为了保证先定义的先析构,后定义的后析构,那么父类的成员是先构造的,显示的去调用析构是无法保证父类的成员先析构的,所以编译器会自动调用父类的析构,所以我们不用显示调用
2.这里的多次析构没出问题的原因是析构中说明都没干,如果说我在里面delete两次资源,则会崩溃
继承从整体来看,父类的成员就应该调用父类的函数来处理
六、继承与友元
友元关系是不能够继承的,也就是说,基类友元不能访问子类私有和保护成员,像访问的话就也要搞成子类的友元,友元会破坏封装,尽量少用,避不开的话还是得用
七、继承与和静态
==在继承中,整个继承体系得static静态成员是同一个,无论派生出了多少个子类。==所以静态成员变量可以用来计数整个继承体系到底创建了多少个实例对象。