基本用法
继承能更好的实现代码服用,假如我要实现和父类相类似,但是有要用很多父类的,那么继承就方便。
class A{
private:
int pri;
protected:
int pro;
pubulic:
void func(){
pri = 10;
}
};
class B:A
{
}
class C:public A
{
}
如上,B是子类(派生类),A是父类。
成员的继承
普通的成员变量和成员函数:
类之间有三种继承关系:公有继承,私有继承,保护继承。
子类会继承父类的所有成员,且继承过来的成员的访问权限,取继承权限和父类成员权限之中更小的那一个。
class 的默认继承权限私有继承,struct的继承权限是公有继承
如上相当于B这个类就是私有继承,那么其继承的三个成员都是是有的。
pri,pro,func 都是私有成员
虽然子类会继承父类的私有成员,但是在子类里,没有对父类private成员的访问或者权限,但是确实子类继承了父类的私有成员,可以通过public成员或者protected成员进行间接访问操作。
静态成员:
子类与父类是共用一套静态成员,继承不算很好的描述,但是其继承过来权限是一样的,因为如果
父类的静态成员是private,子类依然是没有资格访问的。
class A{
private:
static int a;
};
int A::a = 0;
class B :public A{
B()
{
//A::a++;//这里解除注释就会发现报错。
}
};
默认成员函数
1.构造函数
子类会自动在初始化列表调用父类构造函数,初始化从父类继承来的成员,或者显示调用传参初始化也可以。(如果父类的构造函数没有全缺省或者无参数,那就需要显示调用父类构造函数去初始化**
且父类构造函数一定是至于子类所有成员初始化之前调用的
//示例代码
class ParentA{
public:
ParentA(int a = 10)
{
cout<<"ParentA"<<endl;
}
};
class B:public ParentA
{
public:
B()
:ParentA(10) //类的构造函数里面,应该实现父类会自动调用初始化,如果需要显示调用就要如此写(编译器自动生成的也会如此做
//例如父类构造函数不是没有全缺省和无参数构造函数,就必须要显示调用
//且父类构造函数总是在之前调用
{
cout<<"B"<<endl;
}
int b;
};
2.拷贝构造函数
一样的具备构造函数的特性,会有初始化列表,也会在初始化列表最前端去调用一次父类构造函数,值得注意的是拷贝构造函数,其默认的初始化列表式中调用的父类构造函数那一部分,没写就调用的是默认的构造函数了。这就导致,基类给基类拷贝构造的时候,是不会存在父类那一部分对象相同的。
示例代码,打断点监视即可观察。
class ParentA{
public:
ParentA(int a = 10)
{
cout<<"ParentA"<<endl;
}
ParentA(const ParentA& pa)
{
cout<<"ParentA(const ParentA& pa)"<<endl;
}
int c;
};
class B:public ParentA
{
public:
B()
:ParentA(10) //类的构造函数里面,应该实现父类会自动调用初始化,如果需要显示调用就要如此写(编译器自动生成的也会如此做
//例如父类构造函数不是没有全缺省和无参数构造函数,就必须要显示调用
//且父类构造函数总是在之前调用
{
cout<<"B"<<endl;
}
// B(const B& b)
// //:ParentA(b)//拷贝构造如果自己不调用,就调用父类的默认构造函数
// {
// cout<<"B(const B& b)";
// }
int b;
};
void test_1()
{
B b;
b.a = 10;
B c(b);
}
3.赋值重载
赋值重载,编译器自动生成的赋值重载,只会对子类自己单有的成员进行(浅拷贝)赋值,不会对父类的成员进行赋值,需要对父类的赋值,如果想要完成赋值,就要自己写赋值重载,调用父类的赋值重载
//如果你想验证,把B类的赋值重载注释掉即可。
class ParentA{
public:
ParentA(int a = 10)
{
cout<<"ParentA"<<endl;
}
ParentA(const ParentA& pa)
{
cout<<"ParentA(const ParentA& pa)"<<endl;
}
ParentA operator=(const ParentA& pa)
{
cout<<"ParentA operator="<<endl;
a = pa.a;
return *this;
}
~ParentA(){
cout<<"~ParentA"<<endl;
}
int a;
private:
int A_b;
// static int count;
protected:
// static int count;//静态成员变量属于整个类,子类不谈继承一说,子类也可以正常访问,但是需要protect或者是公有
int c;
};
class B:public ParentA
{
public:
B()
:ParentA(10) //类的构造函数里面,应该实现父类会自动调用初始化,如果需要显示调用就要如此写(编译器自动生成的也会如此做
//例如父类构造函数不是没有全缺省和无参数构造函数,就必须要显示调用
//且父类构造函数总是在之前调用
{
cout<<"B"<<endl;
}
B& operator=(const B& b)//编译器自动生成的也会去调用父类的赋值重载
{
if(this==&b)
{
return *this;
}
this->ParentA::operator=(b);//这里要主动去调用父类的赋值重载,所以在赋值时,需要调用父类的赋值重载才行
//已经做深拷贝
cout<<"B& operator=(const B& b)"<<endl;
return *this;
}
B(const B& b)
//:ParentA(b)//拷贝构造如果自己不调用,就调用父类的默认构造函数
{
cout<<"B(const B& b)";
}
}
int b;
};
4.析构函数
子类的析构函数,只需要完成对子类那一部分的处理即可,当子类的析构函数调用完,会自动调用父类的析构函数,就不需要自己再调用,否则会出现不可知的错误。
//示例代码,你可以去main函数创建子类,利用调试观察。
class ParentA{
public:
ParentA(int a = 10)
{
cout<<"ParentA"<<endl;
}
~ParentA(){
cout<<"~ParentA"<<endl;
}
int a;
private:
int A_b;
// static int count;
protected:
// static int count;//静态成员变量属于整个类,子类不谈继承一说,子类也可以正常访问,但是需要protect或者是公有
int c;
};
class B:public ParentA
{
public:
B()
:ParentA(10) //类的构造函数里面,应该实现父类会自动调用初始化,如果需要显示调用就要如此写(编译器自动生成的也会如此做
//例如父类构造函数不是没有全缺省和无参数构造函数,就必须要显示调用
//且父类构造函数总是在之前调用
{
cout<<"B"<<endl;
}
~B(){
//析构函数调用时,会优先调用子类的析构
//ParentA::~ParentA();但是子类会自动调动父亲构造函数,所以就不需要在调用,否则容易出现问题
}
int b;
};
虚继承
C++的继承里面,有一种继承方式是虚继承,为了解决菱形继承的缘故。
菱形继承代码。
class A{
protected:
int m_a = 0;
};
class B:public A{
};
class C:public A{
};
class D:public B,public C{
public:
int m_d = 0;
};
菱形继承示意图。
上面就是菱形继承,会出现一种十分不妙的情况,1.D里面有两份A的数据分别来自与B和C(B和C从A那里继承来的)2.同时也导致在调用A类里面的m_a成员时,出现二义性情况。
第二种情况实际影响不算太大,可以通过类域去调用。如下:
D d;
d.B::m_a = 10;
d.C::m_a = 10;
但是依然没有解决数据冗余的情况,为此CPP里面搞出了一种虚继承。使得在孙子的衍生类之中,只会有一份祖父基类的成员。
实现方法:实现虚继承,只需在会重复出现的祖父类的子类加上virtual,就可以在后续多继承使用不同类来自同一个父类的时候,孙子类或者更往下不会存在多份数据等情况。
使用示例:
class A{
public:
int m_a = 0;
};
class B:virtual public A{
};
class C:virtual public A{
};
class D:public B,public C{
public:
int m_d = 0;
};
VS的实现中,是把属于父类的那一部分成员,给放到了整个类的最底层。