c++继承

c++ 继承

目录:

  • c++ 继承
  • 1.继承的概念和定义
    • 1.1 继承的概念
    • 1.2 继承的定义
      • 1.2.1 定义格式
      • 1.2.2 继承基类成员访问方式的变化
  • 2.基类和派生类的转换
  • 3. 继承中的作用域
    • 3.1 隐藏规则
    • 3.2 考察继承作用域的相关选择题
  • 4.派生类的默认成员函数
    • 4.1 六个常见的默认成员函数
    • 4.2 实现一个不能被继承的类
  • 5.继承和友元
  • 6.继承与静态成员

1.继承的概念和定义

1.1 继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许我们在保持原有类特性的基础上进行扩展,增加方法(成员函数)和属性(成员变量) ,这样产生新的类,称派生类
继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复用,
继承是类设计层次的复用。

下面我们看到没有继承之前我们设计了两个类Student和Teacher,Student和Teacher都有姓名/地址/
电话/年龄等成员变量,都有identity身份认证的成员函数,设计到两个类里面就是冗余的。

当然他们也有一些不同的成员变量和函数,比如老师独有成员变量是职称,学生的独有成员变量是学号;学生
的独有成员函数是学习,老师的独有成员函数是授课。

class student
{
    public:
    //进入校园、图书馆和实验室等需要二维码等身份验证
    void identity()
    {
        //---
    }
    void study()
    {

    }
    protected:
    string _name = "peter"; // 姓名 
    string _address;        // 地址 
    string _tel;            // 电话 
    int _age = 18;          // 年龄 
    int _stuid;             // 学号 
};
class Teacher
{
public:
// 进入校园/图书馆/实验室刷二维码等身份认证 
    void identity()
    {
        // ...
    }
    // 授课 
    void teaching()
    {
        //...
    }
protected:
    string _name = "张三";   // 姓名 
    int _age = 18;          // 年龄 
    string _address;        // 地址 
    string _tel;            // 电话 
    string _title;          // 职称 
};
int main()
{
    return 0;

}

下面我们就将这些公共的成员都放到person类中,student和teacher都继承person这样他们就可以直接服用这些成员, 就不再需要重复的定义这些变量和函数了, 省去了很多的麻烦

class Person
{
public:
    // 进入校园/图书馆/实验室刷二维码等身份认证 
    void identity()
    {
        cout << "void identity()" <<_name<< endl;
    }
protected:
    string _name = "张三";  // 姓名 
    string _address;        // 地址 
    string _tel;            // 电话 
    int _age = 18;          // 年龄 
};
class Student : public Person
{
public:
    // 学习 
    void study()
    {
        // ...
    }
protected:
    int _stuid;             // 学号 
};
class Teacher : public Person
{
public:
    // 授课 
    void teaching()
    {
        //...
    }
protected:
    string title;             // 职称 
};
int main()
{
    Student s;
    Teacher t;
    s.identity();
    t.identity();
    return 0;
}

这个及时简单的继承实现方式。

1.2 继承的定义

1.2.1 定义格式

从前面的代码,我们进行分析:
c++继承_第1张图片

继承方式
public继承
protected 继承
private 继承
访问限定符
public访问
protected 访问
private 访问

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

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

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

  • 实际上面的表格我们进行一下总结会发现,基类的私有成员在派生类都是不可⻅。基类的其他成员在派生类的访问方式(成员在基类的访问限定符,继承方式):
    public>protected>private。

  • 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public不过最好显示的写出继承方式。

  • 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。


// 实例演示三种继承关系下基类成员的各类型成员访问关系的变化   
class Person
{
public :
        void Print ()
        {
                cout<<_name <<endl;
        }
protected :
        string _name ; // 姓名 
private :
        int _age ;         // 年龄 
};
//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected :
        int _stunum ; // 学号 
};


2.基类和派生类的转换

  • public继承的派生类对象可以赋值给基类的指针/基类的引用。这里有个形象的说法叫切片或者切割寓意把派生类中基类那部分切出来,基类指针或引用指向的是派生类中切出来的基类那部分
  • 基类对象不能赋值给派生类对象
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run-TimeType Information)的dynamic_cast来进行识别后进行安全转换。(ps:这个我们后面类型转换章节再单独专门讲解,这里先提一下

c++继承_第2张图片


class Person
{
protected :
    string _name; // 姓名 
    string _sex;  // 性别 
    int    _age;  // 年龄 
};
class Student : public Person
{
public :
        int _No ; // 学号 
};
int main()
{
    Student sobj ;
    // 1.派生类对象可以赋值给基类的指针/引用 
    Person* pp = &sobj;
    Person& rp = sobj;
    
    // 派生类对象可以赋值给基类的对象是通过调用后面会讲解的基类的拷⻉构造完成的 
    Person pobj = sobj;
    
    //2.基类对象不能赋值给派生类对象,这里会编译报错 
    sobj = pobj;
    
    return 0;
}

3. 继承中的作用域

3.1 隐藏规则

  • 在继承体系中基类和派生类都有独立的作用域
  • 派生类和基类中有同名成员,派生类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。(在派生类成员函数中,可以使用基类::基类成员显示访问)
  • 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  • 注意在实际中在继承体系里面最好不要定义同名的成员。
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆 
class Person
{
protected :
       string _name = "小李子"; // 姓名 
       int _num = 111;         // 身份证号 
};
class Student : public Person
{
public:
       void Print()
       {
               cout<<" 姓名:"<<_name<< endl;
               cout<<" 身份证号:"<<Person::_num<< endl;
               cout<<" 学号:"<<_num<<endl;
       }
protected:
       int _num = 999; // 学号 
};
int main()
{
       Student s1;
       s1.Print();
       
       return 0;
};

3.2 考察继承作用域的相关选择题

经过前面相关知识的学习,现在我们就来做两道选择题,看看大家对前面的知识掌握的怎么样。
现在我们有下列这样的代码,请大家回答下列问题:

class A
{
public:
        void fun()
        {
                cout << "func()" << endl;
        }
};
class B : public A
{
public:
        void fun(int i)
        {
                cout << "func(int i)" <<i<<endl;
        }
};
int main()
{
        B b;
        b.fun(10);
        b.fun();
        
        return 0;
}

  • Question One:
    A和B类中的两个func构成什么样的关系?
  • A.重载
  • B.隐藏
  • C.没关系
  • Question Two:
    下面程序的编译结果是什么?
  • A.编译错误
  • B.运行报错
  • C.正常运行

相信大家一定都尝试了这两道题吧 这两道题目的最终答案,我会在文章的最后揭晓哈,大家一定要记得去核对哈。

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

4.1 六个常见的默认成员函数

默认其实就是指我们不写,编译器也会为我们自动生成的东西。那么在派生类中,这几个成员函数又会怎么生成呢?

  • 派生类的构造函数必须调用基类的初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类的初始化列表那里显示调用。
  • 派生类的拷贝构造必须调用基类的拷贝构造完成基类的拷贝初始化。
  • 派生类的operator=必须调用基类的operator=来完成基类的积累的复制,需要注意的是派生类的operator=隐藏了基类的operator=,所以显示调用基类的operator=需要显示基类的作用域!
  • 派生类的析构函数在调用完成之后,会再调用基类的析构函数来清理派生类中的基类成员。这样才能够保证,派生类中的成员先清理,在清理基类成员的顺序
  • 派生类对象初始化,先调用基类的构造函数,再调用派生类的构造函数。
  • 派生类对象的析构清理,先调用基类的析构,再调用基类的析构。
  • 应为多态中在一些场景析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊的处理,处理成destructor(),所以基类析构函数在不加virtual的情况下,派生类析构函数和基类析构函数构成隐藏的关系。
6个默认成员函数
初始化和清理
拷贝复制
取地址重载
构造函数主要 完成初始化工作
析构函数主要 完成清理工作
拷贝构造是使用同类对象初始化创建对象
赋值重载主要是把一个对象赋值给另一个对象
主要是普通对象和const对象取地址,这两个很少会自己实现

c++继承_第3张图片

下面就是一段示例代码,大家可以拿下去自己验证一下前面的结论哟:

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;
}

4.2 实现一个不能被继承的类

  1. 基类的构造函数私有: 派生类的构造必须要先调用基类的构造才行,如果基类的构造函数私有,派生类就无法调用基类的构造函数,那么派生类就没有办法实例化出对象。
  2. c++11 中增加了一个final关键字,final修饰基类,派生类就没有办法再被继承

下面是一些实例代码:

// C++11的方法 
class Base final
{
public:
    void func5() { cout << "Base::func5" << endl; }
protected:
    int a = 1;
private:
    // C++98的方法 
    /*Base()
    {}*/
};
class Derive :public Base 
{
    void func4() { cout << "Derive::func4" << endl; }
protected:
    int b = 2;
};
int main()
{
    Base b;
    Derive d;
    return 0;
}

5.继承和友元

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

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;
}
int main()
{
        Person p;
Student s;
        // 编译报错:error C2248: “Student::_stuNum”: 无法访问 protected 成员 
        // 解决方案:Display也变成Student 的友元即可 
        Display(p, s);
        
        return 0;
}

6.继承与静态成员

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

class Person
{
public:
    string _name;
    static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:
    int _stuNum;
};
int main()
{
    Person p;
    Student s;
    // 这里的运行结果可以看到非静态成员_name的地址是不一样的 
    // 说明派生类继承下来了,父派生类对象各有一份 
    cout << &p._name << endl;
    cout << &s._name << endl;
    // 这里的运行结果可以看到静态成员_count的地址是一样的 
    // 说明派生类和基类共用同一份静态成员 
    cout << &p._count << endl;
    cout << &s._count << endl;
    // 公有的情况下,父派生类指定类域都可以访问静态成员 
    cout << Person::_count << endl;
    cout << Student::_count << endl;
    return 0;
}

c++继承_第4张图片

课后习题答案

Question One : 隐藏
Question Two: 编译报错


继承相关的问题,我们就系那个探讨到这里,我们下期再见拜拜!!

你可能感兴趣的:(c++,学习)