建设银行反钓鱼网站,专业返利网站开发,wordpress申请,手机行业网站一 继承的定义和概念
1.1 继承的定义
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段#xff0c;它允许程序员在保 持原有类特性的基础上进行扩展#xff0c;增加功能#xff0c;这样产生新的类#xff0c;称派生类#xff0c;被继承的称为基类…一 继承的定义和概念
1.1 继承的定义
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段它允许程序员在保 持原有类特性的基础上进行扩展增加功能这样产生新的类称派生类被继承的称为基类 继承呈现了面向对象程序设计的层次结构体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用继承是类设计层次的复用。 下面由代码来进行理解吧
#define _CRT_SECURE_NO_WARNINGS 1
#includeiostream
using namespace std;
class Person
{
public:void Print(){cout name: _name endl;cout age: _age endl;}
protected:string _name 喜羊羊;int _age 21;
};class Student : public Person
{
protected:int _stuid; // 学号
};class Teacher : public Person
{
protected:int _jobid; // 工号
};
int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}
这段代码分为两个个部分
class Person
{
public:void Print(){cout name: _name endl;cout age: _age endl;}
protected:string _name 喜羊羊;int _age 21;
};这个也就是我们之前写的class类的写法
第二部分
class Student : public Person
{
protected:int _stuid; // 学号
};class Teacher : public Person
{
protected:int _jobid; // 工号
}; 对于学生和老师来说他们都有一个共同的特性那就是人那我们把人的属性封装起来然后用老师和学生去复用他你也就达到了目的这里的复用也可以称之为继承。
那继承的方法是什么呢
class 新类的名字:继承方式 继承类的名字{};
就和这个例子一样
class Student : public Person
{
protected:int _stuid; // 学号
};
这里的Student和Tercher我们规范里面称之为派生类对于Person类来说我们称之为基类。
但是一般来说我们会称之为子类和父类。 1.2 继承的访问权限
由上面我们知道public是一种继承的方式但是还有其他的他们继承方式的不同访问权限也就不一样 可以看出全部组合起来有很多但是我们发现一个规律就是他们都是向下取舍的。
我们知道它们三个的权限大小分别为 publicprotectedprivate ,所以如果我们把它们两两组合取的是权限小的那一个。
1.3 继承的细节
1.如果是private成员那么无论什么方式都是不可见的这里不可见是指派生类在类里面和类外面都不能访问但是它们确确实实是继承了
#define _CRT_SECURE_NO_WARNINGS 1
#includeiostream
using namespace std;
class Person
{
public:void Print(){cout name: _name endl;cout age: _age endl;}
private:string _name 喜羊羊;int _age 21;
};class Student : public Person
{
protected:int _stuid; // 学号
};
上面代码中Person成员变量变为私有但是确确实实是继承了唯一的就是不能访问 2.对于protected来说它不像private那样那么严格它可以让你在类里面访问它但是在类外面也不能访问。可以看出保护成员限定符是因继承才出现的。
3. 关键字struct默认继承的方式是public,class默认继承方式是private;
二 .基类和派生类对象赋值转换
我们之前的类型转换是创建一个临时变量然后进行赋值但是这里不一样这里采用的切片的方式进行的 也就是说子类给父类的时候会进行一个类似切片的操作把指给父类但是如果是父类给子类那么就会有问题了因为父类没有子类的_No成员所以给不了它相应的值。
这里也可以用指针和引用进行操作但是也有许多情况
#define _CRT_SECURE_NO_WARNINGS 1
#includeiostream
using namespace std;
class Person
{
protected:string _name;string _sex;int _age;
};
class Student : public Person
{
public:int _No; // 学号
};
int main()
{Student sobj;// 1.子类对象可以赋值给父类对象/指针/引用Person pobj sobj;Person* pp sobj;Person rp sobj;//2.基类对象不能赋值给派生类对象//sobj pobj;// 3.基类的指针可以通过强制类型转换赋值给派生类的指针pp sobj;Student* ps1 (Student*)pp; // 这种情况转换时可以的。ps1-_No 10;pp pobj;Student* ps2 (Student*)pp; // 这种情况转换时虽然可以但是会存在越界访问的问ps2-_No 10;return 0;
}这里着重看一下后面这段 pp sobj;Student* ps1 (Student*)pp; // 这种情况转换时可以的。ps1-_No 10;pp pobj;Student* ps2 (Student*)pp; // 这种情况转换时虽然可以但是会存在越界访问的问ps2-_No 10;
这里的第一部分pp实际上是指向student对象所以这里ps1也指向student对象所以可以访问里面的成员
但是第二部分pp是指向Person对象这里直接强转相当于告诉编译器忽略这个事实虽然也是转了ps2也指向这个对象但是里面没有_No这个成员所以会发生越界访问的存在 三 继承中的作用域 1. 在继承体系中基类和派生类都有独立的作用域。 2. 子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问这种情况叫隐藏 也叫重定义。在子类成员函数中可以使用 基类::基类成员 显示访问 3. 需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。 4. 注意在实际中在继承体系里面最好不要定义同名的成员。 3.1 同名成员变量
#define _CRT_SECURE_NO_WARNINGS 1
#includeiostream
using namespace std;
// Student的_num和Person的_num构成隐藏关系可以看出这样代码虽然能跑但是非常容易混淆
class Person
{
protected:string _name 小李子; // 姓名int _num 111;// 身份证号
};
class Student : public Person
{
public:void Print(){cout 姓名: _name endl;cout 身份证号: Person::_num endl;cout 学号: _num endl;}
protected:int _num 999; // 学号
};
void Test()
{Student s1;s1.Print();
}; void Print(){cout 姓名: _name endl;cout 身份证号: Person::_num endl;cout 学号: _num endl;}
我们在打印身份证号的时候采用了指定类域的方式如果不这样那么就会导致编译器分不清那么就直接打印子类的成员了 3.2 同名成员函数
#define _CRT_SECURE_NO_WARNINGS 1
#includeiostream
using namespace std;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;}
};
void Test()
{B b;b.fun(10);
}; B中的fun和A中的fun不是构成重载因为不是在同一作用域 B中的fun和A中的fun构成隐藏成员函数满足函数名相同就构成隐藏。 四 派生类的默认成员函数 4.1 构造函数
#define _CRT_SECURE_NO_WARNINGS 1
#includeiostream
using namespace std;
class Person {
public:Person(string name 喜羊羊):_name(name){cout name endl;}
protected:string _name;
};class student :public Person {
public:student(string name, int age):_age(age){cout name endl age endl;}
protected:int _age;
};int main()
{student st(沸羊羊, 18);return 0;
}
对于构造来说它是先调用父类的构造函数然后再调用子类的构造函数 子类的构造函数没有去初始化父类的这里是编译器自己去调用父类的默认构造函数跟自定义类型是一个道理。
4.2 析构函数 这里析构函数和构造函数相反这里是先调用子类的再调用父类的 这里也可以用先构造的后析构后构造的先析构来解释 class Person {
public:Person(string name 喜羊羊):_name(name){cout name endl;}~Person(){cout ~Person() endl;}
protected:string _name;
};class student :public Person {
public:student(string name, int age):_age(age){cout name endl age endl;}~student(){cout ~student() endl;}
protected:int _age;
}; 如果我们在子类中调用父类的析构如果是指针类型就会被析构两次。其次之所以先调用子类的析构后调用父类的析构是因为可能析构的时候存在某些记录工作所以不能先调用父类的析构函数 4.3 拷贝构造
派生类对象通常包含基类部分和派生类特有的部分。当创建一个派生类对象的副本时我们需要确保这两部分都被正确地复制。基类部分的复制是通过调用基类的拷贝构造函数来完成的。
如果不调用父类的拷贝构造那么就会导致资源缺失
class Person {
public:Person(string name 喜羊羊):_name(name){cout name endl;}~Person(){cout ~Person() endl;}Person(const Persons):_name(s._name){}
protected:string _name;
};class student :public Person {
public:student(string name, int age):_age(age){cout name endl age endl;}~student(){cout ~student() endl;}student(const students) :Person(s),_age(s._age)//这里Person的拷贝直接传一个s过去切片就行{}
protected:int _age;
};
这里的拷贝构造重点就在于巧妙运用了Person的切片其实这里可以不写编译器默认生成的就够用了但是我们要显示调用就必须这样写
4.4 赋值运算符重载
class Person {
public:Person(string name 喜羊羊):_name(name){cout name endl;}~Person(){cout ~Person() endl;}Person(const Persons):_name(s._name){}Person operator(const Persons){if (this ! s){_name s._name;}return *this;}
protected:string _name;
};class student :public Person {
public:student(string name, int age):_age(age){cout name endl age endl;}~student(){cout ~student() endl;}student(const students) :Person(s),_age(s._age){}student operator(const student s){if (this ! s){Person::operator(s);_age s._age;}return *this;}
protected:int _age;
};
这里的赋值运算符重载需要指定类域不然就会出现死循环一直调用自己的成员函数因为这两个是同名的属于隐藏关系。 五 单继承和多继承
单继承 一个子类只有一个父类 多继承 一个子类有多个父类 菱形继承 菱形继承是多继承的一个特例同时菱形继承也弄出了很多问题 1.在菱形继承中Student和Teach都继承了Person里面的成员变量a,后面Assistant继承了它们两个那么Assistant是不是有两个a呢这里就是数据的二义性。
2.对于二义性我们可以加访问限定符去解决但是我们还是存在一个问题就是有两个a我们继承下来就想要一个这个就是数据冗余对于数据冗余我们能用的就是虚继承去解决
#define _CRT_SECURE_NO_WARNINGS 1
#includeiostream
using namespace std;
class A
{
public:int _a;
};
class B : public A
{
public:int _b;
};
// class C : public A
class C : public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
int main()
{D d;d._a 1;d.C::_a 2;d._b 3;d._c 4;d._d 5;return 0;
}
如果我们直接访问 d._a 1;但是我们加上就欧克了
d.B::_a 1;
这里也就解决数据二义性的问题但是对于数据冗余我们采用虚继承这里继承以后就是把_a当作成共有的了
那么虚继承就是 会在类B和类C里面生成一个虚基表指针这指针指向一张表表里面存有偏移量然后通过这个偏移量找到_a所以这里的_a是共有的。 六 继承其他问题
1. 继承如果父类有友元函数继承以后友元函数是不能被子类使用的。
2. 基类定义了static静态成员则整个继承体系里面只有一个这样的成员。无论派生出多少个子 类都只有一个static成员实例 。这里也可以理解为是共有的所以不会重复。 七 总结
以上就是继承的全部内容了希望对你有所帮助