1、类的继承与派生  保持已有类的特性而构造新类的过程成为继承;

在已有类的基础上新增自己的特性而产生新类的过程称为派生;

被继承的已有类为基类;派生出的新类成为派生类。继承和派生其实是一回事。

继承的目的是实现代码的重用,派生的目的是当新的问题出现的时候,原有的程序不能解决时,需要对原程序进行改造。派生类的声明: class 派生类名:继承方式 基类名{成员声明;}

不同的继承方式的影响主要体现在:派生类成员对基类成员的访问权限;通过派生类对象对基类成员的访问权限

三种继承方式:公有、私有和保护。

公有继承:基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可以直接访问;

派生类的成员函数可以直接访问基类中public和protected成员,但不能直接访问基类的private成员;

通过派生类的对象只能访问基类的public成员。

//Rectangle.h
class Point //基类Point类的声明
{
public: //公有函数成员
 void InitP(float xx=0, float yy=0) {X=xx;Y=yy;}
 void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;}
 float GetX() {return X;}
 float GetY() {return Y;}
private: //私有数据成员
 float X,Y;
};
class Rectangle: public Point //派生类声明部分
{
public: //新增公有函数成员
 void InitR(float x, float y, float w, float h)
 {InitP(x,y);W=w;H=h;} //调用基类公有成员函数
 float GetH() {return H;}
 float GetW() {return W;}
private: //新增私有数据成员
 float W,H;
};//End of Rectangle.h
#include<iostream>
#include<cmath>
#include "rectangle.h"
using namespace std;
int main()
{
 Rectangle rect; //声明Rectangle类的对象
 rect.InitR(2,3,20,10); //设置矩形的数据
 rect.Move(3,2); //移动矩形位置
 cout<<"The data of rect(X,Y,W,H):"<<endl;
 cout<<rect.GetX()<<"," //输出矩形的特征参数
       <<rect.GetY()<<","
       <<rect.GetW()<<","
       <<rect.GetH()<<endl;
}

私有继承:基类的public和protected成员以private身份出现在派生类中,但是基类的private成员不可以直接访问;派生类的成员函数可以直接访问基类的public和protected成员,但不能直接访问private成员;通过派生类的对象不能直接访问基类中的任何成员。

//rectangle.h
class Point //基类声明
{
public:
 void InitP(float xx=0, float yy=0) {X=xx;Y=yy;}
 void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;}
 float GetX() {return X;}
 float GetY() {return Y;}
private:
 float X,Y;
};
class Rectangle: private Point //派生类声明
{
public: //新增外部接口
 void InitR(float x, float y, float w, float h)
 {InitP(x,y);W=w;H=h;} //派生类访问基类公有成员
 void Move(float xOff, float yOff) {Point::Move(xOff,yOff);}//自己重新定义一个move函数,其实是间接调用基类函数,

                                                            //为的是方便在类外(主函数中)使用move函数。
 float GetX() {return Point::GetX();}
 float GetY() {return Point::GetY();}
 float GetH() {return H;}
 float GetW() {return W;}
private: //新增私有数据
 float W,H;
};
//End of rectangle.h

#include<iostream>
#include<cmath>
#include "rectangle.h"
using namespace std;
int main()
{
 Rectangle rect; //声明Rectangle类的对象
 rect.InitR(2,3,20,10); //设置矩形的数据
 rect.Move(3,2); //移动矩形位置,使用的是派生类的函数。
 cout<<"The data of rect(X,Y,W,H):"<<endl;
 cout<<rect.GetX()<<"," //输出矩形的特征参数
       <<rect.GetY()<<","
       <<rect.GetW()<<","
       <<rect.GetH()<<endl;
}

保护继承:基类的public和protected成员都已protected身份出现在派生类中,但基类的private成员不可直接访问;派生中成员函数可以直接访问类中的public和protected成员,但是不可以直接访问private成员;通过派生类的对象不能直接访问基类的任何成员。

protected成员的特点与作用:在类外通过类的对象是不可以访问的,对于派生类来说,在派生类中是与public一样的,既实现了数据隐藏,又方便继承,实现了代码重用。

class A{protected:int x;}       …… class A{protected:int x;} class B:public A{public: void f();}

int main(){ A a; a.x=5;//错误}  …… void B:f(){x=5;}//正确

类型兼容规则:一个公有派生类的对象在使用上被当做基类的对象,反之则禁止。

表现在:(轮胎和汽车的关系),派生类的对象含有的信息大于基类。

派生类的对象可以被赋值给基类对象;

派生类的对象可以初始化基类的引用;

指向基类的指针也可以指向派生类。

通过基类的对象名、指针只能使用从基类继承的成员。

#include <iostream>
using namespace std;
class B0 //基类B0声明
{
public:
 void display(){cout<<"B0::display()"<<endl;} //公有成员函数
};
class B1: public B0 //公有派生类B1声明
{
public:
 void display(){cout<<"B1::display()"<<endl;} //公有成员函数
};
class D1: public B1 //公有派生类D1声明
{
public:
 void display(){cout<<"D1::display()"<<endl;} //公有成员函数
};
void fun(B0 *ptr) //普通函数
{  //参数为指向基类对象的指针
 ptr->display(); //"对象指针->成员名"
}
int main() //主函数
{
 B0 b0; //声明B0类对象
 B1 b1; //声明B1类对象
 D1 d1; //声明D1类对象
 B0 *p; //声明B0类指针
 p=&b0; //B0类指针指向B0类对象
 fun(p);
 p=&b1; //B0类指针指向B1类对象
 fun(p);
 p=&d1; //B0类指针指向D1类对象
 fun(p);
}//运行结果:
B0::display()
B0::display()
B0::display()
多继承时派生类的声明:class 派生类名:继承方式1 基类名1,继承方式2 基类2,……{成员声明;};

每一个继承方式只用于限制对紧随其后的基类的继承。

class A{
    public:
       void setA(int);
       void showA();
    private:
       int a;
};
class B{
    public:
       void setB(int);
       void showB();
private:
       int b;
};
class C : public A, private B{
   public:
       void setC(int, int, int);
       void showC();
   private:
       int c;
};
void  A::setA(int x)
{   a=x;  }

void B::setB(int x)
{   b=x;  }

void C::setC(int x, int y, int z)
{   //派生类成员直接访问基类的
     //公有成员
     setA(x);
     setB(y);
     c=z;
}
//其他函数实现略
int main()
{
     C obj;
     obj.setA(5);
     obj.showA();
     obj.setC(6,7,9);
     obj.showC();
// obj.setB(6);  错误,私有继承的B,在类外不可以直接访问。
// obj.showB(); 错误
     return 0;
}

继承时的构造函数:基类的构造函数是不被继承的,因为基类的构造函数不足以为派生类新增的成员初始化,派生类需要声明自己的构造函数,声明构造函数时,只需要对本类中的新增成员初始化,对继承来的基类的成员初始化,自动调用基类的构造函数完成;派生类的构造函数需要给基类的构造函数传递参数。

单继承:派生类名::派生类名(基类所需形参,本类成员所需形参)基类名(参数表){本类成员初始化;};

多继承:派生类名::派生类名(基类1形参,...基类n形参,本类形参):基类名1(参数),...基类名n(参数){本类成员初始化赋值语句;};

当基类中声明有默认形式的构造函数或者没有声明构造函数时,派生类构造函数可以不向基类构造函数传递参数;若基类没有声明构造函数,派生类中也可以不声明,全部采用默认构造函数;当基类中声明有带形参的构造函数时,派生类也应声明带形参的构造函数,并将参数传递给基类的构造函数。

多继承且有内嵌对象(组合类对象成员)时的构造函数:派生类名::派生类名(基类1形参,...基类n形参,本类形参):基类名1(参数),...基类名n(参数),对象数据成员的初始化{本类成员初始化赋值语句;};

构造函数的调用顺序:调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左到右);调用成员对象的构造函数,调用顺序按照它们在类中的声明顺序;派生类的构造函数体中的内容。

拷贝构造函数:若建立派生类对象时调用默认拷贝构造函数,则自动调用基类的默认构造函数;若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参数。C::C(C&b):B(b){};

#include <iostream>
using namespace std;
class B1 //基类B1,构造函数有参数
{
public:
 B1(int i) {cout<<"constructing B1 "<<i<<endl;}
};
class B2 //基类B2,构造函数有参数
{
public:
 B2(int j) {cout<<"constructing B2 "<<j<<endl;}
};
class B3 //基类B3,构造函数无参数
{
public:
 B3(){cout<<"constructing B3 *"<<endl;}
};
class C: public B2, public B1, public B3 //派生新类C
//注意基类名的顺序
{
public: //派生类的公有成员
 C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b){}
 //注意基类名的个数与顺序
 //注意成员对象名的个数与顺序
private: //派生类的私有对象成员
 B1 memberB1;
 B2 memberB2;
 B3 memberB3;
};
int main()
{
 C obj(1,2,3,4);//传递参数顺序,首先基类:B2(b),B1(a),B3不要参数,接着是内嵌成员memberB1,memberB2,memberB3

}//运行结果:
constructing B2 2
constructing B1 1
constructing B3 *
constructing B1 3
constructing B2 4
constructing B3 *

析构函数也不被继承,需要自行声明,声明方法与一般析构函数一样,不需要显式的调用基类的析构函数,系统会自动调用,析构函数的调用顺序和构造函数相反。

#include <iostream>
using namespace std;
class B1 //基类B1声明
{
public:
 B1(int i) {cout<<"constructing B1 "<<i<<endl;} //B1的构造函数
 ~B1() {cout<<"destructing B1 "<<endl;} //B1的析构函数
};
class B2 //基类B2声明
{
public:
 B2(int j) {cout<<"constructing B2 "<<j<<endl;} //B2的构造函数
 ~B2() {cout<<"destructing B2 "<<endl;} //B2的析构函数
};
class B3 //基类B3声明
{
public:
 B3(){cout<<"constructing B3 *"<<endl;} //B3的构造函数
 ~B3() {cout<<"destructing B3 "<<endl;} //B3的析构函数
};
class C: public B2, public B1, public B3 //派生类C声明
{
public:
 C(int a, int b, int c, int d):B1(a),memberB2(d),memberB1(c),B2(b){}
 //派生类构造函数定义
private:
 B1 memberB1;
 B2 memberB2;
 B3 memberB3;
};
int main()
{ C obj(1,2,3,4);
}
//运行结果
constructing B2 2
constructing B1 1
constructing B3 *
constructing B1 3
constructing B2 4
constructing B3 *
destructing B3
destructing B2
destructing B1
destructing B3
destructing B1
destructing B2

2、同名隐藏规则

当派生类中和基类中有相同成员时,若没有强行指明,通过派生类对象使用的派生类的同名成员;如果通过派生类对象访问基类中被覆盖的同名成员,应使用基类名限定。

#include <iostream>
using namespace std;
class B1 //声明基类B1
{
public: //外部接口
 int nV;
 void fun(){cout<<"Member of B1"<<endl;}
};
class B2 //声明基类B2
{
public: //外部接口
 int nV;
 void fun(){cout<<"Member of B2"<<endl;}
};
class D1: public B1, public B2 //声明派生类D1
{
public:
 int nV; //同名数据成员
 void fun(){cout<<"Member of D1"<<endl;} //同名函数成员
};
int main()
{
 D1 d1;
 d1.nV=1; //对象名.成员名标识
 d1.fun(); //访问D1类成员
 d1.B1::nV=2; //作用域分辨符标识
 d1.B1::fun(); //访问B1基类成员
 d1.B2::nV=3; //作用域分辨符标识
 d1.B2::fun(); //访问B2基类成员
}

二义性:在多继承时,基类和派生类之间,或者基类之间出现同名成员,将出现访问时的二义性(不确定性),这种情况采用虚函数或者同名隐藏规则来解决;当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性,采用虚函数解决。

class A
{
    public:
        void  f();
};
class B
{
    public:
        void f();
        void g()
};

class C: public A, piblic B
{         public:
           void g();
           void h();
};
如果声明:C  c1;
则  c1.f();  具有二义性,解决方法1:用类名限定c1.A::f(); or c1.B::f();解决方法2:同名覆盖在c中声明一个同名函数f(),在根据需要调用A::f or B::f.
而  c1.g();  无二义性(同名覆盖)

class B
{         public:            ……

int b;           ……
}                            ……
class B1 : public B          ……
 {                           ……
       private:              ……
            int b1;          ……
}                            ……
class B2 : public B          ……
{                            ……
       private:              ……
            int b2;          ……
};                           ……

class C : public B1,public B2……
{                           ……   
       public:              ……二义性         无二义性
           int f();         …… C c;          c.B1::b;

       private:             ……c.b;           c.B2::b;
           int d;           ……c.B::b;
} //不光产生二义性,还将基类成员b产生了多次拷贝。                          ……
虚基类:主要用于有共同基类的场合。

声明用virtual修饰基类;如class B1:virtual public B,其作用是用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题;为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝。
注意:在第一级继承时就要将共同基类设计成虚基类。

class B{ private: int b;};
class B1 : virtual public B { private: int b1;};
class B2 : virtual public B { private: int b2;};
class C : public B1, public B2{ private: float d;}

下面的访问是正确的:
C  cobj;
cobj.b;

虚基类的派生类对象存储结构示意图:


#include <iostream>
using namespace std;
class B0 //声明基类B0
{ public: //外部接口
 int nV;
 void fun(){cout<<"Member of B0"<<endl;}
};
class B1: virtual public B0  //B0为虚基类,派生B1类
{ public: //新增外部接口
 int nV1;
};
class B2: virtual public B0  //B0为虚基类,派生B2类
{  public: //新增外部接口
 int nV2;
};
class D1: public B1, public B2 //派生类D1声明
{  public: //新增外部接口
 int nVd;
 void fund(){cout<<"Member of D1"<<endl;}
};
int main() //程序主函数
{   D1 d1; //声明D1类对象d1
 d1.nV=2; //使用最远基类成员的唯一一份拷贝
 d1.fun();
}

虚基类和派生类构造函数:建立对象时所指定的类成为最(远)派生类,虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的;在整个继承结构中,直接或间接继承虚基类的所有派生类都必须在构造函数成员初始化表中给出对虚基类构造函数的调用,否则表示调用该虚基类的默认构造函数;在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略;

#include <iostream>
using namecpace std;
class B0 //声明基类B0
{ public: //外部接口
 B0(int n){ nV=n;}
 int nV;
 void fun(){cout<<"Member of B0"<<endl;}
};
class B1: virtual public B0 
{  public: 
 B1(int a) : B0(a) {}
 int nV1;
};
class B2: virtual public B0 
{  public: 
 B2(int a) : B0(a) {}
 int nV2;
};
class D1: public B1, public B2
{
public: 
 D1(int a) : B0(a), B1(a), B2(a){}
 int nVd;
 void fund(){cout<<"Member of D1"<<endl;}
};
int main() 
{
 D1 d1(1); 
 d1.nV=2;
 d1.fun();
}

C++——继承与派生的更多相关文章

  1. c++学习--继承与派生

    继承和派生 1 含有对象成员(子对象)的派生类的构造函数,定义派生类对象成员时,构造函数的执行顺序如下: 1 调用基类的构造函数,对基类数据成员初始化: 2 调用对象成员的构造函数,对对象成员的数据成 ...

  2. 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成员)

    [源码下载] 不可或缺 Windows Native (21) - C++: 继承, 组合, 派生类的构造函数和析构函数, 基类与派生类的转换, 子对象的实例化, 基类成员的隐藏(派生类成员覆盖基类成 ...

  3. [C++]类的继承与派生

    继承性是面向对象程序设计的第二大特性,它允许在既有类的基础上创建新类,新类可以继承既有类的数据成员和成员函数,可以添加自己特有的数据成员和成员函数,还可以对既有类中的成员函数重新定义.利用类的继承和派 ...

  4. O-c中类的继承与派生的概念

    什么是继承 众所周知,面向对象的编程语言具有: 抽象性, 封装性, 继承性, 以及多态性 的特征. 那么什么是继承呢? 传统意义上是指从父辈那里获得父辈留下的东西 在开发中, 继承就是"复用 ...

  5. 程序设计实习MOOC / 继承和派生——编程作业 第五周程序填空题1

    描述 写一个MyString 类,使得下面程序的输出结果是: 1. abcd-efgh-abcd- 2. abcd- 3. 4. abcd-efgh- 5. efgh- 6. c 7. abcd- 8 ...

  6. 走进C++程序世界------继承和派生

    继承和派生 继承是面向对象编程语言的最重要方面之一,正确的使用继承可编写出设计良好,容易于维护和扩展的应用程序.下面是在其他博客中的总结: ****************************** ...

  7. C++学习之路—继承与派生(一):基本概念与基类成员的访问属性

    (本文根据<c++程序设计>(谭浩强)总结而成,整理者:华科小涛@http://www.cnblogs.com/hust-ghtao,转载请注明) 1   基本思想与概念 在传统的程序设计 ...

  8. C/C++基础知识总结——继承与派生

    1. 类的继承与派生 1.1 派生类的定义 (1) 定义规范 class 派生类名: 继承方式 基类1名, 继承方式 基类2名... { ...派生类成员声明; }; (2) 从以上形式上看可以多继承 ...

  9. python基础——继承与派生、组合

    python基础--继承与派生 1 什么是继承: 继承是一种创建新的类的方式,在python中,新建的类可以继承自一个或者多个父类,原始类成为基类或超累,新建的类成为派生类或子类 1.1 继承分为:单 ...

  10. 【C++ 实验六 继承与派生】

    实验内容 1. 某计算机硬件系统,为了实现特定的功能,在某个子模块设计了 ABC 三款芯片用于 数字计算.各个芯片的计算功能如下: A 芯片:计算两位整数的加法(m+n).计算两位整数的减法(m-n) ...

随机推荐

  1. 15.Android-实现TCP客户端,支持读写

    在上章14.Android-使用sendMessage线程之间通信我们学习了如何在线程之间发送数据. 接下来我们便来学习如何通过socket读写TCP. 需要注意的是socket必须写在子线程中,不能 ...

  2. Python 编程入门(2):复杂数据类型(列表,字典)

    以下所有例子都基于最新版本的 Python,为了便于消化,每一篇都尽量短小精悍,希望你能尽力去掌握 Python 编程的「概念」,可以的话去动手试一下这些例子(就算目前还没完全搞懂),加深理解. 在 ...

  3. Android应用第一次启动时的欢迎界面制作

    原理是这样,我们在SharedPreferences中存储一个int型数据,用来代表第几次登录,每次启动时都读取出来判断是不是第一次启动,然后依次判断是否要显示欢迎界面, 具体实现如下: 设置一个欢迎 ...

  4. Oracle修改用户Profile SESSIONS_PER_USER 限制

    一.Profile目的: Oracle系统中的profile可以用来对用户所能使用的数据库资源进行限制,使用Create Profile命令创建一个Profile,用它来实现对数据库资源的限制使用,如 ...

  5. 50元求解pyqt加载并显示pdf问题

    50块钱求解决pyqt5加载pdf文档并显示,类似briss软件的效果

  6. ORACLE10G非归档模式下RMAN异机迁库

    环境信息: 源库 目标库 操作系统 WIN7 WIN SVR 2012 R2 IP x.x.x.216 x.x.x.112 数据库版本 10.2.0.4.0 - 64bi 10.2.0.4.0 - 6 ...

  7. 创建react项目,用typescript语法

    create-react-app 文档 typescript 单元测试 官网测试相关文档

  8. [Python]PyCharm在创建py文件时自动添加头部注释

    在Pycharm主界面找到 File ----->> Setting ----->> Editor ----->> File and Code Templates ...

  9. Java 【循环语句】

    一.java循环语句分支 二.for循环 在java中for循环和C的循环用法一样 public class demo{ public static void main(String[] args){ ...

  10. Python 安装 virturalenv 虚拟环境

    返回目录 本篇索引 一.概述 二.Linux下安装Python虚拟环境 三.Windows下安装Python虚拟环境 一.概述 有时候会在一台主机上安装多个不同的Python版本,用以运行不同时期开发 ...