C++-带你初步走进继承(1)

简介: C++-带你初步走进继承(1)

1.继承的概念及定义


1.1继承的概念

继承 (inheritance) 机制是面向对象程序设计 使代码可以复用 的最重要的手段,它允许程序员在 保

持原有类特性的基础上进行扩展 ,增加功能,这样产生新的类,称派生类。继承 呈现了面向对象

程序设计的层次结构 ,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用, 继

承是类设计层次的复用。

大家都知道c++的三大特性就是:面向对象,继承,多态。那么继承最重要的特点就是代码的复用。接下来我来带大家了解一下继承的概念。

下面我写了三个类,Student类和Teacher类都继承了Person类,当我们调用监视窗口可以看到,Person类里面的成员变量和成员函数在Student类和Teacher类中都有,并且Student类和Teacher类还多了自己的成员变量。

class Person
{
public:
  void Print()
  {
  cout << "name:" << _name << endl;
  cout << "age:" << _age << endl;
  }
protected:
  string _name = "xxx";
  int _age = 18;
};
class Student : public Person
{
protected:
  int _stuid; // 学号
};
class Teacher : public Person
{
protected:
  int _jobid; // 工号
};
int main()
{
  Student s;
  s.Print();
  Teacher t;
  t.Print();
  return 0;
}

00e7302020d34392bc039dbb635667e2.png

1.2 继承定义

1.2.1定义格式

下面我们看到 Person 是父类,也称作基类。 Teacher是子类,也称作派生类。 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。

34952324b1f34813b4cc17efd6b74584.png

1.2.2继承关系和访问限定符


6871a4e8a13644fe9478c93d11b6a5a5.png

1.2.3继承基类成员访问方式的变化

53cdad1275d84dd7857cf51cdeecabf3.png

总结:

1. 基类 private 成员在派生类中无论以什么方式继承都是不可见的。这里的 不可见是指基类的私 有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面 都不能去访问它 。

2. 基类 private 成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected 。 可以看出保护成员限定符是因继承才出现的 。

3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min( 成员在基类的访问限定符,继承方式 ) , public > protected

> private 。

4. 使用关键字 class 时默认的继承方式是 private ,使用 struct 时默认的继承方式是 public , 不过

最好显示的写出继承方式 。

5. 在实际运用中一般使用都是 public 继承,几乎很少使用 protetced/private 继承 ,也不提倡

使用 protetced/private 继承,因为 protetced/private 继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

当我把Student变成保护继承之后,就会发生报错,因为Print就变成了保护函数,访问不了变量。


2.基类和派生类对象赋值转换

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用 。这里有个形象的说法叫切片

或者切割。寓意把派生类中父类那部分切来赋值过去。

基类对象不能赋值给派生类对象。

a7970faa7a5048928199096b6bfab2a8.png

e4b59ff37bc14f6b8c592f485973b558.png

3.继承中的作用域

1. 在继承体系中 基类 和 派生类 都有 独立的作用域 。

2. 子类和父类中有同名成员, 子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏, 也叫重定义。 (在子类成员函数中,可以 使用 基类 :: 基类成员 显示访问 )

3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

4. 注意在实际中在 继承体系里 面最好 不要定义同名的成员 。

我在Person类和Student类都写了一个fun函数,这两个fun函数只有参数不同,那么是构成重载吗?不是,这两个函数构成隐藏 ,为什么呢?因为两个类的作用域是独立的,而构成重载的前提是同一作用域。

f80ad7a5249d426c8cd15bfc8b53c061.png

fd6f36052db84e928fbf2afe6f40c835.png


如果出现基类和派生类定义了同名成员,可以使用使用 基类::基类成员 显示访问。


ab593a0fd49549038912b8e24addee3d.png

9090aeca433f4b5abff9fd6df91b7a89.png


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

6 个默认成员函数, “ 默认 ” 的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类

中,这几个成员函数是如何生成的呢?

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认

的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。

2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。

3. 派生类的 operator= 必须要调用基类的 operator= 完成基类的复制。

4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能

保证派生类对象先清理派生类成员再清理基类成员的顺序。

5. 派生类对象初始化先调用基类构造再调派生类构造。

6. 派生类对象析构清理先调用派生类析构再调基类的析构。

7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同 ( 这个我们后面会讲

解 ) 。那么编译器会对析构函数名进行特殊处理,处理成 destrutor() ,所以父类析构函数不加

virtual 的情况下,子类析构函数和父类析构函数构成隐藏关系。

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:
protected:
  int _id;//学号
};
int main()
{
  Student s;
  return 0;
}

当我实例化一个Student的对象s时,调用了父类的构造和析构函数,这就说明了当派生类进行实例化时,会调用基类的构造函数来构造派生类中基类的成员。


28796ae80c51423590be0d19fe0d4a2a.png

当我自己写了一个Student的构造函数,那么怎么初始化基类的成员呢?可以在初始化列表调用基类的构造函数,然后我们可以看到,是先调用了基类的构造函数,再构造派生类的成员。

e70711ae60f249fcb2cca1196d8eb2e6.png


90e445b6cc204db683e4782757e0a2b0.png

那么拷贝构造和赋值重载怎么构造的呢?也是差不多的,使用基类的构造函数完成对基类成员的构造,然后使用赋值的切片,把基类的成员切过去构造。

fa887860a24f43eeb3b104e929c49c9c.png

ff369664b6b04bae9f9c99a598531818.png


f20ac2d1df054630a52c31cbc936f22b.png

那析构函数呢?析构函数需要显示调用,而且还有一个特殊处理,就是析构要先析构派生类再析构基类。因为先析构基类的话,万一派生类使用了基类的成员,而且基类已经被析构,有可能出现基类资源已经清理释放掉了,然后派生类还去访问了基类的成员,就会存在野指针的问题。所以在派生类的析构函数被调用之后,基类的析构函数会自动调用。

73e65c75e197445aa97dfae27f1f9d74.png

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 id)
  :_id(id)
  ,Person(name)
  {
  cout << "Student()" << endl;
  }
  Student(const Student& s)
  :Person(s)
  ,_id(s._id)
  {
  cout << "Student(const Student& s)" << endl;
  }
  Student& operator=(const Student& s)
  {
  cout << "Student& operator=(const Student& s)" << endl;
  if (this != &s)
  {
    _id = s._id;
    Person::operator=(s);
  }
  return *this;
  }
  ~Student()
  {
  //Person::~Person();
  cout << "~Student()" << endl;
  }
protected:
  int _id;//学号
};
int main()
{
  Student s1("张三",18);
  Student s2(s1);
  Student s3 = s1;
  return 0;
}


今天的分享到这里就结束了,感谢大家的阅读!

相关文章
|
4月前
|
编译器 C++
【C++】详解C++的继承
【C++】详解C++的继承
|
27天前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
74 11
|
25天前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
51 1
|
25天前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
33 1
|
19天前
|
安全 编译器 程序员
C++的忠实粉丝-继承的热情(1)
C++的忠实粉丝-继承的热情(1)
13 0
|
25天前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
26 0
|
25天前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
31 0
|
2月前
|
C++
C++(二十)继承
本文介绍了C++中的继承特性,包括公有、保护和私有继承,并解释了虚继承的作用。通过示例展示了派生类如何从基类继承属性和方法,并保持自身的独特性。此外,还详细说明了派生类构造函数的语法格式及构造顺序,提供了具体的代码示例帮助理解。
|
2月前
|
C++
c++继承层次结构实践
这篇文章通过多个示例代码,讲解了C++中继承层次结构的实践应用,包括多态、抽象类引用、基类调用派生类函数,以及基类指针引用派生类对象的情况,并提供了相关的参考链接。
|
3月前
|
安全 Java 编译器