C++:类的继承和派生

2.1继承

继承是面向对象的主要特征(此外还要封装和多态)之一,它使得一个类从现有类中派生,而不必重新定义一个新类。继承的实质就是用已有的数据类型创建新的数据类型,并保存已有数据类型的特点,以旧类为基础创建新类,新类包含了旧类的数据成员和成员函数,并且可以在新类中添加新的数据成员和成员函数。旧类被称为基类或者父类,新类被称为派生类或子类。

2.1.1继承的基本语法

例如我们看到很多网页中,都有公共的头部,公共的底部,甚至公共的左侧列表,只有中心内容不同。

#include
using namespace std;
普通实现
//class Java
//{
//public:
//  void head()
//  {
//      cout << "首页、公开课、登录、注册...(公共头部)" << endl;
//  }
//  void foot()
//  {
//      cout << "帮助中心、交流合作..." << endl;
//  }
//  void left()
//  {
//      cout << "Java、Python、C++..." << endl;
//  }
//  void content()
//  {
//      cout << "java学科视频" << endl;
//  }
//};
//class Python
//{
//public:
//  void head()
//  {
//      cout << "首页、公开课、登录、注册...(公共头部)" << endl;
//  }
//  void foot()
//  {

//      cout << "帮助中心、交流合作..." << endl;

//  }

//  void left()

//  {

//      cout << "Java、Python、C++..." << endl;

//  }

//  void content()

//  {

//      cout << "python学科视频" << endl;

//  }

//};

...

//继承实现页面

class BasePage

{

public:

    void head()

    {

        cout << "首页、公开课、登录、注册...(公共头部)" << endl;

    }

    void foot()

    {

        cout << "帮助中心、交流合作..." << endl;

    }

    void left()

    {

        cout << "Java、Python、C++..." << endl;

    }

};

class Java :public BasePage

{

public:

    void content()

    {

        cout << "java学科视频" << endl;

    }

};

class Python :public BasePage

{

public:

    void content()

    {

        cout << "python学科视频" << endl;

    }

};

class Cpp : public BasePage

{

public:

    void content()

    {

        cout << "C++学科视频" << endl;

    }

};

//继承的好处:减少重复代码

//语法:class 子类:继承方式 父类

//子类 也称为 派生类

//父类 也称为 基类

void test01()

{

    cout << "Java页面如下" << endl;

    Java ja;

    ja.head();

    ja.foot();

    ja.left();

    ja.content();

}

int main()

{

    test01();

    system("pause");

    return 0;

}

总结:

继承的好处:可以减少重复的代码

class A:public B;

A类称为子类或派生类

B类称为父类或基类

派生类中的成员,包含两大部分:

一类是从基类继承过来的,一类是自己增加的成员。

从基类继承过来的表现起共性,而新增的成员体现了其个性。

2.1.2 继承的方式

继承方式有三种:

  1. 公共继承
  2. 保护继承
  3. 私有继承

C++:类的继承和派生_第1张图片

#include

using namespace std;

//继承方式

//公共继承

class Base1

{

public:

    int m_A;

protected:

    int m_B;

private:

    int m_C;

};

class Son1 :public Base1

{

public:

    void func() {

        m_A = 10;//父类中的公共权限成员,到子类中依然是公共权限

        m_B = 10;//父类中的保护权限成员,到子类中依然是保护权限

        //m_C=10;//父类中的私有权限成员 子类访问不到

    }

};

//保护继承

class Base2

{

public:

    int m_A;

protected:

    int m_B;

private:

    int m_C;

};

class Son2 :protected Base2

{

    void func() {

        m_A = 100;//父类中公共成员,到子类中变为了保护成员

        m_B = 100;父类中公共成员,到子类中变为了保护成员

        //m_C = 100;//父类中的私有成员 子类访问不到

    }

};

//私有继承

class Base3

{

public:

    int m_A;

protected:

    int m_B;

private:

    int m_C;

};

class Son3 :private Base3

{

    void func() {

        m_A = 100;//父类中公共成员,到子类中变为了私有成员

        m_B = 100;父类中公共成员,到子类中变为了私有成员

        //m_C = 100;//父类中的私有成员 子类访问不到

    }

};

class GrandSon3 :public Son3

{

public:

    void func()

    {

        m_A = 100;//报错,此时Son的时候已经为私有属性

    }

};

void test01()

{

    Son1 son1;

    son1.m_A = 100;

    //son1.m_B = 100;//到Son1中m_B是保护权限

}

void test02()

{

    Son2 s2;

    //s2.m_A = 100;//在son2中m_A变成了保护权限,因此类外访问不到

}

void test03()

{

    Son3 s3;

    s3.m_A = 100;//到Son3中变为私有成员,类外访问不到

    s3.m_B = 1000;//到Son3中变为私有成员,类外访问不到

}

int main()

{

    test01();

    system("pauese");

}

2.1.3继承中的对象模型

问题:从父类继承过来的成员,哪些属于子类对象?

#include

using namespace std;



class Base

{

public:

    int m_A;

protected:

    int m_B;

private :

    int m_C;

};

class Son :public Base

{

public:

    int m_D;

};

void test01()

{

    cout << sizeof(Son) << endl;//16 父类中所有非静态成员属性都会被子类继承下去

    //父类中私有成员属性是被编译器给隐藏了,因此是访问不到,但是确实是被继承下去了

}

int main()

{

    test01();

    system("pause");

    return 0;

}

2.1.4 继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

就是按照栈的原理先进先出,首先子类先创建,所以会调用子类的构造函数,后面是父类的构造函数,销毁时是父类先调用析构函数,子类再调用析构函数。

2.1.5 继承同名成员处理

问题:当子类中出现与父类同名的成员,如何通过子类对象,访问到父类或子类中同名的数据呢?

  1. 访问子类同名成员 直接访问即可
  2. 访问父类同名成员 需要加作用域

#include

using namespace std;

//继承中同名成员处理

class Base

{

public:

    int m_A;

    Base()

    {

        m_A = 100;

    }

    void func()

    {

        cout << "Base-func()调用" << endl;

    }

    void func(int a)

    {

        cout << "Base-func(int)调用" << endl;

    }

};

class Son :public Base

{

public:

    int m_A;

    Son()

    {

        m_A = 200;

    }

    void func()

    {

        cout << "Son-func()调用" << endl;

    }

};

//同名成员变量处理

void test01()

{

    Son s1;

    cout << s1.m_A<< endl;//子类中的100

    //如果通过子类对象访问父类中同名成员,需要加上作用域

    cout << s1.Base::m_A << endl;//父类中的200

}

//同名成员函数处理

void test02()

{

    Son s;

    s.func();//调用子类中的成员函数(直接调用子类中的成员函数)

    s.Base::func();//调用父类中的成员函数(加作用域调用父类中的成员函数)

    s.func(100);//错误

    //如果子类中出现和父类同名的成员函数,子类的同名成员函数会隐藏掉父类中所有同名成员函数

    //如果想要访问发哦父类中的同名成员函数,需要加作用域

    s.Base::func(100);

}

int main()

{

    test02();

    test01();

    system("pause");

    return 0;

}

总结:

  1. 子类对象可以直接访问到子类中同名成员。
  2. 子类对象加作用域可以访问到父类同名成员。
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数。

2.1.6 继承同名静态成员处理方式

问题:继承中同名的静态成员在子类对象上如何进行访问?

静态成员和非静态成员出现同名,处理方式一致。

#include

using namespace std;

//

class Base

{

public:

    static int m_A;

    static void func()

    {

        cout << "访问Base-func()函数" << endl;

    }

};

int Base::m_A = 100;



class Son :public Base

{

public:

    static int m_A;

    static void func()

    {

        cout << "访问Son-func()函数" << endl;

    }

};

int Son::m_A = 200;

//同名静态成员属性

void test01()

{

    Son s;

    //1、通过对象访问

    cout << s.m_A << endl;//访问子类中的静态成员变量

    cout << s.Base::m_A << endl;//访问父类中的静态成员变量

    //2、通过类名访问

    cout << Son::m_A << endl;//访问子类中的静态成员变量

    cout << Base::m_A << endl;//访问父类中的静态成员变量

}

//同名静态成员函数

void test02()

{

    Son s;

    //通过对象访问

    s.func();//访问子类中的成员函数

    s.Base::func();//访问父类中的成员函数

    //通过类名访问

    Son::func();//访问子类中的成员函数

    Base::func();//访问父类中的成员函数

}

int main()

{

    test01();

    system("pause");

    return 0;

}

总结:同名静态成员处理方式与非静态处理方式一样,只不过有两种访问的方式(通过类名和通过对象)。

2.1.7多继承语法

C++允许一个类继承多个类

语法:class子类:继承方式 父类1,继承方式2 父类…

多继承可能会引发父类中有同名成员出现,需要加作用域区分。

C++实际开发中不建议用多继承

#include

using namespace std;

//父类

class Base1

{

public:

    int m_A;

    Base1()

    {

        m_A = 100;

    }

};

class Base2

{

public:

    Base2()

    {

        m_A = 200;

    }

    int m_A;

};

//子类 需要继承Base1和Base2

class Son1:public Base1,public Base2

{

public:

    Son1()

    {

        m_C = 300;

        m_D = 400;

    }

    int m_C;

    int m_D;

};

void test01()

{

    Son1 s;

    cout << sizeof(s) << endl;

    //当父类中出现同名成员,需要加作用域区分

    cout << s.Base1::m_A << endl;

    cout << s.Base2::m_A << endl;

}

int main()

{

    test01();

    system("pause");

    return 0;

}

总结:多继承中如果父类出现了同名情况,子类使用时需要加作用域。

2.1.8菱形继承

菱形继承概念:
(1)两个派生类继承同一个基类;

(2)又有某个类同时继承两个派生类;

(3)这种继承被称为菱形继承,或者钻石继承。

#include

using namespace std;



class Animal

{

public:

    int m_Age;

};

//利用虚继承,解决菱形继承的问题

//Animal类称为虚基类

class Sheep:virtual public Animal

{

public:

};

class Tuo:virtual public Animal

{

public:

};

class SheepTuo:public Sheep,public Tuo

{

public:

};

void test01()

{

    SheepTuo st;

    st.m_Age = 18;//此时不会报错

    cout << st.Sheep::m_Age << endl;

    cout << st.Tuo::m_Age << endl;

    //菱形继承导致数据有两份,资源浪费



}

int main()

{

    test01();

    system("pause");

    return 0;

}

总结:

  1. 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
  2. 利用虚继承可以解决菱形继承问题。

2.2 多态

2.2.1多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类:

  1. 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  2. 动态多态:派生类和虚函数实现运行时多态。

静态多态和动态多态区别:

  1. 静态多态的函数地址早绑定-编译阶段确定函数地址
  2. 动态多态的函数地址晚绑定-运行阶段确定函数地址

#include

using namespace std;

//多态

//动物类

class Animal

{

public:

    virtual void speak()//加virtual关键字就会进行地址晚绑定

    {

        cout << "动物在说话" << endl;

    }



};

//猫类

class Cat:public Animal

{

public:

    void speak()

    {

        cout << "小猫在说话" << endl;

    }

};

//狗类

class Dog :public Animal

{

public:

    //重写 函数返回值类型 函数名 参数列表 完全相同

    void speak()//在此前也可加virtual,可写可不写

    {

        cout << "小狗在说话" << endl;

    }

};

//执行说话的函数

//地址早绑定 在编译阶段确定函数地址

//如果想执行让猫说话,那么这个函数地址就不能早绑定,需要在运行阶段进行绑定,地址晚绑定



//动态多态满足条件

//1、有继承关系

//2、子类要重写父类中的虚函数(如在此中的speak函数,父类中加了virtual关键字的函数)



//动态多态的使用

//父类的指针或者引用 执行子类对象(Animal &animal=cat)



void doSpeak(Animal &animal)//Animal &animal=cat;//允许父类和子类的类型转换

{

    animal.speak();

}

void test01()

{

    Cat cat;

    doSpeak(cat);

    Dog dog;

    doSpeak(dog);

}

int main()

{

    test01();

    system("pause");

    return 0;

}

总结:

父类满足条件:

  1. 有继承关系
  2. 子类重写父类中的虚函数

多态使用条件

  1. 父类指针或引用指向子类对象

重写:函数返回值类型 函数名 参数列表 完全一致称为重写

 2.2.2纯虚函数和抽象类

在多态中,通常父类中虚函数的实现时毫无意义的,主要都是调用子类重写的内容。

因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)=0;

当类中有了纯虚函数,这类也称为抽象类

抽象类特点:
(1)无法实例化对象;

  1. 子类必须重写抽象类中的纯虚函数,否则约束与抽象类。

#include

using namespace std;

class Animal

{

public:

    //纯虚函数

    //只要有一个抽象函数,这个类就称为抽象类

    //抽象类特点:

    //1、无法实例化对象

    //2、抽象类的子类,必须重写父类中的纯虚函数,否则也属于抽象类

    virtual void func() = 0;

};

class Cat :public Animal

{

public:

    void func()

    {

        cout << "func函数调用" << endl;

    }

};

void test01()

{

    //Animal a1;//报错

    Cat cat;

    Animal* animal = new Cat;

    animal->func();

}

int main()

{

    test01();

    system("pause");

    return 0;

}

2.2.3虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构代码改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

  1. 可以解决父类指针释放子类对象
  2. 都需要具体的函数实现

虚析构和纯虚析构区别:

  1. 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名()=0;

类名…~类名(){}

#include

using namespace std;

//虚析构和纯虚析构

class Animal

{

public:

    Animal()

    {

        cout << "Animal构造函数调用" << endl;

    }

    //虚析构和纯虚析构只能存在一个

    //利用虚析构可以解决父类指针释放子类对象时不干净的问题

    /*virtual ~Animal()

    {

        cout << "Animal虚析构函数调用" << endl;

    }*/

    //纯虚析构 需要声明也需要实现

    //有了纯虚析构之后,这个类也属于抽象类,无法实例化对象

    virtual ~Animal() = 0;

    virtual void speak() = 0;//纯虚函数(不需要实现)

};

Animal::~Animal()

{

    cout << "纯虚析构函数调用" << endl;

}

class Cat:public Animal

{

public:

    Cat(string name)

    {

        m_Name = new string(name);

        *m_Name=name;

    }

    virtual void speak()

    {

        cout << *m_Name<<"小猫在说话" << endl;

    }

    ~Cat()

    {

        if (m_Name != NULL)

        {

             cout << "cat析构函数" << endl;

             delete m_Name;

             m_Name = NULL;

        }

    }

    string* m_Name;

};

void test01()

{

    Animal* animal = new Cat("Tom");

    animal->speak();

    //父类指针在析构时候,不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄露

    delete animal;

}

int main()

{

    test01();

    system("pause");

    return 0;

}

总结:

  1. 虚析构或纯虚析构就是用来解决提供父类指针释放子类对象
  2. 如果子类中没有堆区数据,可以不写虚析构或者纯虚析构
  3. 拥有纯虚析构的类也属于抽象类

2.3 友元

在程序中,有些私有属性也想让类外特殊的一些函数或者类访问,就需要用到友元的技术。

友元的目的就是让一个函数或者类访问另一个类中私有成员

友元的关键字friend

友元的三个实现:

  1. 全局函数做友元
  2. 类做友元
  3. 成员函数做友元

2.3.1全局函数做友元

#include

using namespace std;

#include

//建筑物类

class Building

{

    friend void GoodGay(Building* building);//友元

public:

    Building()

    {

        m_SettingRoom = "客厅";

        m_BedRoom = "卧室";

    }

    string m_SettingRoom;//客厅

private:

    string m_BedRoom;//卧室

};

//全局函数做友元

void GoodGay(Building* building)

{

    cout << "好基友的全局函数正在访问:" << building->m_SettingRoom << endl;

    cout << "好基友的全局函数正在访问:" << building->m_BedRoom << endl;

}

void test01()

{

    Building building;

    GoodGay(&building);

}

int main()

{

    test01();

    system("pause");

    return 0;

}

2.3.2 类做友元

#include

using namespace std;

#include

//建筑物类

class Building

{

    friend class GoodGay;//友元

public:

    Building()

    {

        m_SettingRoom = "客厅";

        m_BedRoom = "卧室";

    }

    string m_SettingRoom;//客厅

private:

    string m_BedRoom;//卧室

};

class GoodGay

{

public:

    GoodGay()

    {

        building = new Building;

    }

    void visit()//参观函数 访问Building中的属性

    {

        cout << "好基友类正在访问:" << building->m_SettingRoom << endl;

        cout << "好基友类正在访问:" << building->m_BedRoom << endl;

    }

    Building* building;

};

void test01()

{

    GoodGay gg;

    gg.visit();

}

int main()

{

    test01();

    system("pause");

    return 0;

}

2.3.3 成员函数做友元

#include

using namespace std;

#include

//建筑物类

class Building

{

    friend void GoodGay::visit();//友元

public:

    Building()

    {

        m_SettingRoom = "客厅";

        m_BedRoom = "卧室";

    }

    string m_SettingRoom;//客厅

private:

    string m_BedRoom;//卧室

};

class GoodGay

{

public:

    GoodGay();

    void visit();//参观函数 访问Building中的属性

    Building* building;

};

GoodGay::GoodGay()

{

    building = new Building;

}

void GoodGay::visit()

{

    cout << "好基友类正在访问:" << building->m_SettingRoom << endl;

    cout << "好基友类正在访问:" << building->m_BedRoom << endl;

}

void test01()

{

    GoodGay gg;

    gg.visit();

}

int main()

{

    test01();

    system("pause");

    return 0;

}

你可能感兴趣的:(c++,开发语言,面向对象)