当前位置: 首页 > news >正文

好的设计教程网站诛仙2官方网站西施任务怎么做

好的设计教程网站,诛仙2官方网站西施任务怎么做,台州网站建设推广,中国网站排名网继承的概念 继承机制是面向对象程序设计当中#xff0c;非常重要且好用的手段#xff0c;这种手段可以允许程序员在原有的类的特性基础之上#xff0c;进行拓展从而产生新的类#xff0c;这些新产生的类#xff0c;我们成为派生类。在以前#xff0c;我们实现的复用是函数…继承的概念 继承机制是面向对象程序设计当中非常重要且好用的手段这种手段可以允许程序员在原有的类的特性基础之上进行拓展从而产生新的类这些新产生的类我们成为派生类。在以前我们实现的复用是函数的复用现在衍生到类的设计层次上的复用就是继承就是一种对类设计上的复用。 C 在此处设计的理念是父亲干父亲的活孩子干孩子的活具体的可以在文章当中去感受。 比如一个在一个学校当中有很多的个学生和老师在一个学校当中学生和老师的信息就有相同的和自己类独特的。比如学生和老师有名字电话家庭住址邮箱等等这些信息是每一个老师和学生都有的信息 而对于学生宿舍号学号专业等等就是属于学生这个类的独有信息工号所属学院职称等等这些是属于老师的独有信息所谓独有信息就是两个类之间不共有的信息。 当然在一个学校当中不只有学生和老师两个“类”还有食堂工作人员保安等等多个职位这些职位都是有共同之处的比如都有名字电话住址等等这些信息如果我们把这些“类”都重新设计成各自的类是不是就是非常的冗余了。 基于上述的问题继承的作用就非常明显了基于上述例子我们可以搞一个 person类这个类当中存储的是学校的当中所有人员的所共有的信息比如名字年龄电话号码住址等等。 然后所创建的 Student类 teacher类 等等在类当中只需要写出自己类所独有的信息再去继承 person 这个类就行了。 class Person { public:void Print(){cout name: _name endl;cout age: _age endl;} protected:string _name peter; // 姓名int _age 18; // 年龄 };// 继承后父类的Person的成员成员函数成员变量都会变成子类的一部分。这里体现出了 //Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象可 //以看到变量的复用。调用Print可以看到成员函数的复用。class Student : public Person { protected:int _stuid; // 学号 }; 如上述Student类 继承了 person类。 而派生类当中成员函数肯定不存储在类和对象当中也不止 _stuid 这个成员除了自己的成员之外派生类的当中还有从父类当中继承过来的 成员 也就是说派生类当中分为两个部分一个是 派生类 本身的成员部分另一个是 派生类 从父类当中继承过来的 成员部分。 继承的语法 如下图所示Person是父类也称作基类。Student是子类也称作派生类。 继承关系和访问限定符 在上述的语法介绍当中我们发现有继承方式这一个语法在C当中的继承规则这一块其实设计还是有点复杂的如下图所示根据父类当中的访问限定符有三种继承方式同样有三种如下图所示 在C当中是以 父类当中访问限定符 和 继承方式两两组合形成一种新的访问限定那么总结下来在继承当中就有 9 种访问方式如下图所示 对于上述 9 种结果其实是有规则可循的总结如下 基类的private成员在派生类当中都是不可见的。而 不可见  是  语法上限制访问类里和类外都不能使用而private是类外不能使用类当中说可以使用的。父类的私有成员子类无论以什么方式继承不能用实际上面的表格我们进行一下总结会发现基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 Min(成员在基类的访问限定符继承方式)public protected private取两者小的那一个访问方式作为最后的访问方式基类private成员在派生类中是不能被访问如果基类成员不想在类外直接被访问但需要在派生类中能访问就定义为protected。可以看出保护成员限定符是因继承才出现的。 C当中支持默认继承如果不写继承方式使用关键字 class 时默认是私有private继承使用关键字 struct 默认是公有public继承。但是建议写上继承方式之所以 class 是私有继承是防止多次继承对基类的影响。如果继承方式是 protected的那么子类再被继承产生的派生类还是可以访问到最开始的基类当中的成员但是如果基类当中的成员是被private的那么其子类再被继承而产生的派生类就不能在访问到基类当中的peivate成员了 在实际运用中一般使用都是public继承几乎很少使用protetced/private继承也不提倡使用protetced/private继承因为protetced/private继承下来的成员都只能在派生类的类里面使用实际中扩展维护性不强。 实际当中只用如下图所示的访问方式 基类和派生类的赋值转换 如果是不同类型的内置类型相互赋值是会发生类型转换的 比如 int a 1; double b 0;b a; 而类型转换是会产生临时变量来帮助转换的同样的基类 和 基类派生出的派生类之间相互赋值也是会发生类型的转换的。 需要注意的是基类 和 基类派生出的派生类之间相互赋值所发生的类型转换是有条件的。 只能是 派生类赋值给 基类不能是 基类赋值给 派生类。 因为如果是基类赋值给 派生类那么对于派生类当中 独有信息 是基类当中没有的基类无法给派生类当中的 独有信息 进行赋值。 而且对于基类赋值给派生类这一操作就算给强制类型转换都是无法使用的语法这一操作在语法上直接禁掉了。 报错 而且对于基类和派生类的类型转换还和我们之前了解的 隐式类型转换和强制类型转换不一样后两者在转换的时候会产生临时变量 而且 基类 和 派生类的类型类型转换不会产生临时变量他们之间的类型转换 是 赋值兼容切割切片。 我们认为一个子类对象一定是一个特殊的父类对象至少子类对象的当中一定有父类的成员。子类赋值给父类的话会把子类当中父类的成员切割出来然后再把这些成员拷贝给父类。 如果是父类转化为子类的话单纯的对象之间是不能转换的但是如果是引用和指针的话还是可以的可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行识别后进行安全转换。 验证 赋值兼容 没有产生临时对象 如下例子 student s; ·········1 person p s;int a 1; ·········2 double b a; 如上所示我们知道对于p这个引用如果是发生在内置类型或者是没有父子关系的对象之间会发生隐式类型转换从而产生临时对象所以如果不是 赋值兼容的 话如上述例子2引用应该指向的是 类型转换 所产生的临时变量。 而临时变量具有常性上述例子2 的引用 b 没有用const 修饰就会报错。 而反观例子1 就不会报错说明 例子1 当中没有产生临时变量。 此时例子1 当中的 引用 是 s 对象的一个别名。 继承当中的作用域 对于基类 和 基类派生出的派生类都是有独自的作用域。所以如果你在基类 和 派生类当中定义出相同名字的成员这个语法是通过的。如下代码所示 // 父类 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; // 学号 }; 如上代码所示父类和子类当中都有一个 _num 成员。但是没有报错。 如果我们此时调用 Print函数那么打印的 _num 的值是多少呢 答案是 999。 这是因为如果在父类和子类当中有同名的成员子类成员将屏蔽父类对同名成员的直接访问这种情况叫隐藏也叫重定义。在子类成员函数中可以使用 基类::基类成员 显示访问 显示访问父类成员 void Print(){cout 姓名: _name endl;cout 身份证号: Person::_num endl;cout 学号: Person::_num endl;} 同样的这样的同名不仅仅允许在成员也允许在成员函数而且同名的成员函数也是和同名的成员一样的子类会对父类的进行隐藏默认是调用子类的同名成员函数。当然也可以显示的调用父类的同名成员函数 class Person { public:void Print(){cout 姓名: _name endl;cout 身份证号: Person::_num endl;cout 学号: Person::_num endl;} protected:string _name 小李子; // 姓名int _num 111; // 身份证号 };class Student : public Person { public:void Print(){cout 姓名: _name endl;cout 身份证号: Person::_num endl;cout 学号: Person::_num endl;} protected:int _num 999; // 学号 };void Test() {Student s1;s1.Person::Print(); }; 此时的子类当中Print和父类当中的Print因为不是在同一作用域不是构成重载此时是子类和父类的关系的成员函数满足函数名相同就构成隐藏。 域是查找规则的概念他规定编译器应该去哪一个域当中去寻找变量或者函数而且此时的 查找是在编译时期的查找编译器首先要查找有没有这个变量/函数没有就要报错。还有一个查找是生成代码之后更具地址去查找变量/ 函数所以编译器对于一个域当中没有查找到的变量/函数他会在下一个域去寻找而下一个域也是按照顺序来的。比如子类当中没有的成员就会在父类当中去寻找。 但是即使是允许子类和父类之间使用同名成员我们也不建议这样 在继承体系当中 去 定义子类和父类当中的成员和成员函数。 派生类的默认成员函数 一个类当中有 6 个默认成员函数其中“默认”的意思就是我们不写这6个成员函数编译器会自动生成一个。6 个 当中常用的就是 4 个如下所示 构造函数 和 拷贝构造函数 派生类 中的 构造函数 当中的初始化列表只能初始化本派生类的成员不能初始化父类当中的成员但是在构造函数的定义的当中对 父类当中的成员进行修改是可以。 class Person { protected:string _name 小李子; // 姓名 };class Student : public Person {Student(const char name, int num):_name(name),_num(num){_name nnn;} protected:int _num 999; // 学号 }; 如上父类当中的 _name 成员不能再 子类 的 构造函数 的初始化列表当中用但是可以在构造函数的定义的当中被修改 再如下例子我们把父类Person 的构造函数 和  析构函数写出来并打印一下名字方便给出提示提示我们哪一个函数被调用了。其次我们在主函数当中只定义子类对象 Student。 class Person { public:Person(){cout Person() endl;}~Person(){cout ~Person() endl;} protected:string _name 小李子; // 姓名 };class Student : public Person {Student(int num):_num(num){} protected:int _num 999; // 学号 };int main() {Student s; } 打印结果 但是打印结果 却 打印的父类的 构造函数 和 析构函数名。 这是因为C规定派生类必须调用 父类的构造函数 去初始化父类的成员不管在主函数当中有没有定义 父类对象。这种自动调用都是调用的默认函数此时就是默认构造函数如果我们没有提供默认构造就会报错 所以如果父类没有默认构造函数我们就要在子类的初始化列表当中对父类进行初始化就像定义一个匿名对象一样如下代码所示 class Person { public:Person(const char name):_name(name){cout Person() endl;}~Person(){cout ~Person() endl;} protected:string _name 小李子; // 姓名 };class Student : public Person { public:Student(const char name , int num):_num(num),Person(name){}protected:int _num 999; // 学号 }; 注意 而且此时是 Personname先初始化_num 后初始化。因为初始化列表 初始化的顺序 不是跟初始化列表的顺序相同的是和 各个成员声明的顺序相同的。 而此时是继承的关系子类会优先调用 父类 构造函数去初始化父类的 成员所以会优先 初始化 Person。 对于拷贝构造函数也是和构造函数是一样的不能在子类的拷贝构造函数的 初始化列表当中去初始化 父类的 成员同样会优先调用父类的构造函数去初始化父类的 成员。 对于拷贝构造函数的 对父类的初始化我们也是使用 和之前构造函数一样的 类似于匿名对象的用法来初始化。但是此时就会出现一个问题父类当中的 拷贝构造函数 的参数是 父类的 类型而在拷贝构造函数当中我们只有子类的 类型可以传参。此时不用管这么多可以直接传入子类。如下代码所示 // 父类 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; // 姓名 };// 子类 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(){cout ~Student() endl;} protected:int _num; //学号 }; 因为此处父类的拷贝构造函数的参数是 引用如果传入的是 子类的类型对象会发生 赋值兼容转换也就是切割把子类当中父类的成员切割出来进行赋值。 赋值操作符重载函数 对于 赋值操作符重载函数 也是一样的我们把 子类当中 父类的成员部分和 子类成员部分 都赋值就行 Student operator (const Student s){cout Student operator (const Student s) endl;if (this ! s){Person::operator (s); // 父类当中已经直接 operator 函数此处直接调用_num s._num;}return *this;} 需要注意的是我们上述调用了父类当中的 operator 函数如果我们不显示去调用像如下方式写的话就会 出现 “隐藏” operator (s); 上述的代码默认调用 子类 当中的 operator 函数就会出现递归死循环栈溢出代码奔溃。 所以我们要显示调用父类当中的 operator 函数。 析构函数 析构同样要析构 派生类自己的还要析构 派生类父类的析构父类可以直接调用 父类当中的析构函数但是不能像父类当中其他函数一样直接调用需要显示指定类域 ~Student() {Person::~Person(); } 具体原因是因为C 当中多态的原因。析构的函数名被进行了特殊处理。由于要构成多态被统一处理成了 destructor。 因为都被处理成为了 destructor所以此处就构成了 隐藏 所以要显示调用 父类的析构函数。 但是上述在程序在执行完之后结果如下 在 3 个子类 Student 当中有 3 个 Person按道理在最后应该只调用 3 次 Person 的析构函数但是此处调用了 6 次。 其实此处是Person 对象自动调用了 自己的析构函数因为 整个程序 无论你怎么先都是先构造父类对象在构造子类对象也就是说先构造 Person 在构造 Student·····    这样总共构造了 3 个Student 对象 和 3 个Person对象 单看一组根据 先构造的 后 析构后构造的先析构那么 Student 对象就会先析构然后再析构 Person 对象但是在 Student 的析构函数 已经调用了 Person对象的 析构函数就在此处发生了重复析构的问题。 如果我们不在 Student 的 析构函数当中去调用 Person对象的析构函数就是析构3次是正确的。 因为在构造的时候是先父后子的顺序进行构造的所以在析构的时候应该满足先子后父的析构顺序但是在子类析构函数当中显示调用 父类析构函数是无法保证先子后父的析构顺序所以编译器在子类析构函数完成之后就自动调用父类析构函数。 其实这里可以不用自动析构完全可以自己在 子类 Student 的析构函数当中实现上述顺序的 析构顺序。但是C语法就是有自动调用析构函数所以我们在 继承体系当中写析构函数的时候一定要注意这一点因为多次重复的析构可能会出现问题比如重复析构的对象当中有我们自己手动开辟的空间的时候重复析构就会报错。 为什么在析构的时候要先子后父 之所以说不能先显示调用父类的析构函数是因为在子类的析构函数当中可能还会用到 父类当中的成员或者成员函数如果先就析构父类的话无法调用了而且在父类当中是不会用到子类当中的成员的所以我们决定先析构子类在析构父类。 继承与有元 有元不能继承 也就是爸爸的朋友不是我的朋友我爸爸的朋友不能去访问我的成员。 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); } 上述有元函数 Display 可以访问父类 Person的 _name 但是不能访问 子类 Student 的 _stuNum成员。 如果想要 友元函数 Display 也能访问到 子类的 _stuNum 成员需要在子类当中也加上有元声明。 继承与静态成员 如果在父类当中定义了静态成员变量这个静态成员能不能继承其实认为可以和认为不可以都是对的。 在继承当中子类对象当中都有一个父类对象的存储相当于是在子类当中构造了一个父类的对象每一次构造一次子类都要去调用其父类的构造函数去构造父类出来但是对于 父类当中的静态成员究竟是不是 从父类当中拷贝一份到子类当中我们先看如下例子 class Person { public:Person() { _count; }public:static int _count; // 统计人的个数。string _name; // 姓名 }; // 父类当中的 静态成员变量 int Person::_count 0;class Student : public Person { protected:int _stuNum; // 学号 };class Graduate : public Student { protected:string _seminarCourse; // 研究科目 };int main() {Person p;Student s;Graduate g;// 父类当中的 非静态成员cout p._name endl;cout s._name endl;cout g._name endl;cout -----这是一个分界线----- endl;// 父类当中的 静态成员cout p._count endl;cout s._count endl;cout g._count endl;return 0; } 我们把 父类当中 非静态成员 _name 和 静态成员 _count 在 父类 Person 和 两个子类 Student 和 Graduate 当中打印出地址。 结果输出 000000EEADF6F508 000000EEADF6F548 000000EEADF6F5A0 -----这是一个分界线----- 00007FF6871B2440 00007FF6871B2440 00007FF6871B2440 我们发现非静态成员的地址是不一样的说明在子类当中每一次构造对象时候都对父类当中的非静态成员进行了拷贝构造 而 下面的 静态成员的地址是一样的说明后序子类的对父类的构造并没有对静态成员进行构造上述的父类和两个子类都是共用一个静态成员变量。  由上述例子我们得出结论在父类当中定义的 static 静态成员则在其整个继承体系当中只有这一个静态成员。而且无论派生出多少个子类都只会实例化这一个静态成员。 所以我们才会说对于父类当中的静态成员其子类其实是继承了因为可以在子类当中使用这个静态变量但是这种继承和其他的继承不一样他不会去在子类当中拷贝构造一个新的静态成员出来而是整个体系使用一个几台成员。静态成员继承的是使用权静态成员同时属于父类和其派生类 我们上述的例子还记录了 Person 这个父类总计创建了多少个派生类包括Person自己。实现也非常简单就是在构造函数当中 _count 这个静态成员变量。利用子类在创建时候会先调用父类的构造函数这一特性来记录派生类创建的个数。 复杂的菱形继承及菱形虚拟继承多继承 在现实世界当中会有一种类别同时具有两种多种类别的特征也就是同时继承多个父亲。所以在C当中开发人员也考虑到这种情况的发生就实现了多继承。 像我们之前的举的例子都是单继承如下图所示 而多继承是一个子类有多个父亲 我们发现这这种多继承在一般情况下还是非常符合现实世界的用起来也是没有问题 class Student { protected:int _num; //学号 };class Teacher { protected:int _id; // 职工编号 };class Assistant : public Student, public Teacher { protected:string _majorCourse; // 主修课程 }; 上述这个人既是一个老师又是一个学生这种是没有问题的。 但是如果 Assistant 这个人继承的 Student Teacher 两个父类都继承了一个 Person 父类的话就会出问题 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; // 主修课程 }; 上述这种情况我们称之为菱形继承。他是多继承的一种特殊情况。 这种情况出现的问题不是对person的多次构造和析构因为两个构造的person对象是存在于Student 和 Teacher 当中的不会出问题。 真正的问题是对于 Assistant 对象拥有了多个 Person 类当中的成员属性。如下图所示 在一个 Assistant 类当中有了 两个 Person 当中的 _name 这个属性但是不是每一个人都有多个名字一般一个人经常使用的只有一个名字此时这种情况就是数据冗余和二义性。 就算你认为一个人可以在不同场景下有两个名字比如当学生的时候老师叫你小郑当老师的时候学生叫你郑老师其实只是这里的例子太偶然如果 Person 当中不止一个数据呢 比如还有 _id 这个成员来表示你的身份证号码一个人不会有两个身份证号码。 对于上述的 数据冗余 和 二义性所带来的坑是 数据冗余对于存储空间有一定会的浪费。二义性在访问的时候不知道要访问哪一个成员。 上述例子如果直接使用 Assistant 的对象来访问 _name 就会报错编译器不知道要访问那一个 _name 。如下代码所示 void Test () { // 这样会有二义性无法明确知道访问的是哪一个Assistant a ;a._name peter; } 手动解决二义性可以显示制定域来访问某一个 _name 但是 数据同于问题无法解决 // 需要显示指定访问哪个父类的成员可以解决二义性问题但是数据冗余问题无法解决a.Student::_name xxx;a.Teacher::_name yyy; 只要使用多继承就有可能会发生菱形继承因为只要继承了上一个父类上一个父类的继承关系就会衍生到这一个子类当中。 而对于单继承就不会出现多次继承某一个父亲的情况 当B对象被构造的时候会先去构造 B 的父类 A在构造 A 的时候又会先构造  Person。所以在B当中是没有两个 Person对象的Person对象只是在 构造A的时候被构造了一次。 虚继承 C为了解决 上述菱形继承所带来啊的二义性的问题增加了一个功能——虚继承。 语法 在出现菱形继承的“腰部”使用 virtual 来修饰继承的父类修饰位置如下所示   代码 class Person { public :string _name ; // 姓名 };// virtual 修饰 class Student : virtual public Person { protected :int _num ; //学号 };// virtual 修饰 class Teacher : virtual public Person { protected :int _id ; // 职工编号 };class Assistant : public Student, public Teacher { protected :string _majorCourse ; // 主修课程 }; 虚继承的原理 菱形继承的存储结构 理解原理之前我们先来搞清楚菱形继承当中的对象模型所谓对象模型就是这个对象在内存当中存储的结构。 有下面这个例子 class A { public:int _a; }; // class B : public 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.B::_a 1;d.C::_a 2;d._b 3;d._c 4;d._d 5;return 0; } 这个例子中当中 A B C D 四个类的继承关系如下图所示 上述例子创建了 一个 D类的对象d然后把d当中的 B类对象和C类对象当中各自的A类对象中的 _a 成员变量进行了修改然后再把d当中存储 B类对象当中 _b 成员 和 C 类对象中的 _c 成员修改。最后在对d本身的对象进行修改。 我们在在调试当中查看内存如下所示 看似菱形继承关系很复杂其实在内存当中的存储结构就是依次顺序存储。 菱形虚拟继承的存储结构  class A { public:int _a; }; // class B : public A class B : virtual public A { public:int _b; }; // class C : public A 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;d._a 0; // 不显示指定域直接就修改return 0; } 还是上述的例子但是我们在菱形继承的腰部位置加上了 virtual 修饰虚拟继承。 内存中存储如下图所示 我们发现菱形继承和菱形虚拟继承不同点在于对于 A 类共同继承的父类的存储空间不在是 B 和 C 当中都有一个了而是 B C D 三者共用一个 A对象当中的成员。 菱形继承的存储结构 和 菱形虚拟继承的存储结构 两者的不同 在使用 virtual 修饰之后虚拟继承共同继承的父类不再是分开存储而是只存储一个而派生出的子类或者是派生类派生出的子类都共用一个 父类对象。从而不管是在上述哪一个类当中修改父类对象当中成员修改的都是一个对象当中的成员。 而且不仅仅是这样我们发现虚拟继承 比 继承 还多存储了一些东西 我们调用内存窗口查看这两个地址位置存储了什么如下两图所示 在两个地址位置处存储的都是0但是在两地址的下一地址处存储了一些值第一个是 14转换为二进制就是 20第二个是 0c 转化为二进制就是 12 这里的 20 和 12 代表的就是 在上图 左侧内存块当中两个多出来的存储地址距离 A 对象存储地址位置的相对距离偏移量这里可以理解为指向 那 两个多出来的存储地址 的两个指针。 这里之所以使用偏移量这种方式来代替指针是因为对于上述例子 D 类的对象可能不会只有一个虽然可能 不同的 D类对象 当中存储的值不会相同但是 其中 B类对象 和 C类对象当中对于上述多出的两个地址位置和D类对象其中的 A类对象 地址位置的偏移量是相同的所以不需要每创建一次 D类对象就要把 其中的A类对象的地址保存只需要使用偏移量来计算出 A类对象的存储位置即可。也就是说所谓的 一表多用。 如上图所示创建了 d1 对象 和 d2 对象都可以使用 右边已经计算好的偏移量表内存块。 这里使用 虚基表存找基类偏移量的表 的 方式来解决 每创建一个对象都需要 存储 一次 A类对象 地址的问题。 偏移量存储的意义 还是上述的 菱形继承的结构对于 d1._a 是不需要用偏移量去寻找 A类对象当中的 _a 成员的因为 在 D类对象当中所有的 D类成员 和 继承的所以 A B C 三个类的成员都是有自己的声明定义顺序的之前也描述过编译器找 基类 A类的 _a 尘缘不需要利用偏移量编译器知道 虚拟继承后A类对象就存储在 D类对象空间的最后。 那么既然用不到那为什么要定义偏移量呢 其实上述只是一个普通场景一些特殊场景下会用到比如下面这个例子 D d1; B* pb d1; pb-_a 0; 这里的 pb 是一个B类型的指针所以就算pb直线的是 d1 对象的开头他去数据也只能取到 一个 B类对象 大小的数据而 存储 _a 的A类对象 是在 d1 对象的末尾直接取肯定取不到 _a 的。这时候就可以用偏移量来找到 A类对象的位置。 向上述的 pb 其实发生了 切片本来 B类对象 数在 d1 这个对象里面的但是要我指针类型是 B* 用 pb 访问数据的话不能把 d1 对象的所以数据都访问完只能访问到从 d1对象起始位置开头的 B类对象大小的数据那么其余下面的数据就被切片了。 也正是有了偏移量的存在在上述这种情况切片发生的时候就不用因为找不到 A类来进行特殊处理如果存储的A类地址的话使用偏移量在 pb-_a 访问 _a 的时候只需要按照地址找到 虚基表计算偏移量就行。 再如下例子感受 D d; d-_a 1;B b; b-_a 2;B* ptr b; ptr-_a;ptr d; ptr-_a; 因为 B类 也是被 virtual 修饰的所以在 B类 当中的对象模型和之前的 d1 对象模型是一样的都是共用一个 A类。 但是我们知道 b 对象 和 d 对象 两个对象当中存储的 A类对象都是在其对象的 末尾处但是对于 ptr d 上述说过是要发生切片的此时的 ptr 指针还是只能访问 B类对象大小的数据而A类数据不在其中。ptr指针 不能分辨 它此时 指向的对象是 b 还是 d也就是说上述的两种情况 ptr 是分辨的所以都是使用的 偏移量计算 来得出 A类对象的存储地址而不是直接从指向对象的末尾处找如下图所示 上两处的 ptr-_a;使用的编译语法都是一样的都是取 偏移量计算出 A类对象的地址然后再对 _a 进行操作。
http://www.zqtcl.cn/news/280200/

相关文章:

  • 网站入口设计规范专门做喷涂设备的网站
  • 最简单网站开发软件有哪些企业管理培训课程培训机构
  • 桂城网站制作公司wordpress 导航网站
  • 一个公司做网站需要注意什么条件网站备案 登陆
  • 百度网站介绍显示图片装修公司一般多少钱一平方
  • 网站销售如何做业绩我找伟宏篷布我做的事ko家的网站
  • 建立网站有哪些步骤?jsp网站开发详细教程
  • 网站怎么做直播功能旅游做攻略用什么网站
  • 企业外贸营销型网站如何写好软文推广
  • 免费建站的网址个人网站建设程序设计
  • 淘宝网站建设违规吗上海大公司
  • 大淘客怎么自己做网站自己开网站能赚钱吗
  • 大型门户网站开发北京网站建设管庄
  • 大连建设工程网站网站建设组织管理怎么写
  • wordpress英文站注册域名需要注意什么
  • 营销型网站的建设重点是什么深圳logo设计公司排名
  • 做网站的用什么软件呢网站排名优化服务公司
  • 网站开发完整视频网站集约化建设较好的城市
  • 网站建设和平面设计应用网站如何做
  • 自己做网站需要多少费用asa8.4 做网站映射
  • 商业网站 模板黑龙江省建设厅安全员考试
  • 网站新备案不能访问室内装修网站模板
  • 工程师报考网站wordpress设置视频图片不显示图片
  • 徐州网站建设公司排名成都住建平台
  • 用来备案企业网站国外免费外贸网站
  • 网页背景做的比较好的网站做一个企业网站价格
  • 免费制图网站县级门户网站建设的报告
  • 北京网站建设网怎么用手机做一个网站
  • 网站建设管理办法关于公司门户网站建设的议案
  • 网站开发入职转正申请书体验好的网站