C++——类的继承


继承

继承的概念

通过一种机制表达类型之间的共性和特性的方式,利用已有的数据类型定义新的数据类型,这种机制就是继承。

人类:姓名 年龄 吃饭 睡觉
  学生类:姓名 年龄 吃饭 睡觉 学习 学号
  教师类:姓名 年龄 吃放 睡觉 讲课 工资
  ---------------------------------------
  人类:姓名 年龄 吃饭 睡觉
  学生类继承人类:学习 学号  
  教师类继承人类:讲课 工资
  	
  		 人类(基类/父类)
  		/    \
  	学生    教师(派生类/子类)
  	
  	基类--派生-->子类
  	子类--继承-->基类

继承的语法

class 子类:继承方式 基类,...{
		...
	};

继承方式:

  • public(公有继承)
  • protected(保护继承)
  • private(私有继承)

公有继承的特性

  • 子类对象会继承基类的属性和行为,通过子类对象可以访问基类中的成员,就如同是基类对象在访问它们一样。
    注:子类对象中包含了基类部分称为“基类子对象”
  • 向上造型(重点)
    将子类类型的指针或引用,转换为基类类型的指针或引用。这种操作性缩小的类型转换,在编译器看来是安全,可以直接隐式转换。
基类
    ↑
  子类
	eg:
	class A{};
	class B:public A{};
	class C:public A{};

	void func(A* pa){...}
	B b;
	func(&b);//向上造型
	C c;
	func(&c);//向上造型
  • 向下造型
    将基类类型指针或引用,转换为子类类型的指针或引用。这种操作性放大的类型转换,在编译器看来是危险的,不能隐式转换,但是可以显示转换。
  • 子类集成基类的成员
    • 在子类中可以直接访问基类中公有或保护成员,就如同它们是子类自己的成员一样。
    • 基类中私有成员可以集成过来,但是会受访问控制属性的限制,无法直接访问;如果需要在子类中访问基类的私有成员,需要通过基类中的公有或保护的接口函数间接访问。
  • 子类隐藏基类的成员
    • 子类和基类中定义同名的成员函数,因为作用域不同,不能构成重载关系,而是一种隐藏关系,这时通过子类对象将优先访问子类自己的成员,无法直接访问基类中的成员。
    • 如果需要通过子类访问基类中被隐藏的成员,可以借助“类名::”显式指明。
    • 通过using声明可以将基类中的成员引入子类的作用域,形成重载,通过重载匹配解决。

代码示例

  • inherit.cpp
#include <iostream>
using namespace std;
//人类(基类)
class Human{
public:
    Human(const string& name,int age)
        :m_name(name),m_age(age),m_id(123){}
    void eat(const string& food){
        cout << "我在吃" << food << endl;
    }
    void sleep(int hour){
        cout << "我睡了" << hour << "小时"
            << endl;
    }
//保护的成员可以在类的内部和子类中访问
protected:
    string m_name;
    int m_age;
public:
    //私有成员子类中无法直接访问,但是可以
    //通过基类提供的接口函数间接访问
    const int& getId(void)const{
        return m_id;
    }
private:
    int m_id;
};
//学生类继承人类(人类派生的一个子类)
class Student:public Human{
public:
    //:Human(..),指明基类部分的初始化方式
    Student(const string& name,int age,
        int no):Human(name,age),m_no(no){}
    void learn(const string& course){
        cout << "我在学" << course << endl;
    }
    void who(void){
        cout << "我叫" << m_name << ",今年"
            << m_age << "岁,学号是" << m_no
            << endl;
        cout << "身份证号:"<<getId() <<endl;
    }
private:
    int m_no;
};
//教师类继承人类(人类派生的另一个子类)
class Teacher:public Human{
public:
    Teacher(const string& name,int age,
        int salary):Human(name,age),
                    m_salary(salary){}
    void teach(const string& course){
        cout << "我在讲" << course << endl;
    }
    void who(void){
        cout << "我叫" << m_name << ",今年"
            << m_age << "岁,工资是" << 
            m_salary << endl;
    }
private:
    int m_salary;
};
int main(void)
{
    Student s("关羽",29,10001);
    s.who();
    s.eat("红烧牛肉面");
    s.sleep(8);
    s.learn("孙武兵法");

    Teacher t("悟空",30,50000);
    t.who();
    t.eat("水蜜桃");
    t.sleep(6);
    t.teach("Unix C编程");

    //Student*-->Human*:向上造型
    Human* ph = &s;
    ph->eat("平谷大桃");
    ph->sleep(2);
    //ph->who();
    
    //Human*-->Student*:向下造型(安全)
    Student* ps = static_cast<Student*>(ph);
    ps->who();

    Human h("林黛玉",28);
    //Human*-->Student*:向下造型(危险)
    Student* ps2 =static_cast<Student*>(&h);
    ps2->who();

    return 0;
}
  • 执行结果
    在这里插入图片描述
  • 02inherit.cpp
#include <iostream>
using namespace std;
class Base{
public:
    void func(void){
        cout << "Base::func(void)" << endl;
    }
};
class Derived:public Base{
public:
    void func(int i){
        cout << "Derived::func(int)" <<endl;
    }
    //将基类中func函数声明到当前子类,可以
    //让其和子类中的func形成重载
    //using Base::func;
};
int main(void)
{
    Derived d;
    d.Base::func();
    d.func(10);
    return 0;
}
  • 执行结果
    在这里插入图片描述

继承方式和访问属性

  • 访问控制熟悉:影响访问该类成员的位置
访问控制访问控制内部子类外部友元
限定符属性访问访问访问访问
public公有成员OkOkOkOk
protected保护成员OkOkNoOk
private私有成员OkNoNoOk
  • 集成方式:影响通过子类访问基类中成员的可访问性
基类中的在公有集成子类中变成在保护集成子类中变成在私有继承中子类变成
公有成员公有成员保护成员私有成员
保护成员保护成员保护成员私有成员
私有成员私有成员私有成员私有成员

子类的构造函数

  • 如果子类的构造函数没有指明基类部分(基类子对象)的初始化方式,那么编译器将自动调用基类的无参构造函数来初始化基类子对象。
  • 如果希望基类子对象以有参方式进行初始化,必须要使用初始化表来显式指明。
class 子类:public 基类{
   	//基类(...):显式指明基类子对象初始化方式
   	子类(...):基类(...){}
   };
  • 子类对象构造过程
    • 内存分配
    • 构造基类子对象(按继承表顺序)
    • 构造成员子对象(按声明顺序)
    • 执行子类构造函数代码是

代码示例

  • 03inherit.cpp
#include <iostream>
using namespace std;

class Member{
public:
    Member(void):m_i(0){
        cout << "Member(void)" << endl;
    }
    Member(int i):m_i(i){
        cout << "Member(int)" << endl;
    }
    int m_i;
};

class Base{
public:
    Base(void):m_i(0){
        cout << "Base(void)" << endl;
    }
    Base(int i):m_i(i){
        cout << "Base(int)" << endl;
    }
    int m_i;
};
class Derived:public Base{
public:
    Derived(void){
        cout << "Derived(void)" << endl;
    }
    //Base(i):指明基类子对象的初始化方式
    //m_mem(i):指明成员子对象初始化方式
    Derived(int i):Base(i),m_mem(i){
        cout << "Derived(int)" << endl;
    }
    Member m_mem;//成员子对象
};
int main(void)
{
    Derived d;
    cout << d.m_i << ',' << d.m_mem.m_i
        << endl;//0,0
    Derived d2(123);
    cout << d2.m_i << ',' << d2.m_mem.m_i
        << endl;//123,123
    return 0;
}
  • 执行结果
    在这里插入图片描述

子类的析构函数

  • 子类的析构函数,无论自己定义的还是编译器缺省提供的,都会自动调用基类的析构函数,析构基类子对象。
  • 子类对象的析构过程
    • 执行子类析构函数代码
    • 析构成员子对象(按声明逆序)
    • 析构基类子对象(按集成表逆序)
    • 释放内存
  • 基类的析构函数不会调用子类的析构函数,对一个指向子类对象的基类指针使用delete操作符,实际被调用的仅是基类的析构函数,子类的析构函数不会被调用,有内存泄漏风险。
 解决:虚析构函数
   class A{};
   class B:public A{};
   A* pa = new B;//pa:指向子类对象的基类指针
   delete pa;//有内存泄漏的风险

代码示例

  • inherit.cpp
#include <iostream>
using namespace std;
class Member{
public:
    Member(void):m_i(0){
        cout << "Member(void)" << endl;
    }
    Member(int i):m_i(i){
        cout << "Member(int)" << endl;
    }
    ~Member(void){
        cout << "~Member(void)" << endl;
    }
    int m_i;
};
class Base{
public:
    Base(void):m_i(0){
        cout << "Base(void)" << endl;
    }
    Base(int i):m_i(i){
        cout << "Base(int)" << endl;
    }
    ~Base(void){
        cout << "~Base(void)" << endl;
    }
    int m_i;
};
class Derived:public Base{
public:
    Derived(void){
        cout << "Derived(void)" << endl;
    }
    //Base(i):指明基类子对象的初始化方式
    //m_mem(i):指明成员子对象初始化方式
    Derived(int i):Base(i),m_mem(i){
        cout << "Derived(int)" << endl;
    }
    ~Derived(void){
        cout << "~Derived(void)" << endl;
    }
    Member m_mem;//成员子对象
};
int main(void)
{
/*    Derived d;
    cout << d.m_i << ',' << d.m_mem.m_i
        << endl;//0,0
    Derived d2(123);
    cout << d2.m_i << ',' << d2.m_mem.m_i
        << endl;//123,123
*/
    Base* pb = new Derived;
    //...
    //只调用基类的析构函数,有内存泄露风险
    delete pb;

    return 0;
}
  • 执行结果
    在这里插入图片描述

子类的拷贝构造和拷贝赋值

  • 拷贝构造
    • 如果子类没用定义拷贝构造函数,编译器会为子类提供一个缺省的拷贝构造函数,该函数会自动调用基类的拷贝构造函数,完成对基类子对象的拷贝。
    • 如果自己定义了拷贝构造函数,需要使用初始化表,显式的指明基类子对象也要以拷贝的方式来初始化。
	class Base{};
	class Derived:public Base{
		//Base(that):显式的指明基类子对象也要以拷贝的方式来初始化
		Derived(const Derived& that)
			:Base(that),...{}
	};
  • 拷贝赋值
    • 如果子类没用定义拷贝赋值操作符函数,那么编译器会为子类提供缺省的拷贝赋值函数,该函数会自动调用基类的拷贝赋值函数,完成基类子对象的复制操作。
    • 如果自己定义了拷贝赋值函数,需要显式调用基类的拷贝赋值函数,完成基类子对象的赋值操作。
  class Base{};
  class Derived:public Base{
  		Derived& operator=(const Derived& that){
  			//显式调用基类的拷贝赋值函数
  			Base::operator=(that);
  			......
  		}
  };

代码示例

  • 02inherit.cpp
#include <iostream>
using namespace std;
class Base{
public:
    Base(void):m_i(0){}
    Base(int i):m_i(i){}
    Base(const Base& that):m_i(that.m_i){
        cout << "Base(const Base&)" << endl;
    }
    Base& operator=(const Base& that){
        cout << "Base::operator=" << endl;
        if(&that != this){
            m_i = that.m_i;
        }
        return *this;
    }
    int m_i;
};
class Derived:public Base{
public:
    Derived(void):m_i(0){}
    Derived(int i):Base(i),m_i(i){}
    //Base(that):指明基类部分也要以拷贝方式
    //来初始化
    Derived(const Derived& that)
        :m_i(that.m_i),Base(that){}
    Derived& operator=(const Derived& that){
        if(&that != this){
            //显式调用基类的拷贝赋值函数,
            //完成基类子对象的复制
            Base::operator=(that);
            m_i = that.m_i;
        }
        return *this;
    }
    int m_i;
};
int main(void)
{
    Derived d1(123);
    Derived d2(d1);//拷贝构造
    cout << d1.m_i << ',' << d1.Base::m_i
        << endl;//123 123
    cout << d2.m_i << ',' << d2.Base::m_i
        << endl;//123 123

    Derived d3;
    d3 = d1;//拷贝赋值
    cout << d3.m_i << ',' << d3.Base::m_i
        << endl;//123 123

}
  • 执行结果
    在这里插入图片描述

多重继承

  • 概念
    一个子类同时继承多个基类,这样继承结构称为多重继承。
  电话	播放器  计算机
  	  \	  |	  /	
  		智能手机
  • 多重继承在向上造型时,编译器会根据基类子对象的内存布局,自动进行偏移计算,保证指针的类型与所指向的目标对象类型一致。
  • 名字冲突问题
    一个子类的多个基类中如果存在相同名字的成员,当通过子类访问它们时,编译器会包歧义错误——名字冲突。
    解决名字冲突的一般做法是显式调用“类名::”,指明所访问的成员属于哪个基类。
    如果产生名字冲突的成员是成员函数并满足不同参数重载条件,也可以通过 using 声明使它们在子类中形成重载,通过重载解析来解决。

代码示例

  • mul_inherit.cpp
#include <iostream>
using namespace std;
//电话基类
class Phone{
public:
    Phone(const string& number):
        m_number(number){}
    void call(const string& number){
        cout << m_number << "打给" <<
            number << endl;
    }
private:
    string m_number;
};
//播放器基类
class Player{
public:
    Player(const string& media):
        m_media(media){}
    void play(const string& music){
        cout << m_media << "播放器播放"
            << music << endl;
    }
private:
    string m_media;
};
//计算机基类
class Computer{
public:
    Computer(const string& os):m_os(os){}
    void run(const string& app){
        cout << "在" << m_os << "系统上运行"
            << app << endl;
    }
private:
    string m_os;
};
//智能手机子类
class SmartPhone:public Phone,
                 public Player,
                 public Computer{
public:
    SmartPhone(
        const string& number,
        const string& media,
        const string& os):
            Phone(number),
            Player(media),
            Computer(os){}
};
int main(void)
{
    SmartPhone iphoneX(
        "13866668888","MP4","Android");
    iphoneX.call("010-110");
    iphoneX.play("最炫小苹果.mp3");
    iphoneX.run("Angry Brid");

    SmartPhone* p1 = &iphoneX;
    Phone* p2 = p1;
    Player* p3 = p1;
    Computer* p4 = p1;

    cout << "p1=" << p1 << endl;
    cout << "p2=" << p2 << endl;
    cout << "p3=" << p3 << endl;
    cout << "p4=" << p4 << endl;

    return 0;
}
  • 执行结果
    在这里插入图片描述

钻石继承

  • 一个子类的多个基类源于共同的基类祖先,这样的继承结构称为钻石继承。
	   A
	  / \
	 B   C
	  \ /
	   D
  • 公共基类(A)子对象在汇聚子类(D)对象中存在多个实例。在汇聚子类中或者通过汇聚子类对象去访问公共基类中的成员,会因为继承路径不同而导致结果不一致。
  • 通过虚继承可以让公共基类(A)子对象在汇聚子类(D)对象中实例位移,并为多余中间(B、C)类共享,这样即使沿着不同的继承路径,所访问到公共基类中的成员也一定一致。

代码实例

  • mul_inherit.cpp
#include <iostream>
using namespace std;
class Base1{
public:
    void func(void){
        cout << "Base1::func(void)" << endl;
    }
};
class Base2{
public:
    void func(int i){
        cout << "Base2::func(int)" << endl;
    }
};
class Derived:public Base1,public Base2{
public:
    //using Base1::func;
    //using Base2::func;
};
int main(void)
{
    Derived d;
    d.Base1::func();
    d.Base2::func(10);
    return 0;
}
  • 执行结果
    在这里插入图片描述
  • diamond.cpp
#include <iostream>
using namespace std;
/* 钻石继承
 *   A(int m_data)
 *  / \
 * B   C
 *  \ /
 *   D(汇聚子类)
 * */
class A{
public:
    A(int data):m_data(data){
        cout << "A:" << this << "," 
            << sizeof(A) << endl;
    }
protected:
    int m_data;
};
class B:virtual public A{//虚继承
public:
    B(int data):A(data){
        cout << "B:" << this << "," << 
            sizeof(B) << endl;
    }
    void set(int data){
        m_data = data;
    }
};
class C:virtual public A{//虚继承
public:
    C(int data):A(data){
        cout << "C:" << this << "," << 
            sizeof(C) << endl;
    }
    int get(void){
        return m_data;
    }
};
class D:public B,public C{
public:
    //虚继承,继承链最末端的汇聚子类负责
    //构造公共基类子对象
    D(int data):B(data),C(data),A(data){
        cout << "D:" << this << "," << 
            sizeof(D) << endl;
    }
};
int main(void)
{
    D d(100);
    cout << d.get() << endl;//100
    d.set(200);
    cout << d.get() << endl;//200
    return 0;
}
  • 执行结果
    在这里插入图片描述

虚继承

  • 1)在继承表中使用virtual关键字修饰
  • 2)位于继承链的最末端子类负责构造公共(虚)基类子对象。
   A(int m_data)
  / \
 B   C//:virtual public A
  \ /
   D//负责构造公共基类(A)子对象
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实验题目1:班级学生学期成绩管理系统 (1)程序功能简介 灵活运用继承、对象成员等机制,设计一个能够实现班级学生学期成绩管理的程序。 (2)程序设计说明 ① 个人信息CPerson的数据成员有姓名、性别、年龄、身份证号等数据成员,成员函数根据需要自行设计; ② 学生CStudent从CPerson派生,并增加学号、CCourse对象成员数组(大小至少3个)等数据成员,并根据需要自行设计成员函数,包括能够求解所选修课程的总学分、显示某个学生基本信息和课程信息的成员函数; ③ 课程CCourse包括课程名、学分、分数、任课老师等数据成员,成员函数根据需要自行设计; ④ 班级CClass的数据成员有班级名称、班级人数、CStudent对象成员数组(大小由构造函数确定)等。本班级CClass的对象成员数组需要在构造函数中用new动态分配内存空间,在析构函数中用delete释放。在CClass中设计包括能够求解最高成绩、最低成绩和平均成绩以及通过学号查找并输出某个学生全部信息(例如Seek())等功能在内的成员函数; ⑤ 构造三十个学生的数据,每个学生都有三门课程成绩,输出并显示这些数据; ⑥ 根据的需要添加适当的其它成员,编写完整的程序并测试。 (3)程序调试运行 运行程序查看结果,并进行源代码调试和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值