企业网站建设合同书盖章页,莲花网,学校网站欣赏中文,常德生活网面向对象的三大特性
封装继承多态
继承的概念和定义 继承的本质就是类层次的复用。 继承的概念继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段.它允许程序员在保持原有类特性的基础上进行扩展#xff0c;增加功能#xff0c;这样产生新的类#xf…面向对象的三大特性
封装继承多态
继承的概念和定义 继承的本质就是类层次的复用。 继承的概念继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段. 它允许程序员在保持原有类特性的基础上进行扩展增加功能这样产生新的类称派生类。 继承呈现了面向对象程序设计的层次结构体现了由简单到复杂的认知过程。 继承是类设计层次的复用 基类(父类) 派生类(子类) 父类就是被继承的类。 子类就是继承的类。 子类复用了父类 class Person
{
public:void Print(){cout name: _name endl;cout age: _age endl;}
protected:string _name tongtong; // 姓名 int _age 15; // 年龄
};class Student : public Person
{
protected:int _stuid; // 学号
};
int main()
{Student s;//如果共有子类可以用父类的函数//私有就不可以s.Print();return 0;
}用上面的定义来说 person就是父类student就是子类子类会复用父类 看打印的结果我们发现虽然studen没有名字和年龄但是却可以打印原因就是继承了 继承关系和访问限定符 class Student : public Person 在定义student的时候其中pubilc就是继承方式。 继承关系和访问限定符都是有三种publicprotectedprivate 所以继承关系和访问限定符有9种组合方式具体如下图。 类成员/继承方式pubilc继承protected继承private继承父类的pubilc成员子类的pubilc成员子类的protected成员子类的private成员父类的protected成员子类的protected成员子类的protected成员子类的private成员父类的private成员在子类中不可见在子类中不可见子类中不可见当在继承的时候可以不写继承关系。 但是class默认的继承方式是privatestruct的继承方式是public。 总结访问方式父类访问限定符和限定方式小的。pubicprotectedprivate. protected和private的区别在父类中这两个访问限定符没有区别都是在类中可以访问在类外不可访问。 但是在子类中他们两个有区别。 protected在子类中可见不可用。 private在子类中不可见不可用。 父类和子类的赋值转换 将子类转换为父类不需要发生类型转换不会产生临时变量天然支持的。 可以这样理解父类是特殊的子类。 int main()
{ //发生隐式类型类型转换double d 1.5;int i d;//必须用const临时变量具有常性const int i2 d;//天然支持的不存在类型转换发生Student s;Person p s;//没有产生临时对象所以不用加constPreson p2 s;Preson* ptr s;return 0;
}如果不理解可以形象的看这个图。 那么问一个问题父能不能给给子 不可以父类的东西比子类的东西少。 继承中的作用域
在继承体系中基类和派生类都有独立的作用域。子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问这种情况叫隐藏也叫重定义。在子类成员函数中可以使用基类::基类成员 显示访问需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。注意在实际中在继承体系里面最好不要定义同名的成员。 问题既然子类和父类有自己的作用域那么能不能有同名变量或同名函数(隐藏/重定义)可以独立的作用域当然可以拥有同名的变量或函数。 就近原则优先访问自己作用域 但是如果想访问同名的其他作用域指定作用域就可以。 做下面这个练习题 class A
{
public:void fun(){cout func() endl;}
};
class B : public A
{
public:void fun(int i){A::fun();cout func(int i)- i endl;}
};
//A:两个fun构成函数重载
//B:两个fun构成隐藏
//C:编译报错
//D:以上说法都不对B 在不同的作用域不可能构成函数重载这两个类也没有问题他们构成隐藏。 有人会想他们同名参数不同还构成隐藏吗 如果是成员函数的隐藏只需要保证函数名相同就可以。不用考虑参数和返回值。 如果想用A的fun怎么用 B().A::fun(); 加上域作用限定符就可以我用的是匿名对象大家也可以不用。 子类的默认成员
先考虑默认成员函数 先给大家一个父类写一个student子类 class Person
{
public:Person(const char* name peter): _name(name){cout Person() endl;}Person(const Person p): _name(p._name){cout Person(const Person p) endl;}Person operator(const Person p){cout Person operator(const Person p) endl;if (this ! p)_name p._name;return *this;}~Person(){cout ~Person() endl;}
protected:string _name; // 姓名
};1、子类的构造函数
父类的成员必须调用父类的构造进行初始化。如果在子类中不写构造和析构函数调用父类的构造函数和析构函数。 父类的成员调用父类的构造函数初始化子类的自己的成员自己初始化 用到方法就是在初始化列表中按照这个方式初始化person(name)调用父类的构造用我们自己传的参数构造。 如果不写person(name)也会调用父类的构造函数用缺省参数构造。 我们可以这样理解将父类当成子类的成员。 class Student : public Person
{Student(const char* name,int num):Person(name)//规定这么写调用父类的构造函数初始化//如果不写person(name)也可以调用父类的构造函数。,_num(num){}
protected:int _num;
};
int main()
{Student s(tongtong,20);return 0;
}2、子类的拷贝构造 如果不写子类的拷贝构造会调用父类的默认的拷贝构造。 class Student : public Person
{
public:Student(const Student s){}
protected:int _num;
};
int main()
{Student s(tongtong,20);Student s2(s);return 0;
}对于上面这种写了拷贝构造但不做处理编译器不会调用父类的拷贝构造。只进行了一次构造还是因为传参的时候调用。 初始化父类就传父类的拷贝构造 class Student : public Person
{
public:Student(const Student s)//直接将子类切片。调用父类的拷贝构造:Person(s)_num(s._num){}
protected:int _num;
};3、赋值 重载 Student operator (const Student s){if (this ! s){Person::operator (s);_num s._num;}return *this;}4、析构函数 //析构函数会被处理为Destructor构成隐藏所以用作用域~Student(){Person::~Person();}int main()
{Student s1(tongtong, 20);
}如果这样我们的析构会调用三回本来只需要调用两回多调用了一会的原因是Person::~Person();这个多调用了一会。 析构函数不需要我们自己显示调用他自己调用父类的析构和子类的析构。 所以为了保证析构的顺序先析构子再析构父。 如果我们自己操作不能保证这个顺序 所以编译器帮我们做子类析构函数完成时会自动调用父类析构函数保证先析构子再析构父。 因为要保证顺序 构造的顺序肯定是先构造父类在构造子类。 四个子类的默认函数调用除了析构函数顺序都是先父后子。 5、继承和友元
友元关系不能继承。 下面代码有错误因为友元函数不能继承所以我们再有友元函数中不能访问子类的成员没有继承了父类的友元。 class Student;
class Person
{
public:friend void Display(const Person p, const Student s);
protected:string _name; // 姓名
};
class Student : public Person
{
protected:int _stuNum; // 学号
};
void Display(const Person p, const Student s)
{cout p._name endl;//错误的代码下一句不可访问cout s._stuNum endl;
}
void main()
{Person p;Student s;Display(p, s);
}但是如果想要访问就在子类加上友元函数的声明就可以了 class Student;
class Person
{
public:friend void Display(const Person p, const Student s);
protected:string _name; // 姓名
};
class Student : public Person
{//加上友元就可以使用了。friend void Display(const Person p, const Student s);
protected:int _stuNum; // 学号
};
void Display(const Person p, const Student s)
{cout p._name endl;cout s._stuNum endl;
}
void main()
{Person p;Student s;Display(p, s);
}6、继承与静态成员
父类定义了static静态成员则整个继承体系里面只有一个这样的成员。无论派生出多少个子类都只有一个static成员实例
.7、复杂的菱形继承及菱形虚拟继承
一、单继承
一个子类只有一个直接父类时称这个继承关系为单继承
二、多继承
一个子类有两个或以上直接父类时称这个继承关系为多继承
三、多继承会引起菱形继承
菱形继承是多继承的一种特殊情况。 菱形继承的问题从下面的对象成员模型构造可以看出菱形继承有数据冗余和二义性的问题。
使用虚继承解决数据冗余和二义性
8、虚继承继承
现在有一个菱形继承(A B C D)
class A
{
public:int _a;
};class B : public A
{
public:int _b;
};
class C : public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
在主函数中测试不能再监视窗口观察它已经经过处理我们要在内存窗口观察。
int main()
{D d;d.B::_a 1;d.C::_a 2;d._b 3;d._c 4;d._d 5;return 0;
}上面没有虚继承就是单纯的多继承会有数据冗余和二义性先观察一下 使用虚继承 内存窗口 将公共继承的A放到一块空间 B通过第一个地址找到偏移量20向下走5个地址从而找到A就可以改变A。 C通过第一个地址找到偏移量12向下走3个地址从而找到A就可以改变A。 这样就只有一块A解决数据冗余和二义性 9、继承和组合 public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。 组合是一种has-a的关系。假设B组合了A每个B对象中都有一个A对象。