继承
定义一个派生类的过程:
- 吸收基类的成员
- 添加新的成员(非必须)
- 隐藏基类的成员(非必须)
//基类
class point{
public:point(int _x=0,int _y=0):x(_x),y(_y){cout << "call point" << endl;}void print() const{cout << x << "," << y << endl;}
protected:int x, y;
};
//继承
class point3D:public point{
public:point3D(int _x=0,int _y=0,int _z=0):point(_x,_y),z(_z){cout << "call point3D" << endl;}void disPlay() const{print();//派生类可以访问基类的protected成员cout <<x<<","<<y<<","<< z << endl;}
private:int z;
};
int main(){point3D pt(1, 4, 6);pt.disPlay();
}
访问权限总结
B继承A,C继承B
class A{ public:A(int _x=0):x(_x){ }void showA() { cout << x << endl; } private:int x; }; //B继承A class B:public A{//公有继承 public:B(int _x=0,int _y=0):A(_x),y(_y){ }void showB(){showA();cout << y << endl;} private:int y; }; class C:public B{//公有继承 public:C(int _x=0,int _y=0,int _z=0):B(_x,_y),z(_z){}void showC(){showB();cout << z << endl;} private:int z; }; int main(){C tmp(10, 99, 77);tmp.showC(); }
私有继承和保护继承都可以让派生类访问基类的非私有成员,但是私有继承会影响多次继承对基类的访问
- 不管什么继承方式,
派生类内部
都不能
访问基类的私有成员
; - 不管什么继承方式,
派生类内部
除了基类的私有成员
不可以访问,其他的都可以访问
; - 不管什么继承方式,派生类对象
在类外
除了公有继承基类中的公有成员
可以访问外,其他的都不能访问。
继承关系的局限性
- 创建和销毁类对象不能被继承-----构造函数和析构函数
- 复制操作不能被继承----拷贝构造和赋值运算(operator =)
- 空间分配不能被继承----operator new()和operator delete()
- 友元函数不能被继承
因为四大函数(构造+析构+拷贝构造+重载=)肯定可以访问所有的成员,所以不允许继承
拷贝构造就是用一个已有的给新的赋值,赋值操作就是用已有的给已有的赋值
友元函数破坏了封装性,为了降低影响,一定不能继承
问:创建派生类对象时,先调用基类构造函数,再调用派生类构造函数
错误:派生类会执行自己的构造函数,只不过会中途调用基类构造函数
derive():
base()
{}----只不过不是显式调用
含有其他类对象成员
显式调用基类构造函数,写的是基类类名;显式调用对象成员的构造函数,写的是对象成员的名字。
#include <bits/stdc++.h> using namespace std; //基类 class Base{ public: Base(int x):_base(x){} void display() const{cout << _base << endl; } private: int _base; }; //其他test类 class Test{ public: Test(int x):_test(x){} void display() const{cout << _test << endl; } private: int _test; }; //派生类 class Derived:public Base{ public: /*注意继承基类的构造函数用类名,普通类的构造函数用对象名*/ Derived(int x,int y,int z):Base(x),_Derived(y),obj(z){} void display() const{Base::display();obj.display();cout << _Derived << endl; } private: int _Derived; //派生类有其他类对象 Test obj; }; int main(){ Derived d(10, 20, 30); d.display(); }
拷贝构造函数
#include <bits/stdc++.h> using namespace std; //基类 class Base{ public:Base(int x,int y):a(x),b(y){}Base(const Base &m):a(m.a),b(m.b){}void display() const{cout << a << "," << b << endl;} private:int a,b; }; //其他test类 class Test{ public:Test(char m,char n):c(m),d(n){}Test(const Test& op):c(op.c),d(op.d){}void display() const{cout << c << "," << d << endl;} private:char c,d; }; //派生类 class Derived:public Base{ public:Derived(Base ba,int z,Test op):Base(ba),val(z),obj(op){}void display() const{Base::display();obj.display();cout << val << endl;} private:int val;//派生类有其他类对象Test obj; }; int main(){Test op('c', 'c');Base ba(100, 200);Derived tmp(ba,3,op);tmp.display(); }
运行结果:
100,200
c,c
3
基类对象作为派生类的私有成员,也依然按照
显示调用对象成员
那样初始化//基类 class Base{ public:Base(int x,int y):a(x),b(y){}void display() const{cout << a << "," << b << endl;} private:int a,b; }; //派生类 class Derived:public Base{ public://注意:哪怕是基类作为对象成员,也要用对象名Derived(int a,int b,int c,int d,int e):Base(a,b),obj(c,d),val(e){}void display() const{Base::display();obj.display();cout << val << endl;} private:int val;Base obj;//基类对象 };
一个派生类对象销毁时,调用自己的析构函数,析构函数执行完后,按照对象成员的声明顺序的逆序去调用对象成员的析构函数,最后调用基类的析构函数
对于基类成员的隐藏
从内存角度,派生类会继承基类所有的成员(包括私有成员,因为字节数是包含的),只不过会按照继承属性进行访问
不管是数据成员还是成员函数,只要派生类和基类发生重名,就会对基类同名成员进行隐藏
//基类 class Base{ public:Base(int x,int y):a(x),b(y){}long arg = 1000;void print() const{cout << a << "," << b << "," << arg << endl;} private:int a,b; }; //派生类 class Derived:public Base{ public:Derived(int a,int b,int e):Base(a,b),c(e){}long arg = 200;//与基类arg名称相同,基类该成员被隐藏void print() const{//与基类print()名称相同,基类该函数被隐藏cout <<c<< "," << arg << endl;} private:int c; }; int main(){Derived m(9, 8, 7);//输出的是派生类的结果:200cout << m.arg << endl;//加上作用域输出的是基类结果:1000//不建议使用,因为破坏了封装性cout << m.Base::arg << endl;m.print();//输出派生类的结果 }
注意:如果派生类没有print,调用print是利用基类去调用,此时输出的同名arg结果还是基类的arg
//基类 class Base{ public: Base(int x,int y):a(x),b(y){} long arg = 1000; void print() const{cout << a << "," << b << "," << arg << endl; } private: int a,b; }; //派生类 class Derived:public Base{ public: Derived(int a,int b,int e):Base(a,b),c(e){} long arg = 200; private: int c; }; int main(){ Derived m(9, 8, 7); m.print();//9,8,1000 }
只要是成员函数名相同,不管参数和返回值是否一样,基类相同函数名照样被隐藏
//基类
class Base{
public:Base(int x,int y):a(x),b(y){}void print() const{cout << a << "," << b << endl;}
private:int a,b;
};
//派生类
class Derived:public Base{
public:Derived(int a,int b,int e):Base(a,b),c(e){}int print() const{cout << c << endl;return 98;}
private:int c;
};
int main(){Derived m(9, 8, 7);m.print();//一定会调用派生类,基类函数被隐藏
}
基类和派生类的转换
一般情况下,派生类的内存空间比基类大。
如果都是空类,内存空间有可能相同,涉及平台占位机制的实现
//基类 class Base{ public:Base(int x,int y):a(x),b(y){}void print() const{cout << a << "," << b << endl;} private:int a,b; }; //派生类 class Derived:public Base{ public:Derived(int a,int b,int e):Base(a,b),c(e){} private:int c; };
主函数
int main(){Base _base(11,22);Derived _derived(9, 8, 7);/*把一个基类对象赋值给一个派生类对象❓*///_derived = _base;❌/*把一个派生类对象赋值给一个基类对象❓*/_base = _derived;/*可否将一个基类指针指向一个派生类对象❓*/Base *pbase = &_derived;/*将一个派生类指针指向一个基类对象❓*///Derived *pderived = &_base;❌/*将一个基类引用绑定一个派生类对象❓*/Base& tmp = _derived;/*将一个派生类引用绑定一个基类对象❓*///Derived &der = _base;❌ }
从内存角度,基类指针指向派生类对象,由于基类指针访问内存大小小于派生类内存,所以会存在访问不到的问题,但是程序可以通过;相反利用派生类指针访问基类对象,如果访问派生类多出基类的内存访问,相当于非法访问,这时候是不被允许的。
Derived类的指针指向Base对象,除了操纵Base对象的空间,还需要操纵一片空间,只能是非法空间,所以会报错。
向上转型是可行的,也就是基类的指针引用是左值
往基类方向的转型就叫向上转型
当base指针或引用指向派生类时,向下转型是可行的
dynamic_cast
<指针类型>****(指针名称)
//基类
class Base{
public:Base(int x,int y):a(x),b(y){}virtual void display(){cout << a << "," << b << endl;}
private:int a,b;
};
//派生类
class Derived:public Base{
public:Derived(int a,int b,int e):Base(a,b),c(e){}
private:int c;
};
int main(){Base _base(11,22);Derived _derived(9, 8, 7);Base *pb = &_derived;//向上转型,基类指针指向派生类//利用dynamic_cast可以把基类指针转换成派生类//因为pb本来就是指向派生类对象,所以可以把Base*变成Derived*//但是前提是:Base类中存在虚函数virtualDerived *pd = dynamic_cast<Derived *>(pb);pd->display();
}
失败的转换
Base _base(11,22);Derived _derived(9, 8, 7);Base *pb = &_base;//转换失败,因为pb本身就是一个指向base对象的,转换成派生类一定存在非法访问Derived *pd = dynamic_cast<Derived *>(pb);if(pd){cout << "success" << endl;}else{cout << "fails" << endl;}
复制控制函数
复制控制函数就是 拷贝构造函数、赋值运算符函数
给派生类手动定义复制控制函数,注意在其中显式调用相应的基类的复制控制函数
总结:
-
如果类的数据成员申请了
堆空间
,那么必须手动写出该类的复制控制函数 -
如果父类的数据成员申请了堆空间,那么父类的复制控制函数必须显式定义,派生类类自身的数据成员如果没有申请堆空间,不用显式定义复制控制函数
#include <bits/stdc++.h>
using namespace std;
//基类
class Base{
public:Base(int x,int y):a(x),b(y){}Base(const Base& op):a(op.a),b(op.b){}Base& operator=(const Base& op){a = op.a;b = op.b;return *this;}virtual void display() const { cout << a << "," << b << ","; }private:int a,b;
};
//派生类
class Derived:public Base{
public:Derived(int a,int b,int e):Base(a,b),c(e){}Derived(const Base& op,int e):Base(op),c(e){}Derived& operator=(const Derived& op){Base::operator=(op);c = op.c;return *this;}void display() const{Base::display();cout << c << endl;}
private:int c;
};
int main(){Base ba(100, 200);Base bb = ba;Base bc(ba);Derived da(11, 22, 33);Derived db(ba, 44);Derived dc = da;//向上转型Base &call = da;call.display();Base *p = &db;p->display();//向下转型Derived *point = dynamic_cast<Derived *>(p);point->display();Derived& m = dynamic_cast<Derived&>(call);m.display();
}
不管是基类的运算符重载还是派生类的运算符重载:
- 函数返回类型必须是:本类名的引用
- 函数的参数类型必须是:
const
的本类名的引用
拷贝构造函数
class student{ public:student(int a,int b):aa(a),bb(b){}student(const student& stu){this->aa = stu.aa;this->bb = stu.bb;} private:int aa, bb; }; class graduate:public student{ public:graduate(int a,int b,string c):student(a,b),str(c){}//拷贝构造函数//赋值兼容:子类对象可以给父类对象赋值//因为拷贝构造器不可以继承,只能放在拷贝构造列表里graduate(const graduate& gra):student(gra),str(gra.str){ } private:string str; };
运算符重载
class student{ public:student(int a,int b):aa(a),bb(b){}student& operator=(const student& stu){if(this == &stu)//是否自我赋值return *this;this->aa = stu.aa;this->bb = stu.bb;return *this;} private:int aa, bb; };
class graduate:public student{ public:graduate(int a,int b,string c):student(a,b),str(c){}graduate& operator=(const graduate& gra){if(this==&gra)return *this;//用子类的赋值重载,父子类同名函数会发生shadow(只看函数名)student::operator=(gra);this->str = gra.str;return *this;} private:string str; };
注意:派生类调用基类的运算符重载函数需要加上作用
友元函数
友元函数不是类成员,因引不能被继承
派生类的友元函数
能使用基类中的友元函数
的方法:
通过强制类型转换
,将派生类的指针或引用强转为其类的引用或指针,然后使用转换后的引用或是指针来调用基类中的友元函数。
- 一个友元作为输出函数的例子
//基类
class Base{
public:Base(int x,int y):a(x),b(y){}//友元输出函数friend ostream &operator<<(ostream &, Base &);
private:int a,b;
};
//友元输出函数定义
ostream &operator<<(ostream &os, Base &op){os << op.a << "," << op.b;return os;
}
//派生类
class Derived:public Base{
public:Derived(int a,int b,int e):Base(a,b),c(e){}
private:int c;
};
int main(){Base a(11, 22);Derived b(33, 44, 55);cout << a << endl;cout << b << endl;
}
运行结果:
派生类可以调用基类的友元函数
因为发生了
赋值兼容
,也就派生类成员b向上转型
了基类对象a.
- 派生类强制类型转换可以调用基类同名函数
#include <bits/stdc++.h>
using namespace std;
//基类
class Base{
public:Base(int x,int y):a(x),b(y){}//友元输出函数friend ostream &operator<<(ostream &, Base &);
private:int a,b;
};
//友元输出函数定义
ostream &operator<<(ostream &os, Base &op){os << op.a << "," << op.b;return os;
}
//派生类
class Derived:public Base{
public:Derived(int a,int b,int e):Base(a,b),c(e){}//友元输出函数friend ostream &operator<<(ostream &, Derived &);
private:int c;
};
//派生类的友元输出函数
ostream &operator<<(ostream &os, Derived &m){//调用基类的友元函数os << static_cast<Base &>(m)<<",";os << m.c;return os;
}
int main(){Base a(11, 22);Derived b(33, 44, 55);cout << a << endl;cout << b << endl;
}
输出结果:
或者:
os << dynamic_cast<Base &>(m)<<",";