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

在线电影网站建设论文windows优化大师收费吗

在线电影网站建设论文,windows优化大师收费吗,关于水果的网站建设,网站开发南城科技大厦继承 继承的概念 继承机制是面向对象程序设计使代码可以复用的最重要的手段#xff0c;它允许程序员在保 持原有类特性的基础上进行扩展#xff0c;增加功能#xff0c;这样产生新的类#xff0c;称派生类。继承呈现了面向对象 程序设计的层次结构#xff0c;体现了由简…继承 继承的概念 继承机制是面向对象程序设计使代码可以复用的最重要的手段它允许程序员在保 持原有类特性的基础上进行扩展增加功能这样产生新的类称派生类。继承呈现了面向对象 程序设计的层次结构体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用继承是类设计层次的复用。 继承的定义 定义格式 继承关系和访问限定符  继承基类成员访问方式的变化  基类是 private 这种情况下无论以何种方式继承对于基类私有的部分在子类中都是不可见的这里的不可见指的是无法访问但仍然继承下来了只是无法使用而已。但是可以通过父类提供的方法进行间接的访问和使用。如图Print 方法在上图父类中有写如图 对于父类私有成员子类无论哪种继承都无法访问对于父类其他成员继承后被哪种访问限定符修饰取决于父类成员访问限定符和继承方式中权限小的那一个。 从继承这里我们也可以看出 protect 的意义正是因为有了继承protect 才有意义对于父类不想让外界访问也不想让子类访问的成员父类可以用 private 修饰对于父类不想让外界访问但想让子类访问的成员可以用 protect 修饰。 使⽤关键字 class 时默认的继承⽅式是private使⽤ struct 时默认的继承⽅式是public 基类和派生类对象赋值转换 派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片 或者切割。寓意把派生类中父类那部分切来赋值过去。 基类对象不能赋值给派生类对象。 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类 的指针是指向派生类对象时才是安全的。 子赋值给父向上转换可以 父赋值给子向下转换不可以 不会产生临时变量将子类中父亲的切片出来拷贝给父类 不能向下转换原因  根本问题基类指针/引用可能并不实际指向派生类对象 派生类通常比基类有更多成员错误向下转型会导致访问不存在的数据 基本继承示例代码 #includeiostream using namespace std;// 基类 Person class Person { public:void Print(){cout name: _name endl;cout age: _age endl;} protected:string _name peter; // 姓名int _age 18; // 年龄 };// 公有继承 Person class Student : public Person { protected:int _stuid; // 学号 };// 公有继承 Person class Teacher : public Person { protected:int _jobid; // 工号 };int main() {Person p;Student s;// 赋值兼容转换(切割/切片)Person p1 s; // 派生类对象赋值给基类对象Person rp s; // 基类引用引用派生类对象rp._name 张三; // 通过引用修改派生类中的基类部分Person* ptrp s; // 基类指针指向派生类对象ptrp-_name 李四; // 通过指针修改派生类中的基类部分return 0; } 学生和教师类继承了人这个类人这个类中所有的东西在学生和教师类里已经都具有了需要注意的是成员变量虽然继承过来了但各自的对象都独立有这样一份成员变量使用起来互不影响而对于继承过来的成员方法都使用同一份构造函数除外。 继承中的作用域 1. 在继承体系中基类和派生类都有独立的作用域。 2. 子类和父类中有同名成员子类成员将屏蔽父类对同名成员的直接访问这种情况叫隐藏 也叫重定义。在子类成员函数中可以使用 基类::基类成员 显示访问 3. 需要注意的是如果是成员函数的隐藏只需要函数名相同就构成隐藏。 4. 注意在实际中在继承体系里面最好不要定义同名的成员。 隐藏/重定义子类和父类有同名成员子类隐藏父类成员就近原则 重载同一个作用域 隐藏父子类域中函数名相同 派生类不能直接在初始化列表中初始化基类的成员变量必须通过基类的构造函数来初始化基类成员 成员隐藏(重定义)示例 class Person { public:void fun(){cout Person::func() endl;} protected:string _name 小李子; // 姓名int _num 111; // 身份证号 };// 派生类 Student class Student : public Person { public:// 隐藏了父类的 fun() 函数void fun(){cout Student::func() endl;}void Print(){cout 姓名: _name endl;cout _num endl; // 访问派生类的 _numcout Person::_num endl; // 访问基类的 _num} protected:int _num 999; // 学号 (隐藏了基类的 _num) };int main() {Student s;s.Print();s.fun(); // 调用派生类的 funs.Person::fun(); // 显式调用基类的 funreturn 0; } 派生类的默认成员函数  6个默认成员函数“默认”的意思就是指我们不写编译器会变我们自动生成一个那么在派生类 中这几个成员函数是如何生成的呢 1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认 的构造函数则必须在派生类构造函数的初始化列表阶段显示调用。 class person { public:person():_name(张三){}void Print(){cout person _name endl;} protected:int _age;string _name; };class student :public person { public:student():person(),_num(0){}void Print(){cout student _name _num endl;} protected:int _num; }; class Base { protected:int _x; public:Base(int x) : _x(x) {} // 基类构造函数初始化 _x };class Derived : public Base { public:// 通过 Base(x) 初始化基类成员 _xDerived(int x) : Base(x) {} // 错误不能在派生类初始化列表直接初始化 _x// Derived(int x) : _x(x) {} }; 2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。 3. 派生类的operator必须要调用基类的operator完成基类的复制。 4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。 5. 派生类对象初始化先调用基类构造再调派生类构造。 6. 派生类对象析构清理先调用派生类析构再调基类的析构。 7. 因为后续一些场景析构函数需要构成重写重写的条件之一是函数名相同(这个我们后面会讲 解)。那么编译器会对析构函数名进行特殊处理处理成destrutor()所以父类析构函数不加 virtual的情况下子类析构函数和父类析构函数构成隐藏关系。 成员函数示例 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 operator(const Person p){cout Person operator(const Person p) endl;if (this ! p)_name p._name;return *this;}~Person(){cout ~Person() endl;delete _pstr;} protected:string _name; // 姓名string* _pstr new string(111111111); };class Student : public Person { public:// 先调用基类构造函数再初始化派生类成员Student(const char* name 张三, int id 0):Person(name),_id(id){}// 拷贝构造Student(const Student s):Person(s) // 调用基类拷贝构造,_id(s._id){}// 赋值运算符Student operator(const Student s){if (this ! s){Person::operator(s); // 调用基类赋值运算符_id s._id;}return *this;}~Student(){// 子类析构完成后会自动调用父类析构cout *_pstr endl;delete _ptr;} protected:int _id;int* _ptr new int; };int main() {Student s1;Student s2(s1); // 调用拷贝构造Student s3(李四, 1);s1 s3; // 调用赋值运算符return 0; } Student s2(s1);  // 调用拷贝构造没写拷贝构造默认掉默认构造  派生类只用析构自己的就可以了 构造子类后编译器自动析构父类子可以用父父不可以用子  由于后边多态的问题析构函数函数名被特殊处理了统一处理成destructer 继承与友元 友元关系不能继承也就是说基类友元不能访问子类私有和保护成员 友元函数示例 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; }int main() {Person p;Student s;Display(p, s);return 0; } 前置声明 关键点说明前置声明的作用告诉编译器某个名称是类类型具体定义稍后出现。何时需要前置声明当类的名称在完整定义之前被使用如友元声明、函数参数。何时需要完整定义当需要实例化对象、访问成员、继承或计算类大小时。友元函数的特殊性友元声明中用到未定义的类时必须前置声明该类。 继承与静态成员  基类定义了static静态成员则整个继承体系里面只有一个这样的成员。无论派生出多少个子类都只有一个static成员实例 。 静态成员属于父类和派生类派生类中不会单独拷贝一份继承的是使用权 静态成员属于类本身不属于对象 无论是否涉及继承静态成员static 变量/函数都是类的全局共享成员不属于任何一个对象。所有对象包括基类和派生类的对象访问的是同一份静态成员。 派生类不会单独拷贝静态成员 静态成员不会被派生类复制而是直接继承访问权。 基类和派生类共享同一个静态成员。 静态成员继承示例 class Person { public:Person() { _count; } protected:string _name; // 姓名 public:static int _count; // 统计人的个数 };int Person::_count 0;class Student : public Person { protected:int _stuNum; // 学号 };class Graduate : public Student { protected:string _seminarCourse; // 研究科目 };int main() {Person p;Student s1;Student s2;cout Person::_count endl; // 输出3因为创建了3个对象return 0; } 复杂的菱形继承与菱形虚拟继承 单继承一个子类只有一个直接父类时称这个继承关系为单继承 多继承一个子类有两个或以上直接父类时称这个继承关系为多继承 菱形继承菱形继承是多继承的一种特殊情况。 菱形继承的问题从下面的对象成员模型构造可以看出菱形继承有数据冗余和二义性的问题。 在Assistant的对象中Person成员会有两份。 菱形继承代码  主要问题 1. 数据冗余问题 Assistant 对象会包含 两个 Person 子对象 一个来自 Student 继承路径 一个来自 Teacher 继承路径 这意味着 _name 和 _age 会被存储两次造成内存浪费 2. 二义性问题编译错误 当尝试直接访问 as._name 时编译器无法确定应该使用哪个路径的 _name as._name 张三; // 错误对成员_name的访问不明确 3. 必须明确指定访问路径 要解决二义性问题必须指定访问路径 as.Student::_name 张三; // 通过Student路径访问 // 或 as.Teacher::_name 张三; // 通过Teacher路径访问 虚继承(解决菱形继承问题) 通过virtual关键字来实现虚继承解决菱形继承问题 Person 成为虚基类Virtual Base Class。 Assistant 对象只包含一份 Person 子对象Student 和 Teacher 共享它。 _age 和 _name 不再冗余所有访问都指向同一个内存位置。 virtual要加在第一个会引起数据冗余的类上 class Person { public:string _name; // 姓名int _age; };class Student : virtual public Person { protected:int _num; //学号 };class Teacher : virtual public Person { protected:int _id; // 职工编号 };class Assistant : public Student, public Teacher { protected:string _majorCourse; // 主修课程 };int main() {Assistant as;as.Student::_age 18;as.Teacher::_age 30;as._age 19;return 0; } 虚拟继承解决数据冗余和二义性的原理 菱形继承 菱形继承有数据冗余问题 菱形虚拟继承  虚继承构造函数调用顺序 #includeiostream using namespace std;class A { public:A(const char* s) { cout s endl; // 打印构造信息}~A() {} };class B : virtual public A // 虚继承A { public:B(const char* sa, const char* sb) : A(sa) { // 初始化虚基类Acout sb endl;} };class C : virtual public A // 虚继承A { public:C(const char* sa, const char* sb) : A(sa) { // 初始化虚基类Acout sb endl;} };class D : public B, public C { public:// 注意虚基类A的初始化由D直接负责D(const char* sa, const char* sb, const char* sc, const char* sd) : A(sa), // 显式初始化虚基类实际最先执行B(sa, sb), // 初始化B此时不会重复构造AC(sa, sc) { // 初始化C此时不会重复构造Acout sd endl;} };int main() {// 场景1构造D对象菱形继承D* p new D(class A, class B, class C, class D);/* 输出顺序class A (虚基类A的构造)class B (B的构造)class C (C的构造)class D (D的构造)*/delete p;// 场景2单独构造B对象单继承B b(class A, class B);/* 输出顺序class A (虚基类A的构造)class B (B的构造)*/return 0; } 在虚继承中构造顺序遵循 虚基类最先构造无论它在初始化列表中的位置 非虚基类按声明顺序构造class D : public B, public C 则先 B 后 C 最后构造派生类自身 初始化列表顺序仅决定参数传递 如果交换 B 和 C 的参数如 C(sa, sc) 写在 B(sa, sb) 前面参数会正常传递但构造顺序不变 虚继承内存布局示例 class A { public:int _a; };// 虚继承 A class B : virtual public A { public:int _b; };// 虚继承 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 d;d._a 1; // 虚继承后只有一个_aB b;b._a 2;b._b 3;B* ptr b;ptr-_a; // 通过基类指针访问ptr d;ptr-_a; // 通过基类指针访问派生类对象return 0; } 每个虚继承类存储一个虚基表指针指向其虚基表。 虚基表存的是偏移量不是A的地址用来动态计算虚基类的位置 偏移量在切割和切片中需要 虚基表中偏移量里第一个为其他值进行了预留 d1和d2都直接指向这个数据不需要在内部开额外空间存重复数据 ptr-a不知道是B还是D的B和D中间可能搁这好几部分先通过虚基表指针找到虚基表再凭借偏移量可以找到A。 继承和组合  public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。 组合是一种has-a的关系。假设B组合了A每个B对象中都有一个A对象。    继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称 为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言在继承方式中基类的内部细节对子类可见 。继承一定程度破坏了基类的封装基类的改变对派生类有很 大的影响。派生类和基类间的依赖关系很强耦合度高。 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复 用(black-box reuse)因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系耦合度低。优先使用对象组合有助于你保持每个类被封装。 实际尽量多去用组合。组合的耦合度低代码维护性好。不过继承也有用武之地的有些关系就适合继承那就用继承另外要实现多态也必须要继承。类之间的关系可以用继承可以用组合就用组合。 特性继承组合关系is-ahas-a耦合度高低灵活性低编译时确定高运行时可替换代码复用白盒复用了解实现黑盒复用只使用接口多态支持支持间接支持通过接口基类/组件修改影响影响所有派生类影响范围有限适合场景需要多态/接口扩展代码复用/功能组合 //继承 class A { public:int _a; }; class B : public A { public:int _b; }; //组合 class A { public:int _a; }; class B : { public:A a();int _b; };多态 注意 只有成员函数才能virtual 对象里不存成员函数只有成员变量 不同对象传过去调用不同函数 多态调用看的指向的对象 普通对象看当前类型 派生类的重写虚函数可以不virtual 协变返回值可以不同但返回值必须是父子关系指针和引用 虚函数三同函数名参数列表返回值类型 多态的概念 多态是面向对象编程的三大特性之一封装、继承、多态它允许不同类的对象对同一消息做出不同的响应 多态分为 编译时多态静态多态通过函数重载和模板实现 运行时多态动态多态通过虚函数和继承实现 我们主要了解运⾏时多态。要实现运行时多态必须满足以下条件 继承关系存在基类和派生类 虚函数基类中使用virtual声明函数 指针/引用通过基类指针或引用调用虚函数 多态的定义和实现  多态的构成条件 多态是在不同继承关系的类对象去调用同一函数产生了不同的行为。比如Student继承了 Person。Person对象买票全价Student对象买票半价。 1.调用函数是重写的虚函数 2.基类指针或引用调用子类指针只能指向子类虚表只有子类虚函数父类没有子类成员变量或方法会引发内存错误 基础多态代码 #include iostream using namespace std;// 基类 class Person { public:virtual void BuyTicket() { cout 买票-全价 endl; } };// 派生类 class Student : public Person { public:virtual void BuyTicket() override { cout 买票-半价 endl; } };// 多态调用函数 void Func(Person p) {p.BuyTicket(); // 多态调用 }int main() {cout ----- 基础多态演示 ----- endl;Person Mike;Student Johnson;Func(Mike); // 输出: 买票-全价Func(Johnson); // 输出: 买票-半价return 0; } “调用函数是重写的虚函数” 体现在 Student 类中 virtual void BuyTicket() override它重写了基类 Person 的虚函数 BuyTicket()。 运行时通过 Person p 调用 p.BuyTicket() 时会根据实际对象类型Person 或 Student决定调用哪个版本多态。 “基类指针或引用调用” 体现在 Func(Person p) 的参数是基类引用且 p.BuyTicket() 通过该引用调用虚函数。 如果改为 Func(Person p)传值而非引用则失去多态始终调用 Person::BuyTicket()。 虚函数 虚函数即被virtual修饰的类成员函数称为虚函数。 virtual void BuyTicket() { cout 买票-全价 endl; } 虚函数的重写覆盖 派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的 返回值类型、函数名字、参数列表完全相同)称子类的虚函数重写了基类的虚函数。派生类中virtual可以不写但是父类必须写因为如果父类不写这个函数就不是虚函数了 // 派生类 class Student : public Person { public:virtual void BuyTicket() override { cout 买票-半价 endl; } }; 虚函数重写的两个例外 协变 基类与派生类虚函数返回值类型不同 派生类重写基类虚函数时与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指 针或者引用派生类虚函数返回派生类对象的指针或者引用时称为协变。 协变仅适用于指针或引用类型 Student::f() 返回 int*不合法int* 与 A* 无继承关系不满足协变规则。  class A{}; class B : public A {}; class Person { public:virtual A* f() {return new A;} }; class Student : public Person { public:virtual B* f() {return new B;} };析构函数的重写 基类与派生类析构函数的名字不同 如果基类的析构函数为虚函数此时派生类析构函数只要定义无论是否加virtual关键字 都与基类的析构函数构成重写虽然基类与派生类析构函数名字不同。虽然函数名不相同 看起来违背了重写的规则其实不然这里可以理解为编译器对析构函数的名称做了特殊处 理编译后析构函数的名称统一处理成destructor。 确保通过基类指针删除派生类对象时正确调用派生类析构 内存释放过程 通过虚表找到 Student::~Student() 执行派生类析构 输出~Student() 释放 ptr 指向的数组 自动调用基类析构 Person::~Person() 若不使用虚析构函数 delete p 只会调用 Person::~Person() 导致 ptr 内存泄漏约40字节 防止基类new派生类析构基类 class Person { public:virtual void BuyTicket() { cout 买票-全价 endl; }virtual ~Person() { cout ~Person() endl; } };class Student : public Person { public:virtual void BuyTicket() { cout 买票-半价 endl; }~Student() {cout ~Student() endl;delete[] ptr;}protected:int* ptr new int[10]; }; int main() {//Person p;//Student s;Person* p new Person;p-BuyTicket();delete p;p new Student;p-BuyTicket();delete p; // p-destructor() operator delete(p)// 这里我们期望p-destructor()是一个多态调用而不是普通调用return 0; } 禁止在派生类中释放基类资源 基类析构函数已经负责其自身资源的释放派生类不应干涉。 层级化资源管理 基类管理基类的资源 派生类管理派生类新增的资源 像堆叠的俄罗斯套娃各层管好自己的部分 C11 override 和 final C对函数重写的要求比较严格但是有些情况下由于疏忽可能会导致函数 名字母次序写反而无法构成重载而这种错误在编译期间是不会报出的只有在程序运行时没有 得到预期结果才来debug会得不偿失因此C11提供了override和final两个关键字可以帮 助用户检测是否重写。 final修饰虚函数表示该虚函数不能再被重写 final 的作用 当用于虚函数时如 virtual void Drive() final表示禁止派生类重写该函数。 当用于类时如 class Benz final表示禁止其他类继承 Benz。 class Car { public:virtual void Drive() final {} }; class Benz :public Car { public:virtual void Drive() {cout Benz-舒适 endl;} };override: 检查派生类虚函数是否重写了基类某个虚函数如果没有重写编译报错。  class Car{ public:virtual void Drive(){} }; class Benz :public Car { public:virtual void Drive() override {cout Benz-舒适 endl;} }; 设计不希望被继承类 1.基类构造函数私有 (C98) 1通过将构造函数设为私有阻止派生类实例化因为派生类构造时需要调用基类构造函数。 2派生类析构时需要调用基类析构函数私有化析构函数可阻止继承。  2.基类加一个final C11 重载、覆盖(重写)、隐藏(重定义)的对比 抽象类 概念 在虚函数的后面写上 0 则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类也叫接口类抽象类不能实例化出对象。派生类继承后也不能实例化出对象只有重写纯虚函数派生类才能实例化出对象。纯虚函数规范了派生类必须重写另外纯虚函数更体现出了接口继承。 纯虚函数和抽象类 func 为纯虚函数所以 A 为抽象类。抽象类不允许实例化对象。 class A { public:virtual void fun() 0; }; 虽然抽象类不可以实例化出对象但是我们可以写一个类来继承它并重写里面的纯虚函数这时这个子类是可以实例化出对象并可以调用重写后的方法的。  class A { public:virtual void fun() 0; }; class B:public A { public:virtual void fun(){cout endl;} }; 多态的原理 练习 class A { public:virtual void func(int val 1) { cout A- val endl; }virtual void test() { func(); }//隐含this指针 };class B : public A { public:virtual void func(int val 0) { cout B- val endl; } };int main() {B* p new B;p-test();return 0; } p 指针调 test 方法的时候会去 A 类中调用A 类的 test 方法中又调用了 func 方法类的成员函数是有一个隐含的 this 指针的这个 func 方法就是通过这个 this 指针调用的这个 this 指针类型是 A*基类指针func 方法是重写虚函数满足了多态的条件。 test()是从A继承的调用func()时使用A的默认参数1 但实际调用的是B类的func()实现因为p指向B对象 多态的条件 父类的指针和引用 虚函数的重写 为什么不能是子类指针或引用为什么不能是父类对象 子类赋值给父类对象切片不会拷贝虚表如果拷贝虚表那么父类对象虚表中时父类虚函数还是子类就不确定了 派生类虚表先将父类拷贝一份再将修改的进行覆盖 多态的底层 虚函数表vtable Student 对象会包含一个虚函数表包含 重写的 BuyTicket()半价版本 继承的 Func1()、Func2() 新增的 Func3()应该有的编译器优化了 访问控制 Func3() 是 private无法通过基类指针调用但仍在虚表中存在。vs优化了 _a 在基类中是 public建议改为 protected避免直接暴露。 class Base { public:virtual void Func1(){cout Base::Func1() endl;}virtual void Func2(){cout Base::Func2() endl;}void Func3(){cout Base::Func3() endl;} private:int _b 1; };class Derive : public Base { public:virtual void Func1(){cout Derive::Func1() endl;} private:int _d 2; }; int main() {Base b;Derive d;return 0; } 1.派生类对象d中也有一个虚表指针d对象由两部分构成一部分是父类继承下来的成员虚表指针也就是存在部分的另一部分是自己的成员。 2. 基类b对象和派生类d对象虚表是不一样的这里我们发现Func1完成了重写所以d的虚表中存的是重写的Derive::Func1所以虚函数的重写也叫作覆盖覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法覆盖是原理层的叫法。 3. 另外Func2继承下来后是虚函数所以放进了虚表Func3也继承下来了但是不是虚函 数所以不会放进虚表。 4. 虚函数表本质是一个存虚函数指针的指针数组一般情况这个数组最后面放了一个nullptr。 5. 总结一下派生类的虚表生成 a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。 6. 虚函数存在哪的虚表存在哪的 答虚函数存在虚表虚表存在对象中。 注意上面的回答的错的。虚表存的是虚函数指针不是虚函数虚函数和普通函数一样的都是存在代码段的只是他的指针又存到了虚表中。另外对象中存的不是虚表存的是虚表指针。vs下虚表存在代码段 内存分布 内存问题虚函数表指针  虚函数表也简称虚表 虚函数本质放在代码段 虚表里存的是虚函数的地址 虚函数表指针 (vptr) 当类包含虚函数时编译器会自动添加一个 虚函数表指针 (vptr)指向该类的虚函数表 (vtable)。 在 64 位系统 下指针的大小是 8 字节。 因此Base 类至少占用 8 字节用于存储 vptr。 成员变量 _b _b 是一个 char 类型占用 1 字节。 但由于 内存对齐alignment编译器会在 _b 后面填充 7 字节使得 vptr 和 _b 整体对齐到 8 字节 边界优化访问速度。 组成部分大小64 位大小32 位虚函数表指针8 字节4 字节char _b1 字节1 字节填充字节7 字节3 字节总大小16 字节8 字节 为什么不是9字节 如果 sizeof(Base) 是 9 字节vptr 8  _b 1那么当 Base 对象存储在数组中时第二个对象的 vptr 会错位起始地址不是 8 的倍数导致 性能下降 或 崩溃某些 CPU 架构要求指针地址对齐。 因此编译器会自动填充字节使类的大小是 最大对齐单位8 字节的整数倍。  多态实现指向父类调父类指向子类调子类  指向父类在父类虚函数表中找到父类地址找父类虚表 指向子类在子类虚函数表中找到子类地址切片后看到的还是父类对象但是是子类里的父类他的虚表已经被覆盖了找到的是子类地址 函数调用栈帧才会去开空间 同类型对象共用虚表虚表不在栈上 没有独立函数不建立自己的虚表 C 的多态机制、对象内存布局和继承关系 class Person { public:virtual void BuyTicket() { cout 买票-全价 endl; }virtual void Func1() {cout Person::Func1() endl;}virtual void Func2() {cout Person::Func2() endl;}//protected:int _a 0; };class Student : public Person { public:virtual void BuyTicket() { cout 买票-半价 endl; }private:virtual void Func3(){//_b;cout Student::Func3() endl;} protected:int _b 1; };void Func(Person p) {p.BuyTicket(); }void test() {Person ps1;Student st1; }int main() {Person ps;Student st;st._a 10;ps st;Person* ptr st;Person ref st;test();return 0; }由表可推断虚表是储存在常量区的。 x86环境运行 多继承  class Base1 { public:virtual void func1() { cout Base1::func1 endl; }virtual void func2() { cout Base1::func2 endl; } private:int b1; }; class Base2 { public:virtual void func1() { cout Base2::func1 endl; }virtual void func2() { cout Base2::func2 endl; } private:int b2; }; class Derive : public Base1, public Base2 { public:virtual void func1() { cout Derive::func1 endl; }virtual void func3() { cout Derive::func3 endl; } private:int d1; }; int main() {Derive d;Base1* ptr1 d;ptr1-func1();Base2* ptr2 d;ptr2-func1();Derive* ptr3 d;ptr3-func1();return 0; } 对象内存分布和虚函数表布局 base1  vfptr: 指向Derive类为Base1部分维护的虚表地址0x00007ff7179bbd30 [0]: 重写的Derive::func1()地址0x00007ff7179b1348 [1]: 继承的Base1::func2()地址0x00007ff7179b1334 b1: 未初始化的int成员值-858993460是Debug模式的填充值0xCCCCCCCC base2 vfptr: 指向Derive类为Base2部分维护的虚表地址0x00007ff7179bbd38 [0]: Thunk函数地址0x00007ff7179b1230用于调整this指针并跳转到Derive::func1() [1]: 继承的Base2::func2()地址0x00007ff7179b10eb b2: 同样未初始化的int成员 总结 多重继承的虚表 每个基类Base1/Base2有独立的虚表指针 Derive重写的func1()在Base1虚表中直接替换在Base2虚表中通过Thunk调用 Thunk函数 当通过Base2*调用func1()时需要调整this指针指向Base2子对象的起始位置 Thunk会先修正this指针减去Base2在Derive中的偏移量再跳转到Derive::func1() 未初始化值 -858993460即0xCCCCCCCC是Debug模式的填充值用于标记未初始化的栈内存 两种多态 静态(编译时)的多态函数重载 动态(运行时)的多态继承虚函数重写实现的多态 int main() {//静态多态编译时多态int i 1;double d 1.1;cout i endl;cout d endl;//动态多态运行时多态Person ps;Person* ptr ps;ps.BuyTicket();ptr-BuyTicket();return 0; }静态 实现方式函数重载 operator 针对不同的参数类型int和double有不同的实现 在编译时就能确定调用哪个版本的函数 特点 通过函数重载实现 编译时确定具体调用哪个函数 不需要虚函数或继承关系 效率高无运行时开销 动态  实现方式 继承关系 虚函数重写 通过基类指针或引用调用 特点 通过虚函数表(vtable)实现 运行时确定调用哪个函数 需要继承和虚函数 有一定的运行时开销查虚函数表 两种多态的关键区别 特性静态多态动态多态实现方式函数重载、模板虚函数、继承确定时机编译时运行时性能高效无额外开销有一定开销查虚函数表灵活性较低编译时确定高运行时可改变行为典型应用运算符重载、函数重载接口设计、多态对象处理 虚基表和虚函数表区别 特性虚基表 (Virtual Base Table)虚函数表 (Virtual Function Table, vtable)用途解决虚继承中的共享基类偏移问题实现运行时多态管理虚函数调用触发条件当类使用virtual继承时如class D : virtual public B当类包含virtual成员函数时存储内容存储虚基类相对于当前对象的偏移量存储虚函数的地址指向实际实现的函数指针指针名称vbptr虚基表指针vfptr虚函数表指针内存位置位于对象内存布局的起始或相关位置通常位于对象内存布局的起始位置编译器生成逻辑确保多个派生类共享同一虚基类实例时能正确访问基类成员确保通过基类指针/引用调用时能正确跳转到派生类实现是否依赖运行时是运行时计算偏移量是运行时查表确定函数地址典型场景菱形继承如B ← D1 ← D和B ← D2 ← DB为虚基类基类定义虚函数派生类重写如Shape::draw()访问开销额外间接寻址通过vbptr找到偏移量再访问基类一次指针解引用通过vfptr跳转到函数地址调试查看方式在调试器中观察vbptr和偏移量在调试器中观察vfptr和函数地址列表
http://www.dnsts.com.cn/news/232560.html

相关文章:

  • 云南旅游网站开发公司oa管理系统是什么
  • 南开网站建设优化seo平面设计师服务平台
  • 贵阳手机网站开发福安城乡建设与规划局网站
  • 企业建设网站需要什么资料v2017网站开发
  • 现在网站都是拿什么软件做的怎么做各大视频网站的会员代理
  • seo整站优化外包服务wordpress写文章出现排版乱
  • 有什么网站做图片宣传海报网络规划设计师视频百度云
  • 苏州网站建设网站优化外包加工网官网下载
  • 住房和城乡建设部中国建造师网站建一个网页网站
  • 团支书登录智慧团建网站可以做go分析的网站
  • 牡丹江住房和城乡建设厅网站先做它个天猫网站
  • 惠州市建设局建工办网站申请商标官网
  • 网站建设 规范app软件开发费用多少
  • 罗湖商城网站设计公司wordpress内容分享微信
  • 个人网站建立策划书前言wordpress教程 pdf下载
  • 中企动力做网站一次性付款万网创始人张向东
  • 设计类招聘网站WordPress配置阿里云cdn
  • 做网站却不给客户源代码网站建设及维护费用
  • 建设银行招聘官方网站简单网页设计作业
  • 网站制作及排名优化wordpress中文cms主题模板下载
  • 莞城营销型网站建设wordpress 文章不显示图片
  • 用dw做旅游的网站的设计wordpress怎么接入借口
  • 涪陵网站设计网站建设1
  • 谢岗网站仿做返利网站做鹊桥推广
  • 第三方网站开发优缺点网店营销策略
  • 安阳百度网站制作多少钱网站收录不好
  • 公司网站建设制作难么怎样注册公司流程
  • 网站如何做的有特色开发一个小程序大约需要多少钱
  • 做设计灵感的网站html5软件官网
  • 网站建设中的网页布局主要内容wordpress 自动评论软件