C++继承

本文详细介绍了C++中的继承概念,包括继承的格式、访问权限、构造函数和析构函数的调用顺序,以及同名覆盖问题。还讨论了多级继承、多重继承以及如何解决菱形继承带来的数据冗余问题,特别强调了虚继承在解决这一问题上的作用。
摘要由CSDN通过智能技术生成

目录

继承

概念

继承的格式

继承方式及访问权限

同名覆盖问题

继承中构造函数和析构函数的调用顺序

继承中的特殊成员函数

构造函数

构造函数(默认构造函数和自定义的构造函数)

析构函数

拷贝构造函数

拷贝赋值函数

多级继承

 多重继承

含义及格式

注意

虚继承

菱形继承问题

虚继承


继承

概念

继承是在一个已经存在的类的基础上新建一个类,新的类拥有已经存在的类的特性。已经存在的类称为“父类”或“基类”,新建立的类称为“子类”或“派生类”。

继承的格式

class 子类名:访问权限 父类名

代码示例:

#include <iostream>
using namespace std;
class Student//父类or基类
{
public:
    Student(string n,int a):name(n),age(a){}
protected:
    string name;
    int age;
};
class Teacher : public Student//子类or派生类
{
public:
    //在子类中对父类中继承下来的成员初始化时需要显性调用父类的构造函数
    Teacher(string n,int a,int s):Student(n,a),score(s){}//继承下来的成员必须是显性的调用父类的构造函数
    //Student(string n,int a):name(n),age(a){}
private://如果这个类不想作为另一个类的父类,通常就把成员写为私有的
    int score;
    void show()
    {
        cout<<"name:"<<name<<'\t'<<"age:"<<age<<'\t'<<"score:"<<score<<endl;
    }
protected:
    //string name;
    //int age;
};
int main()
{
    return 0;
}

继承方式及访问权限

访问权限:继承权限只能缩小不能放大。

父类中的成员子类public继承子类protected继承子类private继承
publicpublicprotected

private

protectedprotectedprotectedprivate
private不可访问不可访问不可访问

子类会继承父类中所有的成员。

子类中会继承父类中所有的成员,不包括私有成员、构造函数、析构函数、拷贝构造函数、拷贝赋值函数。

如果子类非要访问父类的私有成员,可以通过父类的公共接口来访问它的私有成员。

同名覆盖问题

当父类和子类中出现同名成员时,优先访问子类中的成员,发生同名覆盖。函数重写是一种特殊的同名覆盖。

代码示例

#include <iostream>
using namespace std;
class Student//父类or基类
{
public:
    Student(string n,int a):name(n),age(a){}
    void show()
    {
        cout<<"name:"<<name<<'\t'<<"age:"<<age<<'\t'<<"score:"<<age<<endl;
    }
protected:
    string name;
    int age;
};
class Teacher:private Student
{
public:
    Teacher(string n,int a,int s):Student(n,a),age(s){}//继承下来的成员必须是显性的调用父类的构造函数
    void show()//发生了同名覆盖
    {
        cout<<"name:"<<name<<'\t'<<"age:"<<age<<'\t'<<"score:"<<age<<endl;
    }
private:
    int age;
    //string name;
    //int age;
protected:
};
int main()
{
    Teacher t1("zhang",16,24);
    t1.show();
    Student s1("zhao",224);
    s1.show();
    return 0;
}

继承中构造函数和析构函数的调用顺序

先调用父类的构造函数再调用子类的构造函数(先父后子)。

先调用子类的析构函数再调用父类的析构函数(先子后父)。

代码示例

#include <iostream>
using namespace std;
class Student//父类or基类
{
public:
    Student(string n,int a):name(n),age(a)
    {
        cout<<"Stdent::constructor"<<endl;
    }
    ~Student()
    {
        cout<<"Student::destructor"<<endl;
    }
protected:
    string name;
    int age;
};
class Teacher:private Student
{
public:
    Teacher(string n,int a,int s):Student(n,a),age(s)//继承下来的成员必须是显性的调用父类的构造函数
    {
        cout<<"Teacher::constructor"<<endl;
    }
    ~Teacher()
    {
        cout<<"Teacher::destructor"<<endl;
    }
private:
    int age;
    //string name;
    //int age;
protected:
};
int main()
{
    Teacher t1("zhang",16,24);
    Student s1("zhao",224);
    return 0;
}
//Stdent::constructor
//Teacher::constructor
//Stdent::constructor
//Student::destructor
//Teacher::destructor
//Student::destructor

继承中的特殊成员函数

构造函数

构造函数分为构造函数(默认构造函数和自定义的构造函数)、析构函数、拷贝构造函数、拷贝赋值函数。

特点:

  1. 自动调用(在创建新对象时,自动调用)
  2. 构造函数的函数名,和类名相同
  3. 构造函数没有返回类型
  4. 可以有多个构造函数(即函数重载形式)
构造函数(默认构造函数和自定义的构造函数)
  1. 子类不会继承父类的构造函数,但是可以调用。
  2. 为了完成在子类中对父类成员的初始化,需要在子类的初始化列表中显性的调用父类的构造函数。
  3. 调用顺序:先调用父类的构造函数再调用子类构造函数。
析构函数
  1. 子类不会继承父类的析构函数,但是可以调用。
  2. 不管子类是否显性调用父类的析构函数,父类的析构函数都会被自动调用来完成子类的资源释放。
  3. 调用顺序:先调用子类的析构函数再调用父类的析构函数。
拷贝构造函数
  1. 子类不会继承父类的拷贝构造函数,但是可以调用。
  2. 子类如果显性的定义了拷贝构造函数,需要在子类的拷贝构造函数的初始化列表显性调用父类的拷贝构造函数;如果子类没有定义,则使用默认的拷贝构造函数。
  3. 显性调用构造函数时,必须使用初始化列表的形式初始化。
拷贝赋值函数
  1. 子类不会继承父类的拷贝赋值函数,但是可以调用。
  2. 如果子类中显性的定义了拷贝赋值函数,需要在子类的拷贝赋值函数,注意需要加作用限定符;如果子类没有定义拷贝赋值函数,使用默认的拷贝赋值函数。
#include<iostream>
using namespace std;
class Person
{
protected:
    string name;
    int age;
public:
    Person(string n,int a):name(n),age(a){}
    Person(const Person& O):name(O.name),age(O.age)//构造函数的初始化列表方式
    {
        cout<<"Person:: copy constructor"<<endl;
    }
    Person& operator=(const Person& O)//拷贝赋值函数
    {
        this->name = O.name;
        this->age = O.age;
        cout<<"Person:: copy assign"<<endl;
        return  *this;
    }
    Person()=default;
    ~Person()
    {
        cout<<"Person::destructor"<<endl;
    }
};
class Student:public Person
{
protected:
    int score;
public:
    Student(int s,string n,int a):score(s),Person(n,a){}
    Student(Student& O):score(O.score),Person(O)
    //Student(Student& O):score(O.score),Person(O.name,O.age)
    /*
    这里需要直接调用父类的拷贝构造函数,形式是Person(const Person& O),传的Person(O.name,O.age)不行
      需要这样操作Person(O)
  */
    {
        cout<<"Student:: copy constructor"<<endl;
    }
    //调用直接父类的构造函数,与基类中的构造无关
    Student& operator=(const Student& O)
    {
        this->score = O.score;
        Person::operator=(O);
        cout<<"Student:: copy assign"<<endl;
        return *this;
    }
    Student()=default;
    ~Student()
    {
        cout<<"Student::destructor"<<endl;
    }
};
int main()
{
    Student s1(32,"zhang",24);
    Student s2(s1);
    Student s3;
    s3 = s1;
    //如果不自定义拷贝构造函数,会使用子类默认的拷贝构造函数不会出错
    return 0;
}

多级继承

在多级继承中,使用子类来初始化父类继承下来的成员的时候只需要看子类的直接父类中的成员来进行构造。

#include<iostream>
using namespace std;
class Person
{
protected:
    string name;
    int age;
public:
    Person(string n,int a):name(n),age(a){}
};
class Student:public Person
{
protected:
    int score;
public:
    Student(int s,string n,int a):score(s),Person(n,a){}
    //调用直接父类的构造函数,与基类中的构造无关
};
class Graduate:public Student
{
private:
    int wage;
public:
    Graduate(int w,int s,string n,int a):wage(w),Student(s,n,a){}
    //调用直接父类的构造函数,与基类中的构造无关
    void show()
    {
        cout<<wage<<","<<score<<","<<name<<","<<age<<endl;
    }
};
int main()
{
    Graduate g1(200,98,"zhang",24);
    g1.show();
    return 0;
}

 多重继承

含义及格式

一个子类可以由多个父类派生而来,在多重继承中,子类会包含每一个父类的所有成员

格式:class 子类名:public 父类名1,public 父类名2,......{}

注意

需要在子类的构造函数函数的初始化列表中来分别调用父类的构造函数。

如果两个或者多个父类中有相同的成员时,需要在子类中对其进行重写,如果没有重写会出错,原因是不知道该调用哪个父类的同名函数;如果不想重写,需要加作用限定符来指定。

子类的构造函数顺序与继承父类的顺序有关。

代码示例:

#include<iostream>
using namespace std;
class A
{
public:
    A(){cout<<"A::no para constructor"<<endl;}
    A(int a):v_a(a){cout<<"A::has para constructor"<<endl;}
    ~A(){cout<<"A::destructor"<<endl;}
    void show()
    {
        cout<<"A::v_a:"<<v_a<<endl;
    }
protected:
    int v_a;
};
class B
{
public:
    B(){cout<<"B::no para constructor"<<endl;}
    B(int b,int a):v_b(b),v_a(a){cout<<"B::has para constructor"<<endl;}
    ~B(){cout<<"B::destructor"<<endl;}
    void show()
    {
        cout<<"B::v_b:"<<v_b<<'\t'<<"B::v_a:"<<v_a<<endl;
    }
protected:
    int v_b;
    int v_a;
};
class C:public B,public A
{
public:
    C(){cout<<"C::no para constructor"<<endl;}
    //需要在子类的构造函数的初始化列表中来分别调用父类的构造函数
    C(int c,int b,int a1,int a2):v_c(c),B(b,a1),A(a2){cout<<"C::has para constructor"<<endl;}
    ~C(){cout<<"C::destructor"<<endl;}
    void show()
    {
        cout<<"C::v_c:"<<v_c<<'\t'<<"C::v_b:"
           <<v_b<<'\t'<<"C::B::v_a"<<B::v_a<<'\t'<<"C::A::v_a:"<<A::v_a<<endl;
           //需要指定相同成员的来源
    }
private:
    int v_c;
};
int main()
{
    C c1(1,3,4,5);
    c1.show();
    return 0;
}
//B::has para constructor
//A::has para constructor
//C::has para constructor
//C::v_c:1        C::v_b:3        C::B::v_a4      C::A::v_a:5
//C::destructor
//A::destructor
//B::destructor

虚继承

菱形继承问题

由上图可知,在菱形继承中,子类B、C都继承了A中的所有public和privated的成员和函数,D同时继承了B和C的public、privated成员和函数,所以D有两个父类,但是D会出现数据冗余问题。

#include<iostream>
using namespace std;
class Person
{
private:
	int _idPerson;
public:
	Person(int id) :_idPerson(id) { cout << "Create Person" << endl; }
	~Person(){}
};
 
class Student:public Person//学生
{
private:
	int _snum;
public:
	Student(int id,int s):Person(id),_snum(s){}
	~Student(){}
};
 
class Employee:public Person//职工
{
private:
	int _enum;
public:
	Employee(int id,int e):Person(id),_enum(e){}
	~Employee(){}
};
 
class GStudent:public Student//研究生
{
private:
	int _gsnum;
public:
	GStudent(int g,int s,int id):Student(s,id),_gsnum(g){}
	~GStudent(){}
};
 
class EGStudent :public GStudent,public Employee//在职研究生
{
private:
	int _egsnum;
public:
	EGStudent(int es,int s,int g,int e,int sid,int eid)
		:GStudent(s,g,sid),Employee(e,eid),_egsnum(es){}
	~EGStudent(){}
};
int main()
{
	EGStudent egs(1,2,3,4,5,6);
	return 0;
}

 该代码中对象egs的内存分布图如图所示:

虚继承

由于在菱形继承中,汇聚子类中会出现由中间子类产生的数据冗余问题,上例中的D会有B和C的数据冗余问题。

虚继承的作用是在菱形继承中当基类通过多条路径被一个派生类继承的时候,该派生类只保留一次该基类成员。

上例中虚继承对象的内存分布:

代码示例2:

#include <iostream>
using namespace std;
class A
{
public:
    A()=default;
    A(int a):v_a(a){}
    void show()
    {
        cout<<"A::v_a:"<<v_a<<endl;
    }
protected:
    int v_a;
};
class B:virtual public A
{
public:
    B()=default;
    B(int a,int b):A(a),v_b(b){}
    void show()
    {
        cout<<"B::v_a:"<<v_a<<'\t'<<"B::v_b:"<<v_b<<endl;
    }
protected:
    int v_b;
};
class C:virtual public A
{
public:
    C()=default;
    C(int a,int c):A(a),v_c(c){}
    void show()
    {
        cout<<"C::v_a:"<<v_a<<'\t'<<"C::v_c:"<<v_c<<endl;
    }
protected:
    int v_c;
};
class D:public B,public C
{
public:
    D(int a,int b,int c,int d):B(a,b),C(a,c),v_d(d){}
    void show()
    {
        cout<<"D::v_d:"<<v_d<<'\t'<<"D::B::v_b:"<<v_b<<'\t'<<"D::C::v_c:"<<v_c<<'\t'<<
              "D::B::v_a:"<<B::v_a<<'\t'<<"D::C::v_a:"<<C::v_a<<endl;
        //以上是不加virtual关键字的时候需要这样处理
        cout<<"D::v_d"<<v_d<<'\t'<<"D::B::v_b:"<<v_b<<'\t'<<"D::C::v_c:"<<v_c<<'\t'<<"v_a:"<<
              v_a<<endl;
        //加了虚继承后只保留了父类B和C中成员的一份
    }
private:
    int v_d;
};
int main()
{
    D d1(24,5,342,2);
    d1.show();
    return 0;
}

 

部分代及码图片引用于:C++中的菱形继承问题详解_c++ 菱形继承_悲伤的鱼香肉丝er的博客-CSDN博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值