终于进入第四部分,面向对象(OOP)与泛型程序设计(GP)
面向对象程序设计三个重要组成部分,数据抽象,类的派生,动态绑定。
前面通过类学习了数据抽象,这一章面向后两个内容。
OOP思想中的关键是多态(polymorphism), [
],生物遗传学上的一个词,在C++里就是指通过基类型的引用或指针的某些应用,见后文。
基类(base class)何以派生出派生类(derived class),派生类可以修改继承自基类的某些成员函数来满足自己的要求,还可以增加一些基类中没有的数据成员。
派生类同样可以作为新的基类派生出新的派生类。
基类中的函数,在其声明前有virtual关键字的,称之为虚函数(
virtual不能在类外函数实现时出现).虚函数在派生类中是可以修改(
redefine)其内容的,没有这个关键字的函数,派生类不能修改其内容。
root的基类声明为virtual的函数,后面层层派生的子类中,都不用加virtual,该函数都为虚函数。
动
态绑定(dynamic
bind):假设有一个函数f,它的某个参数是基类型的引用br,在f中通过br调用了属于基类的虚函数vf,这时就发生了动态绑定。对函数vf的解析是
在运行时刻(run time),而不是编译时(compile
time)。f的参数br虽然是基类型的,但是调用时传进的实参却可以是基类的某派生类的对象,这一点问题都没有,也就是说f中调用的vf有可能是派生类
中的版本,这样程序当然不可能在编译时确定了
class base{
public:
void func(){
cout<<"this is in base\n";
}
virtual void func2(){
cout<<"this is in base 2 \n";
}
};
class derived :public base{
public:
void func(){
cout<<"this is in derived\n";
}
void func2(){
cout<<"this is in derived 2 \n";
}
};
void func3(base & ref_b){
ref_b.func();
ref_b.func2();
}
int main(void) {
derived obj_d;
obj_d.func();
obj_d.func2();
func3(obj_d);
cout<<"end in main\n";
return EXIT_SUCCESS;
} |
运行结果为
this is in derived
this is in derived 2
this is in base
this is in derived 2
end in main |
main函数里创建了一个派生类的对象,通过其调用func函数,基类和派生类都有自己的func函数,并且不是虚函数,派生类自己的虚函数就把基类的给“冲掉了”,所以运行的是派生类的版本。
func2函数虽然是虚函数,但我们是通过派生类对象来进行调用的,所以也是派生类版本。
func3函数,参数为基类型引用,传进的实参为派生类对象,func函数为基类版本的,func2为派生类版本的,因为func2是虚函数,只有func3中
obj_d.func2();只有这里是动态绑定c++中动态绑定只发生在通过指向基类的引用或指针调用虚函数。
当继承类型不是public时,基类型指针或引用无法指向派生类对象,mingw会提示:
xx(base) is inaccessble in yy(derived)。
基类中的公有数据成员在派生类中是仍然存在并可以访问的,私有数据成员就不行了,但是公有成员也可以被类外的代码访问,为了解决此问题,可以将那些不希望能在类外访问,但又能让派生类访问的数据成员声明为保护的(protected)。
派生类能在派生后访问属于其自己的从基类派生来的那些保护成员,而不能访问属于基类的保护成员。
class base{
public:
int pub;
protected:
int pro;
private:
int pri;
};
class derived :public base{
public:
// derived(int a=1,int b=2):pub(a),pro(b){}
derived(int ,int,int);
void func(derived & ref_d){
cout<<ref_d.pub<<" "<<ref_d.pro<<" "<<ref_d.ppp<<endl;
}
private:
int ppp;
};
derived::derived(int a=2,int b=3,int c=4){
pub=a;pro=b;ppp=c;
}
int main(void) {
derived obj1,obj2(4,5,6);
obj1.func(obj2);
cout<<"end in main\n";
return EXIT_SUCCESS;
} |
很
有意思,程序没有错误,在派生类的func函数中,接收一个自身类型的引用,访问输出其公有,保护和私有成员,在main函数中创建两个派生类对象
obj1和obj2,调用obj1.func,传进的参数是obj2,居然直接能访问obj2的私有和保护成员,看来前面classes那章看书不够仔
细,拿基类做实验也一样。
还有一点是派生类中关于构造函数的注释不能去掉,去掉会有错误,看来要看到后面的构造函数等地方的继承可以搞定这个问题。
其原因为构造函数初始化列表只能写在本类中定义的成员,不能是基类中成员,但在构造函数体内可以使用基类成员。派生类也可以不重写继承来自基类的虚函数,那么派生类的该函数与基类的一模一样。派生类如果还要作为基类派生其它的类,则可以在其需要的函数前再加virtual。
派生类重写基类中的虚函数,其重写后的版本需与基类中的函数原型相同(返回值,参数个数及类型)。但有一种例外,当基类中的虚函数原型返回值是指向基类的引用或指针时,派生类重写后的返回值可以是该基类的派生类类型的引用或指针。
每个派生类对象都含有其基类的部分,虽然编译器不一定将这两部分连续存储,这也是派生类的实参能传进基类引用形参的原因。
派生类的组成是:其内部定义的非静态的成员和基类中定义的非静态成员。
类A若是想作为基类派生其它类,在定义派生类前,类A必须完全定义,只有声明不行。
class b;
class d :public b{}; |
编译报错base class `b' has incomplete type
这隐含地说明了
一个类不能作为其自己的基类。
类似地如果只要声明一个派生类,不能写成应该写成
阅读(668) | 评论(0) | 转发(0) |