C++学习——继承

二十一、继承

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

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

2 继承的语法
 

 class 子类:继承方式 基类,...{
          ...
  };
  继承方式:
          1)public,公有继承
          2)protected,保护继承
          3)private,私有继承

参考代码:

#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;
    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("悟空",30,10001);
    cout << "size=" << sizeof(s) << endl;
    s.who();
    s.eat("桃子");
    s.sleep(8);
    s.learn("unix系统编程");
    Teacher t("王建立",48,8800);
    t.who();
    t.eat("饺子");
    t.sleep(7);
    t.teach("unix系统编程");

    //Student*-->Human*:向上造型 
    Human* ph = &s;//ok
    ph->eat("面条");
    ph->sleep(10);
    //ph->learn("C++");

    //Human*-->Student*:向下造型(合理)
    Student* ps = static_cast<Student*>(ph);
    ps->who();

    Human h("老王",48);
    //Human*-->Teacher*:向下造型(不合理)
    Teacher* ps2=static_cast<Teacher*>(&h);
    ps2->who();

    return 0;
}

3.公有继承的特性(public)

1)子类对象会继承基类的属性和行为,通过子类对象可以访问基类中的成员,就如同是基类对象在访问它们一样

注:子类对象中包含基类的部分称为“基类子对象”

2)向上造型(upcast)//重点

将子类类型的指针或引用转换为基类类型的指针或引用,这种操作性缩小的类型转换,在编译器看来是安全的,可以隐式转换。

	class A{};
	class B:public A{};
	class C:public A{};
	...
	void func(A* pa){}
	
	B b;
	func(&b);//向上造型
	C c;
	func(&c);//向上造型
/*
student* ---> Human*:向上造型
Human* ph=&s;
*/

3)向下造型(downcast)//了解

将基类类型的指针或引用转换为子类类型的指针或引用。这种操作性放大的类型转换,在编译器看来是危险的,不能隐式转换,可以显示转换(推荐使用static_cast);

 

4)子类继承基类的成员

①在子类中,可以直接访问基类中的公有和保护成员,就如同他们是子类自己的成员一样

②基类中的私有成员也可以继承过来,但是会受到访问属性的限值,不能直接访问,但是可以让基类提供公有或保护的成员函数来间接访问。

5)子类隐藏基类的成员

①子类和基类定中定义同名的成员函数,因为作用域不同,不能构成重载关系,而是一种隐藏关系,这时通过子类对象将优先访问子类自己的成员,这时如果还希望访问到基类中被隐藏的成员,可以显示使用“类名::”来指明

②如果形成隐藏关系的成员是同名不同参的成员函数,也可以通过using声明,让他们在子类形成重载,通过重载匹配来解决

#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引入到当前子类作用域,让其
    //在子类中形成重载
    //using Base::func;
};
int main(void)
{
    Derived d;
    d.Base::func();
    d.func(10);
    return 0;
}

隐藏三大条件:

①不同作用域(一个派生类,一个基类)

②函数签名相同,基类不被virtual修饰

③函数名相同,参数或常属性不同,不受virtual影响

4.继承方式和访问属性

1)访问控制限定符:影响访问该类成员的位置

访问控制限定符
访问控制限定符访问控制属性内部访问子类访问外部访问友元访问
public公有成员okokokok
protected保护成员okokNOok
private私有成员okNONOok

2)继承方式:影响通过子类访问基类中成员的可访问性

继承方式形象
基类中在公有子类中变成在保护子类中变成在私有子类中变成
公有成员公有成员保护成员私有成员
保护成员保护成员保护成员私有成员
私有成员私有成员私有成员私有成员

注:在私有继承或保护继承中向上造型的语法不适用

参考代码:
 

#include <iostream>
using namespace std;
class A{
public:
    int m_public;
protected:
    int m_protected;
private:
    int m_private;
};
class B:public A{};//公有继承
class C:protected A{};//保护继承
class D:private A{};//私有继承

class X:public B{
    void func(void){
        m_public = 123;//ok
        m_protected = 123;//ok
        //m_private = 123;//no
    }
};
class Y:public C{
    void func(void){
        m_public = 123;//ok
        m_protected = 123;//ok
        //m_private = 123;//no
    }
};
class Z:public D{
    void func(void){
        //m_public = 123;//no
        //m_protected = 123;//no
        //m_private = 123;//no
    }
};

int main(void)
{
    B b;
    b.m_public = 123;//ok
    //b.m_protected = 123;//no
    //b.m_private = 123;//no
    C c;
    //c.m_public = 123;//no
    //c.m_protected = 123;//no
    //c.m_private = 123;//no
    D d;
    //d.m_public = 123;//no
    //d.m_protected = 123;//no
    //d.m_private = 123;//no
}

5.子类的构造函数

1)如果子类的构造函数没有显示指明基类部分(基类子对象)的初始化方式,那么编译器将会自动调用基类的无参构造函数来初始化基类子对象

2)如果希望基类子对象以有参的方式进行初始化,必须使用初始化表来显示指明

	class Base{};
	class Derived:public Base{
		//Base(...):指明基类子对象初始化方式
		Derived(...):Base(...){}
	};

参考代码:

#include <iostream>
using namespace std;
class Member{
public:
    Member(void):m_j(0){
        cout << "Member(void)" << endl;
    }
    Member(int j):m_j(j){
        cout << "Member(int)" << endl;
    }
    int m_j;
};
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(..):指明基类子对象的初始化方式
    //m_mem(..):指明成员子对象的初始化方式
    Derived(int i,int j):Base(i),m_mem(j){
        cout << "Derived(int)" << endl;
    }
    Member m_mem;//成员子对象
};
int main(void)
{
    Derived d;
    cout << d.m_i << ',' << d.m_mem.m_j 
        << endl;//0,0
    Derived d2(123,321);
    cout << d2.m_i << ',' << d2.m_mem.m_j
        << endl;//123,321
}

3)子类对象的创建过程

①分配内存

②构造基类子对象(按继承的顺序)

③构造成员子对象(按声明的顺序)

④执行子类构造函数

 

6.子类析构函数

1)子类的析构函数,无论是自定义的还是编译器缺省提供的,都会自动调用基类的析构函数,完成基类子对象的销毁

2)子类对象的销毁过程

①执行子类析构函数代码

②析构成员子对象(按声明的逆序)

③析构基类子对象(按继承的逆序)

④释放内存

参考代码:

#include <iostream>
using namespace std;
class Member{
public:
    Member(void):m_j(0){
        cout << "Member(void)" << endl;
    }
    Member(int j):m_j(j){
        cout << "Member(int)" << endl;
    }
    ~Member(void){
        cout << "~Member(void)" << endl;
    }
    int m_j;
};
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(..):指明基类子对象的初始化方式
    //m_mem(..):指明成员子对象的初始化方式
    Derived(int i,int j):Base(i),m_mem(j){
        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_j 
        << endl;//0,0
    Derived d2(123,321);
    cout << d2.m_i << ',' << d2.m_mem.m_j
        << endl;//123,321*/
    
    Base* pb = new Derived;
    //...
    //pb->析构函数
    delete pb;//内存泄露

}

3)基类的析构函数不会自动调用子类的析构函数,如果delete一个指向子类的基类指针,实际被调用的仅是基类的析构函数,子类的析构函数不会被执行,有内存泄露的风险。

解决方法:虚析构函数

class Base{...};
class Derived:public Base{...};
Base* pb = new Derived;  //pb指向子类对象的基类指针
delete pb;  //有内存泄露的风险

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

1)子类拷贝构造

①如果自己没有定义拷贝构造函数,那么编译器会提供缺省的拷贝构造函数,该函数会自动调用基类的拷贝构造函数,完成基类子对象的初始化

②如果定义了拷贝构造函数,那么编译器不再提供缺省的拷贝构造函数,这时需要使用初始化表,显式指明基类子对象也要以拷贝方式进行初始化

class Base{...};
class Derived:public Base{
// Base(that):显式指明基类子对象拷贝方式初始化
Derived(const Derived& that):Base(that){...}
};

2)子类的拷贝赋值

①如果子类没有定义拷贝赋值函数,编译器会为子类提供缺省的拷贝赋值函数,该函数会自动调用基类的拷贝赋值函数,完成基类子对象的复制

②如果自己写了拷贝赋值函数,编译器不会为子类提供缺省的拷贝赋值函数,这是需要显式的调用基类的拷贝赋值函数,完成基类子对象的复制

参考代码:

#include <iostream>
using namespace std;
class Base{
public:
    Base(int i=0):m_i(i){}
    Base(const Base& that):m_i(that.m_i){
        cout << "Base的拷贝构造" << endl;
    }
    Base& operator=(const Base& that){
        cout << "Base的拷贝赋值" << endl;
        if(&that != this)
        {
            m_i = that.m_i;
        }
        return *this;
    }
    int  m_i;
};
class Derived:public Base{
public:
    Derived(int i1=0,int i2=0):Base(i1),m_i(i2){}
    //Base(that):显示指明基类子对象以拷贝方式进行初始化
    Derived(const Derived& that):m_i(that.m_i),Base(that){}
    Dericed& operator=(const Derived& taht)
    {
        if(&that != this)
        {
            //显示调用基类的拷贝赋值函数,完成基类子对象的赋值
            Base::operator=(that);
            m_i = that.m_i;
         }
        return *this;
    }
    int m_i;
};

int main()    
{
    Derived d1(10,20);
    Derived d2=d1; //拷贝构造
    cout << d1.Base::m_i << "," << d1.m_i << endl;//10,20
    cout << d2.Base::m_i << ',' << d2.m_i << endl;//10,20
    Derived d3;
    //d3.operator=(d1)
    d3 = d1;//拷贝赋值
    cout << d3.Base::m_i << ',' << d3.m_i << endl;//10,20

    return 0;
}       

8.多重继承

1)概念:一个子类可以同时继承多个基类,这样的继承方式称为多重继承

#include <iostream>
using namespace std;
//电话基类
class Phone{
public:
    Phone(const string& num):m_num(num){}
    void call(const string& num){
        cout << m_num << "打给" << num << 
            endl;
    }
private:
    string m_num;
};
//播放器基类
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& num,
      const string& media,const string& os)
        :Phone(num),Player(media),
            Computer(os){}
};
int main(void)
{
    SmartPhone iphoneXs(
            "13688886666","MP4","Android");
    iphoneXs.call("12315");
    iphoneXs.play("最炫小苹果");
    iphoneXs.run("Angry Bird");
    
    SmartPhone* p1 = &iphoneXs;
    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;
}

2)向上造型时,编译器会根据基类子对象的内部布局,进行适当的偏移计算,保证指针的类型和其所指向的目标基类子对象类型一致

3)名字冲突问题:

①如果子类的多个基类存在相同的名字,当通过子类访问这些名字时,编译器会报歧义错误——名字冲突

②解决名字冲突问题的常规做法就是显式使用“类名::”说明所访问的名字属于哪个基类//常规做法

③如果产生名字冲突的是成员函数,并满足不同参的重载条件,也可以通过using声明,让他们在子类中形成重载,通过重载匹配来解决。//非常规做法

#include <iostream>
using namespace std;
class Base1{
public:
    void func(void){
        cout << "Base1::func" << endl;
    }
    int m_i;
};
class Base2{
public:
    void func(int i){
        cout << "Base2::func" << endl;
    }
    typedef int m_i;
};
class Derived:public Base1,public Base2{
public:
    //通过using声明,让它们在子类中形成重载
    //using Base1::func;
    //using Base2::func;
};
int main(void)
{
    Derived d;
    d.Base1::func();
    d.Base2::func(123);

    d.Base1::m_i = 123;
    cout << d.Base1::m_i << endl;//123

    //int i = 100;
    Derived::Base2::m_i i = 100;
    cout << i << endl;//100
    
    return 0;
}

9.多重继承——钻石继承

1)概念:一个子类的多个继承源自共同的基类祖先,,这样的继承结构称为钻石继承。

参考代码:
 

/* 钻石继承
 *    A
 *   / \
 *  B   C
 *   \ /
 *    D
 * */
#include <iostream>
using namespace std;
class A{
public:
    A(int data):m_data(data){
        cout << "A:" << this << "," << 
            sizeof(A) << endl;
    }
protected:
    int m_data;
};
class B:public A{
public:
    B(int data):A(data){
        cout << "B:" << this << "," << 
            sizeof(B) << endl;
    }
    void set(int data){
        m_data = data;
    }
};
class C: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){
        cout << "D:" << this << "," << 
            sizeof(D) << endl;
    }
};
int main(void)
{
    D d(100);
    cout << sizeof(d) << endl;//8

    cout << d.get() << endl;//100
    d.set(200);
    cout << d.get() << endl;//200?100
    return 0;
}

2)在创建末端子类对象时,公共基类(A)子对象会存在多个实例,在通过末端子类对象访问公共基类中的成员,会因为继承路径不同,导致结果不一致。

3)通过虚继承可以让公共基类子对象实例唯一,并可以为所有的中间类共享,这样即使沿着不同的继承路径,所访问到的公共基类中的成员一定是一致的。

 

参考代码:
 

/* 钻石继承(虚继承)
 *    A
 *   / \
 *  B   C
 *   \ /
 *    D
 * */
#include <iostream>
using namespace std;
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 << sizeof(d) << endl;//8

    cout << d.get() << endl;//100
    d.set(200);
    cout << d.get() << endl;//200?100
    return 0;
}

4)虚继承的语法

①在继承表中使用virtual关键字修饰

②由末端子类负责构造公共基类子对象

5)虚继承实现原理(了解)

注:使用虚继承语法时,创建D对象,它的继承结构会变成类似如下:

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值