C++中继承方式与访问标号的学习笔记

本文详细探讨了C++中继承的三种方式(public、protected、private)及其在有无虚函数情况下的行为,包括访问权限、派生类如何覆盖基类函数以及如何选择合适的继承方式。总结了基类成员在派生类中的访问级别,并讨论了派生类对象访问基类成员的规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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,这样就无法利用基类的接口了,也就无法利用到运行时多态的好处,所以一般也是不推荐的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值