1、类继承(派生)
使用继承的场景:
1)如果新创建的类与现有的类相似,只是多出若干成员变量或成员函数时,可以使用继承
2)当需要创建多个类时,如果它们拥有很多相似的成员变量或成员函数,可以将这些类共同的成员提取出来,定义为基类,然后从基类继承
class father{
}
class son:public father{ //public 继承方式
}
- 类成员的访问权限由高到低依次为: public -> protected -> private,public成员在类外可以访问,private成员只能在类的成员函数中访问。
- 如果不考虑继承关系,protected成员和private成员一样,类外不能访问。但是,当存在继承关系时,protected和private就不一样了。基类中的protected成员可以在派生类中访问,而基类中的private成员不能在派生类中访问
- 继承方式有三种: public(公有的)、protected(受保护的)和private(私有的)。一般就选择public
![](https://i-blog.csdnimg.cn/direct/646e1c05137744538c675672c3168a5e.png)
class A{
public:
int m_a=10;
protected:
int m_b=20;
privated:
int m_c=30;
}
class B:public A{
public:
using A::m_b;
privated:
using A::m_a;
}
2、类的对象模型
1)创建派生类对象时,先调用基类的构造函数,再调用派生类的构造函数
2)销毁派生类对象时,先调用派生类的析构函数,再调用基类的析构函数
3)创建派生类对象时只会申请一次内存(大小是基类+派生类占用的内存空间之和),派生类对象包含了基类对象的内存空间,this 指针相同,成员变量地址也相同
4) 创建派生类对象时,先初始化基类对象,再初始化派生类对象。
创建派生类时,申请一块基类+派生类的内存空间,先调用基类的构造函数初始化基类的成员,再调用派生类的构造函数初始化派生类的成员
5)C++中,不同继承方式的访问权限只是语法上处理(派生类无法访问基类的私有成员,但实际通过直接操作内存的方式是可以访问的,比如通过指针)
//基类A 派生类B
B *P=new B;
*((int*)P+2)=30;//修改私有成员
delete P;
3、如何构造基类
class A{
public:
int m_a;
private:
int m_b;
punlic:
A():m_a(0),m_b(0); //初始化列表
{
cout<<"调用了基类的默认构造函数\n";
}
A(int a,int b):m_a(a),m_b(b); //两个参数的构造函数
{
cout<<"调用了基类的构造函数A(int a,int b)\n";
}
A(const A&a):m_a(a.m_a+1),m_b(a.m_b+1); //基类的拷贝构造函数
{
cout<<"调用了基类的拷贝构造函数\n";
}
void showA(){count<<"m_a="<<m_a<<"m_b"<<m_b<<endl;}
}
class B:public A
{
public:
int m_c;
B():m_c(0),A() //派生类的默认构造函数
{
cout<<"调用派生类的构造函数\n";
}
B(int a,int b,int c):A(a,b),m_c(c) //指明派生类的2参数的构造函数
{
cout<<"调用派生类的构造函数\n";
}
B(const A&a,int c):A(a),m_c(c) //指明派生类的拷贝构造函数
{
cout<<"调用派生类的构造函数\n";
}
void showB(){count<<"m_c="<<m_c<<endl;}
}
int main()
{
B b1;
B b2(1,2,3);
A a(10,20);
B b3(a,30);
}
原则:基类的成员由基类的构造函数初始化,派生类新增的成员由派生类初始化
拷贝构造函数:用一个存在的对象创建新的对象,不会调用普通的构造函数,而是调用拷贝构造函数。
如果类中没有定义拷贝构造函数,编译器将提供一个拷贝构造函数,将已存在对象的成员变量值赋给
新对象的成员变量。
语法:类名 新对象名(存在的对象名) 类名 新对象名=存在的对象名
拷贝构造函数的语法:类名(const 类名&对象名, 其他参数){.........}
注意:
1、拷贝构造函数权限为public
2、函数名与类名相同
3、没有返回值不写void
4、如果以值传递的方式调用函数,函数的实参为对象,会调用拷贝构造函数
多态
基类指针
只能调用基类的成员函数,不能调用派生类的成员函数。如果在基类的成员函数前加 virtual
关键字,把它声明为虚函数,基类指针就可以调用派生类中同名的成员函数,通过派生类中同名的成员函数,就可以访问派生对象的成员变量。
有了虚函数,基类指针指向基类对象时就使用基类的成员函数和数据,指向派生类对象时就使用派生类的成员函数和数据,基类指针表现出了多种形式,这种现象称为多态。 基类引用也可以使用多态。
注意:
1)只需要在基类的函数声明中加上 virtual 关键字,函数定义时不能加。。
2)在派生类中重定义虚函数时,函数特征要相同。
3)当在基类中定义了虚函数时,如果派生类没有重定义该函数,那么将使用基类的虚函数。
4)在派生类中重定义了虚函数的情况下,如果想使用基类的函数,可以加类名和域解析符。指针名->基类名::函数名
5)如果要在派生类中重新定义基类的函数,则将它设置为虚函数;否则,不要设置为虚函数,有两方面的好处:首先效率更高;其次,指出不要重新定义该函数。
多态应用场景
- 基类的虚函数实现基本功能。
- 派生类重定义虚函数,扩展功能、提升性能。
虽然继承也可以实现这种功能,但使用多态可以让编程更简单方便
//根据不同英雄释放不同技能
class hero{
public:
virtual void skill1(){}
virtual void skill2(){}
virtual void skill3(){}
}
class XS:public Hero{
void skill1(){"XS";}
void skill2(){"XS";}
void skill3(){"XS";}
}
class LB:public Hero{
void skill1(){"LB";}
void skill2(){"LB";}
void skill3(){"LB";}
}
class HX:public Hero{
void skill1(){"HX";}
void skill2(){"HX";}
void skill3(){"HX";}
}
int main()
{
Hero*ptr=nullptr;
if(id==1){
ptr=new XS;
}
if(id==2){
ptr=new LB;
}
if(id==3){
ptr=new HX;
}
if(prt!=nullptr){
prt->skill1();
prt->skill2();
prt->skill3();
delte ptr;
}
}
如果基类没有声明虚函数,内存模型里只有定义的静态变量;如果声明了虚函数,多了一个vfptr虚函数指针,指向vftable虚函数列表,在列表中,存放了基类不同虚函数的地址。派生类和基类内存模型相同,在创建派生类对象后,在虚函数列表中,会用派生类成员函数取代基类成员函数的地址。如果派生类没有重写虚函数,那么在派生类的虚函数列表中还是基类函数的地址。