继承的子类可以叫叫做子类,派生类
被继承的就叫做父类,基类
那么继承的作用是什么呢,它是使代码复用的手段,在继承原有类的特性下进行扩展,这也说明了子类逃是没有扩展的话就没有意义了,比如父类可以是人,定义了一个人特有的特性,比如年龄性别等,子类可以是学生,职员,警察等,在人的特性下进行扩充
首先来个父类
class Person {
public:
void SetPerson(const string &name, const string& gender, int age, double money) {
_name = name;
_gender = gender;
_age = age;
_money = money;
}
public:
string _name;
string _gender;
protected:
int _age;
private:
double _money;
};
再来个学生子类
class Student :public Person{
public:
void SetStudent(int stuID) {
_stuID = stuID;
}
void Study() {
cout << "study hard" << endl;
}
private:
int _stuID;
};
在上面可看出class Student :public Person
使用冒号可以选择继承的类,因为class的默认继承方式是私有private的,但是我们定义成共有的public
cout << sizeof(Student) << endl;
打印学生类的大小,发现是72,说明继承了父类的成员
那么接下来来看看继承权限的不同对子类对父类访问的影响
首先
来个基类
class Base {
public:
void SetBase(int pub, int pro, int pri) {
_pub = pub;
_pro = pro;
_pri = pri;
}
void PrintBaseInfo() {
cout << _pub << " " << _pro << " " << _pri << endl;
}
public:
int _pub;
protected:
int _pro;//子类可以访问,类外不可以
private:
int _pri;
};
在来个继承类
class Derived :public Base {
public:
void SetDerivedInfo(int pub, int pro, int pri,int d) {
_pub = pub;
_pro = pro;
//_pri = pri;
}
private:
int _d;
};
在主函数中,只有_pub可以被子类调用,那么在子类中可以访问的有哪些呢
_pub在子类中的权限:public
_pro在子类得权限:protected
_pri在基类中权限:private,在子类中不能被访问
注意的是,_pri在基类中是私有的,子类中不能访问,但是确实继承到了子类中
接下来看protected继承
class Derived :protected Base {//保护继承
public:
void SetDerivedInfo(int pub, int pro, int pri, int d) {
_pub = pub;
_pro = pro;
//_pri = pri;
}
private:
int _d;
};
在子类中,
_pub在子类:protected
_Pro在子类:protected
_pri在基类中权限:private
最后来看private继承
class Derived :private Base {//私有继承
public:
void SetDerivedInfo(int pub, int pro, int pri, int d) {
_pub = pub;//_pub在子类:private
_pro = pro;//_Pro在子类:private
//_pri = pri;//_pri在基类中权限:private
}
private:
int _d;
};
如注释,都为私有
还有一点
class默认继承方式 私有
struct默认继承方式 公有
接下来看:基类和派生类对象赋值转化
首先前提是公有继承
那么首先给出结论
1.派生类的对象可以直接赋值给基类对象,反之不然
这是为什么呢,就要从类的对象模型中看了
给出很简单的父类和子类
class Base {
public:
void SetBase(int b) {
_b = b;
}
protected:
int _b;
};
class Derived :public Base{
public:
void SetDerived(int b, int d) {
_b = b;
_d = d;
}
protected:
int _d;
};
在主函数中
d.SetBase(10);
可以直接给基类对象赋值,把父类的对象模型想象成一个正方形,里面有自己的变量b
把子类的对象模型想象成两个正方形叠起来,上面是继承自父类的b,下面是自己的d,子类可以通过从夫父亲那里继承来的b给父类赋值,但是若是父类对象给子类对象赋值,父类就没有子类的d,所以会产生错误
另外,在所有用到基类对象的位置,都可以使用派生类对象替换
2.基类的指针可以指向子类的对象,反之不然
者也可以用对象模型解释,子类的指针有自己特有成员的空间,指向父类会存在越界访问的问题
接下来就是继承的作用域了
基类子类作用域独立
这里涉及到同名隐藏的问题
什么是同名隐藏,就是子类和基类中具有相同名称的成员(变量,函数),派生类访问会优先访问自己的
可以使用 基类::基类成员 显示访问
注意在实际中在继承体系里面最好不要定义同名的成员。
接下来是派生类中默认成员函数的问题
首先:
- 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
假如一个类没有显式定义任何构造函数,编译器将会生成一个无参的默认构造函数
class Base {
protected:
int _b;
};
这个基类只有默认的构造函数
class Derived :public Base {
protected:
int _d;
};
子类也只有默认的,在创建一个派生类对象的时候,编译器调用基类的构造函数初始化基类的那一部分成员,然后在调用子类自己的构造函数
编译器在给派生类生成默认构造函数时,也会在初始化列表显示调用无参的默认基类构造函数
那么要是父类有自己的构造函数呢
class Base {
public:
Base(int b )
: _b(b) {
}
protected:
int _b;
};
那这就要求子类也有自己的构造函数,并且在派生类构造函数的初始化列表阶段显示调用
Derived(int b,int d)
: Base(b)
, _d(d) {
}
像这样.
就像上面所说,编译器在给派生类生成默认构造函数时,也会在初始化列表显示调用无参的默认基类构造函数,这就说明了要是基类有显示定义的构造函数,那么子类也得有显式定义的构造函数,因为在构造子类对象时,调用自己的默认构造函数,自己的默认构造函数调用基类无参构造函数,但是基类用户定义了显式的,会导致程序出错
- 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化
同理,派生类拷贝构造也得先让继承的基类拷贝构造完,这没什么说的
class Base {
public:
Base(int b )
: _b(b) {
cout << "Base(int)" << endl;
}
Base(Base& b)
: _b(b._b)
{
cout << "Base(Base)" << endl;
}
protected:
int _b;
};
class Derived :public Base {
public:
Derived(int b,int d)
: Base(b)
, _d(d) {
cout << "Derived(int)" << endl;
}
Derived(Derived &d)
:Base(d)//基类的拷贝构造函数
,_d(d._d)
protected:
int _d;
};
- 派生类的operator=必须要调用基类的operator=完成基类的复制
和上面道理一样,派生类运算符重载也得先让继承的运算符重载完
class Base {
public:
Base(int b )
: _b(b) {
cout << "Base(int)" << endl;
}
Base(Base& b)
: _b(b._b)
{
cout << "Base(Base)" << endl;
}
Base& operator=(const Base& b) {
if (this != &b) {
_b = b._b;
}
return *this;
}
protected:
int _b;
};
Derived& operator=(const Derived& d) {
if (this != &d) {
Base::operator=(d);
_d = d._d;
}
return *this;
}
- 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序