c++ 类的继承与派生

 

一、基本概念

1、类的继承,是新的类从已有类那里得到已有的特性。或从已有类产生新类的过程就是类的派生。原有的类称为基类或父类,产生的新类称为派生类或子类。
 
2、派生类的声明:
class 派生类名:继承方式 基类名1, 继承方式 基类名2,...,继承方式 基类名n
{
    派生类成员声明;
};
 
3、一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类,称为单继承。直接派生,间接派生。
 
4、继承方式规定了如何访问基类继承的成员。继承方式有public, private, protected。如果不显示给出继承方式,默认为private继承。继承方式指定了派生类成员以及类外对象对于从基类继承来的成员的访问权限。
 
5、派生类继承基类中除构造和析构函数以外的所有成员。
 
6、派生类生成:
   吸收基类成员(除构造析构函数以外的所有成员);
   改造基类成员(根据继承方式调整基类成员的访问,函数在子类中的覆盖,以及虚函数在子类中的覆盖);
   添加新的成员;
 
7、公有继承
当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问。即基类的公有成员和保护成员被继承到派生类中仍作为派生类的公有成员和保护成员。派生类的其他成员可以直接访问它们。
无论派生类的成员还是派生类的对象都无法访问基类的私有成员。
 
8、私有继承
当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。基类的公有成员和保护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类外部通过派生类的对象无法访问。
无论是派生类的成员还是通过派生类的对象,都无法访问从基类继承的私有成员。
通过多次私有继承后,对于基类的成员都会成为不可访问。因此私有继承比较少用。
 
9、保护继承
保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。派生类的其他成员可以直接访问从基类继承来的公有和保护成员,但是类外部通过派生类的对象无法访问它们,
无论派生类的成员还是派生类的对象,都无法访问基类的私有成员。
 
二、派生类的构造函数和析构函数
1、派生类中由基类继承而来的成员的初始化工作还是由基类的构造函数完成,然后派生类中新增的成员在派生类的构造函数中初始化。
 
2、派生类构造函数的语法:
派生类名::派生类名(参数总表):基类名1(参数表1),基类名(参数名2)....基类名n(参数名n),内嵌子对象1(参数表1),内嵌子对象2(参数表2)....内嵌子对象n(参数表n)
{
    派生类新增成员的初始化语句;
}
注:构造函数的初始化顺序并不以上面的顺序进行,而是根据声明的顺序初始化。
 
3、如果基类中没有不带参数的构造函数,那么在派生类的构造函数中必须调用基类构造函数,以初始化基类成员。
 
4、派生类构造函数执行的次序:
   调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左到右);
   调用内嵌成员对象的构造函数,调用顺序按照它们在类中声明的顺序;
   派生类的构造函数体中的内容。
例子:
 
#include <iostream>
#include <time.h>
using namespace std; class B1
{
public:
B1(int i)
{
cout<<"constructing B1 "<<i<<endl;
}
}; class B2
{
public:
B2(int j)
{
cout<<"constructing B2 "<<j<<endl;
}
}; class B3
{
public:
B3()
{
cout<<"constructing B3"<<endl;
}
}; class C: public B2, public B1, public B3
{
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); return 0;
}
 
输出结果为:
constructing B2 2
constructing B1 1
constructing B3
constructing B1 3
constructing B2 4
constructing B3
 
5、析构函数
派生类的析构函数的功能是在该对象消亡之前进行一些必要的清理工作,析构函数没有类型,也没有参数。析构函数的执行顺序与构造函数相反。
例子:
#include <iostream>
#include <time.h>
using namespace std; class B1
{
public:
B1(int i)
{
cout<<"constructing B1 "<<i<<endl;
}
~B1()
{
cout<<"destructing B1"<<endl;
}
}; class B2
{
public:
B2(int j)
{
cout<<"constructing B2 "<<j<<endl;
}
~B2()
{
cout<<"destructing B2"<<endl;
}
}; class B3
{
public:
B3()
{
cout<<"constructing B3"<<endl;
}
~B3()
{
cout<<"destructing B3"<<endl;
}
}; class C: public B2, public B1, public B3
{
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); return 0;
}
输出结果为:
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
 
三、派生类成员的标识和访问
1、派生类成员属性划分为四种:
   不可访问的成员;私有成员;保护成员;公有成员;
 
2、作用域分辨
形式为:基类名::成员名;基类名::成员名(参数表);
如果某派生类的多个基类拥有同名的成员,同时,派生类又新增这样的同名成员,在这种情况下,派生类成员将覆盖所有基类的同名成员。这就需要这样的调用方式才能调用基类的同名成员。
例子:多继承同名 
 
#include <iostream>
#include <time.h>
using namespace std; class B1
{
public:
int nV;
void fun()
{
cout<<"member of B1 "<<nV<<endl;
}
}; class B2
{
public:
int nV;
void fun()
{
cout<<"member of B2 "<<nV<<endl;
}
}; class D1: public B1, public B2
{
public:
int nV;
void fun()
{
cout<<"member of D1 "<<nV<<endl;
}
}; int main()
{
D1 d1;
d1.nV = 1;
d1.fun();
d1.B1::nV = 2;
d1.B1::fun();
d1.B2::nV = 3;
d1.B2::fun(); return 0;
}
输出结果为:
member of D1 1
member of B1 2
member of B2 3
以上通过作用域分辨符,解决了访问基类中被屏蔽的同名成员。
 
3、如果某个派生类的部分或全部直接基类是从另一个共同的基类派生而来,在这些直接基类中,从上一级基类继承来的成员就拥有相同的名称,因此派生类中也就会产生同名现象,对这种类型的同名成员也要使用作用域分辨符来唯一标识,而且必须用直接基类进行限定。
例子:
#include <iostream>
#include <time.h>
using namespace std; class B0
{
public:
int nV;
void fun()
{
cout<<"member of B0 "<<nV<<endl;
}
}; class B1:public B0
{
public:
int nV1;
}; class B2:public B0
{
public:
int nV2;
}; class D1:public B1, public B2
{
public:
int nVd;
void fund()
{
cout<<"member of D1"<<endl;
}
}; int main()
{
D1 d1;
d1.B1::nV = 2;
d1.B1::fun();
d1.B2::nV = 3;
d1.B2::fun(); return 0;
}
输出结果为:
member of B0 2
member of B0 3
在这种情况下,派生类对象在内存中就同时拥有成员nV及fun的两份拷贝。但是很多情况下,我们只需要这样一个这样的数据拷贝,同一成员的多份拷贝增加了内存的开销。可以通过虚函数来解决这个问题。
 
4、虚基类
为了解决前面提到的多重拷贝的问题,可以将共同基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数也只有一个映射。
虚基类的声明是在派生类的声明过程,其语法形式为:
class 派生类名::virtual 继承方式 基类名;
例子:
#include <iostream>
#include <time.h>
using namespace std; class B0
{
public:
int nV;
void fun()
{
cout<<"member of B0 "<<nV<<endl;
}
}; class B1:virtual public B0
{
public:
int nV1;
}; class B2:virtual public B0
{
public:
int nV2;
}; class D1:public B1, public B2
{
public:
int nVd;
void fund()
{
cout<<"member of D1"<<endl;
}
}; int main()
{
D1 d1;
d1.nV = 2;
d1.fun(); return 0;
}
输出结果为:
member of B0 2
 
5、虚基类及其派生类的构造函数
一般而言,派生类只对其直接基类的构造函数传递参数,但是在虚基类中,不管是直接或间接虚基类的所有派生类,都必须在构造函数的成员初始化列表中列出对虚基类的初始化。
例子:
#include <iostream>
#include <time.h>
using namespace std; class B0
{
public:
B0(int n)
{
nV = n;
}
int nV;
void fun()
{
cout<<"member of B0 "<<nV<<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(); return 0;
}
以上例子看上去B0的构造函数好像被调用了三次,但是实际上只有D1类中的D1(int a):B0(a), B1(a), B2(a)
才是真正的调用了B0构造函数。
 
四、赋值兼容规则
1、赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。
2、赋值兼容规则中所指的替代包括:
   派生类的对象可以赋值给基类对象;
   派生类的对象可以初始化基类的引用;
   派生类对象的地址可以赋给指向基类的指针。
   在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
 
 
 
例子:
#include <iostream>
#include <time.h>
using namespace std; class B0
{
public:
void display()
{
cout<<"B0::display()"<<endl;
}
}; class B1:public B0
{
public:
void display()
{
cout<<"B1::display()"<<endl;
}
}; class B2:public B0
{
public:
void display()
{
cout<<"B2::display()"<<endl;
}
}; void fun(B0 *ptr)
{
ptr->display();
} int main()
{
B0 b0;
B1 b1;
B2 b2;
fun(&b0);
b0 = b1;
fun(&b0);
b0 = b2;
fun(&b0); return 0;
}
 

输出结果为:

B0::display()

B0::display()

B0::display()

通过这种赋值兼容后,每次调用的同名函数都是基类的同名函数,如果想调用派生类的,则需要使用虚函数。

作者:凡程子 
出处:http://www.cnblogs.com/fzhe/ 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

 
 
标签: C++继承

09--c++ 类的继承与派生的更多相关文章

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

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

  2. 模块的封装之C语言类的继承和派生

    [交流][微知识]模块的封装(二):C语言的继承和派生 在模块的封装(一):C语言的封装中,我们介绍了如何使用C语言的结构体来实现一个类的封装,并通过掩码结构体的方式实 现了类成员的保护.这一部分,我 ...

  3. C++学习笔记:07 类的继承与派生

    课程<C++语言程序设计进阶>清华大学 郑莉老师) 基本概念 继承与派生的区别: 继承:保持已有类的特性而构造新类的过程称为继承. 派生:在已有类的基础上新增自己的特性(函数方法.数据成员 ...

  4. Day 5-2 类的继承和派生,重用

    类的继承 派生 在子类中重用父类 组合 抽象类 定义: 继承指的是类与类之间的关系,是一种什么“是”什么的关系,继承的功能之一就是用来解决代码重用问题. 继承是一种创建新类的方式,在python中,新 ...

  5. 4-13 object类,继承和派生( super) ,钻石继承方法

    1,object 类 object class A: ''' 这是一个类 ''' pass a = A() print(A.__dict__) # 双下方法 魔术方法 创建一个空对象 调用init方法 ...

  6. Python基础(16)_面向对象程序设计(类、继承、派生、组合、接口)

    一.面向过程程序设计与面向对象程序设计 面向过程的程序设计:核心是过程,过程就解决问题的步骤,基于该思想设计程序就像是在设计一条流水线,是一种机械式的思维方式 优点:复杂的问题的简单化,流程化 缺点: ...

  7. 对C++类的继承和派生的理解

    C++中的继承是类与类之间的关系,是一个很简单很直观的概念,与现实世界中的继承类似,例如儿子继承父亲的财产. 1.继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程. ...

  8. Python3 面向对象-类的继承与派生

    1.什么是继承? 继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类可称为基类或超类,新建的类称为派生类和或子类. 子类会遗传父类的属性,从而解决代码重用问题. ...

  9. C++——类的继承(派生)

    类的继承就是子类可以拥有父类的成员变量和成员函数 //public 修饰的成员变量 方法 在类的内部 类的外部都能使用//protected: 修饰的成员变量方法,在类的内部使用 ,在继承的子类中可用 ...

随机推荐

  1. 公钥基本结构(PKI)的概念

    公钥证书 ,通常简称为证书 ,用于在 Internet.Extranet 和 Intranet 上进行身份验证并确保数据交换的安全.证书的颁发者和签署者就是众所周知的 证书颁发机构 (CA),将在下一 ...

  2. JavaSE 学习笔记之package包(十一)

    包:定义包用package关键字. 1:对类文件进行分类管理. 2:给类文件提供多层名称空间. 如果生成的包不在当前目录下,需要最好执行classpath,将包所在父目录定义到classpath变量中 ...

  3. CSVHelper在Asp.Net MVC中的使用

    1,从数据库读取数据,然后导出CSV文件 [HttpPost] public FileResult ExportCSV() { var apps =....//linq以及EF从数据库查询数据 Mem ...

  4. noip2013 Day2 T2 花匠 解题报告

    题目: 3289 花匠 2013年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目描述 Description 花匠栋栋种了一排花,每株花都有自己的高度.花儿越长越大, ...

  5. hdu_1056_HangOver_201311071354

    HangOver Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total S ...

  6. 一个神奇的PHP框架:Phalcon 之编译安装

    前言 CentOS7下升级PHP到最新版本以及编译安装phalcon框架,看相关文档无数遍,自己尝试编译安装之后才理解的更深,编译步骤以及碰到的问题做个简单的记录 php-7.0.11编译安装 1.下 ...

  7. [MongoDB]Python 操作 MongoDB

    from pymongo import MongoClient mc = MongoClient('localhost',27017) db = mc.users db.users.save({'na ...

  8. LeetCode 234 Palindrome Linked List(回文链表)(*)(?)

    翻译 给定一个单链表,确定它是否是回文的. 跟进: 你能够在O(n)时间和O(1)空间下完毕它吗? 原文 Given a singly linked list, determine if it is ...

  9. 【HDOJ 2255】奔小康赚大钱(KM算法)

    [HDOJ 2255]奔小康赚大钱(KM算法) 奔小康赚大钱 Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K ...

  10. 【c语言】字符串替换空格:请实现一个函数,把字符串中的每一个空格替换成“%20”

    // 字符串替换空格:请实现一个函数,把字符串中的每一个空格替换成"%20". // 比如输入"we are happy.",则输出"we%20are ...