【C++】你不得不爱的——继承(一)

简介: 【C++】你不得不爱的——继承

1.继承的概念及定义

1.概念

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

持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称 派生类。(子类)

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

那到底是什么意思呢?举个例子:

class Person
{
protected:
  char* _name;
  int _old;
};
class student :public Person
{
private:
  char* _id;
};
int main()
{
  student s;
}

当好多类都需要写Person类中的成员时 ,为了避免数据冗余,就可以使用类的继承,使代码复用,继承是让每一个派生类中,都有一份基类(父类)的成员。

2.继承的定义

继承的方式:(当然继承的目的就是为了让子类可以拥有父类的成员,并访问,所以一般情况下,我们只会进行公有继承:public:)

那么我们来看一下,继承方式和访问之间的关系:


首先必须知道的一点是:基类中有私有成员时,子类中继承的父类的私有成员不可见。


(不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。)


其次就是,基类成员的权限和继承方式的权限,谁的权限更小,在子类中继承的成员就是更小的那个权限。public > protected> private。

如上图所示:2.基类和派生类对象赋值转换

首先得回想起赋值转换这个过程:


不同的类型相互赋值时,中间会产生临时变量,通过临时变量进行赋值转换。


但是若子类和父类进行赋值交换时,并不产生中间的临时变量,而是天然的一个赋值。


(只能向上转换,即子类赋值给父类,字可以给父,父不可以给子)


看下面代码:

class Person
{
protected:
  string _name; // 姓名
  string _sex;  // 性别
public:
  int _age;  // 年龄
};
class Student : public Person
{
public:
  int _No; // 学号
};
int main()
{
  int i = 1;
  double d = 2.2;
  //中间会产生一个临时变量,临时变量具有常性,不可以改变。
    i = d;
  //所以此时ri是中间的临时变量的引用,而不是d的引用,如果不加const,就会放大权限
    const int& ri = d;
    //但父类和子类之间的赋值就不会产生中间的临时变量
    Person p;
  Student s;
  // 中间不存在类型转换,天然的一个赋值
  p = s;
  Person& rp = s;//对s的引用,可以访问和修改成员变量
  rp._age = 1;
  Person* ptrp = &s;
  ptrp->_age++;
  return 0;
}

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

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

切片的具体过程,我们画图来了解:

 

只能向上转换,即子类赋值给父类,字可以给父,父不可以给子


3.继承中的作用域

我们知道,一个类他就是一个域(作用域)。同一作用域不能定义同名的两个变量,但不同作用域它可以定义两个同名变量。所以父类,子类中都有同名的成员变量时,默认会自动访问子类的成员,因为就近原则,若想访问父类的成员,那就可以指定作用域!


同一作用域,定义两个同名函数,且参数不同叫做函数重载;


不同作用域,定义两个同名函数,叫做重定义;


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

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

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

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

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

举例说明:

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

这中情况,函数构成什么???函数重载?? 重定义(隐藏)??编译错误???


首先我们知道两个类中的同名函数,在不同作用域,这就构成了重定义(隐藏)!


若访问父类成员函数即:b.A::fun();


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

1.构造和拷贝构造,赋值

先回顾一下,默认成员函数(无参,全缺省,编译器自己生成)

具体分析:

class Person  //父类
{
public:
  Person(const char* name) 
    : _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)
  {}
  Student(const Student& s)
//子类显示拷贝构造时,父类不可以直接访问进行初始化,必须调用父类自己的显示拷贝构造函数
    :Person(s)
    , _num(s._num)
  {}
  Student& operator=(const Student& s)
  {
    if (this != &s)
    {
//赋值的运算符重载,两个同名函数构成了隐藏,需要指定作用域
      Person::operator=(s);
      _num = s._num;
    }
    return *this;
  }
protected:
  int _num; //学号
};
int main()
{
  Student s1("张三", 18);
  Student s2(s1);
  Student s3("李四", 20);
  s1 = s3;
  return 0;
}

最重要的一句话:父类成员必须调用父类自己的构造函数,拷贝构造完成初始化或拷贝。

或者说:子类中的父类那部分成员由父类自己的构造或者拷贝构造实现初始化或者拷贝。

2.析构函数的两怪!

直接看代码:

class Person
{
public:
  Person(const char* name = " ")
    : _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;
    delete[] p;
  }
protected:
  string _name; // 姓名
  int* p = new int[10];
};
class Student : public Person
{
public:
  Student(const char* name)
    :Person(name)
    , _num(1)
  {}
  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;
  }
  // 第一怪:1、子类析构函数和父类析构函数构成隐藏关系。(由于多态关系需求,所有析构函数都会特殊处理成destructor函数名)
  // 第二怪:子类先析构,父类再析构。子类析构函数不需要显示调用父类析构,子类析构后会自动调用父类析构
  ~Student()
  {
    //Person::~Person();
    cout << "~Student()" << endl;
  }
protected:
  int _num; //学号
};
int main()
{
  Student s("张三");
  return 0;
}

1、子类析构函数和父类析构函数构成隐藏关系。(由于多态关系需求,所有析构函数都会特殊处理成destructor函数名)



目录
相关文章
|
28天前
|
编译器 C++ 开发者
【C++】继承
C++中的继承是面向对象编程的核心特性之一,允许派生类继承基类的属性和方法,实现代码复用和类的层次结构。继承有三种类型:公有、私有和受保护继承,每种类型决定了派生类如何访问基类成员。此外,继承还涉及构造函数、析构函数、拷贝构造函数和赋值运算符的调用规则,以及解决多继承带来的二义性和数据冗余问题的虚拟继承。在设计类时,应谨慎选择继承和组合,以降低耦合度并提高代码的可维护性。
32 1
【C++】继承
|
5月前
|
编译器 C++
【C++】详解C++的继承
【C++】详解C++的继承
|
2月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
90 11
|
2月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
63 1
|
2月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
46 1
|
2月前
|
安全 编译器 程序员
C++的忠实粉丝-继承的热情(1)
C++的忠实粉丝-继承的热情(1)
21 0
|
2月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
39 0
|
2月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
41 0
|
3月前
|
C++
C++(二十)继承
本文介绍了C++中的继承特性,包括公有、保护和私有继承,并解释了虚继承的作用。通过示例展示了派生类如何从基类继承属性和方法,并保持自身的独特性。此外,还详细说明了派生类构造函数的语法格式及构造顺序,提供了具体的代码示例帮助理解。
|
3月前
|
C++
c++继承层次结构实践
这篇文章通过多个示例代码,讲解了C++中继承层次结构的实践应用,包括多态、抽象类引用、基类调用派生类函数,以及基类指针引用派生类对象的情况,并提供了相关的参考链接。