清远建设工程招投标网站,如何调整wordpress页面的顺序,wordpress右侧菜单,永久免费wap建站继承 一、初识继承概念“登场”语法格式 继承方式九种继承方式组合小结#xff08;对九种组合解释#xff09; 二、继承的特性赋值转换 一一 切片 / 切割作用域 一一 隐藏 / 重定义 三、派生类的默认成员函数派生类的默认成员函数1. 构造函数2. 拷贝构造3. 赋值运算符重载4. … 继承 一、初识继承概念“登场”语法格式 继承方式九种继承方式组合小结对九种组合解释 二、继承的特性赋值转换 一一 切片 / 切割作用域 一一 隐藏 / 重定义 三、派生类的默认成员函数派生类的默认成员函数1. 构造函数2. 拷贝构造3. 赋值运算符重载4. 析构函数 四、延伸知识1. 继承与友元2. 继承与静态成员 五、单继承和多继承单继承多继承菱形继承菱形虚拟继承语法原理 总结拓展知识组合 一、初识继承
概念
继承保持原有类特性的基础上进行扩展增加功能产生新的类。新的类就叫做派生类子类原有类就叫做基类父类。 继承的作用继承机制是面向对象程序设计使代码可以复用的最重要的手段继承是类设计层次的复用呈现了面向对象程序设计的层次结构。
“登场”
class Person
{
public:void Print(){cout name: _name endl;cout age: _age endl;cout endl;}protected:string _name 张三;int _age 18;
};//继承后父类的Person成员成员函数 成员变量都会成为子类一部分
class Student : public Person
{
protected:int _stuId;
};int main()
{Student s;s.Print();return 0;
}结论代码体现出Student对Person的继承复用
语法格式 继承方式
继承方式和访问限定符
九种继承方式组合
C中的继承方式和访问限定符组合形成了九种情况的继承结果
类成员/继承方式public继承protected继承private继承基类的public成员派生类的public成员派生类的protected成员派生类的private成员基类的protected成员派生类的protected成员派生类的protected成员派生类的private成员基类的private成员在派生类中不可见在派生类中不可见在派生类中不可见
eg:实例演示三种继承关系下基类成员的各类型成员访问关系的变化
class Person
{
public:void Print(){cout 名字 _name endl;}
protected:string _name 张三;
private:int _age 18;
};//class Student : protected Person
//class Student : private Person
class Student : public Person
{
protected:int _stuId 111;
};int main()
{Student s;s.Print();return 0;
}小结对九种组合解释
不需要全部记完这两种是最常用的记住即可 在九种组合表中基类的私有成员是不可见的。基类的其他成员在子类的访问方式 Min(成员在基类的访问限定符 继承方式)(public protected private)。 Min:两者的较小者。基类private成员在派生类中什么方式都不可见。不可见基类的私有成员还是被继承到了派生类对象中但是语法上限制派生类对象不管在类里面还是类外面都不能访问基类成员不想在类外直接被访问但要在派生类中能访问就要定义为protected。保护成员限定符是因为继承才出现的使用关键字class时默认的继承方式是private使用struct时默认的继承方式是public。建议显示写实际运用一般都是public继承扩展维护性强 二、继承的特性
赋值转换 一一 切片 / 切割 派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。这个过程叫切片或者切割不会产生临时变量发生赋值兼容就同把派生类中父类那部分切来赋值过去。 原理切片 / 切割
eg证明不会产生临时变量
class Person
{
protected:string _name;string _sex;int _age;
};class Student : public Person
{
public:int _No;
};int main()
{int i 0;//double d i; //errorconst double rd i; //int赋值给double类型的值会产生临时变量所以要const//派生类对象可以直接赋值给基类对象不要const也就证明这个过程没有产生临时变量Student s;Person p s;return 0;
}注意派生类对象赋值给基类的对象或者基类的指针或者基类的引用这个过程称为向上转换。
拓展不作详细介绍 基类的指针和引用可以通过强转赋值给派生类的指针或者引用但基类的指针是指向派生类对象时才安全。-- 这个过程称为向下转换。注意基类对象不能赋值给派生类对象。
eg
class Person
{
//protected:
public:string _name peter;string _sex male;int _age 18;
};class Student : public Person
{
public:int _No 2140104111;
};int main()
{Student s;//1.派生类对象可以赋值给父类对象/指针/引用Person p s;Person* ptrp s;ptrp-_age 21;Person rp s;rp._name 张三;//2.基类对象不能赋值给派生类对象//s p; //errorreturn 0;
}代码分析
注意使用保护继承成员权限会发生变化
Person p s; //就会出现错误这是因为保护继承下派生类的对象只能被派生类或派生类的子类引用而不能被基类引用。 原理 因为非公有派生类私有或保护派生类不能实现基类的全部功能例如在派生类外不能调用基类的公用成员函数访问基类的私有成员。因此只有公有派生类才是基类真正的子类型它完整地继承了基类的功能。
作用域 一一 隐藏 / 重定义 在继承体系中基类和派生类都有独立的作用域子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问子类成员隐藏父类成员这叫做隐藏或者重定义。 在子类成员中可以 基类::基类成员 显示访问。但是指定作用域如果找不到会直接报错不会再去访问别的域。eg:派生类成员成员函数隐藏函数名相同就构成隐藏建议最好不要定义同名成员 拓展访问成员遵循就近原则编译器既定顺序局部域-当前类域-父类域-全局域 eg1成员变量
class Person
{
protected:string _name 张三;int _num 111;
};class Student : public Person
{
public:void Print(){cout 姓名 _name endl;//指定显示访问cout Person::_num: Person::_num endl;//默认访问子类。子类隐藏了父类cout _num: _num endl;}protected:int _num 999;
};int main()
{Student s;s.Print();return 0;
}//output:
//姓名张三
//Person::_num:111
//_num : 999eg2成员函数
//fun不构成重载因为不在同一作用域
//fun构成隐藏成员函数满足函数名相同
class A
{
public:void fun(){cout fun() endl;}
};
class B : public A
{
public:void fun(int i){A::fun();cout fun(int i)- i endl;}
};int main()
{B b;b.fun(1);//b.fun(); //参数不匹配//指定访问b.A::fun();return 0;
}//output:
//fun()
//fun(int i)-1
//fun()三、派生类的默认成员函数
派生类的默认成员函数
演示代码后面会分为四个部分进行拆分讲解
class Person
{
public://如果没有默认构造,必须在派生类的初始化列表显示调用//Person(const char* name)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;
};class Student : public Person
{
public:Student(const char* name, int num):Person(name), _num(num){cout Student() endl;}Student(const Student s):Person(s), _num(s._num){cout Student(const Student s) endl;}Student operator(const Student s){cout Student operator(const Student s) endl;if (this ! s){Person::operator(s);_num s._num;}return *this;}~Student(){cout ~Student() endl;}protected:int _num;
};int main()
{Student s1(jack, 18); //构造函数Student s2(s1); //拷贝构造函数Student s3(rose, 17); s1 s3; //赋值运算符重载return 0;
}构造和析构调用和执行顺序图
1. 构造函数 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认构造则必须在派生类构造的初始化列表阶段显式调用派生类对象初始化先调用基类构造再调派生类构造 eg1有默认构造
class Person
{
public:Person(const char* name 张三):_name(name){cout Person() endl;}
protected:string _name;
};class Student : public Person
{
public:Student(const char* name, int num):_stuId(num){cout Student() endl;}
protected:int _stuId;
};int main()
{Student s(jack, 18);return 0;
}代码F11逐语句执行过程
eg2没有默认构造 基类没有默认构造在派生类必须显示调用Person先初始化然后是_stuId。基类先声明所以先初始化Person
//没有默认构造
class Person
{
public:Person(const char* name):_name(name){cout Person() endl;}
protected:string _name;
};class Student : public Person
{
public:Student(const char* name, int num):Person(name) //在初始化列表调用基类默认构造。如同定义匿名对象, _stuId(num){cout Student() endl;}
protected:int _stuId;
};int main()
{Student s(jack, 18);return 0;
}代码F11逐语句执行过程
2. 拷贝构造 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化 演示代码的拷贝构造部分
3. 赋值运算符重载 派生类的operator必须调用基类的operator完成基类的复制 4. 析构函数 派生类的析构会在调用完成后自动调用基类的析构函数清理基类成员。原因为了保证派生类对象先清理派生类成员再清理基类成员的顺序因为后续的一些场景析构函数要构成重写重写的条件之一就是函数名相同。所以编译器对析构函数名进行特殊处理处理成destructor()。所以父类析构函数不virtual子类析构函数和父类析构函数构成隐藏关系。 四、延伸知识
1. 继承与友元 友元关系不能继承所以基类的友元不能访问子类私有成员和保护成员 eg:
class Student; //先声明因为在Person中引用了Student对象
class Person
{friend void Dispaly(const Person p, const Student s);
protected:string _name 张三;
};class Student : public Person
{
protected:int _stuId 0;
};
void Dispaly(const Person p, const Student s)
{cout s._name endl; //okcout p._name endl; //okcout s._stuId endl; //error
}int main()
{Dispaly(Person(), Student());return 0;
}注意如果想要在Display()中调用s._stuId要在Student类中也加上友元
2. 继承与静态成员 基类定义了static静态成员则整个继承体系里面只有一个这样的成员 class Person
{
public:Person(){_count;}
protected:string _name;
public:static int _count;
};
int Person::_count 0;
class Student : public Person
{
protected:int stuId;
};
class Graduate : public Student
{
protected:string _seminarCourse;
};int main()
{Student s1;Student s2;Student s3;Graduate s4;cout 人数 Person::_count endl;Graduate::_count 0;cout 人数 Person::_count endl;return 0;
}//output:
//人数4
//人数0注意静态成员属于父类和派生类在派生类不会单独再拷贝一份继承的是使用权。eg:上面的代码使用的始终都是一个_count
五、单继承和多继承
单继承
单继承一个子类只有一个直接父类时称这个关系为单继承
多继承 菱形继承
菱形继承多继承的一种特殊情况。 菱形继承的问题从下面的对象成员模型构造可以看出菱形继承由数据冗余浪费空间和二义性不知道访问谁 问题在Assistant的对象中Person成员有两份 菱形继承代码
class Person
{
public:string _name;
};class Student : public Person
{
protected:int _stuId;
};class Teacher : public Person
{
protected:int _workId;
};class Assistant : public Student, public Teacher
{
protected:string _course;
};int main()
{Assistant a;//这样会有二义性问题无法明确访问的哪一个//a._name peter; //error//显示指定访问那个父类的成员可以解决二义性问题但是数据冗余问题无法解决a.Student::_name xxx;a.Teacher::_name yyy;return 0;
}对于菱形继承解决不了的问题出现了虚拟继承。
菱形虚拟继承
虚拟继承可以解决菱形继承的二义性和数据冗余问题。
语法
如上面菱形继承代码的继承关系在Student和Teacher继承Person时使用虚拟继承。 eg:
原理
借用简化的菱形继承体系通过内存窗口观察对象成员的模型
菱形继承
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;
}菱形虚拟继承
class A
{
public:int _a;
};class B : virtual public A
{
public:int _b;
};class C : virtual 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;
}注意 D中为什么B和C部分要找属于自己的A 解释 菱形虚拟继承的原理解释
总结 虚拟继承的缺陷虚拟继承会增加程序的复杂性因为派生类需要特别处理虚基类的初始化和访问。虚拟继承还可能导致一些性能上的损失因为派生类需要额外的指针来访问虚基类。-- 不建议使用菱形继承 继承和组合 public继承是一种is-a的关系。eg植物和花组合是一种has-a的关系。 eg轮胎和车 拓展知识组合 **优先使用对象组合而不是类继承**组合耦合度低代码维护性好 白盒测试知道底层黑盒测试不知道底层 继承通过生成派生类的复用称为白箱复用(white-box reuse)。白箱相对可视性而言在继承方式中基类内部细节对子类可见。继承一定程度破坏了基类的封装。耦合度高基类的改变极大的影响派生类两者关系紧密组合新的更复杂的功能可以通过组装或组合对象获得。被组合对象具有良好定义的接口。这种复用风格称为黑箱复用(black-box reuse)**对象内部细节不可见。**组合类之间没有很强的依赖关系耦合度低
eg:
//继承
//Car和BMW Car和Benz构成is-a关系
class Car
{
protected:string _color 白色;string _num 陕IT6666;
};class BMW : public Car
{
public:void Drive(){cout 好开 endl;}
};class Benz : public Car
{
public:void Drive(){cout 好坐 endl;}
};//组合
//Tire和Car构成has-a关系
class Tire
{
protected:string _brand Michelin;size_t _size 17;
};class Car
{
protected:string _color 白色;string _num 陕IT6666;Tire _t;
};