C++之继承<2>【详解】

简介: C++之继承<2>【详解】


1. 派生类的默认成员函数

1.1 1. 构造成员函数

  派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。

  无论是否显示的调用基类的构造成员函数,都会自动调用基类的默认成员函数:

#include <iostream>
using namespace std;
class Person
{
public:
  Person(const char* name = "peter")
    : _name(name)
  {
    cout << "Person()" << endl;
  }
  
  string _name; 
};
class Student : public Person
{
public:
  Student(const char* name, int num)
    : _num(num)
  {
    cout << "Student()" << endl;
  }
protected:
  int _num; 
};
int main()
{
  Student s1("jack", 18);
  return 0;
}

显示调用后:

上述的后半段的意义是:如果基类没有默认的构造函数,那么是这样的:

Person(const char* name = "peter")
    : _name(name)
  {}

  可以进行传参来构造对象,如果你在派生类没有显示的调用它,那么不能进行进行传参来构造。

#include <iostream>
using namespace std;
class Person
{
public:
  Person(const char* name = "peter")
    : _name(name)
  {
    cout << "Person()" << endl;
  }
  
  string _name; 
};
class Student : public Person
{
public:
  Student(const char* name, int num)
    : _num(num)
  {
    cout << "Student()" << endl;
  }
protected:
  int _num; 
};
int main()
{
  Student s1("jack", 18); 
  cout << s1._name; 
  return 0;
}

上图可以看到,传入的参数是“jack”, 但是构造出来的对象属性是“peter”。

  至于必须在初始化列表显示的调用,是因为祖师爷定下的规则是,先构造基类再构造派生类,初始化列表是先于构造函数执行的。

  Person(name)在初始化列表中的顺序可以随意改动的,因为初始化列表的执行顺序只跟声明的顺序有关,跟初始化列表中的先后顺序无关。

1.2 拷贝复制

分别是拷贝构造函数和operator=复制函数:

  1. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  2. 派生类的operator=必须要调用基类的operator=完成基类的复制。

上面两条的原因和构造函数的一样,就不在赘述。

下面是验证的代码:

#include <iostream>
using namespace std;
class Person
{
public:
  Person(const char* name = "peter")
    : _name(name)
  {
    cout << "Person()" << endl;
  }
  Person(const Person& p)
    : _name(p._name)
  {
    cout << "Person(const Person& p)" << endl;
  }
  Person& operator=(const Person& p)
  {
    cout << "Person operator=(const Person& p)" << endl;
    if (this != &p)//防止复制相同的对象,相同的就不必进行下面步骤了
      _name = p._name;
    return *this;
  }
protected:
  string _name; // 姓名
};
class Student : public Person
{
public:
  Student(const char* name, int num)
    : Person(name)
    , _num(num)
  {
    cout << "Student()" << endl;
  }
  Student(const Student& s)
    : Person(s)
    , _num(s._num)
  {
    cout << "Student(const Student& s)" << endl;
  }
  
  Student& operator = (const Student& s)
  {
    cout << "Student& operator= (const Student& s)" << endl;
    if (this != &s)//防止复制相同的对象,相同的就不必进行下面步骤了
    {
      Person::operator =(s);
      _num = s._num;
    }
    return *this;
  }
protected:
  int _num; //学号
};
int main()
{
  Student s1("jack", 18);
  Student s2(s1);
  return 0;
}

1.3 构造函数和析构函数的执行顺序

  1. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
  2. 派生类对象初始化先调用基类构造再调派生类构造。
  3. 派生类对象析构清理先调用派生类析构再调基类的析构。
  • 首先,为什么一定先调用基类构造函数再调用派生类的的构造函数呢?
      如果你先调用派生类的构造函数,派生类是继承基类的,那么派生类中就可以使用基类中的属性和行为,但是此时还没有调用基类的构造函数,所以不能这样。
  • 为什么一定先调用派生类的析构函数再调用基类的析构函数呢?
      如果先调用基类的析构函数的话,会释放掉一些变量或指针,那么派生类使用继承过来的这些变量或者指针的时候,它们已经变成了野指针,因此不能如此。

下面是完整代码,大家可以尝试验证:

#include <iostream>
using namespace std;
class Person
{
public:
  Person(const char* name = "peter")
    : _name(name)
  {
    cout << "Person()" << endl;
  }
  Person(const Person& p)
    : _name(p._name)
  {
    cout << "Person(const Person& p)" << endl;
  }
  Person& operator=(const Person& p)
  {
    cout << "Person operator=(const Person& p)" << endl;
    if (this != &p)
      _name = p._name;
    return *this;
  }
  ~Person()
  {
    cout << "~Person()" << endl;
  }
protected:
  string _name; // 姓名
};
class Student : public Person
{
public:
  Student(const char* name, int num)
    : Person(name)
    , _num(num)
  {
    cout << "Student()" << endl;
  }
  Student(const Student& s)
    : Person(s)
    , _num(s._num)
  {
    cout << "Student(const Student& s)" << endl;
  }
  
  Student& operator = (const Student& s)
  {
    cout << "Student& operator= (const Student& s)" << endl;
    if (this != &s)
    {
      Person::operator =(s);
      _num = s._num;
    }
    return *this;
  }
  
  ~Student()
  {
    cout << "~Student()" << endl;
  }
protected:
  int _num; //学号
};
int main()
{
  Student s1("jack", 18);
  Student s2(s1);
  Student s3("rose", 17);
  s1 = s3;
  return 0;
}

2. 继承和友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员

#include <iostream>
using namespace std;
class Student;
class Person
{
public:
  friend void Display(const Person& p, const Student& s);
protected:
  string _name = "zhangsan"; // 姓名
};
class Student : public Person
{
public:
  
protected:
  int _num; //学号
};
void Display(const Person& p, const Student& s)
{
  cout << p._name << endl;
  cout << s._num << endl;
}
int main()
{
  Student s;
  Person p;
  Display(p, s);
  return 0;
}

从上面图中可以看出。

3. 继承与静态成员

  基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。

#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
  Person()
  {
    ++_count;
    ++age;
  }
public:
  static int _count;
  int  age = 0; // 姓名
};
int Person::_count = 0;
class Student : public Person
{
public:
protected:
  int _num; //学号
};
int main()
{
  Student s;
  Person p;
  cout << "Person::_count:  "<<Person::_count<<endl;
  cout << "Person::age:  "<<p.age<<endl;
  return 0;
}

运行结果是:

  由此可见,静态成员_count是共有的,只有一个。


  😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄

相关文章
|
19天前
|
Java C++
C++的学习之路:21、继承(2)
C++的学习之路:21、继承(2)
18 0
|
2月前
|
C++
8. C++继承
8. C++继承
27 0
|
2月前
|
安全 程序员 编译器
C++之继承
C++之继承
|
2天前
|
安全 前端开发 Java
【C++】从零开始认识继承二)
在我们日常的编程中,继承的应用场景有很多。它可以帮助我们节省大量的时间和精力,避免重复造轮子的尴尬。同时,它也让我们的代码更加模块化,易于维护和扩展。可以说,继承技术是C++的灵魂。
10 1
|
2天前
|
安全 程序员 编译器
【C++】从零开始认识继承(一)
在我们日常的编程中,继承的应用场景有很多。它可以帮助我们节省大量的时间和精力,避免重复造轮子的尴尬。同时,它也让我们的代码更加模块化,易于维护和扩展。可以说,继承技术是C++的灵魂。
19 3
【C++】从零开始认识继承(一)
|
19天前
|
安全 编译器 程序员
c++的学习之路:20、继承(1)
c++的学习之路:20、继承(1)
29 0
|
2月前
|
安全 Java 编译器
C++:继承
C++:继承
32 0
|
2月前
|
安全 Java 程序员
【C++练级之路】【Lv.12】继承(你真的了解菱形虚拟继承吗?)
【C++练级之路】【Lv.12】继承(你真的了解菱形虚拟继承吗?)
|
2月前
|
安全 Java 编译器
C++:继承与派生
C++:继承与派生
|
1天前
|
存储 编译器 C++
C++中的继承
C++中的继承
9 0