继承
前面讲到c++的继承是子类在继承时声明继承的权限,之前描述有点不够准确。以下时书中提及的能继承的成员。
成员函数属性 ==当使用private继承时,父类的所有public成员在当前子类中会变为private。当使用protected继承时,父类的所有public成员在当前子类中会变为protected。==。
c++中,被定义为虚函数的成员,能被子类重写,虚函数是用virtual修饰的函数。原理是每个有虚函数的类对象内部维护一个的虚方法表成员,记录包含的方法以及对象的类型信息,虚函数只有在运行期(runtime)才会去动态确定使用哪个实现方法 比如:
class SuperClass{
public:
SuperClass(const int & a){
std::cout<<"superClass "<<a<<std::endl;
}
SuperClass(const SuperClass & another){
std::cout<<"copy superClass "<<&another<<" "<<this<<std::endl;
}
void show(){
std::cout<<"I am superClass "<<std::endl;
}
virtual void vShow(){
std::cout<<"vShow I am superClass "<<std::endl;
}
};
class SubClass : public SuperClass{
public:
SubClass(const int & a):SuperClass(a){//可以通过这样指定使用的父类构造函数
}
void show(){
std::cout<<"I am subClass "<<std::endl;
}
virtual void vShow(){
std::cout<<"vShow I am subClass "<<std::endl;
}
};
上述代码定义了SuperClass和SubClass,并具备show方法和vShow方法,假设有如下代码
SuperClass s1 = SubClass(1);
//copy superClass
s1.show();//I am superClass
s1.vShow();//vShow I am superClass
SuperClass && s2 = SubClass(1);
s2.show();//I am superClass
s2.vShow();//vShow I am subClass
SuperClass * s3 = new SubClass(1);
s3->show();//I am superClass
s3->Show();//vShow I am subClass
右边的注释为每个方法调用的输出,可以看到,如果使用普通变量定义来初始化子类对象,子类的对象可以作为父类对象使用,这时候因为会调用拷贝构造函数,最终变为一个新的父类对象,所以没有意义。 而如果通过引用,则不会执行拷贝构造。因为引用类型是父类型,在调用普通方法时,仍是父类方法,只有调用虚方法时,使用了真正的子类方法。而指针类型也是与引用类型类似。
c++中子类析构函数结束会自动调用父类析构函数。接下来看看继承下析构的表现,假设我们将析构改为如下。
class SuperClass{
public:
~SuperClass(){
std::cout<<"SuperClass destructor"<<std::endl;
}
};
class SubClass : public SuperClass{
public:
~SubClass (){
std::cout<<"SubClass destructor"<<std::endl;
}
};
执行代码
SuperClass && s = SubClass();
//SubClass destructor
//SuperClass destructor
证实引用类型会调用被引用的对象的真实类型的析构函数
SuperClass * s = new SubClass();
delete s;
//SuperClass destructor
对于new出来的堆对象进行delete删除时,只调用了指针类型对应的析构函数,因为delete是显示调用当前指针类型的析构函数处理,面对这种情况可以通过把父类的析构函数定义为虚函数,则delete调用时为调用虚函数,要去动态绑定会重新根据内存对象的类型选择子类的析构函数
class SuperClass{
public:
virtual ~SuperClass(){
std::cout<<"SuperClass destructor"<<std::endl;
}
};
class SubClass : public SuperClass{
public:
virtual ~SubClass (){
std::cout<<"SubClass destructor"<<std::endl;
}
};
此时再执行以下代码
SuperClass * s = new SubClass();
delete s;
//SubClass
//SuperClass destructor
发现已经被定向为子类的析构函数了
在java中我们有接口的定义,接口定义的方法必须是抽象方法,要求子类必须实现,纯在抽象方法的类不能实例化。在c++中有对应的纯虚函数,具备纯虚函数的类不能进行实例化,纯虚函数指将虚函数赋值为0的函数,如
class A{
virtual pureVirtualFunction() = 0;
}
类与函数类似,都具备提前声明提高作用域的方法,用法如下
class B;
class A{
B b;
}
class B{}
前面讲过友元函数的作用,类中的方法也可以作为友元函数看待,比如
class B;
class A{
void show(B b){}
}
class B{
friend void A::show(B b);
}
当我们想把整个类的所有成员函数都作为友元时,可以直接将类作为友元,如
class B;
class A{
void show(B b){}
}
class B{
friend class A;
}
cpp中为了对强制转换进行更高一级的优雅限制,提供了RTTI(Runtime Type identify),中文叫运行时类型识别。我们先看看以前的强制类型转换
long a = 10l;
int * b = (int *) (&a);
这样可以将long类型指针强制转为int类型指针,但是这种转化方式是直接更改编译器对该内存空间的读取方式,而不是经过检查的,存在一定风险。为此,cpp提供了四大强制转化运算专门处理
dynamic_cast运算符,判断传入对象是否可以安全的转为给定的指针类型/引用(是否为该类父类指针或子类指针/该类父类引用或子类引用),可以则传递该对象地址/转化后的引用,否则返回空指针(对于引用类型则是抛出异常) ,要向下转化要求传入参数的类型对应的类中需要有虚函数,否则编译出错,因为虚方法表里包含了类型信息type_info,向下转型需要使用type_info来判断是否可以转型(动态联编),因而称为动态转化,向上不用(编译器已知继承关系),用法
SuperClass * ss = new SubClass();
SubClass* s = dynamic_cast<SubClass*>(ss );//向下转型,SuperClass中要有虚方法
static_cast与dynamic_cast用法相同,唯一区别是他没有动态检查,也就是向下转型不强制要求传入参数的类型对应的类中有虚函数。并且如果向下转型是错误的,也不会报错,static_cast与强制转化类似,将当前引用/指向的内存空间作为转化后的类型来用,这会导致一些不可知的错误,如读取从成员变量所对应的空间是别的用途或者未初始化的,当把较大的数据单元转化为较小的数据单元时,static_cast的处理时丢掉溢出的部分
除了对继承关系的转化,还有const与volatile关键字的限定关系的转化。const_cast用来对指针/引用变量转化为,带const/volatile修饰的,或者完成取消const/volatile修饰。也就是用来消灭const限定和volatile限定的,因为const指针/引用只能赋值给const指针/引用。比如:
class root{
public:
int j;
}
const root * a = new root();
//a->j = 6; err
root * b = const_cast<root *>(a);
b->j = 6;//ok
reinterpret_cast与static_cast类似,也是没有检查的转化,唯一区别是,reinterpret_cast是按照二进制来解释,也就是说,你甚至可以把对象类型cast为整形(因为按照二进制来解释,多的位丢掉)
c++中可以通过throw关键字抛出一个任意对象,程序会将其作为一个异常对象处理,处理步骤 1.查找被包围的匹配类型的catch块,有就跳到catch块代码 2.没有找到匹配的catch块,则调用terminate函数,一般编译器处理是调用abort函数,以异常情况结束程序 noexcept标明告诉编译器,本方法不会抛出异常,有写情况下能提高性能,同时c++中也有exception类,在头文件exception。其中有what()虚函数,返回一个const char *,一般通过重写该方法表示异常信息。代码示例
try{
throw "I am string";
}catch(const char * msg){
//code
}
与java类似,c++也有final,通过在类名后面或者虚函数后面加上final关键字代表禁止继承或者禁止重写,如
class A final{
virtual void show() final{}
}
但子类编写与父类具备不同形参的同名虚方法时,编译器会认为是覆盖,将对子类隐藏父类的同名方法,为了加强对这种情况的检查,可以通过在子类方法后面加上override关键字,代表是重写父类方法而不是覆盖,此时如果形参类型不一致,会导致编译失败。如
class A{
virtual void show(int a){}
};
class B : public A{
virtual void show(int * p){}
};
//导致 new B()->show(1);不能调用,被隐藏
这时使用override,严格检查重写,发现父类没有存在形参为int *的show方法,编译出错
class A{
virtual void show(int a){}
};
class B : public A{
virtual void show(int * p) override{}
};