1.C++类继承的三种关系
(对C++类继承的三种关系的理解)
C++中继承主要有三种关系:public,protected,和private。
1)public继承
public继承是一种接口继承,子类可以代替父类完成父类接口所声明的行为。此时子类可以自动转换成为父类的接口,完成接口转换。从语法角度来说,public继承会保留父类中成员(包括函数和变量等)的可见性不变,也就是说,如果父类中的某个函数时public的,那么在被子类继承后仍然是public的。
2)protected继承
protected继承是一种实现继承,子类不能代替父类完成父类接口所声明的行为,此时子类不能自动转换成为父类的接口。从语法角度上来说,protected继承会将父类中的public可见性的成员修改成为protected可见性,相当于在子类中引入了protected成员,这样在子类中同样还是可以调用父类的protected和public成员,子类的子类也就可以调用被protected继承的父类的protected和public成员。
3)private继承
private继承是一种实现继承,子类不能代替父类完成接口所声明的行为,此时子类不能自动转换成为父类的接口。从语法角度上来说,private继承会将父类中的public和protected可见性的成员修改成为private可见性。这样一来,虽然子类中同样还是可以调用父类的protected和public成员,但是子类的子类就不可以再调用被private继承的父类的成员了。
下面的代码程序说明了protected和private的区别:
#include<iostream>
using namespace std;
class Base
{
protected:
void printProtected(){cout<<"print Protected"<<endl;}
public:
void printPublic(){cout<<"print Public"<<endl;}
};
class Derived1:protected Base //protected继承
{
};
class Derived2:private Base//private继承
{
};
class A:public Derived1
{
public:
void print()
{
printProtected();
printPublic();
}
};
class B:public Derived2
{
public:
void print()
{
printProtected();//编译错误,不能访问
printPublic();//编译错误,不能访问
}
};
int main()
{
class A a;
class B b;
a.print();
b.print();
return 0;
}
Derived1类通过protected继承Base类,因此它的派生类A可以访问Base基类的protected和public成员函数。
Derived2类通过private继承Base类,因此它的派生类B不可以访问Base基类的任何成员函数。
2.C++继承关系
(对C++类继承的三种关系的理解)
请考虑下面的标记为A~J的语句在编译时可能出现的情况。如果能够成功编译,请记为”RIGHT“,否则记为”Error“。
#include<iostream>
using namespace std;
class Parent
{
public:
Parent(int var=-1)
{
m_nPub=var;
m_nPtd=var;
m_nPrt=var;
}
public:
int m_nPub;
protected:
int m_nPtd;
private:
int m_nPrt;
};
class Child1:public Parent
{
public:
int getPub() {return m_nPub;}
int getPtd() {return m_nPtd;}
int getPrt() {return m_nPrt;}//A
};
class Child2:protected Parent
{
public:
int getPub(){return m_nPub;}
int getPtd(){return m_nPtd;}
int getPrt(){return m_nPrt;}
};
class Child3:private Parent
{
public:
int getPub(){return m_nPub;}
int getPtd(){return m_nPtd;}
int getPrt(){return m_nPrt;}//C
};
int main()
{
Child1 cd1;
Child2 cd2;
Child3 cd3;
int nVar=0;
//public inherited
cd1.m_nPub=nVar;//D
cd1.m_nPtd=nVar;//E
nVar=cd1.getPtd();//F
//protected inherited
cd2.m_nPub=nVar;//G
nVar=cd2.getPtd();//H
//private inherited
cd3.m_nPub=nVar;//I
nVar=cd3.getPtd();//J
return 0;
}
A,B,C错误。m_nPrt是基类Parent的私有变量,不能被派生类访问。
D正确。Child1是public继承,可以访问并修改基类Parent的public成员变量。
E错误。m_nPtd是基类Parent的protected成员变量,通过公有继承后变成了派生类Child1的protected成员,因此只能在Child1类内部访问,不能使用Child1对象访问。
F正确。可以通过Child1类的成员函数访问其protected变量。
G错误。Child2是protected继承,其基类Parent的public和protected成员变成了它的Protected成员,因此m_nPub只能在Child2类内部访问,不能使用Child2对象访问。
H正确。可以通过Child2类的成员函数访问其protected变量。
I错误。Child3是private继承,其基类Parent的public和protected成员变成了它的private成员,因此m_nPub只能在Child3类内部访问,不能使用Child3对象访问。
J正确。可以通过Child3类的成员函数访问其private成员。
A,B,C,E,G,I为”ERROR“。
D,F,H,J为”RIGHT“。
3.看代码找错——C++继承
(对C++类继承的三种关系的理解)
#include<iostream>
using namespace std;
class base
{
private:
int i;
public:
base(int x){i=x;}
};
class derived:public base
{
private:
int i;
public:
derived(int x,int y){i=x;}
void printTotal()
{
int total=i+base::i;
cout<<"total="<<total<<endl;
}
};
int main()
{
derived d(1,2);
d.printTotal();
return 0;
}
这个程序有如下两个错误。
1)在derived类进行构造时,它首先要调用其基类(base类)的构造方法,由于没有指明何种构造方法,因此默认调用base类不带参数的构造方法。然而,基类base中已经定义了带一个参数的构造函数,所以编译器就不会给它定义默认的构造函数。因此代码第17行会出现”招不到构造方法”的编译错误。解决方法:可以在derived的构造函数中显示调用base的构造函数。
derived(int x,int y):base(y){i=x;}//原代码第17行
2)在derived类的printTotal()中,使用base::i方式调用base类的私有成员i,这样会得到“不能访问私有成员”的编译错误。解决方法:把成员i的访问权限设为public。
4.私有继承有什么作用
(对C++私有继承的理解)
#include<iostream>
using namespace std;
class Person
{
public:
void eat(){cout<<"Person set"<<endl;}
};
class Student:private Person//私有继承
{
public:
void study(){cout<<"Student Study"<<endl;}
};
int main()
{
Person p;
Student s;
p.eat();
s.study();
s.eat();//编译错误
p=s;//编译错误
return 0;
}
此程序的两个编译错误分别说明了私有继承的两个规则。
第一个规则正如大家所看到的,和公有继承相反,如果两个类之间的继承关系为私有,编译器一般不会将派生类对象(如Student)转换成基类对象(如Person)。这就是代码第24行失败的原因。
第二个规则是,从私有基类继承而来的成员都成为了派生类的私有成员——即使它们在基类中是保护或公有成员。这就是代码23行失败的原因。
可以看出,私有继承时派生类与基类不是’is a’的关系,而是意味着’Is-Implement-InTerms-of’(以….实现)。如果使类D私有继承于类B,这样做是因为你想利用类B中已经存在的某些代码,而不是因为类B的对象和类D的对象之间有什么概念上的关系。因此,私有继承在软件“设计”过程中毫无意义,只是在软件”实现“时才有用。
5.私有继承和组合有什么相同点和不同点
(私有继承和组合有什么相同点和不同点?该如何选择?)
使用组合表示”有一个(Has-A)“的关系。如果在组合中需要使用一个对象的某些方法,则完全可以利用私有继承代替。
私有继承下派生类会获得基类的一份备份,同时得到了访问基类的公共以及保护接口的权力和重写基类虚函数的能力。它意味着”以…..实现“(Is-Implement-In-Term-Of)”,它是组合的一种语法上的变形(聚合或者”有一个“)。
例如,”汽车有一个(Has-A)引擎”关系可以用单一组合表示,也可以用私有继承表示。例如下面的程序。
#include<iostream>
using namespace std;
class Engine
{
public:
Engine(int num):numCylinders(num){}//Engine构造函数
void start()
{
cout<<"Engine start,"<<numCylinders<<" Cylinders"<<endl;
}
private:
int numCylinders;
}
class Car_pri:private Engine//私有继承
{
public:
Car_pri():Engine(8){}//调用基类的构造函数
void start()
{
Engine::start();//调用基类的start
}
};
class Car_comp
{
private:
Engine engine;//组合Engine类对象
public:
Car_comp():engine(8){}//给engine成员初始化
void start()
{
Engine::start();//调用engine的start()
}
};
int main()
{
Car_pri car_pri;
Car_comp car_comp;
car_pri.start();
car_comp.start();
return 0;
}
由此看出,“有一个”关系既可以用私有继承表示,也可以用单一组合表示。
类Car_pri和类Car_comp有很多相似点:
1)它们都只有一个Engine被确切地包含于car中。
2)它们在外部都不能进行指针转换,如将Car_pri*转换为Engine*。
3)它们都有一个start()方法,并且都在包含的Engine对象中调用start()方法。
也有下面一些区别:
1)如果想让每个Car都包含若干Engine,那么只能用单一组合的形式。
2)私有继承形式可能引入不必要的多重继承。
3)私有继承形式允许Car的成员将Car*转换为Engine*。
4)私有继承形式允许访问基类的保护(protected)成员。
5)私有继承形式允许Car重写Engine的虚函数。
应该在组合和私有继承之间如何选择呢?这里有一个原则:尽可能使用组合,万不得已才用私有继承。
请看下例:
#include<iostream>
using namespace std;
struct Base//抽象
{
public:
virtual void Func1()=0;//纯虚函数
virtual void Func2()=0;//纯虚函数
void print()
{
Func1();//调用派生类的Func1()
Func2();//调用派生类的Func1()
}
};
struct T:private Base
{
public:
virtual void Func1(){"cout<<"Func1"<<endl;}//覆盖基类的Fun1
virtual void Func2(){"cout<<"Func2"<<endl;}//覆盖基类的Fun2
void UseFunc()
{
Base::print();//调用基类的print()
}
};
int main()
{
T t;
t.UseFunc();
return 0;
}
程序输出如下:
1 Func1
2 Func2
上面的代码中Base类含有虚函数Fun1()以及Fun2(),因此它为抽象类,它通过虚函数调用了T中的重写版本。这种情况就不能使用组合了,因为组合的对象关系中不能使用一个抽象类,抽象类不能被实例化。
相同点:都可以表示“有一个”关系。
不同点:私有继承中派生类能访问基类的protected成员,并且可以重写基类的虚函数,甚至当基类是抽象类的情况,组合不具有这些功能。
注意:选择他们的原则为尽可能使用组合,万不得已才用私有继承。