C++中的继承

2人阅读 评论(0) 收藏 举报
分类:

继承方式

继承方式位于定义子类的”:”后面,比如:

class Line : public Object  //是public继承
{

};

继承方式默认为private

在C++中,继承方式共有3种:

public继承

-指父类的成员(变量和函数)访问级别,在子类中保持不变

private继承

-指父类的成员,在子类中变为private私有成员.

-也就是说子类无法访问父类的所有成员

protected继承

-指父类的public成员 ,在子类中变为protected保护成员,其它成员级别保持不变

如下图所示:
这里写图片描述
注意: protected继承只针对子类有效

比如当父类是protected继承时,则子类的子类就无法访问父类的所有成员

一般而言,C++项目只用到public继承

显示调用父类构造函数

当我们创建子类对象时,编译器会默认调用父类无参构造函数
若有子类对象,也会默认调用子类对象的无参构造函数。
比如以下代码:

class  StrA
{
public:
          StrA()
          {
             cout<<"StrA()"<<endl;
          }
          StrA(string s)
          {
             cout<<"StrA(string s):"<<s<<endl;
          } 
};

class  StrB : public StrA
{
public:
          StrB(string s)
          {
             cout<<"StrB(int i):"<<s<<endl;
          }
};

int main()
{
       StrB b("123");
       return 0;
}

编译

StrA()                    //父类无参构造函数
StrB(int i):123也可以通过子类构造函数的初始化列表来显示调用

接下来,修改上面子类的StrB(string s)函数,通过初始化列表调用StrA(string s)父类构造函数

StrB(string s): StrA(s)
{
  cout<<"StrB(int i):"<<s<<endl;
}StrA(string s):123
StrB(int i):

123 的同名成员和同名函数以定义父类中的同名成员和同名函数
子类中的成员变量和函数将会隐藏父类的同名成员变量和函数
父类中的同名成员变量和函数依然存在子类中
通过作用域分辨符(::)才可以访问父类中的同名成员变量和函数
比如:

class Parent{

public:
       int mval;
       Parent()
       {
              mval=1000;
       }

       void add(int i)
       {
              mval+=i;
       } 
};

class Child : public Parent
{
public:
       int mval; 
       Child()
       {
              mval=100;
       }

       void add(int i,int j)
       {
              mval+=i+j;
       }
};
在main()函数执行:

       Child c;

       //c. add(10);        //该行会报错,由于子类有add函数,所以编译器会默认在子类里寻找add(int i);

       c.Parent::add(10);   //该行正确,执行父类的成员函数

       c.add(2,3);

       cout<<"Child.mval="<<c.mval<<endl;

       cout<<"Parent.mval="<<c.Parent::mval<<endl;
      Child.mval=105
      Parent.mval=1010

从打印结果看到,父类和子类之间的作用域是不同的, 所以执行父类的同名成员变量和函数需要作用域分辨符(::)才行

父子间的兼容

以上示例的Parent父类Child子类为例

子类对象可以直接赋值给父类对象使用,比如: Parent p; Child c; p=c;
子类对象可以初始化父类对象,比如: Parent p1(c);
父类引用可以直接引用子类对象,比如: Parent& p2 =c; //p2是c对象的别名
父类指针可以直接指向子类对象,比如: Parent* p3=&c;
其实是编译器是将子类对象退化为了父类对象, 从而能通过子类来赋值初始化父类

所以上述的父类对象(包括指针/引用)也只能访问父类中定义的成员.
如果父类对象想访问子类的成员,只能通过强制转换,将父类对象转为子类类型

示例1,通过C方式转换:

Child c;
Parent* p3=&c;
Child *c2 = (Child*)p3; 

示例2,通过static_cast转换:

Child c;
Parent* p3=&c;
Child *c2 = (static_cast*)<Child*>(p3);

虚函数

实现多态性,通过指向子类的父类指针或引用,可以访问子类中同名覆盖成员函数

首先参考下面,没有虚函数的示例:

class Parent
{
    int i; 
public:  
         void example()
        {
            cout<<"class Parent"<<endl;
        }

}; 

class Child : public Parent  
{
    int j; 
public:
        void example()
        {
            cout<<"class Child"<<endl;
        }     
};


void print(Parent* p)
{
     p->example();
}
int main()
{
    Parent t; 
    Child c;

    print(&t);
    print(&c);     

    cout<<"SIZEOF Parent:"<<sizeof(t)<<endl;
    cout<<"SIZEOF Child:"<<sizeof(c)<<endl; 
}

运行打印:

class Parent
class Parent
SIZEOF Parent:4
SIZEOF Child:8

从结果看出,即使example函数的指针p指向了Child c,也只能调用父类的example(),无法实现多态性.

所以C++引入了虚函数概念,根据指针指向的对象类型,来执行不同类的同名覆盖成员函数,实现不同的形态

定义: 在父类成员函数的返回值前面,通过virtual关键字声明,这样便能访问子类中的同名成员函数了

接下来将上个示例的父类成员函数example()改写为虚函数:

virtual void print()        //将父类的成员函数定为虚函数
{
cout<<"class Parent"<<endl;
}        

运行打印:

class Parent
class Child
SIZEOF Parent:8
SIZEOF Child:12

可以发现,父类和子类的长度都增加了4字节,这4个字节就是用来指向“虚函数表”的指针,编译器便会更据这个指针来执行不同类的虚函数,实现多态性.

虚析构函数

-在使用基类指针指向派生类对象时用到

-通过基类析构函数可以删除派生类对象

示例

#include <iostream>

using namespace std;

class Base
{
public:
     Base()
    {
        cout << "Base()" << endl;
    }

     virtual ~Base()
    {
        cout << "~Base()" << endl;
    }
};

class Derived : public Base
{
public:
     Derived()
    {
        cout << "Derived()" << endl;
    }

     ~Derived()
    {
        cout << "~Derived()" << endl;
    }
};

int main()
{
    Base* p = new Derived(); 
    // ...
    delete p;

    return 0;
}
复制代码
运行打印:

Base()
Derived()
~Derived()
~Base()

可以发现,由于基类的析构函数是虚函数,所以我们delete基类指针时,派生类也跟着调用了析构函数,从而避免了内存泄漏,也能满足使用dynamic_cast强制转换了

一般而言,虚构造函数只有在继承下才会被使用,单个类是不会使用虚构函数的,因为虚函数表会产生额外的空间

注意:构造函数不能成为虚函数,因为虚函数表是在构造函数执行后才会进行初始化

查看评论

关于C++中的继承和重载的区别

C++中的很多特性光从概念上的话,很难做区分。或者说,概念让人容易模糊,比如说函数重载和函数继承。        先说重载,重载分为操作符重载和函数名重载,其中,操作符重载就是对运算操作符的原有功能...
  • helinlin007
  • helinlin007
  • 2016-06-02 18:33:46
  • 7832

C++继承(一) 常用的继承方式

继承的一些基本知识
  • zyl_1102179268
  • zyl_1102179268
  • 2017-03-07 23:28:53
  • 4345

20170327_请说出C++中继承的概念

20170327_请说出C++中继承的概念
  • cmm0401
  • cmm0401
  • 2017-03-27 10:15:57
  • 912

c++ 继承重要理解

首先,为什么c++需要继承,理解继承到底是什么意思?继承分为哪三种继承方式? 为什么需要继承? 在编写大型程序时,往往有很多的类,每个类都有自己的数据成员和函数,但有些类之间的数据成员和函数却相同,为...
  • xiaoyuxianshenging
  • xiaoyuxianshenging
  • 2017-03-03 11:18:53
  • 2870

C++继承与转换

理解基类类型和派生类之间的转换,对于理解面向对象编程在C++中如何工作非常关键。 每个派生类对象都包含了一个基类的部分,这意味着可以像使用基类对象一样在派生类对象上执行操作。因为派生类对象也是基类对...
  • u010949971
  • u010949971
  • 2017-03-02 09:10:06
  • 262

c++的继承详解

一、前言 继承是c++语言一个重要的机制,该机制自动地为一个类提供来自另一个类的操作和数据结构,这使得程序员只需在新类中定义已有的类中没有的成分来建立一个新类。二、继承解释 继承是类...
  • qq_35644234
  • qq_35644234
  • 2016-10-15 21:12:51
  • 2212

c++继承和组合的区别

类的组合和继承一样,是软件重用的重要方式。组合和继承都是有效地利用已有类的资源。但二者的概念和用法不同。通过继承建立了派生类与基类的关系,它是一种 “是(is a)”的关系,如“白猫是猫”,“黑人是人...
  • caoyan_12727
  • caoyan_12727
  • 2016-08-27 21:59:44
  • 1270

C++中继承与派生是如何实现的?

面向对象编程的主要目的之一就是提供可重用的代码,强调可重用性。当我们在面临一个大的工程的时候,一般都会重用以前的代码,首先我们使用现有的代码可以节省编码时间,我们称重复的编码为重复的造轮子,重复的代码...
  • lidiya007
  • lidiya007
  • 2016-10-10 16:50:33
  • 712

详细讲解C++ 类的继承

一个私有的或保护的派生类不是子类,因为非公共的派生类不能做基类能做的所有的事,就是指在公开场合,但是在类内部可以的一、引言在C++中,类是提供封装的逻辑单位,类的每一个对象都包含有描述其自身状态的数据...
  • ecitnet
  • ecitnet
  • 2008-01-23 09:16:00
  • 20207

C++之继承(继承方式+隐藏+覆盖)

我们在定义对象的时候,会发现很多的对象之间都是有联系的,如以下两个类:class Person { public: void eat(); string m_strName; ...
  • u013486414
  • u013486414
  • 2017-03-03 23:04:35
  • 378
    个人资料
    持之以恒
    等级:
    访问量: 2857
    积分: 502
    排名: 10万+