继承规则及派生类的成员函数

(23) 2024-04-07 07:01:01

继承是面向对象复用的重要手段。通过继承定义一个类,继承是类型之间的关系建模,共享共有的东西,实现各自本质不同的东西。

成员访问限定符&继承关系:

继承规则及派生类的成员函数 (https://mushiming.com/)  第1张

这是一个简单的继承关系:

#include<iostream>
#include<Windows.h>
using namespace std;
class person
{
public:
     char* name;
     char* sex;
     int age;
public:
     void display()
     {
           cout << "name:" << name << "  sex:" << sex << "  age:" << age << endl;
     }
};
class student :public person
{
public:
     int stdnum;
public:
     void show()
     {
           cout << "stdnum:" << stdnum << endl;
     }
};
int main()
{
     student s;
     s.name = "xiaowang";
     s.sex = "man";
     s.age = 18;
     s.stdnum = 2016;
     s.display();
     s.show();
     system("pause");
     return 0;
}

代码执行时的情况:
继承规则及派生类的成员函数 (https://mushiming.com/)  第2张
继承是一种复用手段,在继承关系基类的成员类的成员派生类的成员,从而达到复用多个目的。

三种继承关系下基类成员在派生类的访问关系变化:
继承规则及派生类的成员函数 (https://mushiming.com/)  第3张
总结:

  • 基类的私有成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
  • public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象都是一个父类对象。
  • protected继承/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是has-s的原则,所以大多数情况下不使用这两种继承,绝大多数场景都是公有继承。继承规则及派生类的成员函数 (https://mushiming.com/)  第4张
  • 不管哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见,即不能访问。
  • 使用关键字class默认继承方式是private使用struct时默认的继承方式是public,最好显示出继承方式。

赋值兼容规则–public继承

  1. 子类对象可以赋值给父类对象(切割/切片)继承规则及派生类的成员函数 (https://mushiming.com/)  第5张
  2. 父类对象不能赋值给子类对象
  3. 父类的指针/引用 可以指向子类对象
  4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成,但不能调用成员函数,会崩溃)
class person
{
public:
     char* name;
     char* sex;
     int age;
public:
     void display()
     {
           cout << "name:" << name << "  sex:" << sex << "  age:" << age << endl;
     }
};
class student :public person
{
public:
     int stdnum;//学号
public:
     void show()
     {
           cout << "stdnum:" << stdnum << endl;
     }
};
int main()
{
     person p;
     p.name = "xiaozhang";
     p.age = 18;
     
     student s;
     s.name = "xiaowang";
     p.age = 20;
     
     // 切割/切片
     // 子类可以给父类赋值
     p = s;  
     //父类不能给给子类赋值
     //s=p;
     //父类的指针可以指向子类对象
     person* p1 = &s;
     //父类的引用可以指向子类对象
     person& r1 = s; 
     p1->name = "xx";
     r1.name = "yy";
     //子类的指针/引用不能指向父类(可以通过强制类型转换完成)
     student* p2 = (student*)&p;
     student& r2 = (student&)p;
     p2->name = "xx";
     r2.name = "yy";
     system("pause");
     return 0;
}

继承规则及派生类的成员函数 (https://mushiming.com/)  第6张

派生类的默认成员函数

在继承关系里面,在派生类中如果没有显示定义的这六个成员函数,编译系统则会默认合成这六个默认的成员函数
继承规则及派生类的成员函数 (https://mushiming.com/)  第7张
1.构造函数
子类的构造函数会自动调用父类的构造函数。
派生类构造函数必须使用基类构造函数。
构造函数不同于其他类的方法,应为它创建新的对象,要调用基类中的私有成员,因为派生类不能直接访问基类的私有成员,必须要调用基类方法进行访问,而其他类仅仅是被现有的对象调用而已,所以构造函数不能被继承;
基类的构造函数先被调用,后面是派生类的构造函数,其实,在main函数中,先调用的是派生类的构造函数,基类的构造函数是在派生类的初始化列表中调用

class Person
{
public:
     Person(const char* name = "", int age = 0)
           :_name(name)
           , _age(age)
     {
           cout << "Person()" << endl;
     }
}
class Student : public Person
{
public:
     Student(const char* name, int age, int stunum)
           :Person(name, age)
     {
           _stunum = stunum;
           cout << "Student()" << endl;
     }
}

2.拷贝构造函数
子类的拷贝构造函数会自动调用父类的拷贝构造函数。
拷贝构造会有切片。
在下列情况下使用拷贝构造:

  1. 将新的对象初始化为一个同类对象
  2. 按值将对象传递给函数
  3. 函数按值返回对象
  4. 编译器生成临时对象

如果程序没有显式定义拷贝构造函数,编译器将自动生成一个。
当然,如果想在派生类中构造基类对象,那么不仅仅可以用构造函数,也可以用拷贝构造函数

class Person
{
public:
      Person(const Person& p)
           :_name(p._name)
           , _age(p._age)
     {
           cout << "Person(const Person& p)" << endl;
     }
}
class Student : public Person
{
public:
      Student(const Student& s)
           :Person(s)
     {
           _stunum = s._stunum;
     }
}

3.析构函数
子类析构函数与父类析构函数会构成隐藏(虽然名字不同,但编译器会使他们重名,从而构成隐藏)
为了保证析构顺序,系统会自己调用析构函数,依据栈的规则,先call子类析构,在call父类析构
初始化时,父类先初始化,子类后初始化
析构时,子类先析构,父类后析构。栈的后进先出

class Person
{
public:
     ~Person()
     {
           cout << "~Person()" << endl;
     }
}
class Student : public Person
{
public:
     ~Student() //隐藏了父类的析构函数
     {
           Person::~Person();
           cout << "~Student()" << endl;
     }
}

4.赋值操作符重载
子类对象可以赋值给父类对象
子类的赋值操作符重载会自动调用父类的赋值操作符重载。
赋值运算符是不能被继承的,原因很简单。派生类继承的方法的特征与基类完全相同,但赋值操作符的特征随类而异,因为它包含一个类型为其所属类的形参。
如果编译器发现程序将一个对象赋给同一个类的另一个对象,它将自动为这个类提供一个赋值操作符。这个操作符的默认版本将采用成员赋值,即将原对象的相应成员赋给目标对象的每个成员。
如果对象属于派生类,编译器将使用基类赋值操作符来处理派生对象中基类部分的赋值,如果显示的为基类提供了赋值操作符,将使用该操作符。

class Person
{
public:
      Person& operator=(const Person& p)
     {
           if (this != &p)
           {
                _name = p._name;
                _age = p._age;
           }
           return *this;
     }
}
class Student : public Person
{
public:
     Student& operator=(const Student& s)
     {
           if (this != &s)
           {
                Person::operator=(s);
                _stunum = s._stunum;
           }
           return *this;
     }
}
THE END

发表回复