c++ 类的继承(一)

1. 简介

继承是一种面向对象编程的重要特性,它允许一个类(称为派生类或子类)从另一个类(称为基类或父类)继承属性和行为。基类中定义的成员变量和成员函数可以在派生类中使用,从而实现代码重用和层次化设计。

1.1 是否能继承

并不是所有的类都能存在继承关系。必须满足: is a 判定
大千世界,不是什么东西都能产生继承关系。只有存在某种联系,才能表示有继承关系。如:哺乳动物是动物,猫是哺乳动物,因此,猫是动物,等等。所以在学习继承后面的内容之前,先说说两个术语 is Ahas A

1.1.1 is A

is A是一种继承关系,指的是类的父子继承关系。表达的是一种方式:这个东西是那个东西的一种。例如:西红柿是蔬菜的一种。 西红柿:子类,蔬菜:父类

1.1.2 has A

has a 是一种组合关系,是关联关系的一种(一个类中有另一个类型的实例),是整体和部分之间的关系(比如电脑和GPU之间),并且代表的整体对象负责构建和销毁部分对象,代表部分的对象不能共享。

2. 特点

  1. 代码重用:通过继承,派生类可以获得基类的成员变量和成员函数,避免了重复编写相同的代码。
  2. 层次化设计:通过继承,可以构建对象之间的层次化关系,形成类的继承体系。派生类可以进一步扩展和特化基类的功能。
  3. 隐藏和覆盖:派生类可以隐藏基类中的成员函数(包括构造函数和析构函数)和成员变量,并且可以重新定义(覆盖)基类中的虚函数,以实现多态性。
  4. 多态性:通过基类的指针或引用,可以在运行时动态地调用派生类的成员函数,实现多态性的效果。
    代码:
#include 
#include 

using namespace std;

//父类
class Vegetable{
public:
    string name;
    bool delicious;
};


//子类
class Cabbage:public Vegetable{

};


int main(){

    //子类虽然没有声明name 和 delicious ,但是继承了Vegetable类,等同于自己定义的效果一样
    Cabbage ca;
    ca.name = "Chinese cabbage";
    ca.delicious = true;

    cout << ca.name << "  " << ca.delicious << endl;


    return 0 ;
}

3. 继承访问权限

当一个类派生自基类,该基类可以被继承为 public、protectedprivate 几种类型。继承类型是在继承基类时指定的。 如: class DerivedClass : public BaseClass。 我们几乎不使用 protectedprivate 继承,通常使用 public继承。

  • public

表示公有成员,该成员不仅可以在类内可以被访问,在类外也是可以被访问的,是类对外提供的可访问接口

  • private

表示私有成员,该成员仅在类内可以被访问,在类的外面无法访问;

  • protected

表示保护成员,保护成员在类的外面同样是隐藏状态,无法访问。但是可以在子类中访问。

  1. 公有继承(public)
    基类成员在派生类中的访问权限保持不变,也就是说,基类中的成员访问权限,在派生类中仍然保持原来的访问权限;
#include 

using namespace std;

class BaseClass{
public:
    string name;
protected:
    int no;
private:
    int num;

};

class DerivedClass: public BaseClass{
    void print(){
        name = "aili";
        no = 2;
        num = 45; //编译错误! 无法访问 num
    }
};

int main(){

    DerivedClass de;
    de.name = "baidu" ; 
    de.no = 12;   //编译错误! 无法访问 no
    de.num = 18 ; //编译错误! 无法访问 num

    return 0 ;
}
  1. 私有继承(private)
    基类所有成员在派生类中的访问权限都会变为私有(private)权限;
#include 

using namespace std;

class BaseClass{
public:
    string name;
protected:
    int no;
private:
    int num;

};

class DerivedClass: private BaseClass{
    void print(){
        name = "aili";
        no = 2;
        num = 45; //编译错误! 无法访问 num
    }
};

int main(){

    DerivedClass de;
    de.name = "baidu" ;//编译错误! 无法访问 name
    de.no = 12;   //编译错误! 无法访问 no
    de.num = 18 ; //编译错误! 无法访问 num

    return 0 ;
}
  1. 保护继承(protected)
    除了private还是私有的属性之外,其他的属性都变成了protected,在子类中具有访问权限,但是在类的外面则无法访问。
#include 

using namespace std;

class BaseClass{
public:
    string name;
protected:
    int no;
private:
    int num;

};

class DerivedClass: protected BaseClass{
    void print(){
        name = "aili";
        no = 2;
        num = 45; //编译错误! 无法访问 num
    }
};

int main(){

    DerivedClass de;
    de.name = "baidu" ;//编译错误! 无法访问 name
    de.no = 12;   //编译错误! 无法访问 no
    de.num = 18 ; //编译错误! 无法访问 num

    return 0 ;
}

4. 继承关系下的构造函数的运行顺序

构造函数是对象在创建时调用,析构函数是对象在销毁时调用。但是在继承关系下,无论在对象的创建还是销毁,都会执行父类和子类的构造和析构函数。 它们一般会有以下规则:

  1. 子类对象在创建时会首先调用父类的构造函数;父类构造函数执行完毕后,执行子类的构造函数;
  2. 当父类的构造函数中有参数时,必须在子类的初始化列表中显示调用;
  3. 析构函数执行的顺序是先调用子类的析构函数,再调用父类的析构函数
  1. 子类对象在创建时会首先调用父类的构造函数,父类构造函数执行完毕后,执行子类的构造函数
#include 

using namespace std;


class Base{
public:
    Base(){
        cout<< "基类的构造..." <<endl;
    }
};


class Derived : public Base {
public:
    Derived(){
        cout<< "派生类的构造..." <<endl;
    }
};

int main() {

    Base b ;

    cout << "------------------------" << endl;

    Derived d;
    
    return 0;
}

运行结果:

基类的构造...
------------------------
基类的构造...
派生类的构造...

创建派生类对象的时候,会先执行基类的构造,然后在执行派生类的构造。
因为派生类里面有可能使用到了基类的成员(例如指针成员),这些成员应该在基类先完成初始化,然后派生类才能使用他们。

  1. 子类析构函数执行完毕后,执行父类的析构造函数
#include 

using namespace std;

class Base{
public:
    Base(){
        cout<< "基类的构造..." <<endl;
    }
    ~Base(){
        cout<< "基类的析构..." <<endl;
    }
};


class Derived : public Base {
public:
    Derived(){
        cout<< "派生类的构造..." <<endl;
    }
    ~Derived(){
        cout<< "派生类的析构..." <<endl;
    }
};

int main() {

    //Base b ;

    cout << "------------------------" << endl;

    Derived d;

    return 0;
}

运行结果:

------------------------
基类的构造...
派生类的构造...
派生类的析构...
基类的析构...

先执行子类的析构,然后在执行父类的析构
确保子类完全释放了之后,再走父类的释放回收工作,有可能子类里里面使用到了父类的一些成员。
3. 继承和组合
如果在继承状态下,子类中的成员又含有其他类的对象属性,那么他们之间的构造很析构调用顺序,遵循以下原则:

a. 先调用父类的构造函数,再调用组合对象的构造函数,最后调用自己的构造函数;
b. 先调用自己的析构函数,再调用组合对象的析构函数,最后调用父类的析构函数。
代码:

#include 

using namespace std;

class Base{
public:
    Base(){
        cout<< "基类的构造..." <<endl;
    }
    ~Base(){
        cout<< "基类的析构..." <<endl;
    }
};
class Other{
public:
    Other(){
        cout<< "Other的构造..." <<endl;
    }
    ~Other(){
        cout<< "Other的析构..." <<endl;
    }
};

class Derived : public Base {
public:
    Other o;
    Derived(){
        cout<< "派生类的构造..." <<endl;
    }
    ~Derived(){
        cout<< "派生类的析构..." <<endl;
    }
};

int main() {

    //Base b ;

    cout << "------------------------" << endl;

    Derived d;

    return 0;
}

运行结果:

------------------------
基类的构造...
Other的构造...
派生类的构造...
派生类的析构...
Other的析构...
基类的析构...

5. 调用父类有参构造

继承关系下,子类的默认构造函数会隐式调用父类的默认构造函数,假设父类没有默认的无参构造函数,那么子类需要使用参数初始化列表方式手动调用父类有参构造函数。
在创建子类对象前,就必须完成父类对象的创建工作,也就是在执行子类构造函数之前,必须先执行父类的构造函数。c++ 使用初始化列表来完成这个工作

#include 

using namespace std;


class Base{
public:
    string name;
    int num;
    Base(){
        cout<< "基类的无参构造..." <<endl;
    }
    Base(string name,int num){
        cout<< "基类的有参构造..." <<endl;
    }

};


class Derived : public Base {
public:

    Derived(){
        cout<< "派生类的无参构造..." <<endl;
    }
    Derived(string name,int num){
        cout<< "派生类的有参构造..." <<endl;
    }
};

int main() {



    Derived d;

    cout << "------------------------" << endl;

    Derived d1("libai",24);


    return 0;
}

运行结果:

基类的无参构造...
派生类的无参构造...
------------------------
基类的无参构造...
派生类的有参构造...

默认情况下,子类的所有构造永远会调用父类的无参构造。如果父类没有无参构造,只有有参构造

#include 

using namespace std;


class Base{
public:
    string name;
    int num;
    Base(){
        cout<< "基类的无参构造..." <<endl;
    }
    Base(string name,int num){
        cout<< "基类的有参构造..." <<endl;
    }

};


class Derived : public Base {
public:

    Derived(){
        cout<< "派生类的无参构造..." <<endl;
    }
    Derived(string name,int num):Base(name,num){
        cout<< "派生类的有参构造..." <<endl;
    }
};

int main() {



    Derived d;

    cout << "------------------------" << endl;

    Derived d1("libai",24);


    return 0;
}

运行结果:

基类的无参构造...
派生类的无参构造...
------------------------
基类的有参构造...
派生类的有参构造...

创建子类对象,必须要先走父类的构造函数,才能走子类的构造函数
在子类里面手动调用父类的有参构造,需要使用初始化列表的方式来完成,初始化列表的工作是在子类构造函数体调用之前就执行了。

你可能感兴趣的:(c/c++,c++,开发语言)