C++进阶--继承

简介: C++进阶--继承

概念

继承,允许一个类(称为子类或派生类)从另一个类(称为父类或基类)继承属性和方法

继承的主要目的是实现代码的重用和构建类之间的层次关系。通过继承,子类可以获得父类的特性,包括数据成员和成员函数,而无需重新编写相同的代码。

定义

class Person
{
public:
  void Print()
  {
    cout << "name:" << _name << endl;
    cout << "age:" << _age << endl;
  }
protected:
  string _name = "peter";
  int _age = 18;
};
class Student : public Person
{
protected:
  int _SId;
};
class Teacher :public Person
{
protected:
  int _TId;
};
int main()
{
  Student s;
  
  Teacher t;
  s.Print();
  t.Print();
  return 0;
}

格式

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

可以看出,基类的private成员在派生类无论以何种方式继承都是不可见的这里的不可见是虽然基类的成员仍然被继承到了,但在语法上限制了对private成员的访问,也就说如果要初始化派生类的继承成员,是没被办法初始化的

基类的protected成员以任何方式继承都是可以的,但是不同的继承方式展示出不同的权限;

使用关键字class默认继承方式是private,使用关键字struct默认继承方式是public,但还是写出继承方式比较好;

在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承。

基类和派生类的赋值转换

1.派生类对象可以赋值给基类的对象/基类的指针/基类的引用。由于子类可能会比父类多一些成员什么的,当派生类赋值给基类时,会切割基类没有的数据成员和成员函数,这种做法被称为“切片”;

2.基类对象不能赋值给派生类对象

3.基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用

class Person
{
protected:
  string _name="PersonName"; // 姓名
  string _sex; // 性别
  int _age=10; // 年龄
};
class Student : public Person
{
public:
  void Print()
  {
    cout<<"-name:" << _name << endl;
    cout<<"age:" << _age << endl;
    cout << "Personname:" << Person::_name << endl;
  }
protected:
  int _No; // 学号
  string _name="StudentName";
};
int main()
{
  Student s;
  
  //派生类赋值给基类
  Person p1=s;
  Person* p2 = &s;
  Person& p3 = s;
  //基类赋值给派生类
  //s = p1;//基类不能赋值给派生类
  Student* ps1 = (Student*)p2;//可以通过强制类型转换赋值给派生类
  
  return 0;
}

继承中的作用域

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

2.在继承体系中最好不要定义同名的成员

3. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏

也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)

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

class A
{
public:
  void fun()
  {
    cout << "func()" << endl;
  }
};
class B : public A
{
public:
  void fun(int i)
  {
    A::fun();
    cout << "func(int i)->" << i << endl;
  }
};

派生类的默认成员函数

1.派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认构造函数,那么可以在派生类中的构造函数初始化列表中进行显示调用;

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

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

4.派生类的析构函数会在被调用完成后再调用基类的析构函数完成清理。这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。

class Person
{
public:
  Person(string name = "hama")
    : _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(string 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("傻风",12);//构造
  Student s2(s1);//拷贝构造
  Student s3("傻怕", 18);//赋值
  s1 = s3;
  return 0;
}

继承与友元

友元函数无法继承,也就是说,基类的友元不能访问子类私有和保护成员。

class Student;
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
cout << p._name << endl;
cout << s._stuNum << endl;
}
void main()
{
Person p;
Student s;
Display(p, s);
}

这个好理解,父亲的朋友怎么会是孩子的朋友呢?除非让父亲的朋友也跟孩子做朋友,当然这样就不叫继承了;

继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员,它是独立于基类和派生类之外作用域的;只是恰好在基类中当成员。静态成员伴随着整个运行阶段;

class Person
{
public:
  Person() { ++_count; }
protected:
  string _name; // 姓名
public:
  static int _count; // 统计人的个数。
};
int Person::_count = 0;//初始化
class Student : public Person
{
protected:
  int _stuNum; // 学号
};
class Graduate : public Student
{
protected:
  string _seminarCourse; // 研究科目
};
int main()
{
  Student s1;
  Student s2;
  Student s3;
  Graduate s4;
  cout << " 人数 :" << Person::_count << endl;
  Student::_count = 0;
  cout << " 人数 :" << Person::_count << endl;
}

菱形继承

在上面最近代码中,一个子类只有一个父类时这种继承成为单继承;

多继承:一个子类有两个或以上的父类时称这个继承为多继承

菱形继承:如下

菱形继承的问题:

class A
{
public:
  int _a;
};
// class B : public A
class B :virtual  public A
{
public:
  int _b;
};
// class C : public A
class C :virtual  public A
{
public:
  int _c;
};
class D : public B, public C
{
public:
  int _d;
};

在上面代码中,类D同时继承了类C和类B,而类C和类B又都继承了类A,这就构成了菱形继承的情况了;

菱形继承的主要问题是二义性。由于类D继承了两个父类,并且这两个父类都有相同的成员a,编译器无法确定具体调用B类的a还是C类的a给D类继承,从而导致二义性问题;

int main()
{
  D d;
  //虽然菱形继承可以通过显示调用来解决二义性问题,当仍然存在数据冗余
  d.B::_a = 1;
  d.C::_a = 2;
  d._b = 3;
  d._c = 4;
  d._d = 5;
  return 0;
}

为了解决菱形继承的二义性,C++提供了虚拟继承的机制;就是在类B和类C继承类A时加上关键字virtual,可以告诉编译器在构建类D的对象时只保留一份A类的实例

在实际应用中,我们很少用到多继承,也就没有菱形继承的问题;

组合和继承

继承和组合是面向对象编程中两种常见的代码复用机制。

继承:继承是一种通过创建新类基于已有类的属性和方法来实现代码复用的机制,子类可以继承父类的所有公有和受保护的成员,并且可以在此基础上添加新的特性或修改现有的行为。通过继承可以实现类之间的“is-a”关系,即子类是父类的一种特殊类型。

组合:组合是通过将一个类的对象作为另一个类的成员来实现代码复用的机制。像我们前面的逆向迭代器,stack和queue都是用到了组合;通过组合,一个类可以拥有其他类的对象,并且可以通过调用这些对象的方法来实现自己的功能。组合主要用于表示“has-a”关系,即一个类包含另一个类的对象。

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