C++面向对象核心-继承

1、继承

1.1 概念

继承是面向对象的三大特性之一,体现了代码复用的思想。

继承就是在一个已存在的类的基础上建立一个新的类,并拥有其特性。

  • 已存在的类被称为“基类”或者“父类”
  • 新建立的类被称为“派生类”或者“子类”
  • 对象间没有继承关系

#include <iostream>

using namespace std;

// 基类
class Father
{
private:
    string name = "孙";
public:
    void set_name(string name)
    {
        this->name = name;
    }

    string get_name()
    {
        return name;
    }

    void work()
    {
        cout << "我的工作是厨师,我负责炒菜" << endl;
    }
};

// 派生类
class Son:public Father
{

};

int main()
{
    Son son;
    cout << son.get_name() << endl;
    son.work();

    return 0;
}

派生类差异化

上面的代码,Son类的功能几乎与Father类重叠,在实际的使用过程中,派生类会做出一些与基类的差异化。

  • 修改继承过来的基类内容

属性:1、公有的属性可以直接更改。2、私有的属性,需要使用基类提供的公有函数,进行更改。

成员函数:函数隐藏,通过派生类实现一个同名同参的函数,来隐藏基类的函数。

调用隐藏函数,需要指定作用域

  • 新增派生类的内容

#include <iostream>
#include <array>
#include <vector>
#include <list>
#include <deque>
#include <map> // 头文件
using namespace std;
class Father
{
private:
    string name = "孙";
public:
    void set_name(string name)
    {
        this->name = name;
    }
    string get_name()
    {
        return name;
    }
    void work()
    {
        cout<<"我的工作是厨师,我负责炒菜 "<<endl;
    }
};
//派生类
class Son:public Father
{
 public:
    void init()
    {
        set_name("王");
    }
    //函数隐藏,隐藏父类同名函数
    void work()
    {
        cout<<"我的工作是学生"<<endl;
    }
    void game()
    {
        cout<<"我还玩游戏"<<endl;
    }
};
int main()
{
    Son son;//无参构造函数
    cout << son.get_name()<<endl;//孙
    son.work();//我的工作是学生
    son.game();//我还玩游戏
    son.init();
    cout << son.get_name()<<endl;//王
    son.Father::work();//”我的工作是厨师,我负责炒菜“;调用隐藏函数,需要指定作用域
    return 0;
}

派生类往往是类的具象化,基类则是派生类的抽象。

基类和派生类是相对的,一个类可能又存在基类,又存在派生的情况,取决于那两个类进行比较。

1.2 构造函数

1.2.1 派生类与基类构造函数的关系

构造函数和析构函数不能被继承。

#include <iostream>
using namespace std;
// 基类
class Father
{
private:
    string name;
public:
    // 有参构造函数
    Father(string name):name(name){}

    string get_name()
    {
        return name;
    }
};
// 派生类
class Son:public Father
{
public:
    // 编译器自动添加的构造函数,若没有该构造函数,编译器自动添加
    Son():Father(){}
};
int main()
{
    Son son;//创建对象时发现该类由基类,则找基类构造对象
    cout << son.get_name() << endl;
    return 0;
}

派生类的任意一个构造函数,都必须直接或者间接调用基类的任意构造函数。

1.2.2 解决方案

1.2.2.1 补充基类的无参构造函数

#include <iostream>
using namespace std;
// 基类
class Father
{
private:
    string name;
public:
    Father()
    {
        name  = "王";
    }

    // 有参构造函数
    Father(string name):name(name){}

    string get_name()
    {
        return name;
    }
};
// 派生类
class Son:public Father
{
public:
    // 编译器自动添加的构造函数
    Son():Father(){}
};
int main()
{
    Son son;
    cout << son.get_name() << endl;

    return 0;
}


1.2.2.2 手动在派生类中调用基类构造函数
1.2.2.2.1 透传构造

在派生类的构造函数中,调用基类的构造函数,实际上编译器自动添加派生类的构造函数,调用基类无参构造时,采用的就是这种方式。

#include <iostream>
using namespace std;
// 基类
class Father
{
private:
    string name;
public:
//    Father()
//    {
//        name  = "王";
//    }
    // 有参构造函数
    Father(string name):name(name){}
    string get_name()
    {
        return name;
    }
};
// 派生类
class Son:public Father
{
public:
    // 编译器自动添加的构造函数,透传构造
    // Son():Father(){}
    // 手动添加的构造函数,透传构造
    Son():Father("张"){}
    // 手动添加派生类的有参构造函数
    Son(string name):Father(name){}
};
int main()
{
    Son son("王");
    cout << son.get_name() << endl;
    return 0;
}

1.2.2.2.2 委托构造

一个类的构造函数可以调用这个类中的另一个构造函数。要避免循环委托。

委托构造的性能低于透传构造,但是代码的维护性更好。因为通常一个类中构造函数都会委托给能力最强(参数最多)的构造函数。(效率低)代码重构时,只需要更改这个能力最强的构造函数即可。

#include <iostream>
using namespace std;
// 基类
class Father
{
private:
    string name;
public:
//    Father()
//    {
//        name  = "王";
//    }
    // 有参构造函数
    Father(string name):name(name){}
    string get_name()
    {
        return name;
    }
};
// 派生类
class Son:public Father
{
public:
    // 编译器自动添加的构造函数,透传构造
    // Son():Father(){}
    // 手动添加的构造函数,透传构造
    Son():Son("张"){}
    // 手动添加派生类的有参构造函数
    Son(string name):Father(name){}
};
int main()
{
    Son son("王");
    cout << son.get_name() << endl;

    return 0;
}

#include <iostream>

using namespace std;

// 基类
class Father
{
private:
    string name;
    int i;
public:

    // 有参构造函数

    Father(string name,int i):name(name),i(i){}
    string get_name()
    {
        return name;
    }

    int get_i()
    {
        return i;
    }


};

// 派生类继承基类  公有继承
class Son:public Father
{
public:
    // 编译器自动添加的构造函数,透传构造
    // Son():Father(){}

    // 手动添加构造函数,委托构造
    Son():Son("张"){}

    Son(string name,int i = 1):Father(name,i){}

};

int main()
{
    Son son("hello",2);
    cout << son.get_name() << endl;
    cout << son.get_i() << endl;

    return 0;
}

1.2.2.2.3 继承构造

只需要加这一句话,编译器就会添加上面两种构造函数(using Father::Father;)

C++11新增的写法,只需要一句话,就可以自动给派生类添加n个(n为基类构造函数的个数)构造函数。并且每个派生类的构造函数与基类的构造函数格式相同,每个 派生类的构造函数都通过透传构造调用对应格式的基类构造函数。

#include <iostream>
using namespace std;
//基类
class Father
{
private:
    string name;
public:
    //委托构造
    Father():Father("王"){}
    //有参函数构造
    Father(string name):name(name){}
    string get_name()
    {
        return name;
    }
};
//派生类
class Son:public Father
{
public:
    //    //编译器自动添加的构造函数,透传构造
    //    //Son():Father(){}
    //    //手动添加的构造函数,透传构造
    //    Son():Father("张"){}
    //    //手动添加派生类的有参构造函数
    //    Son(string name):Father(name){}
    //只需要加这一句话,编译器就会添加上面两种构造函数
    using Father::Father;
};
int main()
{
    Son son;//无参构造函数
    Son son1("张");
    cout << son.get_name()<<endl;
    cout << son1.get_name()<<endl;
    return 0;
}

1.3 对象的创建与销毁流程

在继承中,构造函数与析构函数的调用。

#include <iostream>

using namespace std;

class Value
{
private:
    string str;
public:
    Value(string str):str(str)
    {
        cout << str <<"构造函数" << endl;
    }

    ~Value()
    {
        cout << str << "析构函数" << endl;
    }
};

class Father
{
public:
    static Value s_value;
    Value val = Value("Father 成员变量");
    Father()
    {
        cout << "Father 构造函数被调用了" << endl;
    }
    ~Father()
    {
        cout << "Father 析构函数被调用了" << endl;
    }
};
Value Father::s_value = Value("静态FatherValue创建了");

class Son:public Father
{
public:
    static Value s_value;
    Value val = Value("Son 成员变量");

    Son()
    {
        cout << "Son构造函数被调用了" << endl;
    }
    ~Son()
    {
        cout << "Son析构函数被调用" << endl;
    }

};
Value Son::s_value = Value("静态SonValue被创建了");

int main()
{
    cout << "主程序开始执行" << endl;
    // 局部代码块
    {
        Son s;
        cout << "对象执行中" << endl;
    }
    cout << "主程序结束了" << endl;
    return 0;
}

上面的执行结果中,可以得到下面的规律:

  • 以“对象执行中”为轴,上下对称,先创建静态后创建非静态,先创建基类,后创建派生类,先创建成员变量,后创建对象。先析构成员变量后析构函数,先析构派生类,后析构基类,先析构非静态,后析构静态。
  • 在创建的过程中,同类型的内存区域,基类先开辟。
  • 静态的创建早于非静态
  • 对象的创建,晚于类中成员变量的创建。

1.4 多重继承

1.4.1 概念

C++支持多重继承,即一个派生类可以有多个基类。派生类对于每个基类的关系仍然可以看作是一个单继承。

#include <iostream>
using namespace std;
class Sofa
{
public:
    void sit()
    {
        cout << "沙发可以坐着" << endl;
    }
};
class Bed
{
public:
    void lay()
    {
        cout << "床可以躺着" << endl;
    }
};
class SofaBed:public Sofa,public Bed
{
   public:
};
int main()
{
    SofaBed sb;
    sb.sit();
    sb.lay();
    return 0;
}

1.4.2 可能出现的问题

1.4.2.1 问题1-重名问题

当多个基类具有重名成员时,编译器在调用的过程中会出现二义性。

解决方式:使用基类的类名::方式调用。

#include <iostream>
using namespace std;
class Sofa
{
public:
    void sit()
    {
        cout << "沙发可以坐着" << endl;
    }
    void test()
    {
        cout << "打扫沙发" << endl;
    }
};
class Bed
{
public:
    void lay()
    {
        cout << "床可以躺着" << endl;
    }
    void test()
    {
        cout << "打扫床" << endl;
    }
};
class SofaBed:public Sofa,public Bed
{
   public:
};
int main()
{
    SofaBed sb;
    sb.sit();
    sb.lay();
//    sb.test(); // 错误二义性,两个类中都继承到相同的成员函数名
    sb.Bed::test();//作用域指定
    sb.Sofa::test();
    return 0;
}

1.4.2.2 问题2-菱形继承

当一个派生类有多个基类,且这些基类又有同一个基类,就会出现二义性问题,这种情况也被称为菱形继承(钻石)继承。

1)作用域加类名解决

#include <iostream>

using namespace std;

class Furniture
{
public:
    void func()
    {
        cout << "家具厂里有家具" << endl;
    }

};
class Sofa:public Furniture
{
public:
};

class Bed:public Furniture
{
public:
};

class SofaBed:public Sofa,public Bed
{
   public:
};


int main()
{
    SofaBed sb;
//    sb.func();
//作用域加类名完成
    sb.Sofa::func();
    sb.Bed::func();
    return 0;
}

2)使用虚继承virtual

虚继承解决二义性是通过虚基类指针和虚基类表实现的。在下面代码中Sofa和Bed类会创建一个虚基类指针和虚基类表。所有同类型对象共用一张虚基类表,每个对象内部增加一个隐藏的虚基类指针成员变量,这个虚基类指针指向虚基类表。当Sofa和Bed类作为基类有了派生类SofaBed时,SofaBed对象中也会有隐藏的虚基类指针,但是SofaBed没有自己的虚基类表。在调用Furniture的成员时,SofaBed对象会通过虚基类指针找到对应的虚基类表,通过查表避免二义性。

#include <iostream>

using namespace std;

class Furniture
{
public:
    void func()
    {
        cout << "家具厂里有家具" << endl;
    }

};
// 虚继承
class Sofa:virtual public Furniture
{
public:
};

class Bed:virtual public Furniture
{
public:
};

class SofaBed:public Sofa,public Bed
{
   public:
};


int main()
{
    SofaBed sb;
    sb.func();
    return 0;
}

练习多重继承:

定义学生类,有姓名,学号,性别,年龄等私有成员变量,有构造函数,有打印信息的成员函数。

要求通过构造函数可以给属性赋予初始值。

定义大学生类,继承自学生类,大学生有专业名、成绩的私有成员变量,还有是否获得奖学金的成员函数(成绩为判断依据)。隐藏基类打印信息的成员函数,新的打印信息的成员函数也要能打印姓名、学号、性别、年龄信息。

要求通过构造函数可以给属性赋予初始值。

再定义研究生类,继承自大学生类,有导师姓名和工资的私有成员变量,有打印工资这个成员函数。

要求通过构造函数可以给属性赋予初始值。

#include <iostream>

using namespace std;
class Student
{
private:
    string name;
    int stdnum;
    string sex;
    int age;
public:
    //有参构造
    Student(string name,int stdnum,string sex,int age):name(name),stdnum(stdnum),sex(sex),age(age){}
    void show_student()
    {
        cout<<"姓名:"<<name<<endl<<"学号:"<<stdnum<<endl<<"性别:"<<sex<<endl<<"年龄 "<<age<<endl;
    }
    string get_name()
    {
        return name;
    }
};
class Undergraduate:public Student
{
private:
    string major;
    float grade;
public:
//透传构造
    Undergraduate(string name,int stdnum,string sex,int age,string major,float grade):Student(name,stdnum,sex,age),major(major),grade(grade){}
    //奖学金判断
    void jodge_scholarship()
    {
        if(this->grade>=95)
        {
            cout<<Student::get_name()<<"获得奖学金"<<endl;
        }
        else
        {
            cout<<Student::get_name()<<"未获得奖学金"<<endl;
        }
    }
    //打印大学生信息
    void show_undergraduate()
    {
        Student::show_student();
        cout<<"专业:"<<major<<endl<<"成绩:"<<grade<<endl;
    }
};
class Graduate:public Undergraduate
{
private:
    string totor;//导师
    int pay;
public:
    //构造
    Graduate(string name,int stdnum,string sex,int age,string major,float grade,string totor,int pay):Undergraduate(name,stdnum,sex,age,major,grade),totor(totor),pay(pay){}
    void get_pay()
    {
        cout<<"薪资待遇:"<<pay<<endl;
    }
    void show_graduate()
    {
        cout<<"导师:"<<totor<<endl;
        cout<<"薪资:"<<pay<<endl;
    }

};

int main()
{
    cout<<"学生类"<<endl;
    Student std1("张三",123,"男",18);
    std1.show_student();
    cout<<"大学生类"<<endl;
    Undergraduate std2("张三",123,"男",18,"嵌入式",99);
    std2.show_undergraduate();
    std2.jodge_scholarship();
    cout<<"研究生类"<<endl;
    Graduate std3("张三",123,"男",18,"嵌入式",99,"沈子",9999);
    std3.show_student();
    std3.show_graduate();
    std3.get_pay();
    return 0;
}


  • 38
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

满山的猴子我的腚最红

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值