继承的构造函数
一个类只继承其直接基类的构造函数,不能继承默认、拷贝、移动构造函数
在子类B中使用using A::A;
就可以继承A的构造函数,using
的作用是让某个名字在当前作用域中可见,也就是说,遇到这条代码的时候,会把基类的每个构造函数,都生成一个与之对应的派生类构造函数,如果基类A的构造函数有默认参数的话,那么使用using A::A
,编译器会帮我们在派生类B中创建多个构造函数
class A{
public:
A(int a,int b,int c = 5){}
};
class B : public A{
public:
using A::A; //继承A的构造函数
//B(int a,int b,int c):A(a,b,c){} //A无默认参数时,编译器会自动创建这个
//B(int a,int b):A(a,b){} //A有默认参数时,编译器会自动创建这个和上面的
};
int main(){
B ad(3,4,5);
return 0;
}
如果基类含有多个构造函数,则多数情况下,派生类会继承所有这些函数,但有如下例外:
1)如果你在派生类中定义的构造函数与基类构造函数有相同的参数列表,那么从基类中继承来的构造函数会被在派生类中定义的构造函数所覆盖
2)默认、拷贝、移动构造函数不会被继承
多重继承
多重继承概述
如果从多个父类中产生子类,就叫多重继承
class Grand{
public:
Grand(int i):m_valuegrand(i){}
virtual ~Grand(){}
void myinfo(){
cout<<m_valuegrand<<endl;
}
int m_valuegrand;
};
class A : public Grand{
public:
A(int i):Grand(i),m_valuea(i){} //每个子类的构造函数,负责解决自己父类的初始化问题
virtual ~A(){}
void myinfo(){
cout<<m_valuea<<endl;
}
int m_valuea;
};
class B{
public:
B(int i):m_valueb(i){}
virtual ~B(){}
void myinfo(){
cout<<m_valueb<<endl;
}
int m_valueb;
};
// class C : A, B{} //如果这样写,class为私有继承,struct为公有继承
class C : public A, public B{ //没有明确规定最多能继承多少个父类
public:
C(int i,int j, int k):A(i),B(j),m_valuec(k){}
virtual ~C(){}
void myinfoC(){
cout<<m_valuec<<endl;
}
int m_valuec;
};
C ctest(10,20,50);
ctest.myinfoC(); //50
ctest.myinfo(); //报错,调用不明确,两个父类都有该函数
ctest.A::myinfo(); //10
ctest.B::myinfo(); //20
静态成员变量
静态成员变量属于类,不属于对象
//修改上面的示例代码
class Grand{
public:
Grand(int i):m_valuegrand(i){}
virtual ~Grand(){}
void myinfo(){
cout<<m_valuegrand<<endl;
}
int m_valuegrand;
static int m_static; //添加静态成员变量声明
};
int Grand::m_static = 5; //定义静态成员变量,分配内存
Grand::m_static = 1; //可以使用
A::m_static = 1; //可以使用
B::m_static = 1; //不能使用
C::m_static = 1; //可以使用
ctest.m_static =1; //可以使用
派生类构造函数与析构函数
C ctest(10,20,30);
的执行顺序为Grand构造函数->A构造函数->B构造函数->C构造函数->C析构函数->B析构函数->A析构函数->Grand析构函数
所以说,构造一个派生类对象,将同时构造并初始化所有基类子对象;派生类的构造函数初始化列表只初始化它的直接基类,每个类的构造函数都负责初始化它的直接基类;派生类构造函数初始化列表将实参分别传递给每个基类,基类的构造顺序于派生列表中基类的出现顺序一致
修改class C : public A, public B{}
为class C : public B, public A{}
则执行顺序为B构造函数->Grand构造函数->A构造函数->C构造函数->C析构函数->A析构函数->Grand析构函数->B析构函数
隐式初始化基类问题,如果派生类初始化时,没有初始化基类,则会使用基类的默认构造函数(不带参数的构造函数)来初始化基类子对象
从多个父类继承构造函数
如果一个类从它的基类中继承了相同的构造函数,这个类必须为该构造函数定义它自己的版本
class A{
public:
A(int i){}
virtual ~A(){}
};
class B{
public:
B(int i){}
virtual ~B(){}
};
class C : public A, public B{
public:
using A::A; //继承A的构造函数
using B::B; //继承B的构造函数 ,报错
C(int tv):A(tv),B(tv){} //定义自己的构造函数,不报错
}
类型转换
基类指针可以指向派生类对象:编译器帮助我们隐式的执行这种派生类到基类的转换,转换成功的原因是因为每个派生类对象都包含一个基类对象部分,所以基类的引用或者指针是可以绑定到基类对象这部分
class Grand{
public:
Grand(int i):m_valuegrand(i){}
virtual ~Grand(){}
void myinfo(){
cout<<m_valuegrand<<endl;
}
int m_valuegrand;
};
class A : public Grand{
public:
A(int i):Grand(i),m_valuea(i){} //每个子类的构造函数,负责解决自己父类的初始化问题
virtual ~A(){}
void myinfo(){
cout<<m_valuea<<endl;
}
int m_valuea;
};
class B{
public:
B(int i):m_valueb(i){}
virtual ~B(){}
void myinfo(){
cout<<m_valueb<<endl;
}
int m_valueb;
};
// class C : A, B{} //如果这样写,class为私有继承,struct为公有继承
class C : public A, public B{ //没有明确规定最多能继承多少个父类
public:
C(int i,int j, int k):A(i),B(j),m_valuec(k){}
virtual ~C(){}
void myinfoC(){
cout<<m_valuec<<endl;
}
int m_valuec;
};
Grand *pg = new C(1,2,3); //可以
A *pa = new C(1,2,3); //可以
B *pb = new C(1,2,3); //可以
C myc(1,2,3);
Grand myg(myc); //可以
虚基类、虚继承
派生列表中,同一个基类只能出现一次,但是下面的两种情况除外
1)派生类可以通过它的两个直接基类分别继承同一个间接基类
2)直接继承某个基类,然后通过另一个基类间接继承该类
class Grand{
public:
Grand(int i):m_valuegrand(i){}
virtual ~Grand(){}
void myinfo(){
cout<<m_valuegrand<<endl;
}
int m_valuegrand;
};
class A : public Grand{
public:
A(int i):Grand(i),m_valuea(i){}
virtual ~A(){}
void myinfo(){
cout<<m_valuea<<endl;
}
int m_valuea;
};
class A2 : public Grand{
public:
A2(int i):Grand(i),m_valuea2(i){}
virtual ~A2(){}
void myinfo(){
cout<<m_valuea2<<endl;
}
int m_valuea2;
};
class B{
public:
B(int i):m_valueb(i){}
virtual ~B(){}
void myinfo(){
cout<<m_valueb<<endl;
}
int m_valueb;
};
// class C : A, B{} //如果这样写,class为私有继承,struct为公有继承
class C : public A, public A2, public B{ //没有明确规定最多能继承多少个父类
public:
C(int i,int j, int k):A(i),A2(i),B(j),m_valuec(k){}
virtual ~C(){}
void myinfoC(){
cout<<m_valuec<<endl;
}
int m_valuec;
};
C ctest(10,20,30);//这样导致Grand被构造了两次,多余,占内存,名字冲突
ctest.m_valuegrand =100; //报错,访问不明确
虚基类:无论这个类在继承体系中出现多少次,在派生类中,都只会包含唯一一个共享的虚基类子内容
虚继承:通过虚继承实现虚基类,虚继承只对子类C有意义,对于父类A,A2无意义,类A和类A2都要从Grand类虚继承,也就是每一个Grand的子类都要虚继承Grand类,这样才能保证Grand的孙类C能够虚继承Grand类
class A : virtual public Grand{ //虚继承,virtual和public位置可互换
public:
A(int i):Grand(i),m_valuea(i){}
virtual ~A(){}
void myinfo(){
cout<<m_valuea<<endl;
}
int m_valuea;
};
class A2 : virtual public Grand{ //虚继承,virtual和public位置可互换
public:
A2(int i):Grand(i),m_valuea2(i){}
virtual ~A2(){}
void myinfo(){
cout<<m_valuea2<<endl;
}
int m_valuea2;
};
class C : public A, public A2, public B{ //没有明确规定最多能继承多少个父类
public:
C(int i,int j, int k):Grand(i),A(i),A2(i),B(j),m_valuec(k){} //虚继承时,孙类初始化爷爷类
virtual ~C(){}
void myinfoC(){
cout<<m_valuec<<endl;
}
int m_valuec;
};
只要子类中都加了virtual
继承,那么Grand类自然成为了虚基类;virtual
表示一种意愿,表示后续从A、A2派生的类应该共享虚基类中的同一份示例
注意:
1)上述代码中,有C类初始化Grand类,如果C类有了派生类,则有C的派生类初始化Grand类。也就是说,虚基类是由最低层的派生类初始化
2)不管虚基类的位置在派生列表的什么位置,都先初始化虚基类,然后再按照派生列表中出现的顺序初始化其他类,如果有多个虚基类,按照派生列表的直接基类往回追溯,按顺序初始化虚基类,销毁顺序于初始化顺序相反
类型转换构造函数
可以将某个其他数据类型转换为该类类型的对象
特点:
1)只有一个参数,且不是本类的const引用,该参数为待转换的数据类型
2)指定转换的方法
class Test(){
public:
// explicit :禁止做隐式类型转换
Test(int x = 0):m_i(x){
if(x<0) m_i = 0;
if(x>100) m_i =100;
} //类型转换运算符
int m_i;
}
Test ti = 12; //成功,隐式类型转换,调用了类型转换构造函数;添加exlicit后报错
Test ti2(22); //成功,调用了类型转换构造函数,但没有隐式类型转换;添加exlicit后不报错
谨慎使用
类型转换运算符
能力于类型转换函数相反,将一个类类型的对象,转换为其他数据类型
格式operator type() const;
const为可选项;type为要转换的类型,能够作为函数返回类型的都可以;没有形参,形参列表为空,一般是隐式执行,没有办法传递参数,同时也不能指定返回类型,但是却能返回一个type指定类型的值;必须定义为类的成员函数
class Test(){
public:
// explicit :禁止做隐式类型转换
Test(int x = 0):m_i(x){
if(x<0) m_i = 0;
if(x>100) m_i =100;
} //类型转换运算符
operator int() const{ //也可以加explicit :禁止做隐式类型转换
return m_i;
}
int m_i;
}
Test ti = 12; //成功,隐式类型转换,调用了类型转换构造函数
int k = ti + 5; //成功,调用类型转换运算符,隐式
int k2 = ti.operator int(); //显示调用
谨慎使用
类成员函数指针
是个指针,指向类成员函数,都有真正的地址
成员函数属于类,不属于类对象,只要有类在就有成员函数地址在;但是如果要使用这个成员函数指针,就必须把他绑定到一个类对象上才能调用
对于普通成员函数,格式:类名::*函数指针变量名
来声明普通成员函数指针
&类名::成员函数名
来获取类成员函数地址,使用函数指针的格式类对象名.*函数指针变量名
来调用,如果是个对象指针指针名->*函数指针变量名
class CT
{
public:
void ptfunc(int tmpvalue){
cout<<"ptfunc() tmpvalue = "<<tmpvalue<<endl;
}
virtual void virtualptfunc(int tmpvalue){
cout<<"virtualptfunc() tmpvalue = "<<tmpvalue<<endl;
}
static void staticptfunc(int tmpvalue){
cout<<"staticptfunc() tmpvalue = "<<tmpvalue<<endl;
}
}
//定义普通类成员函数指针
void (CT::*myfuncpoint)(int); //变量名为myfuncpoint
myfuncpoint = &CT::ptfunc; //类成员函数指针变量赋值
CT ct,*pct;
pct = &ct;
(ct.*myfuncpoint)(100); //对象ct调用myfuncpoint指向的成员函数ptfunc()
(pct->*myfuncpoint)(200); //指针pct调用myfuncpoint指向的成员函数ptfunc()
//对于虚函数,也必须绑定到类对象上才能使用
void (CT::*myvirtualfuncpoint)(int) = &CT::virtualptfunc;
(ct.*myvirtualfuncpoint)(100); //对象ct调用myvirtualfuncpoint指向的成员函数virtualptfunc()
(pct->*myvirtualfuncpoint)(200); //指针pct调用myvirtualfuncpoint指向的成员函数virtualptfunc()
//对于静态函数
//使用 *函数指针变量名 来声明静态成员函数指针,使用 &类名::成员函数名 来获取成员函数地址
void (*mystaticfuncpoint)(int) = &CT::staticptfunc;
mystaticfuncpoint(100); //直接使用静态成员函数指针名就可以调用
类成员变量指针
是个指针,指向类成员变量,普通成员变量指针并不是真正意义上的地址,而是该成员变量与该类对象的偏移量(类对象指针);静态成员变量指针有真正的地址
若类中无虚函数,则m_a的地址应为0x00000000;若类中有虚函数,则会生成一个虚函数表(虚函数表属于类),那么对象中就会有一个指向这个虚函数表的指针,这个指针占4个字节,所以m_a的地址为0x00000004
class CT
{
public:
virtual void virtualptfunc(int tmpvalue){
cout<<"virtualptfunc() tmpvalue = "<<tmpvalue<<endl;
}
int m_a;
static int m_static; //声明静态成员变量,属于类,不属于对象
}
int CT::m_static = 5; //静态成员变量的定义
int CT::*mp = &CT::m_a; //普通成员变量,属于对象
CT ctest;
ctest.*mp = 18; //通过类成员变量指针来修改成员变量值,等价于ct.m_a=18;
int *pstatic = &CT::m_static; //定义一个静态成员变量指针
*pstatic = 20; // 等价于CT::m_static = 20;
评论区