网站关于我们模板,青海seo关键词排名优化工具,网站管家,门户网站建设文案1. 继承的概念及定义
1.1 继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要手段#xff0c;他允许我们在保证原有类的特性基础上还进行扩展#xff0c;通过继承产生的类叫做派生类#xff08;子类#xff09;#xff0c;被继承的类叫做基类他允许我们在保证原有类的特性基础上还进行扩展通过继承产生的类叫做派生类子类被继承的类叫做基类父类。继承呈现了面向对象程序设计的层次结构。和我们之前Date类那里重载运算符的那块有异曲同工之妙重载运算符那里的实现是函数的复用而继承则是类设计层次的复用。
ok 当我们想创建两个类一个是学生一个是教师他们都有一些共同的特点例如名字、地址、电话号码、年龄等等的时候但他们也有不同的一些特点例如学生有学号老师有职称等等我们就可以用继承来实现创建一个父类Person储存他们的共同特点然后再对父类进行继承出新的子类即可
语法为
class Person{};
class Teacher: public: Person{};
class Student: public: Person{};
当我们想实现一个Student类和Teacher类的时候会有一些相同的信息如下代码所示
#includeiostream
using namespace std;class Student
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){// ...}// 学习void study(){// ...}
protected:string _name peter; // 姓名string _address; // 地址string _tel; // 电话int _age 18; // 年龄int _stuid; // 学号
};
class Teacher
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){// ...}// 授课void teaching(){//...}
protected:string _name 张三; // 姓名int _age 18; // 年龄string _address; // 地址string _tel; // 电话string _title; // 职称
};
int main()
{return 0;
}
那么我们就可以用继承进行解决代码如下
#includeiostream
using namespace std;class Person
{
public:// 进⼊校园/图书馆/实验室刷⼆维码等⾝份认证void identity(){cout void identity() _name endl;}
protected:string _name 张三; // 姓名string _address; // 地址string _tel; // 电话int _age 18; // 年龄
};class Student : public Person
{
public:// 学习void study(){// ...}
protected:int _stuid; // 学号
};class Teacher : public Person
{
public:// 授课void teaching(){//...}
protected:string title; // 职称
};
int main()
{Student s;Teacher t;s.identity();t.identity();return 0;
}
这样Student 和Teacher都有Person类的成员例如name、address、tel而有不需要我们再过多写
首先又在这里提一嘴就是成员变量被protected修饰的话就代表他的子类或者内部类可以使用而在类外或者派生类子类外都不可以使用private则是子类和类外都不能使用 1.2 继承的定义
定义的格式
如下图可见 1. 如果基类父类的成员是private的话在派生类子类不可以被访问但是这些成员也一样会被继承到派生类去只是不能访问而已。
2. 基类父类的成员是protected的话在派生类子类中可以被访问。而protected关键词其实就是因为继承才出现的。
3. 继承方式一般是按最小级别的来级别如public protected private 如果说继承方式是public的话基类成员是private修饰的那么基类的成员访问限定符按最小级别来即private如果说继承方式是private的话无论基类成员是public还是protected基类的访问限定符都为private。
4. 使用关键字class时默认的继承方式是private而struct时默认方式是public但最好显示写出来。
5. 一般都是用public便于维护 1.3 继承类模板
顾名思义继承一个类模板如下代码和注释来看
#includeiostream
#includevector
#includelist
#includedeque
#define Container std::vector
using namespace std;namespace lwt
{//templateclass T//class vector//{};// stack和vector的关系既符合is-a也符合has-a//is-a是说stack底层实现就是vector//has-a是说包含关系即vector可以被stack继承因为stack的特性包含vector的特性templateclass Tclass stack : public std::vectorT{public://如果说我们直接调用的push_back(x);的话会出错因为按需实例化的时候//push_back在本类里面找不到然后就会进到父类里面找但都没找到所以我们要指定一下//类域即vectorT::void push(const T x){// 基类是类模板时需要指定⼀下类域// 否则编译报错:error C3861: “push_back”: 找不到标识符// 因为stackint实例化时也实例化vectorint了// 但是模版是按需实例化push_back等成员函数未实例化所以找不到//push_back(x);vectorT::push_back(x);}void pop(){vectorT::pop_back();}const T top(){return vectorT::back();}bool empty(){return vectorT::empty();}};//下面是上面的一个新用法//我们可以使用宏在头部定义一个Container放置std::vectortemplateclass Tclass stack : public ContainerT{public:void push(const T x){vectorT::push_back(x);}void pop(){vectorT::pop_back();}const T top(){return vectorT::back();}bool empty(){return vectorT::empty();}};
这里再来讲一下里面为什么要用vectorT::push_back因为模板是遵守按需实例化的规则来的class stack : public std::vectorT如果我们我们实例化stackint那就只实例化了vectorint编译器不知道我们还要实例化里面的功能这里只是单纯实例化了一个vectorint对象所以我们需要指定一下vectorT域内的push_back即可 2. 基类和派生类之间的转换
public继承的派生类子类对象可以给基类父类的对象、指针和引用赋值。但不是把派生类子类对象的所有成员都赋值给基类父类对象而是把原先属于基类部分的成员赋值给基类对象这个过程称为切割或者切片很好理解就是把派生类内不是从基类继承来的成员切割掉基类的对象不能赋值给派生类的对象但是如果那个基类的对象的指针原先就是指向派生类的指针的话那还是可以通过强制类型转换赋值给派生类对象的但是这里使用到一个dynamic_cast这里只是演示一下如下代码所示 #includeiostreamusing namespace std;
class Person
{
protected://多态后面会说virtual void func(){}
public:Person(const string name, const string sex, int age):_name(name),_sex(sex),_age(age){cout Person(const string name, const string sex, int age) endl;cout name: _name endl;cout sex: _sex endl;cout age: _age endl;}string _name; // 姓名string _sex; // 性别int _age; // 年龄
};class Student :public Person
{
public:Student(const string namelwt, const string sexman, int age20):Person(name, sex, age){}int _No; //学号
};int main()
{Student sobj;//子类对象可以赋值给父类对象/指针/引用//这样就是上面所说的切割即会把非父类对象的部分切割然后//把是父类对象的部分赋值给父类Person pobj sobj;Person* pp sobj;Person rp sobj;//基类的指针或者引⽤可以通过强制类型转换赋值给派⽣类的指针或者引⽤。但是必须是基类的指针//是指向派⽣类对象时才是安全的。这⾥基类如果是多态类型可以使⽤RTTI(Run - Time Type//Information)的dynamic_cast 来进⾏识别后进⾏安全转换。ps这个我们后⾯类型转换章节再//单独专⻔讲解这⾥先提⼀下Student* sp1 dynamic_castStudent*(pp);cout sp1 endl;cout pp endl;pp pobj;Student* sobj2 dynamic_castStudent*(pp);//2.父类对象不能赋值给子类对象这里会编译报错//sobj (Student)pobj;return 0;
} 运行结果为 这里需要提一点的就是在派生类想要初始化基类的话需要调用基类的构造函数不可以直接使用基类的成员变量在派生类的初始化链表初始化 3. 继承的作用域
3.1 隐藏
1. 在继承体系中基类和派生类都有独立的作用域
2. 派生类和基类中如果有同名的成员的话那么就会屏蔽掉基类的成员访问这种就称作隐藏那如果我们在派生类想要访问基类中被隐藏的成员我们就可以通过指定访问域进行访问例如基类::基类成员即可
3. 如果是成员函数的话构成隐藏的条件只需要一个即函数名相同即使他们的参数不相同也不会构成重载照样构成隐藏如下代码所示
#includeiostream
using namespace std;class Person
{
public:void Fun(){cout PersonFunc() endl;}void test(int a){cout Persontest() endl;}
protected:string _name 小李子; // 姓名int _num 111; // 身份证号
};//隐藏即如果基类和派生类有同名的成员的话
//基类的成员就会被隐藏只是用派生类的成员
//如果我们想使用基类里面的话我们就需要指定类域
//即可//如果说有函数名相同的则会直接隐藏基类的函数不管他们参数是否一样
//不会构成重载
//只会被隐藏掉class Student : public Person
{
public:void Print(){cout _num endl;cout Person::_num endl;}void Func(){cout StudentFunc() endl;}void test(){cout Studenttest() endl;}protected:int _num 999; // 学号
};int main()
{Student s1;s1.Print();s1.Func();s1.Person::Fun();//报错//s1.test(1);s1.Person::test(1);return 0;
} 运行结果为 看完这个知识点我们直接就来两道面试题如下图 #includeiostream
using namespace std;
class A
{
public:void fun(){cout func() endl;}
};
class B : public A
{
public:void fun(int i){cout func(int i) i endl;}
};
int main()
{B b;b.fun(10);b.fun();return 0;
};
答案AA
解析首先上面说了只要函数名相同就构成隐藏不管他参数相同还是不同都不会构成重载所以第一题选A。
第二题由于是构成隐藏关系那我们想访问那个无参的func的话就必须显式调用即b.A::func()指定一定访问域不然就报错 4. 派生类的默认成员函数
默认的意思就是指我们不写编译器会帮我们自动生成一个
1. 基类如果没有默认构造的话那么在派生类的构造函数里需要调用基类的构造函数对基类的成员进行初始化调用基类的构造函数就类似于匿名对象一样但这里不叫匿名对象如下代码所示
#includeiostream
using namespace std;class Person
{
public:Person(const string name ):_name(name){cout Person() endl;}protected:string _name;
};class Student:public Person
{
public:Student(const string name, int num):Person(name),_num(num){cout Student() endl;}protected:int _num;
};int main()
{Student s1(lwt, 12);return 0;
} 运行结果为 2. 拷贝构造的也是一样派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的初始化
#includeiostream
using namespace std;class Person
{
public:Person(const string name ):_name(name){cout Person() endl;}Person(const Person p):_name(p._name){cout Person拷贝构造调用 endl;}protected:string _name;
};class Student:public Person
{
public:Student(const string name, int num):Person(name),_num(num){cout Student() endl;}// 严格说Student拷贝构造默认生成的就够用了// 如果有需要深拷贝的资源才需要自己实现Student(const Student s1):Person(s1),_num(s1._num){cout Student拷贝构造调用 endl;}protected:int _num;
};int main()
{Student s1(lwt, 12);Student s2(s1);return 0;
} 运行结果为 3. 派生类的operator必须调用基类的operator完成基类的赋值。但需要注意的是派生类的operator隐藏了基类的operator所以要显示调用基类的operator。
#includeiostream
using namespace std;class Person
{
public:Person(const string name ):_name(name){cout Person() endl;}Person(const Person p):_name(p._name){cout Person拷贝构造调用 endl;}Person operator(const Person p){cout Person operator(const Person p) endl;if (this ! p){_name p._name;}return *this;}
protected:string _name;
};class Student:public Person
{
public:Student(const string name, int num):Person(name),_num(num){cout Student() endl;}// 严格说Student拷贝构造默认生成的就够用了// 如果有需要深拷贝的资源才需要自己实现Student(const Student s1):Person(s1),_num(s1._num){cout Student拷贝构造调用 endl;}void print(){cout _num endl;cout _name endl;}Student operator(const Student s1){if (this ! s1){//父类和子类的operator构成隐藏关系Person::operator(s1);_num s1._num;}return *this;}protected:int _num;
};int main()
{Student s1(lwt, 12);Student s2(s1);Student s3(lll, 15);cout endl;s2.print();cout endl;s2 s3;s2.print();return 0;
} 运行结果为
有一个很巧妙的点就在于你看在Student内重载operator那里的Person::operator(s1)如果我们没有学到基类和派生类之间的转换的话我们不懂为什么Person内重载的operator的参数是Person而在这里我们传了类型为Student的s1进去这里就造成了切割或切片。 4. 派生类对象初始化先调用基类构造再调用派生类构造
#includeiostream
using namespace std;class Person
{
public:Person(const string name ):_name(name){cout Person() endl;}Person(const Person p):_name(p._name){cout Person拷贝构造调用 endl;}protected:string _name;
};class Student:public Person
{
public:Student(const string name, int num):Person(name),_num(num){cout Student() endl;}// 严格说Student拷贝构造默认生成的就够用了// 如果有需要深拷贝的资源才需要自己实现Student(const Student s1):Person(s1),_num(s1._num){cout Student拷贝构造调用 endl;}void print(){cout _num endl;cout _name endl;}protected:int _num;
};int main()
{Student s1(lwt, 12);return 0;
} 运行结果为 5. 析构函数的调用如果说这里面没有动态申请空间的话默认的析构函数也够用了如果动态申请了的话我们就需要写析构函数清理申请的内存那问题又来了我们不仅要清理派生类里面的空间还要清理基类内的空间那么我们就需要写两个析构函数但是呢问题又来了基类的析构和派生类的析构构成隐藏原因是无论析构函数的名字怎么样最后都会被编译器转为一个名为destructor()的函数所以会导致隐藏那么我们就需要指定一下作用域如下代码所示
#includeiostream
using namespace std;
class Person
{
public:Person(const char* name 胡图图): _name(name){cout Person() endl;}Person(const Person p): _name(p._name){cout Person(const Person p) endl;}~Person(){cout ~Person() endl;}
protected:string _name; // 姓名
};class Student : public Person
{
public:Student(const char* name, int num, const char* addrss):Person(name), _num(num), _addrss(addrss){}// 严格说Student拷贝构造默认生成的就够用了// 如果有需要深拷贝的资源才需要自己实现Student(const Student s):Person(s), _num(s._num), _addrss(s._addrss){// 深拷贝}// 严格说Student析构默认生成的就够用了// 如果有需要显示释放的资源才需要自己实现// 析构函数都会被特殊处理成destructor() ~Student(){// 子类的析构和父类析构函数也构成隐藏关系// 规定不需要显示调用子类析构函数之后会自动调用父类析构// 这样保证析构顺序先子后父显示调用取决于实现的人不能保证// 先子后父cout~Student()endl;Person::~Person();delete _ptr;}
protected:int _num 1; //学号string _addrss 翻斗花园;int* _ptr new int[10];
};int main()
{Student* ptr new Student(胡图图,10,翻斗花园);delete ptr;return 0;
} 运行结果为
这里为什么会有两个~Person呢原因是其实派生类的析构函数调用后会自动调用基类的析构函数所以不需要我们写我们可以把~Student里面的Person::~Person()删掉即可 运行结果为
最后总结一下一般如果没有深拷贝的话拷贝构造和赋值运算符重载不需要我们自己写用默认的就行析构函数也是然后析构函数的调用顺序是先子后父 5. 实现一个不能被继承的类
方法一让构造函数的访问限定符为private这样就无法调用了。
#includeiostream
using namespace std;class Person
{
public:private:Person(){}
};class Student : public Person
{
public:
};
int main()
{Student s1;return 0;
}
方法二c11新加的关键字finalfinal加到基类的类名后就不能被继承了。 6. 继承和友元
友元的关系不能被继承说明基类的友元不能访问派生类的私有保护成员举个很简单的例子方便记忆“父亲的朋友不是你的朋友”如下代码所示 7. 继承与静态成员
基类定义了static静态成员则整个继承体系只有一个这样的成员派生出多少个派生类都只有一个static成员实例
#includeiostream
using namespace std;class Person
{
public:string _name;static int _count;};int Person::_count 0;class Student:public Person
{
protected:int _stuNum;
};int main()
{Student s;Person p;//地址不一样说明已经继承了cout s._name endl;cout p._name endl;//地址一样说明只有一份cout s._count endl;cout p._count endl;return 0;
} 运行结果为 7. 多继承以及菱形继承的问题
7.1 继承模型
单继承一个派生类只有一个基类。
多继承一个派生类有两个或两个以上的基类“我爸”有一个Son的基类和Dad的基类多继承对象在内存中的模型是先继承的基类放在前面后继承的放后面。
菱形继承是多继承的一种特殊情况可能祖师爷那会喝醉了没想到这个情况吧。菱形继承会导致数据冗余和二义性的问题Assistant里面有两份Person因为Student里面有一份PersonTeacher里面也有一份Person
#includeiostream
using namespace std;class Person
{
public:string _name;
};class Student :public Person
{
protected:int _num;
};class Teacher :public Person
{
protected:int _id;
};class Assistant :public Student, public Teacher
{
protected:string _majorCourse;
};int main()
{Assistant a;a._name() 胡图图;
} 如果我们想解决这个问题的话可以指定访问指定的基类成员就可以了 7.2 虚继承
有了多继承菱形继承就无法避免最好不要实现出来但我们也可以在其中做出一些操作在两个会继承到同一个基类的派生类处加关键字virtual即可如下代码所示
#includeiostream
using namespace std;class Person
{
public:string _name;
};class Student :public virtual Person
{
protected:int _num;
};class Teacher :public virtual Person
{
protected:int _id;
};class Assistant :public Student, public Teacher
{
public:
protected:string _majorCourse;
};int main()
{Assistant a;a._name 胡图图;//a.Student::_name 胡图图;//a.Teacher::_name 胡英俊;return 0;
}
这串代码就可以执行了而这个东西的原理就是把那些继承的数据通过虚基表和虚基指针来管理这些共享的数据从而避免了数据的冗余简单点理解就是放在一个公共区域共同使用。 8. 继承和组合
继承是子类继承了父类的特性可以说子类其实就是一个特殊的父类而组合则是将另一个类当做自己的成员变量进行使用
继承是is-a的关系举个例子例如狗是一个动物那么狗就是子类而动物就是父类而狗有动物的所有特性即是有个新的类需要一个类的所有特性的时候就可以使用继承。
而组合是has-a这里也举个例子就是汽车有一个轮子那么我们有一个class Car{class wheels{};};即有个类需要某些特定的功能的时候则可以使用组合。组合可以很好地保护类的安全性不破坏封装 END!