C++继承概念
继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为父类,新建的类称为子类。继承代表了 is a 关系
继承示例
// 父类
class Animal {// eat() 函数// sleep() 函数
};//子类
class Dog : public Animal {// bark() 函数
};
继承形式:class 父类 : 继承方式 子类
继承方式: public、protected 或 private 其中的一个,使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public
分析继承代码,熟悉继承
#include <iostream>
using namespace std;//父类
class Shape
{public:void setWidth(int w){width = w;}void setHeight(int h){height = h;}protected:int width;int height;
};// 子类 继承方式为公有继承
class Rectangle: public Shape
{public:int getArea(){ return (width * height); }
};int main()
{Rectangle Rect; //创建子类对象Rect.setWidth(5);Rect.setHeight(7);cout << "Total area: " << Rect.getArea() << endl;return 0;
}
所以:子类不但会继承父类的成员变量,同时也会继承父类的成员函数
继承方式造成的一些具体问题
继承方式有三种:public、private、protected
公有继承(public):
当一个类派生自公有的父类,父类的公有成员也是子类的公有成员,父类的保护成员也是子类的保护成员,父类的私有成员子类继承过去了,但是语法上规定子类对象不管是在类内还是类外都不可以访问
父类private成员在子类中是不能被访问,如果父类成员不想在类外直接被访问,但需要在
子类中能访问,就定义为protected。父类的protected成员继承下来都可以在子类对象类内使用
#include <iostream>
using namespace std;class Person
{
public:void Print(){cout << "Print()" << endl;}
protected:void protected_print(){cout << "protected_print" << endl;}
private:void private_Print(){cout << "private_Print" << endl;}
};class Student : public Person //公有继承
{
protected:int _stunum; // 学号
};int main()
{Student s;s.Print(); //公有继承:子类将父类的public成员继承之后,变成自己的的成员return 0;
}
保护继承(protected):
当一个类派生自保护父类时,父类的公有成员和保护成员将成为子类的保护成员
私有继承(private):
当一个类派生自私有父类时,父类的公有成员和保护成员将成为子类的私有成员
父类和子类对象赋值转换(public继承)
子类对象 可以赋值给 父类的对象 / 父类的指针 / 父类的引用。这里有个形象的说法叫切片或者切割。寓意把子类中父类那部分切来赋值过去。
父类对象不能赋值给子类对象,父类的指针可以通过强制类型转换赋值给子类的指针(子类指针在使用时可能会存在越界风险)。但是必须是父类的指针是指向子类对象时才是安全的
Person p;Student s;p = s; // 这里不存在类型转换,语法支持Person* ptr = &s;Person& ref = s; //注意:这里父类修改给可以改变子类的数据
这不支持private继承和protected继承的子类与父类之间的赋值,是因为存在权限的改变。
class Person
{
public:string _name;int _age;char _sex;void Print(){cout << "_name _age _sex" << endl;}};class Student :private Person // 继承方式为private
{ // 那么在Student中父类的成员都是私有的
protected:string _id;
};int main()
{Person p;Student s;p = s; // 将子类成员赋值给父类时,子类成员与父类成员的访问限定符不一样,所以无法赋值Person* ptr = &s; Person& ref = s; // protected继承同理return 0;
}
继承中的作用域
- 在继承体系中父类和子类都有独立的作用域
- 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 父类::父类成员 显示访问)
- 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏
注意在实际中在继承体系里面最好不要定义同名的成员- 因为函数重载两个函数必须在同一个作用域当中
class Person
{
public:string _name;int _age;char _sex;void Print(){cout << "_name _age _sex" << endl;}};class Student :public Person
{
public:void Print(){cout << "_id" << endl;}
protected :string _id;
};// Student中的Print和Person中的Print不是构成重载,因为不是在同一作用域
// Student中的Print和Person中的Print构成隐藏,成员函数满足函数名相同就构成隐藏
int main()
{Student s;s.Print(); // 子类调用自己的打印函数s.Person::Print(); // 子类调用父类的打印函数(显式调用)return 0;
}
子类的默认成员函数
对于一个空类,经由编译器处理过后,会为它声明一个默认构造函数、一个拷贝构造函数、一个赋值运算符重载、一个析构函数,且这些函数都是public的。称之为默认成员函数。
默认构造函数:
会先(在初始化列表的位置)调用父类的默认构造函数,完成父类的创建,再完成子类的对象构造
构造函数:
如果父类没有默认构造函数(编译器自己提供的无参构造函数、自己定义的全缺省的构造函数、自己定义的无参构造函数),则需要在初始化列表显示调用父类的构造函数
如果此时父类定义了构造函数,但是父类的构造函数时无参的或者是全缺省的构造函数此时子类的构造函数可以定义也可以不用定义。
如果父类显示定义了构造函数,但是父类的构造函数不是无参的或者全缺省的构造函数,此时子类必须要定义自己的构造函数,并且需要在其子类的构造函数初始化列表位置显示调用父类的构造函数,目的就是为了完成从父类中继承下来的成员的初始化。
拷贝构造函数:
如果父类和子类的拷贝构造函数都是编译器生成的,会先(在初始化列表的位置)调用父类的拷贝构造函数。如果父类显示定义了,则需要在子类的拷贝构造函数中显示调用,否则不会完成对父类部分的值拷贝(如果不是在参数列表位置调用的,则会先自动调用父类的默认构造函数,如果父类无默认构造,则会报错)
赋值运算符重载
子类的operator=必须要显式调用父类的operator=完成父类的赋值。
因为子类和父类的运算符,编译器默认给与了同一个名字,所以构成了隐藏,所以每次调用=这个赋值运算符都会一直调用子类,会造成循环,所以这里的赋值显式调用父类的
析构函数
子类的析构函数会在被调用完成后自动调用父类的析构函数清理父类成员。因为这样才能保证子类对象先清理子类成员再清理父类成员的顺序。