> 技术文档 > c++的继承_c++类封装继承

c++的继承_c++类封装继承

封装、继承和多态是c++的三大特性,他们的关系甚为紧密

封装的概念简单易懂,其实就是将数据和操作数据的方法结合在一起,形成一个独立的单元(类)通过访问控制符(如privateprotectedpublic),封装可以隐藏内部实现细节,只暴露必要的接口,使得外部代码无法直接访问内部数据,从而提高了安全性和代码的可维护性

首先需要了解什么是继承

1.继承的概念和定义 

1.1继承的概念 

继承是使代码可以复⽤的最重要的⼿段,它允许我们在保持原有类特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称派⽣类

假如我们需要使用两个类,Student类和Teacher类,Student和Teacher都有姓名、电话、年龄这样的共同属性,但是有也有不同的部分,如:Student需要有学习函数study(),Teacher需要授课函数teaching() 。这两个类分别独立实现,有很多重复的代码,有没有办法不重复定义这些公共的部分,让代码变得简洁、高效?有,可以继承

class Student
{
public:
    //身份
    void identity()
    {
        cout << \"void identity()\" << _name << endl;
    }
    //学习
    void study()
    {
    }
protected:
    string _name = \"name\";  // 姓名
    string _address;  // 地址
    string _tel;  //电话号码
    int _age = 18;  //年龄
};

class Teacher
{
public:
    void identity()
    {
    }
    // 授课
    void teaching()
    {
    }
protected:
    string _name = \"张三\";  // 姓名
    int _age = 18;  // 年龄
    string _address;  // 地址
    string _tel;  //电话号码
    string _title;  //称号
};

使用继承: 

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;  //头衔
}; 

对于上面的代码,可以简单的理解为:定义了三个类,分别是Person类、Student类和Teacher类。关系上看,Student类与Teacher类都继承自Person类,称作派生类(子类)。内容上看,Student类和Teacher类公共的部分放在基类(父类),而c++定义子类中含有父类的属性和方法,同时,子类中自己还定义了额外的属性和方法,如上面的Student类当中定义了学号、学习函数,Teacher类定义了头衔、教学函数

 这样一来,与没有继承进行定义的类相比,Student类和Teacher类共同部分定义在了基类中,不同的部分就当独定义,使之前大量重复的代码不再重复,实现了代码复用

1.2继承的定义 

 1.2.1定义格式

有三种继承方式: 

三种访问方式:

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

类成员/继承方式 public继承 protected继承 private继承 基类的public成员 派生类的public成员 派生类的protected成员 派生类的private成员 基类的protected成员 派生类的protected成员 派生类的protected成员 派生类的private成员

 而基类的private成员,无论被哪种方式继承,在子类中都是不可见的,这⾥的不可⻅是指基类的私有成员还是被继承到了派⽣类对象中,但是语法上限制派⽣类对象不管在类⾥⾯还是类外⾯都不能去访问它

如果基类成员不想在类外直接被访问,但需要在派⽣类中能访问,就定义为protected

对于上面表格的总结:基类的私有成员外,基类的其他成员 在派⽣类的访问⽅式==Min(成员在基类的访问限定符,继承⽅式),public >protected> private

  • 派生类可以用struct定义也可以用class定义,区别是:使⽤关键字class时默认的继承⽅式是private,使⽤struct时默认的继承⽅式是public,不过最好显⽰的写出继承⽅式
  • 在实际运⽤中⼀般使⽤都是public继承,⼏乎很少使⽤protetced/private继承,也不提倡使⽤ protetced/private继承,因为protetced/private继承下来的成员都只能在派⽣类的类⾥⾯使⽤,实 际中扩展维护性不强 

2.基类和派生类之间的转换 

  • public继承的派⽣类对象可以赋值给基类的指针/基类的引⽤。这⾥有个形象的说法叫切⽚或者切 割。寓意把派⽣类中基类那部分切出来,基类指针或引⽤指向的是派⽣类中切出来的基类那部分 
  • 基类对象不能赋值给派⽣类对象
  • 基类的指针或者引⽤可以通过强制类型转换赋值给派⽣类的指针或者引⽤。但是必须是基类的指针 是指向派⽣类对象时才是安全的

 

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隐藏规则

  1. 在继承体系中基类和派⽣类都有独⽴的作⽤域
  2. 派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。 (在派⽣类成员函数中,可以使⽤基类::基类成员显⽰访问)
  3. 需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏
  4. 注意在实际中在继承体系⾥⾯最好不要定义同名的成员

例如:

 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; // 学号

};

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

4.1常⻅默认成员函数 

  •  派⽣类的构造函数必须调⽤基类的构造函数初始化基类的那⼀部分成员。如果基类没有默认的构造 函数,则必须在派⽣类构造函数的初始化列表阶段显⽰调⽤
  • 派⽣类的拷⻉构造函数必须调⽤基类的拷⻉构造完成基类的拷⻉初始化
  • 派⽣类的operator=必须要调⽤基类的operator=完成基类的复制。需要注意的是派⽣类的 operator=隐藏了基类的operator=,所以显⽰调⽤基类的operator=,需要指定基类作⽤域
  • 派⽣类的析构函数会在被调⽤完成后⾃动调⽤基类的析构函数清理基类成员。因为这样才能保证派 ⽣类对象先清理派⽣类成员再清理基类成员的顺序
  • 派⽣类对象初始化先调⽤基类构造再调派⽣类构造
  • 派⽣类对象析构清理先调⽤派⽣类析构再调基类的析构
  • 因为多态中⼀些场景析构函数需要构成重写,重写的条件之⼀是函数名相同。那么编译器会对析构函数名进⾏特殊处理,处理成destructor(),所以基类析构函数不加 virtual的情况下,派⽣类析构函数和基类析构函数构成隐藏关系

5.与继承相关的要点 

  •  友元关系不能继承,也就是说基类友元不能访问派⽣类私有和保护成员
  • 基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都只有⼀个static成员实例
  • c++支持多继承,意味着存在菱形继承,而菱形继承有数据冗余和⼆义性的问题,在有更好的解决方法时,千万不要使用菱形继承

 6.虚继承

很多⼈说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形继承就有 菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱形继承。多继承可 以认为是C++的缺陷之⼀,后来的⼀些编程语⾔都没有多继承,如Java 

class Person{public:    string _name; // 姓名    /*int _tel;    int _age;    string _gender;    string _address; */    // ...};//使⽤虚继承Person类class Student : virtual public Person{protected:    int _num; //    学号};// 使⽤虚继承Person类class Teacher : virtual public Person{protected:    int _id; // 职⼯编号};// 教授助理class Assistant : public Student, public Teacher{protected:    string _majorCourse; // 主修课程};

我们可以设计出多继承,但是不建议设计出菱形继承,因为菱形虚拟继承以后,⽆论是使⽤还是底层 都会复杂很多。当然有多继承语法⽀持,就⼀定存在会设计出菱形继承,像Java是不⽀持多继承的, 就避开了菱形继承 

class Person{public:Person(const char* name):_name(name){}string _name; //姓名};class Student : virtual public Person{public: Student(const char* name, int num) :Person(name) , _num(num) {}protected: int _num; //学号};class Teacher : virtual public Person{public: Teacher(const char* name, int id) :Person(name) , _id(id) {}protected: int _id; //职⼯编号};// 不建议玩菱形继承class Assistant : public Student, public Teacher{public: Assistant(const char* name1, const char* name2, const char* name3)  :Person(name3)  ,Student(name1, 1)  ,Teacher(name2, 2) {}protected: string _majorCourse; // 主修课程};int main(){ // 思考⼀下这⾥a对象中_name是\"张三\", \"李四\", \"王五\"中的哪⼀个? Assistant a(\"张三\", \"李四\", \"王五\"); return 0;}