面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行时间的效果。
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
继承有三种继承方式:公有继承, 保护继承,私有继承。如下图所示:
一个简单的公有继承:
class Person//父类(基类)
{
public:
Person()
{
cout << "Person" << endl;
}
private:
int _a;
};
class Student : public Person//子类(派生类)
{
public:
Student()
{
cout << "Student" << endl;
}
private:
int _b;
};
总结:
1. 基类的私有成员在派生类中是不能被访问的,如果一些基类成员不想被基类对象直接访问,但需要在派生类中能访问,就定义为保
护成员。可以看出保护成员限定符是因继承才出现的。
2. public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。
3. protetced/private继承是一个实现继承,基类的部分成员并未完全成为子类接口的一部分,是has-a 的关系原则,所以非特殊情
况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。
4. 不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,但是基类的私有成员存在但是在子类中不可见(不能
访问)。
5. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
6. 在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承。
继承与转换-赋值兼容规则(公有继承):
1、子类的对象可以赋值给父类(切片)
2、父类的对象不可以赋值给子类
3、父类的指针和引用赋值给子类
4、子类的指针和引用不可以赋值给父类(可以强制类型转换)
class Person//父类(基类)
{
public:
Person()
{
cout << "Person" << endl;
}
private:
int _a;
};
class Student : public Person//子类(派生类)
{
public:
Student()
{
cout << "Student" << endl;
}
private:
int _b;
};
void test()
{
Person p;
Student s;
p = s;//子类可以赋值给父类
//s = p;//父类不能赋值给子类
Person* p1 = &s;//父类的指针和引用可以赋值给子类
Person& p2 = s;
//Student* s1 = &p;//子类的指针和引用不可以赋值给父类
//Student& s2 = p;
Student* s3 = (Student*)&p;//但是可以强制类型转换赋值给父类
Student& s4 = (Student&)p;
}
我们用下图来解释切片到底是一个什么东西:
隐藏:
子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以使用基类::基类成员访问)–隐藏(重定义)
简单明了来说就是就是一个在基类一个在派生类中,函数名相同,不构成重写就构成隐藏。不过这里的重写下面提到,这里先这样写着。
下面我们再来说说派生类的六大默认成员函数:
在我们的派生类中,调用构造函数时,会有一个神奇的事情发生,它先会对在基类继承下来的成员进行初始化,然后再对派生类的成员进行初始化。其他四个和构造函数一样先调用父类的相对应的函数,然后派生类的,不过的是,析构函数有点区别,析构函数先对派生类的成员进行清理,在对父类的成员进行清理,毕竟栈上的规则为后进先出。
拿一个简答的构造函数举个例子吧:
#include<string>
class Person
{
public:
Person(const char* a)
:_a(a)
{}
Person(const Person& p)
:_a(p._a)
{}
protected:
string _a;
};
class Student : public Person
{
public:
Student(const char* a,int b)
:Person(a)
,_b(b)
{}
Student(const Student& s)
:Person(s)
,_b(s._b)
{}
private:
int _b;
};