目录
3.1 兼容性规则
C++面向对象编程中一条重要的规则是: 公有继承意味着"是一个”。一定要牢牢记住这条规则。
在任何需要基类对象的地方都可以用公有派生类的对象来代替,这条规则称赋值兼容规则。它包括以下情况:
1.派生类的对象可以赋值给基类的对象,这时是把派生类对象中从对应基类中继承来的隐藏对象赋值给基类对象。反过来不行,因为派生类的新成员无值可赋。
2.可以将一个派生类的对象的地址赋给其基类的指针变量,但只能通过这个指针访问派生类中由基类继承来的隐藏对象,不能访问派生类中的新成员。同样也不能反过来做。
3.派生类对象可以初始化基类的引用。引用是别名,但这个别名只能包含派生类对象中的由基类继承来的隐藏对象。
class Object
{
public:
int value;
public:
Object(int x=0):value(x){}
~Object(){}
Print(){}
};
class Base:public Object
{
public:
int num;
public:
Base(int x=0):Object(X),num(x+10){}
Print(){}
};
int main()
{
Base base(10);
Object obja(0);
Object&ob=base;//ok
obja=base;//产生切片现象
Object* op=&base;//ok
//可以将派生类对象赋值给基类对象也包括指针和引用,但不可以反过来
op->Print();//调用Object类的Print方法,因为op是Object类指针
//除虚函数以外,所有的函数绑定规则都是静态绑定规则,即在编译时按照类型进行绑定
//如果想要使其调用Base类的Print方法,就需要对指针进行强转
((Base*)op)->Print();//ok调用Base类的Print方法
//相同的。也可以对引用以此类方法使用
//如果派生类对象base想要调用基类中的Print方法,不需要强转,只需对函数进行作用域声明,因为派生类方法将同名基类方法同名隐藏了
base.Object::Print();//ok调用Object类的Print方法
return 0;
}
3.2构造和析构
class Person
{
int _id;
public:
Person(int id):_id(id)
{cout<<"Create Person "<<this<<endl;}
~Person()
{cout<<"Destroy Person "<<this<<endl;}
};
class Trans
{
int c_num
public:
Trans(int num):c_num(num)
{cout<<"Create Trans "<<this<<endl;}
~Trans()
{cout<<"Destroy Trans "<<this<<endl;}
};
class Student:public Person
{
int _s_id;
Trans table;
public:
Student(int id,int s,int n):_s_id(s),table(n),Person(id)
{cout<<"Create Student "<<this<<endl;}
~Student()
{cout<<"Destroy Student "<<this<<endl;}
}
int main()
{
Student stud(90010,202201,23);
//先构建基类无名对象Person,在按照设计顺序依次进行构建_s_id,table
return 0;
}
当基类中存在拷贝构造、赋值运算符重载函数而派生类中没有时,系统会合成默认的上述函数,并且能正常运行。但如果自己在派生类中给出拷贝构造函数,但未明确调用基类拷贝构造,在运行时系统将会自己调用默认的基类构造函数,这样就会导致数据拷贝存在错误。同样的,对于赋值运算符重载函数,如果在派生类中没有明确调用基类的赋值函数,则派生类对象基类属性的值不会发生改变。
3.3面试题
1、设计一个类型,不能在外部环境中创建该类的对象
方法:将构造函数设计为私有、删除拷贝构造和赋值重载函数
class Object
{
private:
int value;
private:
Object(int x=0):value(x){}
Object(const Object& ob)=delete;//C++11
Object& operator=(const Object& ob)=delete;
public:
~Object(){}
};
int main()
{
Object obj;//error
return 0;
}
2、设计一个不能被继承的类
方法一:将该类的构造函数设置为私有
class Object final
{
private:
int value;
private:
Object(int x=0):value(x){}
public:
~Object(){}
};
class Base:public Object
{};
int main()
{
Base base;//err,派生类无法调用基类的构造函数创建对象
return 0;
}
方法二:在该类名后加上关键字 final
class Object final
{
private:
int value;
public:
Object(int x=0):value(x){}
public:
~Object(){}
};
class Base:public Object//error
{};
int main()
{
Base base;//err
return 0;
}
3、设计一个不能被继承的类,但可以在外部环境创建该类型的对象
方法:在该类名后加上关键字 final
class Object final
{
private:
int value;
public:
Object(int x=0):value(x){}
Object(const Object& ob)=default;//设置为默认函数
Object& operator=(const Object& ob)=default;
~Object(){}
};
class Base:public Object//error
{};
int main()
{
Object obj;//ok
Base base;//err
return 0;
}
4、设计一个能被继承的类,但不能在外部环境创建该类型的对象
方法:将构造、拷贝构造、赋值函数设置为保护
class Object final
{
private:
int value;
protected:
Object(int x=0):value(x){}
Object(const Object& ob)=default;//设置为默认函数
Object& operator=(const Object& ob)=default;
~Object(){}
};
class Base:public Object//ok
{};
int main()
{
Base base;//ok
Object obj;//err
return 0;
}
3.4公有继承的优缺点
优点: 支持扩展,通过继承父类,可以设计较为复杂的系统,体现了由简单到复杂的认识过程。易于修改被复用的代码。 缺点: 代码白盒复用,父类的实现细节暴露给子类,破坏了封装性.当父类的实现代码修改时,可能使得子类也不得不修改,增加维护难度了类缺乏独立性,依赖于父类,耦合度较高不支持动态拓展,在编译期就决定了父类。