zoukankan      html  css  js  c++  java
  • C++-继承,公有继承,继承方式和访问控制属性,子类构造函数,子类析构函数(day8)

    一、继承

    1、继承

      通过一种机制,表达出类型之间的共性和特性的方式,利用已有的数据类型定义新的数据类型,这种机制称为继承。这个过程也叫做派生,所以子类也叫派生类。

    继承语法:

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

    继承方式分为:公有继承(pubic)、保护继承(protected)、私有继承(private)

     protected:只能在类的内部和子类中被访问,注意不能被子类的对象所访问

    继承后,基类的成员不需要重复定义,可以直接使用

    class Hman{
    
    public:
    
      Human(const string& name,int age):m_name(name),m_age(age){}
    
      void eat(const string& food){}
    
      void sleep(int time){}
    
    protected:
      string m_name;
      int age;
    
    };
    
    class Student:public Human{
    
    public:
    
      Student(const string& name,int age,int no)
    
        :Human(name,age),m_no(no){}//注意基类成员初始化的方式
    
      void who(void){
    
        ...
      }
    
    private:
    
      int m_no
    
    }
    
    class Teacher:public Human{
    
    public:
    
      Teacher(const string& name,int age,double salary):Human(name,age),m_salary(salary){}
    
    private:
    
      double m_salary;
    
    };

    2、公有继承的特性:

      (1)子类对象会继承基类的属性和行为,通过子类对象访问基类中的成员,如果基类对象在访问他们也一样。假如一个函数需要几个基类类型的参数,传子类的对象也是合法的,

    子类对象的中包含的基类部分称为“基类子对象”。注意区别成员子对象

    (2)向上造型(重要,在多态和函数传参中经常出现)

       将子类的指针或引用转换为基类类型的指针或引用。向上造型可以隐式完成

    Student s(...);
    
    //Student-->Human
    
    Human* people=&s;//通过向上造型的类型转换,缩小了可操作内存范围,所以编译器认为安全,people指针只能访问Human的成员,而不能访问Student的成员

    可参考day3类型转换部分

    (3)向下造型

      将基类的类型的指针或引用转换子类类型的指针或引用。 向下转换编译器不允许,必须使用显式转换。编译器并没有规定不允许向下转换,只是不允许指针可操作范围扩大的操作。

    Studen* ps=people;//编译器报错, 必须做显式的转换
    
    Student* ps=static_cast<Human*>people;

     向下造型是否安全,应该有程序员自己来判断,如果向下造型时,多出来得操作内存是确定的,有效的内存,则认为安全。如果多出来的操作内存是不确定操作内容的,那么就认为不安全。例如多出来的内存没有初始化,可能出现不确定的值。

    (4)子类继承基类的成员

      在子类的中,可以访问基类中的公有成员和保护成员,基类的私有成员在子类中存在,但是在基类中不可见,无法直接访问,即子类的继承父类私有成员也占内存大小。可以通过公有的接口函数访问基类的私有成员。

    (5)基类的构造函数和析构函数无法被子类继承,但是可以在子类的构造函数中,通过初始化表显式的说明基类子对象的初始化方式。如果不指定,那么将使用无参的方式进行初始化

    (6)子类隐藏基类的成员

      当基类和子类同名时,会隐藏基类的成员,优先使用子类的成员

    注意:函数重载三个条件

    (1)相同的作用域,子类和父类中的函数不能构成重载关系,会被认为是同名函数,父类函数会被隐藏。如果要访问父类的函数,必须加作用域限定。例如,假如父类A和子类B中都存在foo()函数,调用父类的foo()函数如下:

    B b;
    
    b.A::foo();

      也可以把子类和父类认为是不同的命名空间,通过using关键字可以在一个作用域使用另外一个作用域的成员,比如在B中引入A的foo()构成重载。

    //在B的定义中

    using A::foo;//引入到B中,可构成重载关系

      一般使用前一种方式较多,因为不能保证子类和父类中的同名函数参数是否不同。

    (2)相同的函数名

    (3)不同的而参数个数或类型

    3、继承方式和访问控制属性

    (1)访问控制限定符以及成员可见范围

    访问控制限定符 访问控制属性 内部 外部 外部 友元
    public 公有成员 ok ok ok ok
    protected 保护成员 ok ok no ok
    private 私有成员 ok no no ok

    (2)不同继承方式对基类成员的可见性

    基类中的 公有继承的子类 保护继承的子类 私有继承的子类
    公有成员 公有 保护 私有
    保护成员 保护 保护 私有
    私有成员 私有 私有 私有

    注意:

      (1)假如,一个基类的成员是公有的或者保护的,那么在基类私有继承之后,基类的公有或者保护成员对子类来说是子类的私有成员。而父类的私有成员,子类不可访问,公有继承和保护继承也是相同的道理。所以继承方式对于子类来说,对父类的访问影响并不大,但是子类的子类会有一定的影响。

      (2)私有继承和保护继承不能向上造型

    因为继承的时候已经把基类公有部分继承为私有的,或者保护的了,如果在向上造型回去称为公有的,存在安全问题。

    class Base{
    
    public:
    
      int m_data;
    
    };
    
    class Derived:private Base{};
    
    int main(){
    
      Derived d;
    
      Base* pb=&d;//不支持向上造型
    
      Base* pb=static_cast<Base*>&d;//也不支持显式静态类型转换,它会做安全检查
    
      Base* pb=(Base*)&d;//支持强制转换,但是成功后不安全。强制转换不会做安全检查
      return 0
    
    }

    二、子类的构造函数与析构函数

    1、基类子对象的构造 

      如果子类的构造函数没有显式的指明基类子对象的初始化方式,那么编译器会调用基类的无参构造函数来初始化基类自对象。

    class Base{
    
    public:
      Base(void):m_i(0){//无参构造
        
      }
      Base(int i):m_i(i){//有参构造
    
      }
    
      ~Base(void){}
      int m_i;
    
    };
    
    class Derived:private Base{
    
    public:
      Derived(void){}
      Derived(int i):Base(i){}
      ~Derived(void){}
    };
    
    
    int main(){
    
      Derived d;//调用基类的无参构造函数
    
      Derived d2(100);//基类和子类都调用有参构造函数
      return 0
    
    }

    构造顺序:

      分配内存->构造基类子对象(多个基类按继承表顺序构造)->构造成员子对象(按声明顺序构造)->执行子类构造函数

      先基类后子类构造,如果基类子对象以有参的方式初始化,必须在子类的构造函数初始化表中显式的指定初始化方式

    关于基类子对象和成员子对象

      先构造基类子对象,再构造成员子对象

    2、子类的析构函数

      子类的析构函数,无论是自定义的还是编译器缺省提供的,都会调用基类的析构函数,析构基类子对象。

    析构顺序:

      执行子类的析构函数->析构成员子对象(按声明逆序)->执行基类子对象析构(多个基类按继承表逆序执行)->释放内存

    子类可以调用基类的析构函数,但是基类不会调用子类的析构函数,对于上述代码,

    Base* pb =new Derived;
    
    //...
    
    //delete pb;//只能调用基类的析构函数,存在内存泄漏
    //解决方法是将pb向下造型转换为子类的指针,通过多态方式也可以解决
  • 相关阅读:
    sdut1282Find the Path (floyd变形)
    sdut1933WHUgirls(dp)
    二分图入门题集
    Codeforces Round #230 (Div. 1)
    PHP中关于 basename、dirname、pathinfo 详解
    nginx php mysql日志配置
    确保 PHP 应用程序的安全 -- 不能违反的四条安全规则
    mysql日期时间处理
    mysql索引类型和索引方法
    php Redis函数使用总结(string,hash,list, set , sort set )
  • 原文地址:https://www.cnblogs.com/ptfe/p/11286814.html
Copyright © 2011-2022 走看看