5.6-day06-C++继承与多态/多重继承/私有继承和保护/虚函数与多态
五、关于操作符重载的限制
1.至少有一个操作数是类类型的。
int a = 10, b = 20;
int c = a + b; // 200
int operator+ (int a, int b) {
return a * b;
} // ERROR !
2.不是所有的操作符都能重载。
:: - 作用域限定
. - 直接成员访问
.* - 直接成员指针解引用
?: - 三目运算符
sizeof - 获取字节数
typeid - 获取类型信息
3.不是所有的操作符都可以用全局函数的方式实现。
= - 拷贝赋值
[] - 下标
() - 函数
-> - 间接成员访问
4.不能发明新的操作符。
x ** y; // x^y
#
@
第四课 继承与多态
一、继承的基本概念
人类:姓名、年龄、吃饭
学生是人:学号、学习
教师是人:工资、讲课
人类 - 基类,共性
/ \ 派生V^继承
学生 教师 - 子类,个性
二、继承的语法
class 子类名 : 继承方式1 基类1, 继承方式2 基类2, ... {
...
};
继承方式:
公有继承 - public - 最常用方式
私有继承 - private - 缺省方式
保护继承 - protected - 特殊的私有继承
三、公有继承
1.通过继承,在基类中定义的任何成员,也都成为了子类的成员,但是基类的私有成员,子类虽然拥有却不能直接访问。
2.基类中的保护成员,可以被子类直接访问,但不能在无关的类和全局域中被访问。
3.任何一个子类对象中都包含着它的基类子对象。如果在子类的构造函数中没有明确指明其基类子对象如何被构造,系统将采用无参的方式构造该子对象。如果在初始化表中指明了基类子对象的构造方式,就调用相应的构造函数构造该子对象。
4.子类对象的构造和析构顺序
按照继承表的顺序依次构造每个基类子对象->按照声明的顺序依次构造每个成员变量->执行子类构造函数体中的代码
析构的过程与构造严格相反
5.一个子类对象在任何都可以被视为它的基类对象——IsA。
任何时候,一个子类对象的指针或者引用,都可以被隐式地转换为它的基类类型的指针或者引用,但是反过来,将基类类型的指针或者引用转换为它的子类类型必须显示地(static_cast)完成。
Student s (...);
Human* h = &s; // OK !
6.在子类中定义的任何和基类成员同名的标识符,都可以将基类中的该成员隐藏起来。通过作用域限定操作符“::”,可对该成员解隐藏。
四、继承方式对访控属性的影响
class A {
X:
void foo (void) { ... }
};
class B : Y A {
void bar (void) {
foo (); // 仅需考虑X
}
};
int main (void) {
B b (...);
b.foo(); // 不仅要考虑X还要考虑Y
}
class C : Z B {
void fun (void) {
foo(); // 不仅要考虑X还要考虑Y
}
};
当通过一个子类对象(B)访问它从基类(A)中继承过来的成员(foo)的时候,需要考虑子类(B)从基类(A)继承时所采用的继承方式。
1.访控属性
关键字 属性 基类 子类 外部 友员
public 公有 OK OK OK OK
protected 保护 OK OK NO OK
private 私有 OK NO NO OK
2.基类的成员被继承到子类中以后,其访控属性会因不同的继承方式而异。
基类 公有继承 保护继承 私有继承
公有 公有 保护 私有
保护 保护 保护 私有
私有 私有 私有 私有
五、子类的构造函数和析构函数
1.子类隐式地调用基类的构造函数
在子类的构造函数中没有显示地指明其基类部分如何构造,隐式地调用基类的无参构造函数。如果子类没有定义任何构造函数,其缺省无参构造函数同样会隐式地调用基类的无参构造函数。
2.子类显式地调用基类的构造函数
在子类构造函数的初始化表中指明其基类部分的构造方式。
class A {
public:
A (void) : m_data (0) {}
A (int data) : m_data (data) {}
private:
int m_data;
};
class B : public A {
public:
B (int data) : A (data) {}
};
3.继承链的构造和初始化顺序
class A { ... };
class B : public A { ... };
class C : public B { ... };
C c (...);
构造:A->B->C
析构:C->B->A
任何时候子类中基类子对象的构造都要先于子类构造函数中的代码。
4.delete一个指向子类对象的基类指针,实际被执行的基类的析构函数,基类的析构函数不会调用子类析构函数,因此子类所特有的资源将形成内存泄漏。
Human* p = new Student (...);
delete p; // ->Human::~Human()
delete static_cast<Student*> (p);
六、子类的拷贝构造和拷贝赋值
子类的缺省拷贝构造和拷贝赋值除了复制子类的特有部分以外,还会复制其基类部分。如果需要自己定义子类的拷贝构造和拷贝赋值,一定不要忘记在复制子类特有部分的同时,也要复制其基类部分,否则将无法得到完整意义上的对象副本。
七、私有继承和保护继承
用于防止或者限制基类中的公有接口被从子类中扩散。
class DCT {
public:
void codec (void) { ... }
};
class Jpeg : protected DCT {
public:
void render (void) {
codec (...);
}
};
Jpeg jpeg;
jpeg.codec (...); // ERROR !
Jpeg Has A DCT,实现继承
class Jpeg2000 : public Jpeg {
public:
void render (void) {
codec (...); // OK !
}
};
八、多重继承
从多于一个基类中派生子类。
电话 媒体播放器 计算机
\ | /
智能手机
1.多重继承的语法和语义与单继承并没有本质的区别,只是子类对象中包含了更多的基类子对象。它们在内存中按照继承表的先后顺序从低地址到高地址依次排列。
2.子类对象的指针可以被隐式地转换为任何一个基类类型的指针。无论是隐式转换,还是静态转换,编译器都能保证特定类型的基类指针指向相应类型基类子对象。但是重解释类型转换,无法保证这一点。
3.尽量防止名字冲突。
4.钻石继承和虚继承
1)钻石继承
A
/ \
B C
\ /
D
class A { ... };
class B : public A { ... };
class C : public A { ... };
class D : public B,public C{ ...};
在最终子类(D)对象中存在公共基类(A)子对象的多份实例,因此沿着不同的继承路径访问公共基类子对象中的成员,会发生数据不一致的问题。
2)虚继承
在继承表中通过virtual关键字指定从公共基类中虚继承,这样就可以保证在最终子类对象中,仅存在一份公共基类子对象的实例,避免沿着不同的继承路径访问公共基类子对象中的成员时,所引发的数据不一致的问题。
只有当所创建对象的类型回溯(su)中存在钻石结构时,虚继承才起作用,否则编译器会直接忽略virtual关键字。
图形:位置,绘制
/ \
矩形:宽和高 圆:半径
绘制 绘制
九、虚函数与多态
如果将基类中的一个成员函数声明为虚函数,那么子类中的同型函数就也成为虚函数,并且对基类版本形成覆盖。这时,通过一个指向子类对象的基类指针,或者一个引用子类对象的基类引用,调用该虚函数时,实际被调用的函数不由该指针或引用的类型决定,而由它们的目标对象决定,最终导致子类中覆盖版本被执行。这种现象称为多态。
十、函数覆盖的条件
overload - 重载
override - 覆盖、重写、改写
1.基类版本必须是虚函数。
2.函数名、形参表和常属性必须严格一致。
3.如果返回基本类型或者对象,那么也必须严格一致。如果返回类类型的指针或引用,那么子类版本也可以返回基类版本的子类。
class B : public A { ... };
基类:virtual A* foo (void) {...}
子类:A* foo (void) { ... }
B* foo (void) { ... }
4.子类的覆盖版本不能比基类版本声明更多的异常抛出。
5.子类覆盖版本的访控属性与基类无关。
class A {
public:
virtual void foo (void) { ... }
};
class B : public A {
private:
void foo (void) { ... }
};
int main (void) {
B* b = new B;
b->foo (); // ERROR !
A* a = new B;
a->foo (); // OK ! -> B::foo
}
十一、多态=虚函数+指针/引用
Rect rect (...);
Shape shape = rect;
shape->draw (); // Shape::draw
Shape& shape = rect;
shape->draw (); // Rect::draw
class A {
public:
A (void) {
bar (); // A::bar
}
~A (void) {
bar (); // A::bar
}
void foo (void) {
this->bar (); // B::bar
}
virtual void bar (void) {
cout << 'A' << endl;
}
};
class B : public A {
void bar (void) {
cout << 'B' << endl;
}
};
int main (void) {
B b; // A
b.foo (); // B
return 0;
}
diamond.cpp
#include<iostream>
usingnamespace std;
class A {
public:
A (int i): m_i (i){}
protected:
int m_i;
};
class B :virtualpublic A {
public:
B (int i): A (i){}
voidset(int i){
m_i = i;
}
};
class C :virtualpublic A {
public:
C (int i): A (i){}
int get (void){
return m_i;
}
};
class D :public B,public C {
public:
D (int i): B (i), C (i), A (i){}
};
int main (void){
D d (1000);
cout << d.get ()<< endl;// 1000
d.set(2000);
cout << d.get ()<< endl;// 2000
return0;
}
通过虚继承解决数据不一致的问题;
共享一个A,产生这样一个共享内存的结构。
公共基类也需要用i去初始化。
human.cpp
#include<iostream>
usingnamespace std;
classHuman{
public:
Human(const string& name,int age):
m_name (name), m_age (age){}
void who (void)const{
cout << m_name <<","<< m_age << endl;
}
void eat (const string& food)const{
cout <<"我在吃"<< food << endl;
}
protected:
string m_name;
int m_age;
};
classStudent:publicHuman{
public:
Student(const string& name,int age,int no):
Human(name, age), m_no (no){}
Student(constStudent& that):
Human(that), m_no (that.m_no){}
Student&operator=(constStudent& that){
if(&that !=this){
Human::operator=(that);
m_no = that.m_no;
}
return*this;
}
void learn (const string& lesson)const{
cout <<"我("<< m_name <<","<< m_age
<<","<< m_no <<")在学"<< lesson
<< endl;
}
usingHuman::eat;
void eat (void)const{
cout <<"我绝食!"<< endl;
}
// int eat;
private:
int m_no;
};
int main (void){
Student s1 ("张飞",25,1001);
s1.who ();
s1.eat ("包子");
s1.learn ("C++");
Human* h1 =&s1;
h1 -> who ();
h1 -> eat ("KFC");
// h1 -> learn ("C");
Student* ps =static_cast<Student*>(h1);
ps -> learn ("C");
Student s2 = s1;
s2.who ();
s2.learn ("英语");
Student s3 ("赵云",20,1002);
s3 = s2;
s3.who ();
s3.learn ("数学");
return0;
}
using 为作用域声明,形成重载关系。
任何时候,一个子类对象的指针或者引用,都可以被隐式地转换为它的基类类型的指针或者引用;但是,反过来,将基类类型的指针或者引用转换为它的子类类型必须显示地(static_cast)完成。
shape.cpp
#include<iostrea
usingnamespace std;
classShape{
public:
Shape(int x,int y): m_x (x), m_y (y){}
virtualvoid draw (void){
cout <<"形状("<< m_x <<','<< m_y <<')'
<< endl;
}
protected:
int m_x, m_y;
};
classRect:publicShape{
public:
Rect(int x,int y,int w,int h):
Shape(x, y), m_w (w), m_h (h){}
void draw (void){
cout <<"矩形("<< m_x <<','<< m_y <<','
<< m_w <<','<< m_h <<')'<< endl;
}
private:
int m_w, m_h;
};
classCircle:publicShape{
public:
Circle(int x,int y,int r):
Shape(x, y), m_r (r){}
void draw (void){
cout <<"圆形("<< m_x <<','<< m_y <<','
<< m_r <<')'<< endl;
}
private:
int m_r;
};
void render (Shape* shapes[]){
for(size_t i =0; shapes[i];++i)
shapes[i]->draw ();
}
int main (void){
Shape* shapes[1024]={};
shapes[0]=newRect(1,2,3,4);
shapes[1]=newCircle(5,6,7);
shapes[2]=newCircle(8,9,10);
shapes[3]=newRect(11,12,13,14);
shapes[4]=newRect(15,16,17,18);
render (shapes);
return0;
}
函数覆盖的条件:
overload - 重载
override - 覆盖、重写、改写
1.基类版本必须是虚函数。
2.函数名、形参表和常属性必须严格一致。
3.如果返回基本类型或者对象,那么也必须严格一致。如果返回类类型的指针或引用,那么子类版本也可以返回基类版本的子类。
class B : public A { ... };
基类:virtual A* foo (void) {...}
子类:A* foo (void) { ... }
B* foo (void) { ... }
smartphone.cpp
#include<iostream>
usingnamespace std;
classPhone{
public:
Phone(const string& numb): m_numb (numb){}
void call (const string& numb){
cout << m_numb <<"致电"<< numb << endl;
}
void foo (void){
cout <<"Phone::foo"<< endl;
}
private:
string m_numb;
};
classPlayer{
public:
Player(const string& media): m_media (media){}
void play (const string& clip){
cout << m_media <<"播放器播放"<< clip
<< endl;
}
void foo (int data){
cout <<"Player::foo"<< endl;
}
private:
string m_media;
};
classComputer{
public:
Computer(const string& os): m_os (os){}
void run (const string& prog){
cout <<"在"<< m_os <<"上运行"<< prog
<< endl;
}
private:
string m_os;
};
classSmartPhone:publicPhone,publicPlayer,
publicComputer{
public:
SmartPhone(const string& numb,
const string& media,const string& os):
Phone(numb),Player(media),
Computer(os){}
usingPhone::foo;
usingPlayer::foo;
};
int main (void){
SmartPhone sp ("13910110072","MP3","Android");
sp.call ("01062332018");
sp.play ("High歌");
sp.run ("愤怒的小鸟");
Phone* p1 =reinterpret_cast<Phone*>(&sp);
Player* p2 =reinterpret_cast<Player*>(&sp);
Computer* p3 =reinterpret_cast<Computer*>(&sp);
cout <<&sp <<' '<< p1 <<' '<< p2 <<' '
<< p3 << endl;
sp.foo ();
sp.foo (100);
return0;
}
多重继承:从多于一个基类中派生子类。
把相关的基类子针指向相应类型基类子对象;
重解释类型转换,机械地把一种类型转化为另一种类型;
只管类型不管值,值不该。
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">