C++ note (10)
Eiaton

继承和派生 Ⅲ

类型兼容性

赋值运算的类型兼容性

  • 可以将后代类的对象赋值给祖先类对象,反之不可。
  • 每个派生类对象包含一个基类部分,这意味着可以将派生类对象当作基类对象使用。
    1
    2
    3
    4
    5
    6
    7
    ---------    -----------
    | i | <- | i |
    | j | <- | j |
    | x_tmp | <- | x_tmp |
    --------- | nmember |
    obj1 -----------
    obj2
    1
    2
    3
    4
    base obj1;
    y1 obj2;
    obj1 = obj2; // 把obj2中基类部分的内容赋给obj1
    obj2 = obj1; // wrong

    赋值运算符必须是成员函数实现。因此,赋值运算总是使用左操作数类型的赋值运算。

    因此,子类对象赋值给父类变量是正确的,但会放弃派生扩展的内容。反之,必须从语法层面禁止父类对象赋值子类。

    如果需要完成 obj2 = obj1,必须显式给出 *(base *)(&obj2)= obj1

y1继承base,且

base obj1

y1 obj2

  • 指向基类对象的指针也可指向公有派生类对象
    1
    2
    3
    4
    5
    6
    7
    base *p;
    y1 *p;
    p = &obj1; // ok
    p1= &obj1; // wrong
    p = &obj2; // ok
    p1= &obj2; // ok
    p = p1; // ok
  • 只有公有派生类才能兼容基类类型(上述规则只适用于共有派生)

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// b.h

class base {
public:
void display();
};

class d1 : public base {
public:
void display();
};

class d2 : public d1 {
public:
void display();
};
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
// b.cpp

#include "b.h"

void base::display() {
cout << "base::display()" << endl;
}

void d1::display() {
cout << "d1::display()" << endl;
}

void d2::display() {
cout << "d2::display()" << endl;
}

void fun(base *ptr) {
ptr->display();
}

int main() {
base b; // 声明base类对象
d1 d1; // 声明d1类对象
d2 d2; // 声明d2类对象
base *p;// 声明base类指针
p = &b; // base类指针指向base类对象
fun(p);
p = &d1; //base类指针指向d1类对象
fun(p);
p = &d2; //base类指针指向d2类对象
fun(p);
}
1
2
3
4
5
base::display()
base::display()
base::display()

// 解释:形参是指针类型,其基类名为base

类的类型转换

Upcasting and Downcasting

Upcasting(向 上/基 类型转换)

  • Assigning a pointer of a derived class type to a pointer of its base class type. Done implicitly
    1
    2
    3
    geometricObj *p = new circle(1);
    circle *p1 = new circle(2);
    p = p1;
    Downcasting(向 下/派生 类型转换)
  • Assigning a pointer of a base class type to a pointer of its derived class type
  • Done explicitly using dynamic_cast
    1
    p1 = dynamic cast<circle *>(p);

Dynamic Casting

The display function :

1
2
3
4
// A function for displaying a geometric object
void display(Figure *p) {
cout << "The area is " << p->get_area() << endl;
}
1
2
3
4
5
// A function for displaying a geometric object
void display(Figure *p) {
Circle* circlePtr = p;//?
cout << "The area is " << circlePtr ->get_area() << endl;
}

Casts

static_cast

  • Used to convert one data type to another and hands all reasonable casts
    1
    2
    average = (float) hits / (float) at_bats;
    average = static_cast<float>(hits) / static_cast<float>(at_bats);
    const_cast
  • Used to cast away constness.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <iostream>
    using namespace std;
    int main() {
    const int i = 100;
    const int *p = &i;
    int *q = const_cast<int*>(p);
    int j = i;

    cout << i << endl << j<<endl
    << *p << endl << *q <<endl;
    return 0;
    }

    很坑,请按案例。

    Const 转为 非 const,

    (1)c语言中,const 默认解释为 常量或字面量,所以转后也无法修改;

    (2)解释为,编译生成一个temp可变量,给你修改

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
using namespace std;
int main() {
const int i = 100;
const int &p = i;
int &q = const_cast<int&>(p);
int j = i;

cout << i << endl << j << endl
<< p << endl << q <<endl;
return 0;
}

reinterpret_cast

  • converts between unrelated types such as an integer to a pointer or a pointer to an unrelated pointer type.
    1
    2
    3
    4
    5
    6
    7
    8
    int *ip;
    char *cp;
    void *gp;
    cp=ip;
    ip=cp;
    cp=gp;
    gp=cp;
    cp=reinterpret_cast<char*>(gp);

    请打开代码中注释,编译去掉出错的语句,解释出错原因:

    (1)有类型指针间,不能隐式转换,除非向上转换;

    (2)有类型指针可隐式转为通用类型指针,反之不行;

    (3)其他需要显式转换,c++建议用 reinterpret 转指针

dynamic_cast

  • Used for casting across or within inheritance.
  • This cast is used with classes having virtual functions.

多重继承

C++支持的多继承

多重继承:派生类继承多个基类

  • 代表概念:C既 is a A 又 is a B
    1
    2
    3
    class 派生类名 : 继承控制1, 基类名1, 继承控制2, 基类名2, ... {
    成员声名;
    }
    重复继承:菱形继承
  • 多重继承特例,base A被派生两次以上

举例——device

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
class device1 {
public:
device1() : volume(5), powerOn(false) {}
device1(int vol, bool onOrOff) : volume(vol), powerOn(onOrOff) {}
void showVol() {
cout << "Volume is " << volume << endl;
}

protected:
int volume;
bool powerOn;
};

class device2 {
public:
device2() : talkTime(10), standByTime(300), power(100) {}
device2(int newTalkTime,int newStandByTime, float powerCent) : talkTime(newTalkTime), standByTime(newStandByTime), power(powerCent) {}

void showProperty() {
cout << "The property of the device : "<< endl;
cout << "talk time: " << talkTime << " hours" <<endl;
cout << "standbyTime: " << standbyTime << " hours" <<endl;
}
void showPower() {
cout <<" Power: " << power << endl;
}

protected:
int talkTime; //可通话时间(小时)
int standbyTime; //可待机时间(小时)
float power; //剩余电量百分比
};

class deviceNew: public device1, public device2 {
public:
deviceNew() {
weight = 0.56;
}

deviceNew(float newWeight, int vol, bool onOrOff, int newTalkTime, int newStandbyTime, float powerCent) :
device2(newTalkTime, newStandbyTime, powerCent),
device1(vol, onOrOff) {
weight = newWeight;
}

float getWeight() {
return weight;
}

private:
float weight; // 重量(克)
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {
deviceNew device(0.7, 3, false, 10, 250, 80); //声明派生类对象

// getWeight()函数是DEVICE_NEW类自身定义的
cout << "The weight of the device : " <<device.getWeight()<<endl;

// showVol()函数是从DEVICE1类继承来的
device.showVol();

// showProperty()函数是从DEVICE2类继承来的
device.showProperty();

return (0);
}
1
2
3
4
5
6
The weight of the device : 0.7
Volume is 3
The property of the device :
talk time: 10 hours
standbyTime: 250 hours

虚基类

继承基类时,在继承访问控制前添加保留字 “virtual”。 那么这个基类就是一个虚拟基类。
虚拟基类用于共享继承。

普通基类与虚基类之间的唯一区别只有在派生类重复继承了某一基类时才表现出来。

若派生类有一个虚基类作为祖先类,则在派生类构造函数中需要列出对虚基类构造函数的调用(否则,调用虚基类的默认构造函数),且对虚基类构造函数的调用总是先于普通基类的构造函数。

创建后代类对象时,当该后代类列出的虚基类构造函数被调用,Virtual关键字保证了虚基类的唯一副本只被初始化一次

创建派生类对象时构造函数的调用次序

  • 最先调用虚基类的构造函数;
  • 其次调用普通基类的构造函数,多个基类则按派生类声明时列出的次序、从左到右调用,而不是初始化列表中的次序;
  • 再次调用对象成员的构造函数,按类声明中对象成员出现的次序调用,而不是初始化列表中的次序
  • 最后执行派生类的构造函数。

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class base {
public:
int i;
}; 
class base1: virtual public base {
public:
int j;
};
class base2: virtual public base {
public:
int k;
}; 
class derived: public base1, public base2 {
public:
int sum;
}; 
int main() {
derived obj; // 声明一个派生类对象 
obj.i = 3; // 正确:从base继承的i在derived中只有一份
obj.j = 5; // 正确:使用从base1继承的j
obj.k = 7; // 正确:使用从base2继承的k
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
26
27
28
29
30
31
32
33
34
class baseA {
public:
baseA() {
cout << "This is baseA class." << endl;
}
};
class baseB {
public:
baseB() {
cout << "This is baseB class." << endl;
}
};
class derivedA : public baseB, virtual public baseA {
public:
derivedA() {
cout << "This is derivedA class." << endl;
}
};
class derivedB : public baseB, virtual public baseA {
public:
derivedB() {
cout << "This is derivedB class." << endl;
}
};
class derived : public derivedA, virtual public derivedB {
public:
derived() {
cout << "This is Derived class." << endl;
}
};
int main() {
derived obj;
return (0);
}
1
2
3
4
5
6
7
This is baseA class.
This is baseB class.
This is derivedB class.
This is baseB class.
This is derivedA class.
This is derived class.

构造顺序:

  • 先基类后成员
  • 先虚后实
  • 先左后右

析构顺序:

  • 与构造顺序相反