C++中继承方式与访问标号的学习笔记
1 无虚函数的情况
基类有一个public函数,一个protected函数,一个private函数:
class A {
public:
voidf() {
cout<<"A::f"<<endl;
g();
h();
}
protected:
void g() {cout<<"A::g"<<endl;}
private:
voidh() {cout<<"A::h"<<endl;}
};
1.1 第一种情况:public继承
派生类public继承基类,没有新增的函数。
class B:public A {
};
int main(int argc, char *argv[]) {
B b;
b.f();
return 0;
}
运行结果:A::f
A::g
A::h
只覆盖public函数:class B:public A {
public:
void f() {
cout<<"B::f"<<endl;
g();
h();
}
};
编译错误: `void A::h()' is private。只覆盖protected和private函数:
class B:public A {
protected:
void g() {cout<<"B::g"<<endl;}
private:
voidh() {cout<<"B::h"<<endl;}
};
运行结果:A::f
A::g
A::h
覆盖所有函数:class B:public A {
public:
void f() {
cout<<"B::f"<<endl;
g();
h();
}
protected:
void g() {cout<<"B::g"<<endl;}
private:
voidh() {cout<<"B::h"<<endl;}
};
运行结果:B::f
B::g
B::h
1.2 第二种情况:protected继承
只覆盖public函数:
编译错误: `void A::h()' is private。
只覆盖protected与private函数:
编译错误:`A' is not an accessible base of `B'。
覆盖所有函数:
正常运行,结果与public继承时相同。
1.3 第三种情况:private继承
与protected情况相同。
1.4 本节总结
C++Primer中对此的总结:
1) public继承:基类成员保持自己的访问级别。
2) protected继承:基类的public和protected成员在派生类中为protected成员。
3) private继承:基类所有成员在派生类中为private成员。
这段文字我理解起来有点头疼,下面是自己对它的理解:
1) C++Primer中提到的,派生类对象中包含之前的所有基类子对象。所以当运行由基类继承而来的函数时,就相当于是派生类对象将请求转发给了基类对象,才会出现当B覆盖了g()和h()后运行f(),结果与直接运行A中的f()相同的情况,因为此时运行的f()就是A中的版本。
2) 派生类从基类继承来的成员中,public成员可被外界访问,protected成员可在类中使用,private成员只能被基类中的函数使用。所以B覆盖了f()后出现h()不可使用的编译错误,因为此时是B中的成员f()试图调用继承来的private成员h(),而不是A中的成员f()。而B在protected继承A,且没有覆盖f()时,无法在main函数中运行b.f(),因为此时f()是B中的protected成员,不能被外界访问。
3) 在没有虚函数时,派生类对象与基类子对象相当于简单的并列关系,是两个独立的个体,且基类子对象中的private函数对派生类对象是封闭的。所以在这种情况下覆盖基类中继承过来的private函数是没有覆盖的意义的,比如B::h(),与A::h()只是单纯的名字相同。
2 有虚函数的情况
派生类与测试函数:
class B:public A {
public:
void f() {
cout<<"B::f"<<endl;
g();
h();
}
protected:
void g() {cout<<"B::g"<<endl;}
private:
voidh() {cout<<"B::h"<<endl;}
};
int main(int argc, char *argv[]) {
A*b=new B;
b->f();
return 0;
}
2.1 第一种情况:protected与private继承
这两种继承方式时,出现编译错误: `A' is an inaccessible base of `B'。百度了一下原因,自己总结了一下:
A *b=new B这种方式相当于将A当作一个接口,而这两种继承方式下B并没有从A中继承到public成员,也就等于是没有利用到A这个接口,所以编译器拒绝将派生类的指针或引用赋值给A的指针,因为没有意义。
2.2 第二种情况:public函数为虚函数
基类:
class A {
public:
virtual void f() {
cout<<"A::f"<<endl;
g();
h();
}
protected:
void g() {cout<<"A::g"<<endl;}
private:
voidh() {cout<<"A::h"<<endl;}
};
不覆盖public函数(注释掉B中的f()):运行结果:
A::f
A::g
A::h
不覆盖protected函数:(注释掉g()):运行结果:
B::f
A::g
B::h
不覆盖private函数:(注释掉h()):编译错误: `void A::h()' is private。
2.3 第三种情况:protected函数为虚函数
不覆盖public函数(注释掉B中的f()):
运行结果:
A::f
B::g
A::h
不覆盖protected函数:(注释掉g()):运行结果:
A::f
A::g
A::h
不覆盖private函数:(注释掉h()):编译错误: `void A::h()' is private。
2.4 第四种情况:private函数为虚函数
不覆盖public函数(注释掉B中的f()):
运行结果:
A::f
A::g
B::h
不覆盖protected函数:(注释掉g()):运行结果:
A::f
A::g
B::h
不覆盖private函数:(注释掉h()):编译错误: `void A::h()' is private。
2.5 本节总结
C++实现虚函数的方式是在类的开头存放一个虚函数表,表中每个函数名项都指向了一个实际的函数地址。派生类如果覆盖了基类中的虚函数,则派生类的虚函数表中此项将指向派生类的实现版本。C++通过这种方法几乎没有额外消耗的实现了运行时多态。
简单的说,A或A的子类中有这么一张表:
f |
g |
h |
A::f() |
A::g() |
A::h() |
|
|
|
如果B覆盖了A中的一个函数,比如f(),B中的表就变成了:
f |
g |
h |
B::f() |
A::g() |
A::h() |
A::f() |
|
|
这样,即使A *b=new B中a的静态类型为A*,在运行时也能找到正确的函数版本,即B::f()。
而对于B中没有覆盖的函数,比如g()和h(),通过B::f()调用时仍然遵循类继承时的访问原则:派生类中可见基类继承过来的protected成员,不可见继承过来的private成员。因此当B覆盖h()时,B::f()可以成功运行,并调用B::h();但如果B没有覆盖h(),而是从基类中继承过来h(),因为它是private成员,B::f()中不可见,因此编译不通过。
3 访问同类型的其它对象的情况
刚开始学习这部分时我成功的被书上的话给绕晕了,以为派生类对象可以访问其它的基类对象的protected成员,导致对下面这种问题迷惑不解:
class A {
public:
virtual void f(A *a) {
cout<<"A::f"<<endl;
a->g(this);
}
protected:
virtual void g(A *a) {cout<<"A::g"<<endl;}
};
class B:public A {
public:
void f(A *a) {
cout<<"B::f"<<endl;
a->g(this);
}
protected:
void g(A *a) {cout<<"B::g"<<endl;}
};
int main(int argc, char *argv[]) {
A*b=new B;
b->f(b);
return 0;
}
编译错误: `virtual void A::g(A*)' is protected。摸索了一段时间,才弄清楚原来书上说的派生类对象与基类对象,都指的是派生类对象内部,派生类部分和基类部分,而如果要访问一个独立的基类对象的protected成员是不可能的,这里要遵循类中成员的访问标号原则。
刚才在C++Primer的15.2.2里找到一句话:派生类只能通过派生类对象访问其基类的protected成员,派生类对其基类类型对象的protected成员没有特殊访问权限。
4 怎么选择继承方式与访问标号
以下是我的一些理解:
基类应该把想让用户使用的功能设为public,或者是把所有子类都需要的通用的功能设为public,这样就相当于发挥了接口的作用。
基类应将派生类需要重定义的函数定义为虚函数。如果基类不想让派生类可以调用基类的版本,也就是强制派生类实现自己的版本的话,就将这样的函数设为private,而将允许派生类调用的版本设为protected。
如果派生类只需要利用基类已有的功能,不想实现自己的版本,就不应该重载或实现基类的public函数。
在继承方式上,一般来说都使用public继承。private继承会将基类中的所有成员都设为private,相当于派生类无法使用基类的任何成员和功能,C++Primer中说这种继承的情况非常少见,我也从来没见过这种继承方式。protected继承会将基类中的public成员变成protected,这样就无法利用基类的接口了,也就无法利用到运行时多态的好处,所以一般也是不推荐的。