面向对象的三大特征:封装、继承和派生。
一、继承和派生
继承机制:是类型层级结构设计实现代码复用的重要手段。
派生:保持原有类特性的基础上进行扩展,增加新属性和方法 ,从而产生新的类型。
在面向对象程序设计中,继承和派生是构造出新类型的过程。呈现类型设计的层次结构,体现了程序设计人员对现实世界由简单到复杂的认识过程。
二、继承的概念和定义
继承的层次结构:C++通过类派生的机制来支持继承。被继承的类称为基类或超类,新产生的类为派生类或子类。基类和派生类的集合称作继承层次结构。
设计形式为:
class 派生类名: 访问限定符 基类名1,访问限定符 基类名2...
{
private:
public:
};
(如果没有访问限定符默认私有继承)
公有继承是“是一个”的意思
派生反映了事物之间的联系,事物的共性和特性之间的关系。派生与独立设计若干相关的类,前者 工作量少,重复的部分可以从基类继承来,不需要单独编程。继承是类型设计层面的复用。
注意:
1、构造函数和析构函数不能被继承。我们所创建派生类的对象,虽然和基类的对象有相同之处,但仍然是 不同的对象。所以适用于基类的不可能完全满足派生类对象,所以构造和析构不可以被继承。
继承方式,也称为访问控制,是对基类成员进一步的限制。继承方式也是三种:
公有方式(是一个)
保护方式、私有方式(组合)
保护在继承相当于公有,在访问相当于私有。
2.1对象模型与继承关系
不论采取何种继承方式,基类中的所有数据成员都继承到派生类。
2.2继承关系中的访问权限
2.2.1单一继承
这个代码中表示,自己可以访问自己的公有私有保护成员。
class person
{
private:int pa;
protected:int pb;
public:int pc;
};
class Student :public person
{
private:
int ta;
protected:
int tb;
public:
int tc;
void func()
{
ta = 1;
tb = 2;
tc = 3;
}
};
但是,公有继承不可以访问基类中的私有数据成员。
私有继承也不可以访问基类中的私有数据成员,但是可以访问公有和保护。
无论是什么继承,都不可以访问私有数据成员,都可以访问公有和保护成员。
但是在外部调用的时候,私有继承的数据成员所有都不可以被访问。
总结:成员方法和外部调用是两种完全不同的方式。
私有继承和公有继承相当于把继承来的数据成员变成本类的私有成员或公有成员。
成员函数当然可以调用自己的私有数据成员,但是外部函数对类中的私有和保护是不可见的。
无论采取何种继承方式,派生类对象的成员方法都可以去访问基类对象中的保护和公有属性。
2.2.2多重继承
class person
{
private:int pa;
protected:int pb;
public:int pc;
};
class Student :public person
{
private:
int ta;
protected:
int tb;
public:
int tc;
};
class XiaoStudent :public Student
{
private:
int xa;
protected:
int xb;
public:
int xc;
};
继承关系:
xs对象一共有四个成员:xa,xb,xc和继承来的Student。
由于都是公有继承,所以在外部访问的时候,所有公有的数据成员都是可以访问的。
在成员函数访问的时候,由于XIaoStudent 公有继承STudent,所以Student公有和保护可以访问,同样的Student公有继承了person,所以person 中的公有和保护也可以访问。
如果Student采取私有继承,那么在XiaoStudent成员函数中,person中的成员相当于Student的私有成员,所以不可以访问。
三、同名隐藏
3.1成员属性同名隐藏
class person
{
private:int pa;
protected:int pb;
public:int pc;
};
class Student :public person
{
private:
int ta,pc;
protected:
int tb;
public:
int tc;
void func()
{
pc = 4;
}
};
3.2 成员函数的同名隐藏
class person
{
private:int pa;
protected:int pb;
public:int pc;
};
class Student :public person
{
private:
int ta;
protected:
int tb;
public:
int tc,pc;
void func()
{
pc = 4;
}
};
int main()
{
Student stud;
stud.pc = 100;
}
这个代码中,优先访问的是自己类中的成员:
我们可不可以 进行这样子的区分把它当成函数的重载呢?
答案是不行的。因为重载的前提条件是在一个域里面。
在C++11中我们可以利用作用域解析符声明还有一个func函数在Object中来解决这个问题
注意 :要分清这几个概念,同名隐藏是根据非虚函数继承来说的,同名覆盖是根据 虚函数来讲的,函数重载要求在同一个域中。
四、赋值兼容规则
赋值兼容规则只适用于公有继承。
c++面向对象编程中一条重要的规则是:公有继承意味着“是一个一定要牢牢记住这条规则。
在任何需要基类对象的地方都可以用公有派生类的对象来代替,这条规则称赋值兼容规则。它包括以下情况:
1.派生类的对象可以赋值给基类的对象,这时是把派生类对象中从对应基类中继承来的隐藏对象赋值给基类对象。反过来不行,因为派生类的新成员无值可赋。
class Person
{
int p_id;
public:
Person(int id = 1) :p_id(id) {}
};
class Student :public Person
{
int s_id;
public:
Student(int pid, int sid) :Person(pid), s_id(sid) {}
};
int main()
{
Person per(10);
Student stud(100, 200);
per = stud;
stud = per;
return 0;
}
可以Student赋值给person,但反过来不可以。
这里发生了切片现象,将Student对象里面的person切一半下来赋值。
2.可以将一个派生类的对象的地址赋给其基类的指针变量,但只能通过这个指针访问派生类中由基类继承来的不具名对象,不能访问派生类中的新成员。同样也不能反过来做。
class Person
{
int p_id;
public:
Person(int id = 1) :p_id(id) {}
void Down()
{
cout << "Persin" << endl;
}
};
class Student :public Person
{
int s_id;
public:
Student(int pid, int sid) :Person(pid), s_id(sid) {}
void study()
{
cout << "student" << endl;
}
};
int main()
{
Person* p = nullptr;
Person per(10);
Student stud(100, 200);
p = &per;
p->Down();
p = &stud;
p->Down();
return 0;
}
person类的指针,就算把stud的地址给他,也只能调动person 中的方法,而不能调动Student,因为Student中的其他方法对它是向下不可见的。
3.派生类对象可以初始化基类的引用。引用是别名,但这个别名只能包含派生类对象中的由基类继承来的隐藏对象。