私有继承
在C++中,私有继承是一种继承方式,它定义了一个私有派生类,也称为派生类。私有继承意味着派生类继承了基类的所有成员,但这些成员在派生类中是私有的,对外部不可见。
要进行私有继承请使用private关键字,或者不使用任何关键字(因为private是默认值,因此省略访问限定符也将导致私有继承)
格式如下
class 派生类名:private 基类名
{
}
或者
class 派生类名:基类名
{
}
使用私有继承时,只能在派生类的方法里使用基类的方法
私有继承允许使用类名和作用域解析运算符来调用基类的方法
我们看个例子
#include<iostream>
using namespace std;
class AA
{
private:
int a_;
public:
AA(int a):a_(a){}
void A()
{
cout << a_ << endl;
}
};
class BB :AA
{
private:
int b_;
public:
BB(int a,int b):AA(a),b_(b){}
void B()
{
A();//可以
AA::A();//可以
}
};
int main()
{
BB r(2, 3);
r.B();
}
我们怎么通过派生类访问基类对象呢?当然是用强制类型转换
我们看个例子
#include<iostream>
using namespace std;
class AA
{
private:
int a_;
public:
AA(int a):a_(a){}
void A()
{
cout << a_ << endl;
}
};
class BB :AA
{
private:
int b_;
public:
BB(int a,int b):AA(a),b_(b){}
AA& B()
{
return (AA&)*this;//派生类强制转换为基类
}
};
int main()
{
BB r(2, 3);
AA t=r.B();
t.A();
}
在私有继承中,未进行显式类型转换的派生类引用或指针,无法赋值给基类的引用或指针
#include<iostream>
using namespace std;
class AA
{
private:
int a_;
public:
AA(int a):a_(a){}
void A()
{
cout << a_ << endl;
}
};
class BB :AA
{
private:
int b_;
public:
BB(int a,int b):AA(a),b_(b){}
};
int main()
{
BB r(2, 3);
AA& t = r;//这是不行的
AA* y = &r;//这是不行的
}
保护成员
cpp
class A
{
protected:
int a;
}
- 和私有成员类似,受保护的成员对于类的用户来说是不可访问的
- 和公有成员类似,受保护的成员对于派生类的成员和友元来说是可访问的。
- 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。派生类对于个基类对象中的受保护成员没有任何访问特权。
为了理解最后一条规则,请考虑如下的例子:
cpp
class Base
{
protected:
int prot_mem; // protected成员
};
class Sneaky:public Base
{
friend void clobber(Sneaky&); // 能访问 Sneaky::prot_mem
friend void clobber(Base&); // 不能访问Base::prot mem
int j; // j默认是private
};
//正确:clobber能访问Sneaky对象的private和protected成员
void clobber(Sneaky &s)
{s.i =s.prot_mem =0;}
// 错误:clobber不能访问Base的protected成员
void clobber(Base &b)
{b.prot_mem=0;}
如果派生类(及其友元)能访问基类对象的受保护成员,则上面的第二个clobber(接受一个Base&)将是合法的。该函数不是Base的友元,但是它仍然能够改变一个Base对象的内容。
如果按照这样的思路,则我们只要定义一个形如 Sneaky 的新类就能非常简单地规避掉protected提供的访问保护了。
要想阻止以上的用法,我们就要做出如下规定,即派生类的成员和友元只能访问派生类对象中的基类部分的受保护成员;对于普通的基类对象中的成员不具有特殊的访问权限。
保护继承
保护继承是私有继承的变体。保护继承在列出基类时使用关键字protected:
class 派生类名:protected 基类名
{
}
使用保护继承时,基类的公有成员和保护成员都成为派生类的保护成员。
和私有继承一样,基类的接口在派生类中也是可用的,但是在继承层次结构之外是不可用的。当从派生类派生出另一个类时,私有继承和保护继承的区别就体现出来了。
使用私有继承时,第三代类将不能使用基类的接口,这是因为基类的公有方法在派生类中变成私有方法;使用保护继承时,基类的公有方法在第二代中变成受保护的,因此第三代派生类可以使用它们。
三种继承方式的比较
公有继承(public)继承、私有继承(private)、保护继承(protected)是常用的三种继承方式。
1.公有继承(public)
** 公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的 状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。**
2.私有继承(private)
** 私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这 个派生类的子类所访问。**
3.保护继承
** 保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能 被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。**
三种不同继承方式的基类特性和派生类特性
|----------|------------|------------|------------|
| | 公有继承 | 保护继承 | 私有继承 |
| 公有成员变为 | 派生类的公有成员 | 派生类的保护成员 | 派生类的私有成员 |
| 保护成员变成 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
| 私有成员变为 | 只能通过基类接口访问 | 只能通过基类接口访问 | 只能通过基类接口访问 |
| 能否隐式向上转换 | 是 | 是(但只能在基类里) | 否 |
注:隐式向上转换:意味着无需进行显式类型转换,就可以将基类指针或引用指向派生类对象
对于公有继承方式
** 1.基类成员对其对象的可见性:公有成员可见,其他不可见。这里保护成员同于私有成员。**
** 2.基类成员对派生类的可见性:公有成员和保护成员可见,而私有成员不可见。这里保护成员同于公有成员。**
** 3.基类成员对派生类对象的可见性:公有成员可见,其他成员不可见。**
** 所以:在公有继承时,派生类的对象可以访问基类中的公有成员;派生类的成员函数可以访问基类中的公有成员和保护成员。这里,一定要区分清楚派生类的对 象和派生类中的成员函数对基类的访问是不同的。**
对于私有继承方式** 1.基类成员对其对象的可见性:公有成员可见,其他成员不可见**
** 2.基类成员函数对派生类的可见性:公有成员和保护成员是可见的,而私有成员是不可见的。**
** 3.基类成员对派生类对象的可见性:所有成员都是不可见的。**
** 所以:在私有继承时,基类的成员只能由直接派生类访问,而无法向下继承。**
对于保护继承方式这种继承方式与私有继承方式的情况相同。两者的区别仅在于对派生类的成员而言,而对于基类成员有不同的可见性。(可见性也就是可访问性)。
关于可访问性还有另外一种说法。这种规则中,称派生类的对象对基类访问为水平访问,称派生类的派生类对基类的访问为垂直访问。
1 #include<iostream>
2 class A{
3 private:
4 int privatedataA;
5 protected:
6 int protecteddataA;
7 public:
8 int publicdataA;
9 };
10 //基类A的派生类B(公有继承)
11 class B :public A{
12
13 public:
14 void funcA()
15 {
16 int b;
17 b = privatedataA;
18 //错误:基类中的私有成员在派生类中不可见
19 b = protecteddataA;
20 //正确:基类的保护成员在派生类中是保护成员
21 b = publicdataA;
22 //正确:基类的公共成员在派生类是公共成员
23 }
24 };
25 //基类A的派生类C 私有继承
26 class C :private A{
27
28 public:
29 void funcA()
30 {
31 int c;
32 c = privatedataA;
33 //错误:基类中的私有成员在派生类中不可见
34 c = protecteddataA;
35 //正确:基类的保护成员在派生类中是私有成员
36 c = publicdataA;
37 //正确:基类的公共成员在派生类是私有成员
38 }
39 };
40 //基类A的派生类D 保护继承
41 class D :protected A{
42 public:
43 void funcA()
44 {
45 int d;
46 d = privatedataA;
47 //错误:基类中的私有成员在派生类中不可见
48 d = protecteddataA;
49 //正确:基类的保护成员在派生类中是保护成员
50 d = publicdataA;
51 //正确:基类的公共成员在派生类是保护成员
52 }
53 };
54 void main()
55 {
56 int value;
57 B objB;
58 value = objB.privatedataA;//错误:基类的私有成员在派生类不可见,对对象不可见
59 value = objB.protecteddataA;//错误:基类的保护成员在派生类中是保护成员,对对象不可见
60 value = objB.publicdataA;//错误:基类的公共成员在派生类中是公共成员,对对象可见
61
62 C objC;
63 value = objC.privatedataA;//错误:基类的私有成员在派生类不可见,对对象不可见
64 value = objC.protecteddataA;//错误:基类的保护成员在派生类中是私有成员,对对象不可见
65 value = objC.publicdataA;//错误:基类的公共成员在派生类中是私有成员,对对象不可见
66
67 D objD;
68 value = objD.privatedataA;//错误:基类的私有成员在派生类不可见,对对象不可见
69 value = objD.protecteddataA;//错误:基类的保护成员在派生类中是保护成员,对对象不可见
70 value = objD.publicdataA;//错误:基类的公共成员在派生类中是保护成员,对对象不可见
71 system("pause");
72 }
公有,私有和受保护继承
某个类对其继承而来的成员的访问权限受到两个因素影响**:一是在基类中该成员的访问说明符,二是在派生类的派生列表中的访问说明符。**
举个例子,考虑如下的继承关系:
cpp
class Base {
public:
void pub mem();
protected:
int prot_mem;
private:
char priv_mem;
};
struct Pub_Derv : public Base {
//正确:派生类能访问protected成员
int f() { return prot mem; }
//错误:private成员对于派生类来说是不可访问的
char g() { return priv_mem; }
};
struct Priv_Derv : private Base {
// private不影响派生类的访问权限
int f1() const
{ return prot_mem;}
派生访问说明符对于派生类的成员(及友元)能否访问其直接基类的成员没什么影响,对
基类成员的访问权限只与基类中的访问说明符有关。
Pub_Dery和Priv_Derv都能须问613 受保护的成员prot_mem,同时它们都不能访问私有成员priv_mem。
派生访问说明符的目的是控制派生类用户(包括派生类的派生类在内)对于基类成前的访问权限:
cpp
Pub_Dery d1; // 继承自Base的成员是public的
Priv_Derv d2;//继承自Base的成员是private的
dl.pub_mem();// 正确:pub_mem在派生类中是public的
d2.pub_mem();//错误:pub_mem在派生类中是private的
Pub_Derv和Priv_Derv都继承了pub_mem函数。
如果继承是公有的, 则成员将遵循其原有的访问说明符,此时d1可以调用pub_mem。
在Priv_Derv中,Base 的成员是私有的,因此类的用户不能调用pub_mem。
派生访问说明符还可以控制继承自派生类的新类的访问权限:
cpp
struct Derived_from_Public : public Pub_Derv
{//正确:Base::prot mem在Pub_Derv中仍然是protected的
int use_base
{return prot mem;}
};
struct Derived_from_Private: pubiic Priv_Derv {
// 错误:Base::prot_mem在Priv_Derv中是private的
int use_base()
{return prot_mem;}
};
Pub_Derv 的派生类之所以能访问Base的prot_mem成员是因为该成员在Pub Dery中仍然是受保护的。相反,Priv Derv 的派生类无法执行类的访问,对于它们来说,Priv_Derv继承自Base的所有成员都是私有的。
假设我们之前还定义了一个名为Prot_Derv的类,它采用受保护继承,则Base的所有公有成员在新定义的类中都是受保护的。Prot_Derv的用户不能访问pub mem,但是Prot_Derv的成员和友元可以访问那些继承而来的成员。
派生类向基类转换的可访问性
派生类向基类的转换是否可访问由使用该转换的代码决定,同时派生类的派生访问说明符也会有影响。假定D继承自B:
- 只有当D公有地继承B时,用户代码才能使用派生类向基类的转换;如果D继承B的方式是受保护的或者私有的,则用户代码不能使用该转换。
- 不论D以什么方式继承B,D的成员函数和友元都能使用派生类向基类的转换;派生类向其直接基类的类型转换对于派生类的成员和友元来说永远是可访问的。
- 如果D继承B的方式是公有的或者受保护的,则D的派生类的成员和友元可以使用D向B的类型转换;反之,如果D继承B的方式是私有的,则不能使用
对于代码中的某个给定节点来说,如果基类的公有成员是可访问的,则派生类Tp 向基类的类型转换也是可访问的;反之则不行。
访问控制与继承
不考虑继承的话,我们可以认为一个类有两种不同的用户:普通用户和类的实现者
其中,普通用户编写的代码使用类的对象,这部分代码只能访问类的公有(接口)成员:实现者则负责编写类的成员和友元的代码,成员和友元既能访问类的公有部分,也能访问类的私有(实现)部分。
如果进一步考虑继承的话就会出现第三种用户,即派生类。基类把它希望派生类能够使用的部分声明成受保护的。普通用户不能访问受保护的成员,而派生类及其友元仍旧不能访问私有成员。
和其他类一样,基类应该将其接口成员声明为公有的;同时将属于其实现的部分分成两组:一组可供派生类访问,另一组只能由基类及基类的友元访问。对于前者应该声明为受保护的,这样派生类就能在实现自己的功能时使用基类的这些操作和数据;对于后者应该声明为私有的。
友元与继承
就像友元关系不能传递一样,友元关系同样也不能继承。基类的友元在访问派生类成员时不具有特殊性,类似的,派生类的友元也不能随意访问基类的成员:
cpp
class Base
{
protected:
int prot_mem; // protected成员
// 添加friend声明,其他成员与之前的版本一致
friend class Pal; // Pal在访问Base的派生类时不具有特殊性
};
class Sneaky:public Base
{
friend void clobber(Sneaky&); // 能访问 Sneaky::prot_mem
friend void clobber(Base&); // 不能访问Base::prot mem
int j; // j默认是private
};
class Pal {
public:
int f(Base b) { return b.prot_mem; } //正确:Pal是Base的友元
int f2(Sneaky s) { return s.j;} //错误:Pal不是Sneaky的友元
// 对基类的访问权限由基类本身控制,即使对于派生类的基类部分也是如此
int f3(Sneaky s) { return s.prot mem;} // 正确:Pal是Base的友元
};
如前所述,每个类负责控制自己的成员的访问权限,因此尽管看起来有点儿奇怪,但f3 615确实是正确的。Pal是Base的友元,所以Pal能够访问Base对象的成员,这种可访问性包括了Base对象内嵌在其派生类对象中的情况。
当一个类将另一个类声明为友元时,这种友元关系只对做出声明的类有效。对于原来那个类来说,其友元的基类或者派生类不具有特殊的访问能力:
cpp
// D2对Base的protected和private成员不具有特殊的访问能力
class D2 : public Pal {
public:
int mem(Base b)
{ return b.prot_mem; } //错误:友元关系不能继承
不能继承友元关系;每个类负责控制各自成员的访问权限。
改变个别成员的可访问性
有时我们需要改变派生类继承的某个名字的访问级别,通过使用using声明可以达到这一目的:
cpp
class Base
{
public:
std::size_t size() const { return n;}
protected:
std::size_t n;
// 注意:private继承
class Derived : private Base {
public://保持对象尺寸相关的成员的访问级别
using Base::size;
protected:
using Base::n;
};
因为Derived 使用了私有继承,所以继承而来的成员size和n(在默认情况下)是Derived的私有成员。
然而,我们使用using声明语句改变了这些成员的可访问性。改变之后,Derived的用户将可以使用size成员,而Derived的派生类将能使用通过在类的内部使用using 声明语句,我们可以将该类的直接或间接基类中的任备可访问成员(例如,非私有成员)标记出来。
using声明语句中名字的访问权限由该声明语句之前的访问说明符来决定。
- 如果一条using 声明语句出现在类的private部分,则该名字只能被类的成员和友元访问;
- 如果using声明语句位于public部分,则类的所有用户都能访问它;
- 如果 using 声明语句位于protected 部分,则该名字对于成员、友元和派生类是可访问的。
派生类只能为那些它可以访问的名字提供using声明。
默认的继承保护级别
我们曾经介绍过使用struct和class关键字定义的类具有不同的默认访问说明符。
类似的,默认派生运算符也由定义派生类所用的关键字来决定。
默认情况下,使用class关键字定义的派生类是私有继承的;而使用 struct 关键字定义的派生类是公有继承的:
cpp
class Base { /*...*/};
struct D1: Base{ /*...*/}; // 默认 public继承
class D2 : Base{/*..,*/} // 默认 private 继承
人们常常有一种错觉,认为在使用struct关键字和class关键字定义的类之间还有更深层次的差别。事实上,唯一的差别就是默认成员访问说明符及默认派生访问说明符;除此之外,再无其他不同之处。
一个私有派生的类最好显式地将private声明出来,而不要仅仅依赖于默认的设置。显式声明的好处是可以令私有继承关系清晰明了,不至于产生误会。