美文网首页
C++基础:类与对象三

C++基础:类与对象三

作者: 慧科集团华东校区 | 来源:发表于2017-08-15 09:42 被阅读0次

    作者:慧科集团华东校区-朱家聪老师,转载请注明出处及本链接。

    继承与派生

    基本概念

    在C++中,继承和派生的概念基本与Objective-C相同。子类继承于基类,基类中所有的成员都会继承给子类,包括成员变量和成员函数。子类也能够定义新的成员变量和成员函数。

    //定义基类 Person
    class Person{
    private:
        string name;
        char sex;
    };
    
    //派生子类
    class Student : public Person{      
    private:
        int score;
        int num;
    };
    

    以上述代码为例,Student类继承于Person类。对于Student类来说,成员变量有两部分组成。第一部分是继承自Person类的name和sex,第二部分是派生类中所定义的score和num。派生类的定义格式如下所示:

    class 派生类名 : 继承方式 基类名 {
        派生的成员变量和函数
    }
    

    继承方式

    继承方式指的是派生类中对基类的访问控制,主要体现在两方面。通俗一点可以理解为,从基类中继承而来的成员在派生类中的类型。

    1. 派生类的成员函数对基类成员的访问控制。
    2. 派生类对象对基类成员的访问控制。
    

    继承方式有公有继承(public),私有继承(private),受保护继承(protected)三种。

    基类中的私有成员

    私有成员(private)比较特殊,任何一个基类中的私有成员,都只能在当前类中,已经其友元中能够访问。而在其派生类的成员函数中,以及派生类的对象中都无法访问到这个私有成员。

    公有成员和受保护成员的继承

    除了私有成员之外,其他两种类型的成员都能够被派生类所访问。只不过这些属性在被派生到子类中时,会因为继承方式的不同,呈现出不同的类型。

    三种类型的区别

    公有继承是最常用的一种继承方式。公有继承的基类成员在派生类中保持原有的访问级别。
    私有继承,所有继承自基类的成员,在派生类中都改变为私有类型。
    受保护继承,所有继承自基类的成员,在派生类中都改变为受保护类型。

    继承 公有继承 私有继承 保护基础
    公有成员 公有 私有 保护
    私有成员 不可访问 不可访问 不可访问
    保护成员 保护 私有 保护

    私有继承和保护继承,都不会在派生类中对于基类成员的访问。而是改变了基类成员在子类中的属性类型。从而间接的影响下一次继承时的成员派生。

    class A {
    protected:
        int data_1;
    public:
        int data_2;
    };
    class B:public A{ 
    };
    class C:public B{
    public:
        //C的成员函数
        void func1(){
            cout<<data_1<<endl;
            cout<<data_2<<endl;
        }
    };
    
    //外部函数
    void func2(const C &c){
        cout<<c.data_1<<endl;
        cout<<c.data_2<<endl;
    }
    
    

    如代码所示:

    B类是公有继承

    在B类中两条属性的类型与A中相同,data_1为受保护类型,data_2为公有类型。所以C的成员函数(func1)中既能够访问data_1,也能够访问data_2。而外部函数(func2)中只能够访问data_2。

    B类是私有继承

    在B类中,从基类中继承来的两条属性都会改变为私有类型。虽然这两条属性依然能够在B类中使用,但是在B类派生出C类时,C类中是无权访问的。也就是说func1和func2中都无法访问到data_1和data_2。

    B类是保护继承

    在B类中,data_1和data_2都是受保护的类型。当B类派生出C类时,两个成员都能够在C类中使用。但是data_2变为了受保护类型,所以无法在外部函数func2中使用了。所以func2中两条属性都无法使用。

    总结

    1. 私有成员只能在当前类的中,以及友元中使用。任何继承都不会影响私有成员。
    2. 受保护成员能够被派生类所继承,可以在派生类中来使用,但是依然不能在外部函数中之用受保护成员。
    3. 公有成员能够在任何地方访问。
    4. 继承类型不会影响派生类中对于基类成员的使用权限,比如私有成员在公有继承的子类中无法使用。保护成员在私有继承的子类中依然能够使用。
    5. 公有继承不会影响成员的访问权限,所以是最常用的一种继承类型。
    6. 私有继承会将原本的成员类型改为私有类型。不论原本是公有类型还是保护类型的成员在私有,在私有继承之后都只能在派生类内部进行使用,并且不能再次派生给第三个类。
    7. 保护继承会将原本的公有类型成员改变为受保护类型,原本能够在任何地方使用的公有成员,在保护继承之后,只能在派生类内部来使用。但是和私有继承不同的是,这些属性能够在此被派生给第三个类。

    继承类型对成员类型的影响相对来说比较复杂,首先先要去理解三种类型成员的访问权限。然后还要思考多次派生的复杂度,以及不同的继承类型对成员类型的影响。

    继承和静态成员

    静态成员指的是使用关键词static修饰的成员变量。这样的成员变量的特点是,无论这个类实例化了多少个对象,这个成员变量都只有一个(类似于单例设计模式)。而在继承过程中,也不会改变这一点。也就是说B继承于A,不论有多少个A对象或者B对象。s_data变量都只有一个。亦或者再有C类继承于B类,D类继承于C类,都不会影响这一点。
    在静态成员的访问是时,可以有多种访问方式。例如 类名::成员名 的形式进行访问。由于基类和派生类用的是同一个成员,所以不论是基类名还是子类名都能过通过这种方式来访问这个静态成员。也能过通过实例化的对象来直接访问这个成员变量。
    静态成员也遵守常规的访问权限控制,

    class A {
    public:
        static int s_data;
    };
    class B:public A{
    };
    
    // 静态成员的访问
    int main(){
        A::s_data;
        B::s_data;
        A a1();
        a1.s_data;
    }
    

    继承与转换

    派生类的对象中包含有基类对象的所有成员,所以可以将一个指向派生类对象的指针转换为一个指向基类对象的指针。但是如果反过来操作则不行。引用类型的使用同理。可以理解为与OC中的“多态”相似。

    B b;
    A *pa = &b;
    A &ra = b;
    

    转换的可访问性

    1. 公有继承,类外和后代类成员函数中可以实现派生类到基类的转换。
    2. 私有继承,类外和后代类成员函数中都不能实现派生类到基类的转换。
    3. 受保护继承,类外能实现派生类到基类的转换,后代类成员函数中可以实现派生类到基类的转换。
    

    总结:查看派生类继承自基类的public成员在需要转换的位置的访问权限,如果可以访问,则可以实现派生类到基类的转换。

    派生类的构造和复制控制

    派生类的构造和析构

    派生类由基类中继承而来的成员和派生类自身所定义的成员组成。派生类的对象中,除了自身的成员之外还包含有一个或者多个基类子对象。在对象的构造,析构,复制,赋值的时候,也需要对这些子对象进行相应的构造,析构,复制,赋值。

    构造函数

    派生类如果没有显式定义构造函数的话,会自动的合成一个默认的构造函数。这个默认的构造函数会调用基类的构造函数对基类成员进行初始化操作。一般来说我们会对派生类的构造函数进行自定义。在自定义时,只需要对派生类中所定义成员进行初始化,然后对基类成员直接调用基类的初始化方法即可。如果基类中没有默认构造函数则必须使用初始化列表来初始化基类成员。

    class A {
    protected:
        int data_1;
    public:
        int data_2;
        //基类的构造方法
        A(int i=0, int j=0):data_1(i), data_2(j){}
    };
    
    class B:public A{
    private:
        int data_3;
    public:
        //派生类中的构造方法
        B(int i=0, int j=0,int k = 0):A(i,j),data_3(k){};
    };
    

    在派生类中定义构造函数时,所使用的格式是:派生类名(总参数列表):基类名(基类参数列表),派生类成员初始化列表.....。在派生的构造方法中需要调用基类的构造方法对基类成员进行初始化。这里需要注意的是任何一个派生类都只能调用自己基类的构造方法,而不能去调用基类的基类的构造方法。或者说派生类智能初始化自己的直接基类。

    析构函数

    在派生类的析构函数中,只需要对派生类中自己的成员进行销毁和释放即可。编译器会自动的调用基类的析构函数来处理基类中的成员。在运行析构函数时,先运行派生类的析构函数,然后在运行成员对象类的析构函数, 最后运行基类的析构函数。这一点和构造函数的运行顺序相反。

    派生类的复制控制

    在派生类中,如果没有显式的定义复制构造函数。则系统会自动的生成默认的复制构造函数。在默认的复制构造函数中,首先会判断基类中是否定义了复制构造函数,如果有则调用。如果没有则会调用基类默认的复制构造函数。所以当我们自定义派生类的复制构造函数时,也需要手动的来对基类的成员进行复制。

    class Person{
    private:
        string name;
    public:
        Person(string n = ""):name(n){};
        
        //基类的复制构造函数
        Person(const Person &other):name(other.name){};
        //基类的复制操作符重载
        Person &operator = (const Person &other){
            name = other.name;
            return *this;
        };
    };
    
    class Student:public Person{
    private:
        int num;
    public:
        Student(string str, int i):Person(str),num(i){};
        //派生类的复制构造函数,需要显式调用基类的复制构造函数
        Student(const Student &other):Person(other),num(other.num){};
        
        //派生类的赋值操作符重载
        Student &operator = (const Student &other){
            if (this != &other) {
                //手动调用基类的赋值操作符
                Person::operator=(other);
                //赋值派生类的属性
                this->num = other.num;
            }
            return * this;
        };
    
    };
    

    多重继承派生类

    和OC中不同的是,C++中允许多继承。一个派生类允许继承于多个基类,这个派生类的对象中包含有多个基类的子对象。

    多重继承的定义

    在定义多重继承的派生类时,将多个基类以列表的形式放在派生类名后面。 每个基类都要单独的标出继承的类型。

    class A{
    private:
        int data1;
    public:
        A(int i):data1(i){};
    };
    
    class B{
    private:
        int data2;
    public:
        B(int i):data2(i){};
    };
    
    class C:public A, private B{
    private:
        int data3;
    public:
        C(int i, int j, int k);
    };
    

    多重继承的构造

    多重继承的派生类,在构造时需要以此构造每一个基类,成员对象类,以及派生类对象自身。而多个基类之间构造的先后次序由定义派生类时基类列表中的顺序来决定。和构造函数的初始化列表中的顺序无关。

    class C:public A, private B{
    private:
        int data3;
    public:
        C(int i, int j, int k):B(j),A(i),data3(k){};
    };
    

    例如在派生类C中,构造函数的调用顺序是,A->B->C。当这个派生类中存在内嵌对象时,按照基类对象->内嵌对象->派生类对象的顺序调用构造函数。如果有多个内嵌对象的话,则按照这些对象的定义顺序来调用。

    相关文章

      网友评论

          本文标题:C++基础:类与对象三

          本文链接:https://www.haomeiwen.com/subject/dsrmrxtx.html