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”关系,即一个类包含另一个类的对象。

相关文章
|
6天前
|
Java C++
C++的学习之路:21、继承(2)
C++的学习之路:21、继承(2)
14 0
|
30天前
|
C++
8. C++继承
8. C++继承
22 0
|
30天前
|
安全 Java 编译器
C++:继承
C++:继承
32 0
|
1月前
|
安全 Java 编译器
C++:继承与派生
C++:继承与派生
|
3天前
|
设计模式 编译器 数据安全/隐私保护
C++ 多级继承与多重继承:代码组织与灵活性的平衡
C++的多级和多重继承允许类从多个基类继承,促进代码重用和组织。优点包括代码效率和灵活性,但复杂性、菱形继承问题(导致命名冲突和歧义)以及对基类修改的脆弱性是潜在缺点。建议使用接口继承或组合来避免菱形继承。访问控制规则遵循公有、私有和受保护继承的原则。在使用这些继承形式时,需谨慎权衡优缺点。
15 1
|
5天前
|
设计模式 C语言 C++
【C++进阶(六)】STL大法--栈和队列深度剖析&优先级队列&适配器原理
【C++进阶(六)】STL大法--栈和队列深度剖析&优先级队列&适配器原理
|
5天前
|
存储 缓存 编译器
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
【C++进阶(五)】STL大法--list模拟实现以及list和vector的对比
|
5天前
|
算法 C++ 容器
【C++进阶(四)】STL大法--list深度剖析&list迭代器问题探讨
【C++进阶(四)】STL大法--list深度剖析&list迭代器问题探讨
|
5天前
|
编译器 C++
【C++进阶(三)】STL大法--vector迭代器失效&深浅拷贝问题剖析
【C++进阶(三)】STL大法--vector迭代器失效&深浅拷贝问题剖析
|
1月前
|
安全 编译器 程序员
C++中的继承
C++中的继承
22 2