面向对象三大机制包括:
1.封装,隐藏内部实现。
2.继承,复用现有代码。(面向对象最显著的特征)
3.多态,改写对象行为。
继承,是一种复用的手段。是从先辈处得到属性和行为特征。类的继承就是新的类从已
有的类那里得到已有的特征。
类成员的访问限定有三种:public,protected,private,在类外可以访问类的公有成员,
私有和保护都不可以访问。代码举例:
#include<iostream> using namespace std; class A { public: A() :_pub(1) ,_pro(2) ,_pri(3) {} public: int _pub; protected: int _pro; private: int _pri; }; int main() { A a; cout << a._pub << endl; cout << a._pro << endl; cout << a._pri << endl; system("pause"); return 0; }
代码编译不通过,提示不可访问类的私有和保护成员。
也有3种继承关系:public(公有继承),protected(保护继承),private(私有继承)
那么父类成员在子类中的访问属性究竟会是怎样的呢?
【总结】在继承机制下,无论哪种继承方式,子类中可以访问父类的公有保护成员,
私有成员不可访问;在类外,只能访问公有成员,私有和保护都不可访问。这个可以通
过代码去验证。
类外只能访问类的公有成员。如果一些父类成员不想被父类对象直接访问,但是需
要在派生类中访问,就定义成保护成员。protected成员的特点:“外人”不可访问,“儿
子”可以访问。
class中继承方式默认是private,struct中默认是public。
类与类之间的关系有三种:
1.has-a,一个类的成员是另一个类的对象;私有继承是另一种实现has-a的途径。
2.uses-a,一个类的成员函数的参数是另一个类的对象
3.is-a,公有继承,父类可用的成员对子类也是可用的。
赋值兼容规则:
前提:public继承下。
1.父类对象 = 子类对象
举例:人通过继承得到超人,即超人具备人的特性,当然也有其本身特有的功能。
人 = 超人(超人的特异功能不被赋值)
2.父类对象不可赋值给子类对象。
3.父类指针可以指向子类对象,如图。子类指针不可以指向父类对象。但是可以通过
强制类型转换完成。但是也可能会出现一定的问题。(下边的代码)
4.父类对象的引用可以引用子类对象。(与指针同理)
class B { public: B() :_pub(10) , _pro(20) , _pri(30) { cout << "B()" << endl; } void show() { cout << _pub << endl; cout << _pro << endl; cout << _pri << endl; } public: int _pub; protected: int _pro; private: int _pri; }; class D:public B { public: D() :B() ,_i(100) { cout << "D()" << endl; } void show(int ) { cout << _pub << endl; cout << _pro << endl; //cout << _pri << endl; } int _i; }; int main() { D d; B b; D *pd; pd = (D *)&b;//子类对象通过强转指向父类对象 pd->_i = 10; system("pause"); return 0; }
运行之后会出现程序崩溃。所以尽量不要强转。
注:创建子类对象时,会先调用父类的构造函数,然后再调用子类的构造函数,结束
时,先调用子类的析构函数,再调用父类的析构函数。若父类没有默认的构造函数,子
类构造函数中对父类成员的初始化必须在初始化列表中实现。
如果子类中有与父类同名的函数或者成员,父类的函数或者成员被隐藏,与函数的参
数,返回值类型都无关。隐藏,父类的函数仍然存在。
举例:
class B { public: B() :_b(1) {} void Show() { cout << _b << endl; } private: int _b; }; class D:public B { public: D() :B() ,_b(2) ,_d(3) {} void Show() { cout << _b << endl; cout << _d << endl; } private: int _b; int _d; }; int main() { B b; D d; b.Show(); d.Show(); cout << sizeof(b) << endl; cout << sizeof(d) << endl; system("pause"); return 0; }
D中有与B同名的成员和函数,所以子类中就会隐藏父类的成员,但是父类的成员依然存
在,b.Show()依然可以打印出B中的成员。b的大小是4字节,d的大小是12字节。这就是
所谓的隐藏。
多重继承:
前边整理的都是都是单继承,即一个类从一个父类派生而来。当一个子类有多个父类
时,这种继承方式成为多重继承。
在多重继承中,最典型的继承方式就是菱形继承。
为了解决菱形继承中出现的问题,我们引入了虚基类。看下边的代码:
class A { public: A() :_a(1) {} protected: int _a; }; class B :virtual public A { public: B() :_b(2) {} protected: int _b; }; class C :virtual public A { public: C() :_c(3) {} protected: int _c; }; class D :public B, public C { public: D() :_d(4) {} protected: int _d; }; int main() { D d; cout << sizeof(d) << endl; system("pause"); return 0; }
要是没有在B,C的派生列表中加上virtual关键字,最终输出d的大小是20字节,加上之
后呢就是24字节。
我们知道,如果是单继承的话,子类成员在内存中的情况就是:先是来自父类的成员,
然后就是来自子类的成员,紧挨存储。那么虚继承的对象模型究竟是什么,打开内存看
一下:(windows下的情况)linux下的情况之后补充 。
大家也可以打开内存看单继承和菱形继承的对象模型,这里就不给出了。
友元与继承:
由于友元不可以传递,所以友元就不能继承。父类的友元不可以访问子类的私有和保
护成员,但是可以访问子类从父类继承下来的成员。父类的友元要想访问子类,必须将
该友元声明为子类的友元。
class D; class B { friend void Show(B &b,D &d); public: B() :_b(1) {} protected: int _b; }; class D:public B { public: D() :_d(4) ,_e(5) ,_f(6) {} public: int _d; protected: int _e; private: int _f; }; void Show(B &b, D &d) { cout << b._b << endl; cout << d._b << endl; cout << d._d << endl; //cout << d._e << endl; //cout << d._f << endl; } int main() { B b; D d; Show(b,d); system("pause"); return 0; }
父类的友元函数并不能访问子类的私有成员和保护成员。
静态成员与继承:
如果父类定义了一个静态成员,则在整个继承体系中都只有该成员的唯一定义,不管
从父类派生出多少子类,对于每个静态成员来说只存在唯一的实例。并且子类的对象都
能访问父类的静态成员。这里就不给出代码了,感兴趣的可以自己写出代码。
