一、继承

C++很重要的一个特征就是代码重用。在C语言中重用代码的方式就是拷贝代码、修改代码。C++可以用继承或组合的方式来重用。通过组合或继承现有的的类来创建新类,而不是重新创建它们。

继承是使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作,也可以在原有类的基础上作一些修改和增补。
新类称为派生类或子类,原有类称为基类或父类
派生类是基类的具体化

(一)、派生类的声明语法为:
class 派生类名 : 继承方式  基类名
{
           派生类新增成员的声明;
}

(二)、公有/私有/保护成员

在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。
在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。
在关键字protected后面声明,与private类似,其差别表现在继承与派生时对派生类的影响不同

(三)、公有/私有/保护继承

(四)、接口继承与实现继承

我们将类的公有成员函数称为接口。
公有继承,基类的公有成员函数在派生类中仍然是公有的,换句话说是基类的接口成为了派生类的接口,因而将它称为接口继承。
实现继承,对于私有、保护继承,派生类不继承基类的接口。派生类将不再支持基类的公有接口,它希望能重用基类的实现而已,因而将它称为实现继承。

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
 
#include <iostream>
using namespace std;

class Base
{
public:
    int x_;
protected:
    int y_;
private:
    int z_;
};

class PublicInherit : public Base
{
public:
    void Test()
    {
        x_ = 10;
        y_ = 20;
        //z_ = 30; error
    }
private:
    int a_;
};

class PublicPublicInherit : public PublicInherit
{
public:
    void Test()
    {
        y_ = 20;
    }
};

class PrivateInherit : private Base
{
public:
    void Test()
    {
        x_ = 10;
        y_ = 20;
        //z_ = 30; error
    }
};

int main(void)
{
    PublicInherit pub;
    pub.x_ = 20;

PrivateInherit pri;
    //pri.x_ = 10; error
    return 0;
}

(五)、继承与重定义

对基类的数据成员的重定义
对基类成员函数的重定义分为两种

overwrite(隐藏)

override(覆盖)

(六)、继承与组合

无论是继承与组合本质上都是把子对象放在新类型中,两者都是使用构造函数的初始化列表去构造这些子对象。
组合通常是在希望新类内部具有已存在的类的功能时使用,而不是希望已存在类作为它的接口。组合通过嵌入一个对象以实现新类的功能,而新类用户看到的是新定义的接口,而不是来自老类的接口。(has-a)
如果希望新类与已存在的类有相同的接口(在这基础上可以增加自己的成员)。这时候需要用继承,也称为子类型化。(is-a)

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
 
#include <iostream>
using namespace std;

class Base
{
public:
    Base() : x_(0), y_(48)
    {

}
    int GetBaseX() const
    {
        return x_;
    }

int GetBaseY() const
    {
        return y_;
    }
    void Show()
    {
        cout << "Base::Show ..." << endl;
    }
    int x_;
private:
    int y_; //继承后无法被直接访问,可通过GetBaseY访问
};

class Derived : public Base
{
public:
    Derived() : x_(0)
    {

}
    int GetDerivedX() const
    {
        return x_;
    }
    void Show(int n)//与下面的show 构成重载,基类的show被隐藏
    {
        cout << "Derived::Show " << n << endl;
    }

void Show()
    {
        cout << "Derived::Show ..." << endl;
    }
    int x_; //重定义x_,基类的x_被隐藏
};

//组合关系
class Test
{
public:
    Base b_;
    int x_;
};

int main(void)
{
    Derived d;
    d.x_ = 10;
    d.Base::x_ = 20; //访问被隐藏的基类x_;
    cout << d.GetBaseX() << endl;
    cout << d.GetDerivedX() << endl;
    cout << d.GetBaseY() << endl;

d.Show();
    d.Base::Show();//访问被隐藏的基类show

cout << sizeof(Derived) << endl;
    cout << sizeof(Test) << endl;

return 0;
}

下面总结一下overload/overwrite/override 之间的区别

成员函数被重载(overload)的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。

覆盖(override)是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。
隐藏(overwrite)(派生类与基类)
(1)不同的范围(分别位于派生类与基类);
(2)函数名与参数都相同,基类无virtual关键字
(3)函数名相同,参数不同,virtual可有可无
当隐藏发生时(实际上是继承了但不可见),如果在派生类的成员函数中想要调用基类的被隐藏函数,可以使用
“ 基类名::函数名(参数)”的语法形式,如果被隐藏的函数是public的,则在类体外也可以使用“ 派生类对象.基类名::函数名(参数)” 的语法,也可用“ 派生类指针->基类名::函数名(参数)”的语法,同理被隐藏的数据成员也可以使用上述列举的方法访问。
或者是 parent* p = new child(); p->func(param);  形式。
注:经试验,即使是覆盖的情况,也可以使用上面说的原则(不包括最后一种方式)去访问父类的虚函数,
此时的调用就不是多态了。
如果不属于上述的情况,则是一般的继承,则使用一般的访问语法即可。

二、用C++设计一个不能继承的类

在Java中定义了关键字final,被final修饰的类不能被继承。但在C++中没有final这个关键字,要实现这个要求还是需要花费一些精力。

首先想到的是在C++ 中,子类的构造函数会自动调用父类的构造函数。同样,子类的析构函数也会自动调用父类的析构函数。要想一个类不能被继承,我们只要把它的构造函数和析构函数都定义为私有函数。那么当一个类试图从它那继承的时候,必然会由于试图调用构造函数、析构函数而导致编译错误。

可是这个类的构造函数和析构函数都是私有函数了,我们怎样才能得到该类的实例呢?这难不倒我们,我们可以通过定义静态来创建和释放类的实例。基于这个思路,我们可以写出如下的代码:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 
///////////////////////////////////////////////////////////////////////
// Define a class which can't be derived from
///////////////////////////////////////////////////////////////////////
class FinalClass1
{
public:
    static FinalClass1 *GetInstance()
    {
        return new FinalClass1;
    }

static void DeleteInstance( FinalClass1 *pInstance)
    {
        delete pInstance;
        pInstance = 0;
    }

private:
    FinalClass1() {}
    ~FinalClass1() {}
};

这个类是不能被继承,但在总觉得它和一般的类有些不一样,使用起来也有点不方便。比如,我们只能得到位于堆上的实例,而得不到位于栈上实例。能不能实现一个和一般类除了不能被继承之外其他用法都一样的类呢?办法总是有的,不过需要一些技巧。请看如下代码:
 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
///////////////////////////////////////////////////////////////////////
// Define a class which can't be derived from
///////////////////////////////////////////////////////////////////////
template <typename T> class MakeFinal
{
    friend T;

private:
    MakeFinal() {}
    ~MakeFinal() {}
};

class FinalClass2 : virtual public MakeFinal<FinalClass2>
{
public:
    FinalClass2() {}
    ~FinalClass2() {}
};

这个类使用起来和一般的类没有区别,可以在栈上、也可以在堆上创建实例。尽管类MakeFinal<FinalClass2>的构造函数和析构函数都是私有的,但由于类FinalClass2是它的友元函数,因此在FinalClass2中调用MakeFinal<FinalClass2>的构造函数和析构函数都不会造成编译错误。但当我们试图从FinalClass2继承一个类并创建它的实例时,却不同通过编译。
 C++ Code 
1
2
3
4
5
6
7
8
9
 
class Try : public FinalClass2
{
public:
    Try() {}
    ~Try() {}
};

Try temp;

由于类FinalClass2是从类MakeFinal<FinalClass2>虚继承过来的,在调用Try的构造函数的时候,会直接跳过FinalClass2而直接调用MakeFinal<FinalClass2>的构造函数。非常遗憾的是,Try不是MakeFinal<FinalClass2>的友元,因此不能调用其私有的构造函数。
基于上面的分析,试图从FinalClass2继承的类,一旦实例化,都会导致编译错误,因此是FinalClass2不能被继承。这就满足了我们设计要求。
为什么需要虚继承?
调用try的构造函数时,会先调用它包含的所有virtual base类的构造函数,然后再调用它上层的base类构造函数,然后是设置vptr,最后是初始化列表和子类构造函数体内的用户代码。try不能调用MakeFinal的私有成员,因此引发编译错误。
如果不是virtual继承,那么try首先调用的是它上层base类的构造函数,也就是FinalClass的构造函数,然后由FinalClass的构造函数来调用MakeFinal的构造函数,由于FinalClass是MakeFinal的友元,因此该调用合法,所以try得以正确构造,而没有编译错误。

参考:

C++ primer 第四版
Effective C++ 3rd
C++编程规范

http://zhedahht.blog.163.com/

公有/私有/保护继承、overload/overwrite/override之间的区别的更多相关文章

  1. 从零开始学C++之继承(一):公有/私有/保护继承、overload/overwrite/override之间的区别

    一.继承 C++很重要的一个特征就是代码重用.在C语言中重用代码的方式就是拷贝代码.修改代码.C++可以用继承或组合的方式来重用.通过组合或继承现有的的类来创建新类,而不是重新创建它们. 继承是使用已 ...

  2. Java中的Overload和Override有什么区别

    Overload和Override的区别 1.Overload 定义 Overload是重载的意思.它是指我们可以定义一些名称相同的方法,通过定义不同的输入参数来区分这些方法,然后在调用时,虚拟机就会 ...

  3. C++学习笔记14,private/protected/public继承,私有继承,保护继承,公有继承(五)(总结)

    各种继承方式: 特征 公有继承 保护继承 私有继承 公有成员变为 派生类的公有成员 派生类的保护成员 派生类的私有成员 保护成员变为 派生类的保护成员 派生类的保护成员 派生类的私有成员 私有成员变为 ...

  4. C++ 中私有继承、保护继承与公有继承

    区别 下面通过一个示例来介绍三种继承的区别. 定义一个基类(假设为一个快退休的富豪): class RichMan { public: RichMan(); ~RichMan(); int m_com ...

  5. C++中公有继承、保护继承、私有继承的区别

    公有继承时基类中各成员属性保持不变,基类中private成员被隐藏.派生类的成员只能访问基类中的public/protected成员,而不能访问private成员:派生类的对象只能访问基类中的publ ...

  6. C++中overload 、override、overwrite 之间的区别

    Overload(重载):在C++程序中,可以将语义.功能相似的几个函数用同一个名字表示,但参数或返回值不同(包括类型.顺序不同),即函数重载.(1)相同的范围(在同一个类中):(2)函数名字相同:( ...

  7. C++中的Overload、Override和Overwrite

    在C++语言中有一组基础的概念一直都容易混淆:Overload.Override和Overwrite分别表示什么意思?下面把这三个概念整理一下: 1. Overload(重载) 重载的概念最好理解,在 ...

  8. overload和override二者之间的区别

    overload和override三者之间的区别 Overload是重载,是有相同的方法名,但参数类型或个数彼此不同Override是重写,是在子类与父类中,子类中的方法的方法名,参数个数.类型都与父 ...

  9. C++之共有继承、保护继承、私有继承

    1.封装,public,private作用就是这个目的. 类外只能访问public成员而不能方位private成员: private成员只能被类成员和友元访问: 2.继承,protected的作用就是 ...

随机推荐

  1. mysql中如何比较日期

    做项目,需求是要做一个统计的功能,首次进入默认显示今天以及七天前的数据,这个很好解决. 然后就是用户点击日历插件选择日志,根据日期来统计当天的情况,我数据库里存的时间是使用的时间戳 前台获取到的日期是 ...

  2. FPGA LVDS I/O as an Analog Programmable Comparator

    http://www.eetimes.com/author.asp?section_id=36&doc_id=1320289 Seeing the new ADC IP being bandi ...

  3. spring学习之@ModelAttribute运用详解

    @ModelAttribute使用详解 1.@ModelAttribute注释方法     例子(1),(2),(3)类似,被@ModelAttribute注释的方法会在此controller每个方法 ...

  4. Python 列表(Lists)

    Python 列表(Lists) 序列是Python中最基本的数据结构.序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推. Python有6个序列的内置类 ...

  5. KB/KiB,MB/MiB,GB/GiB了解一下

    Kibibyte是一种资讯计量单位,代表1024字节,即210字节,一般简称为KiB.Kibibyte是来自英文 kilo binary byte 的缩写,意思为“千位二进制字节”. 而KB是kilo ...

  6. django ajax增 删 改 查

    具于django ajax实现增 删 改 查功能 代码示例: 代码: urls.py from django.conf.urls import url from django.contrib impo ...

  7. 进程控制块(PCB)结构

    一.进程控制块(PCB)结构 进程控制块(PCB)是系统为了管理进程设置的一个专门的数据结构.系统用它来记录进程的外部特征,描述进程的运动变化过程.同时,系统可以利用PCB来控制和管理进程,所以说,P ...

  8. C#对.zip 存档读取和写入【转】

    Framework4.5支持 引用: System.IO.Compression.dll,System.IO.Compression.FileSystem.dll 提取压缩文件 ZipFile.Ext ...

  9. unity 的reflection probe和environmentmap

    unity做了个很恶心的事情 unity_SpecCube0这里如果在reflectionprobe范围内就传reflectionprobe 如果在probe范围外这里就传environmap 在GI ...

  10. Token_使用JWT生成token

    1.token三部分 header   { "typ": "JWT", "alg": "HS256"   } paylo ...