C++ note (9)
Eiaton

继承和派生 Ⅱ

派生与构造函数

类不可继承的成员

类不可继承的成员有

  1. 私有成员
  2. 构造函数与析构函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class MyString : public std::string {
    public:
    MyString(const char* s): string(s) {};
    };

    int main() {
    //ex1: 编译,并观看编译日志
    MyString str1("继承 string(const *char)");
    str1 = "继承 string::operator=(...)";
    cout << str1 << endl;
    MyString str2 = "hello ";
    cout << str2 + str1 << endl;
    //ex2: 取消第 6 行注释,编译并运行
    return 0;
    }

向基类构造函数传递实参

基类构造函数带参数,则定义派生类构造函数时,仅能通过初始化列表显式调用基类构造函数,并向基类构造函数传递实参。

带初始化列表的派生类构造函数的一般形式如下

1
2
3
派生类名(形参表) : 基类名(实参表) {
派生类新成员初始化赋值语句;
};

举例:time类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//  SPECIFICATION FILE (time.h)

class time {
public:
void set(int hours, int minutes, int seconds); // 将被修改
void increment(); // 将被继承
void write() const;
time(int initHrs, int initMins, int initSecs);
time();

private:
int hrs;
int mins;
int secs;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SPECIFICATION FILE ( extTime.h)
#include “time.h”

enum zoneType {
EST, CST, MST, PST, EDT, CDT, MDT, PDT
}; // 枚举类型

class extTime : public time {
public:
extTime(int initHrs, int initMins, int initSecs,
zoneType initZone);
extTime();
void set(int hours, int minutes, int seconds,
zoneType timeZone) ; // 扩展函数成员
void write() const;

private:
zoneType zone; // 增加数据成员
};
1
2
3
4
5
// IMPLEMENTATION FILE (extTime.cpp)

extTime::extTime(int initHrs, int initMins, int initSecs, zoneType initZone, zone(initZone)) : time(initHrs, initMins, initSecs) { // 初始化基类成员
zone = initZone; // 初始化派生类成员
}
  1. 初始化器列表中写 zone(initZone) 更显 C++ 风格
  2. 传递给基类构造函数
  3. 基类构造函数在派生类构造函数之前调用
    1
    2
    3
    4
    5
    //base class default constructor is called prior to the derived //class default constructor.

    extTime::extTime() { // 编译器默认添加 `:time()` 初始化基类成员
    zone = EST; // 如果枚举值标识符崇明,使用类型::枚举值。例如:zoneType::EST
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void extTime::set( int hours, int minutes, int seconds, zoneType timeZone) {
    time::set(hours, minutes, seconds); //调用基类函数。Why?
    zone = timeZone;
    }

    void extTime::write() const
    {
    static string zoneString[8] ={
    "EST", "CST", "MST", "PST", "EDT", "CDT", "MDT", "PDT"
    }; // 数组比switch语句好
    time::write();
    cout << ' ' << zoneString[zone];
    }

派生与成员函数

概念

  1. 重载(overload)

    1. 具有相同的作用域(即同一个类定义中);
    2. 函数名字相同
    3. 参数类型(包括const 指针或引用) ,顺序 或 数目不同
  2. 覆盖(override)- 修改基类函数定义

    (记得加链接)

  3. 隐藏(overwrite)- 屏蔽基类的函数定义

    1. 派生类的函数与基类的函数同名,但是参数列表有所差异。
    2. 派生类的函数与基类的函数同名,参数列表也相同,但是基类函数没有 virtual 关键字。
  4. 继承(inheritance)

    1. 没有被覆盖或隐藏的基类函数,包括在基类中重载的函数

重载和隐藏的区别

例如

  • time::set(int, int, int)
  • exTime::set(int, int, int)

    注: int, int, const int 算不同的参数类型

    ownership 还在函数调用点外部

    int 和 const int 算相同的参数类型

    传参已经 copy,不再管 constant modifier

如果 set 在一个类中定义,则是重载

set 的签名不一样

如果同名函数出现在 baseClass 和 derivedClass 中,且满足隐藏的特征 1 或 2

  • exTime 案例 set 满足特征 1
  • exTime 案例 write 满足特征 2
    所以它们都属于隐藏

隐藏的应用

  • 利用隐藏,实现在派生类中修改成员函数的功能,如 write
  • 利用隐藏,赋予派生类成员函数新的功能
    1
    2
    3
    4
    5
    6
    //  IMPLEMENTATION FILE ( time.cpp )
    void time::set( int hours, int minutes, int seconds) {
    hrs = hours;
    mins = minutes;
    secs = seconds;
    }
    1
    2
    3
    4
    5
    // IMPLEMENTATION FILE ( extTime.cpp )
    void extTime::set( int hours, int minutes, int seconds, zoneType timeZone) {
    time::set(hours, minutes, seconds);
    zone = timeZone;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // base.h

    class base {
    public:
    // 没有默认构造
    base(int p1, int p2);
    int inc1();
    int inc2(); // 被继承
    void display();

    private:
    int mem1, mem2;
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include "base.h"
    // derived.h

    class derived : public base {
    public:
    derived(int x1, int x2, int x3, int x4, int x5);
    int inc1();
    int inc3(); // 新添成员
    void display(); // 隐藏规则 2, 修改定义

    private:
    int mem3;
    base mem4; // 类成员,注意初始化方法
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include "base.h"

    base::base(int p1, int p2) {
    mem1 = p1;
    mem2 = p2;
    }

    int base::inc1() {
    return ++mem1;
    }

    int base::inc2() {
    return ++mem2;
    }

    void base::display() {
    cout << "mem1 = " << mem1 << ", mem2 = " << mem2 << endl;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include "derived.h"

    derived::derived(int x1, int x2, int x3, int x4, int x5) : base(x1, x2), mem4(x3, x4) { // 基类、类成员初始化
    mem3 = x5; // 基类、类成员 不能在这里初始化!
    }

    int derived::inc1() {
    return base::inc1();
    }

    int derived::inc3() {
    return ++mem3;
    }

    void derived::display() {
    base::display(); // 被隐藏函数成员调用
    mem4.display();
    cout << "mem3 = " << mem3 << endl;
    }
    1
    2
    3
    4
    5
    6
    7
    #include "derived.h"
    int main() {
    derived obj(17, 18, 1, 2, -5);
    obj.inc1();
    obj.display();
    return (0);
    }

存储结构

(todo)

改变访问控制

恢复访问控制方式

基类中的 public 或 protected 成员,因使用 protected 或 private 继承访问控制而导致在派生类中的访问方式发生改变,可以使用“访问声明”恢复为原来的访问控制方式

访问声明的形式

1
using 基类名::成员名;(放于适当的成员访问控制后)

使用情景

  • 在派生类中希望大多数继承成员为 protected 或 private,只有少数希望保持为基类原来的访问控制方式
    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
    class base {
    public:
    void set_i(int x) {
    i = x;
    }
    int get_i() {
    return i;
    }
    protected:
    int i;
    };

    class derived : private base {
    public:
    using base::set_i;
    using base::i;
    void set_j(int x) {
    j = x;
    }
    int get_ij() {
    return i + j;
    }
    protected:
    int j;
    };
    1
    2
    3
    4
    5
    6
    7
    int main() {
    derived obj; // 声明一个派生类的对象
    obj.set_i(5); // set_i()已从private转为public
    obj.set_j(7);
    cout << obj.get_ij() << endl;
    return (0);
    }

屏蔽基类成员

目的:

  • 使得客户代码通过派生类对象不能访问继承成员。

方法:

  • 使用继承访问控制protected和private(真正屏蔽)
  • 在派生类中成员访问控制 protected 或 private 之后,使用 “using 基类名::成员名”(非真正屏蔽,仍可通过使用“基类名::成员名”访问)

用于继承对象的重命名

目的:

  • 解决名字冲突。
  • 在派生类中选择更合适的术语命名继承成员。

方法

  • 在派生类中定义新的函数,该函数调用旧函数;屏蔽旧函数。
  • 在派生类中定义新的函数,该函数的函数体与旧函数相同。

使用基类构造函数

目的:

  • 使得派生类对象直接使用基类的构造函数。

方法:

  • 在派生类中使用 using 基类名::基类名

继承机制的应用举例-图形的处理

将圆看作是一种带有半径的点,将点看作是一种带有显示状态的位置

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
//说明:类location以x和y坐标描述了计算机屏幕上的一个位置。

#include <graphics.h>
// basegraph.h

class location {
public:
location(int x, int y); // 构造函数,将当前位置设置为(x, y)
int get_x(); // 返回当前位置的x坐标
int get_y(); // 返回当前位置的y坐标

protected:
// 位置的内部状态,在LOCATION的派生类中需要访问
int x_pos, y_pos;
};

class point : public location {
public:
point(int x, int y);

bool isVisible();
void show();
void hide();
void moveTo(int x, int y);
protected:
bool visible;
};

class circle : public point {
public:
circle(int x, int y, int r);
void show();
void hide();
void moveTo(int x, int y);
void expand(int delta);
void contract(int delta);
protected:
int radius;
};
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
#include "basegraph.h"
// location.cpp

location::location(int x, int y) {
x_pos = x;
y_pos = y;
}

int location::get_x() {
return x_pos;
}

int location::get_y() {
return y_pos;
}

point::point(int x, int y) : location(x, y) {
visible = false;
}

bool point::isVisible() {
return visible;
}

void point::show() {
if(!isVisible()) {
visible = true;
putpixel(x_pos, y_pos, getcolor());
}
}

void point::hide() {
if(isVisible()) {
visible = false;
putpixel(x_pos, y_pos, getbkcolor());
}
}

void point::moveTo(int x, int y) {
hide();
x_pos = x;
y_pos = y;
show();
}
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
#include "basegraph.h"
// circle.cpp

circle::circle(int x, int y, int,r) : point(x, y), radius(r) {}

void circle::show() {
if(!isVisible()) {
visible = true;
circle(x_pos, y_pos);
}
}

void circle::hide() {
unsigned int tmpColor;
if(isVisible()) {
tmpColor = getcolor();
setcolor(getbkcolor());
visible = false;
circle(x_pos, y_pos, radius);
setcolor(tmpColor);
}
}

void circle::moveTo(int x, int y) {
hide();
x_pos = x;
y_pos = y;
show();
}

void circle::expand(int delta) {
hide();
radius = radius + delta;
if(radius < 0) {
radius = 0;
}
show();
}

void circle::contract(int delta) {
expand(-delta);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <conio.h> // 利用其中的getch()函数暂停
#include "basegraph.h"
// graphdemo.cpp

int main() {
int graphdriver = DETECT, graphmode ; // 初始化图形系统所需变量
// 声明一个圆,圆心在(100, 200),半径为50
CIRCLE circle(100, 200, 50);
initgraph(&graphdriver, &graphmode, ""); // 初始化图形系统
circle.show(); // 声明一个圆并显示它
circle.move_to(200, 250); // 移动圆
circle.expand(50); // 放大圆
circle.expand(50);
circle.contract(65); // 缩小圆
circle.contract(65);
closegraph(); // 关闭图形系统
return (0);
} //注:此代码需要电脑事先安装好图形相关库环境

继承对象的重定义

派生类中修改继承成员函数的语义(即,修改函数体,而保持函数原型不变)

派生类中的名字屏蔽基类中的名字

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
// 说明:类point描述了某一个位置是隐藏的还是显示的。
// 以public继承表示x_pos和y_pos在POINT中是protected
class point : public location {
public:
:
void show(); // 在当前位置显示点
void hide(); // 将点隐藏起来
// 将当前点移动到新位置(x, y)并显示它
void move_to(int x, int y);
protected:
:
};

// 说明:类circle描述了一个在屏幕上由point派生出来的圆。
// 由point类派生,从而也继承了location类
class circle : public point {
public:
:
void show(); // 在屏幕上画出圆
void hide(); // 将圆隐藏起来
void move_to(int x, int y); // 将当前圆移动到新位置(x, y)
:
protected:
:
};
1
2
3
4
5
6
7
8
9
10
11
12
13
void point::show() {
if (!isVisible()) {
visible = true;
putpixel(x_pos, y_pos, getcolor());
}
}

void circle::show() {
if (!isVisible()) {
visible = true;
circle(x_pos, y_pos, radius);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "basgraph.h" // 基本图形元素的类界面 
#include <conio.h> // 利用其中的getch()函数暂停
int main() {
:
// 声明一个圆,圆心在(100, 200),半径为50
point point( 20, 10 );
circle circle(100, 200, 50);

circle.show(); // 显示圆
point.show(); // 显示点

circle.moveTo(200, 250); // 移动圆
point.moveTo(100,20); // 移动点
:
}