如何建设自己的网站 知乎网站开发的prd 怎么写

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

如何建设自己的网站 知乎,网站开发的prd 怎么写,网站建设科技风,网络营销案例分析论文目录 一、多态的概念 二、多态的定义及实现 #x1f31f;多态的构成条件 #x1f31f;虚函数 #x1f31f;虚函数的重写 #x1f320;小贴士#xff1a; #x1f31f;C11 override 和 final #x1f31f;重载、重写#xff08;覆盖#xff09;、重定义#xf…目录 一、多态的概念 二、多态的定义及实现 多态的构成条件 虚函数 虚函数的重写 小贴士 C11 override 和 final 重载、重写覆盖、重定义隐藏的对比 三、抽象类 概念 接口继承和实现继承 四、多态的原理 虚函数表 多态的原理  动态绑定与静态绑定 小贴士 一、多态的概念 多种形态去完成某个行为当不同的对象去完成时会产生出不同的状态。 二、多态的定义及实现 多态的构成条件 多态是在不同关系的类对象去调用同一函数产生了不同的行为。比如Student继承了Person。Person对象买票全价Student对象买票半价。 在继承中构成多态的两个条件指向谁调用谁 1、必须通过基类的指针或者引用调用虚函数 2、被调用的函数必须是虚函数对象都不行且派生类必须对基类的虚函数进行重写 虚函数 即 被 virtual 修饰的类成员函数称为虚函数。 class A { public:virtual void test(){ cout A : test() endl;} };
虚函数的重写 虚函数的重写覆盖派生类中有一个跟基类完全相同的虚函数即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同。 #includeiostreamusing namespace std;class Person { public:virtual void BuyTicket(){cout 买票–全价 endl;} };class Student : public Person { public:virtual void BuyTicket(){cout 买票–半价 endl;} };void Func(Person p) {p.BuyTicket(); }int main() {Person p;Student s;Func(p);Func(s);return 0; } 注意 在重写基类虚函数时派生类的虚函数在不加 virtual 关键字时虽然也可以构成重写因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性但是改种写法不是很规范不建议这样使用。 ✨函数重写的两个例外 1协变基类与派生类虚函数返回值类型不同 派生类重写虚函数时与基函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用派生类虚函数返回派生类对象的指针或者引用时称为协变。 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;} }; 2析构函数的重写基类与派生类析构函数的名字不同  如果基类的析构函数为虚函数此时派生类析构函数只要定义无论是否加 virtual 关键字都与基类的析构函数构成重写虽然基类与派生类析构函数名字不同。函数名不相同看起来违背了重写的规则其实不然在这可以理解为编译器对析构函数的名称做了特殊处理编译析构函数的名称统一处理成 destructor 。 class Person { public:virtual ~Person(){cout ~Person() endl;} };class Student : public Person { public:virtual ~Student(){cout ~Student() endl;} };//只有派生类Student的析构函数重写了Person的析构函数 //下面的delete对象调用析构函数才能构成多态 //才能保证p1和p2指向的对象正确的调用析构函数 int main() {Person* p1 new Person;Person* p2 new Student;delete p1;delete p2;return 0; } 小贴士 1 多态调用看指向对象类型指向谁调用谁的虚函数        普通调用看调用者的类型调用调用者的函数 2 重写是一种特殊的隐藏        隐藏不符合多态就是隐藏。 3对于普通对象写不写成虚函数都不受影响 class Person { public:virtual ~Person() { cout ~Person() endl;} };class Student : public Person { public:virtual ~Student() {cout ~Student() endl;} };int main() {Student s;//加不加 virtaul 普通对象都不受影响 //析构先调用子类析构在调用父类析构//所以说在子类/派生类结束了以后会自动调用父类的析构//派生类里面有一个父类对象return 0; } 4为什么析构函数一定建议设计成虚函数 只有派生类的析构函数重写了基类的析构函数delete对象调用析构函数才能构成多态才能保证指向的对象能正确的调用析构函数。 //加 virtual 变成多态此时指向子类调用子类指向父类调父类 //析构是先析构子类再自动调用父类的析构 //如果不加虚函数就是直接调用父类 // 这样子的话子类的析构就没有调用到就会产生内存泄露 class Person { public:virtual ~Person(){cout ~Person() endl;} };class Student : public Person { public:virtual ~Student(){delete _ptr;cout ~Student() _ptr endl;} protected:int* _ptr new int[10]; }; //内存泄漏 //没有调到派生类的析构函数 _ptr 这个指针没有被释放int main() {// 只有派生类Student的析构函数重写了Person的析构函数下面的delete对象调用析构函//数才能构成多态才能保证p1和p2指向的对象正确的调用析构函数。Person p1 new Student;//指向子类调用子类的析构最后默认调用父类的析构delete p1;Person* p2 new Person;//指向父类调用父类的析构delete p2; //但是不会自动调用子类的析构就会有可能参数子类的内存泄漏//因此析构函数一定建议设计成虚函数在继承里面尤其是基类的析构函数//因为只有当基类的析构函数设计成虚函数派生类不管加不加virtual派生类都构成了重写//构成了重写后才能正常去选择调用子类/父类的析构才能符合指向谁调用谁return 0; } C11 override 和 final C对函数重写的要求比较严格这两个关键字可以帮助用户检测是否重写。 1 final修饰虚函数表示该虚函数不能再被重写。 class Person { public:virtual ~Person() final //不想被完成重写此时子类就不能被重写{cout ~Person() endl;} };class Student : public Person { public:~Student() //报错不能完成重写{delete _ptr;cout ~Student() _ptr endl;} protected:int* _ptr new int[10]; };int main() {Person* p1 new Student;delete p1;Person* p2 new Person;delete p2;return 0; } 2 override 检查派生类虚函数是否重写了基类的某个函数如果没有重写编译报错。 class Person { public:~Person()//没有完成重写{cout ~Person() endl;} };class Student : public Person { public:~Student() override //检查派生类是否完成重写没有完成就报错{delete _ptr;cout ~Student() _ptr endl;} protected:int* _ptr new int[10]; };int main() {Person* p1 new Student;delete p1;Person* p2 new Person;delete p2; return 0; }小贴士 1 final可修饰类修饰的类叫最终类语法规定这个类不能被继承继承就会报错。 class A final {};class B : public A//报错 {}; 2把基类的构造函数私有子类就生不成对象 class A { private:A(){} };class B : public A//报错 {};int main() {B bb;return 0; }此时可以把基类的构造函数写成静态成员就可以进行构造 class A { public:static A CreateObj()//此时就可以用构造{return A();} private:A(){} };class B : public A {};int main() {//B bb;A::CreateObj();return 0; } 重载、重写覆盖、重定义隐藏的对比 三、抽象类 概念 在虚函数的后面写上 0 则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类也叫接口类抽象类不能实例化出对象。派生类继承后也不能实例化出对象只有重写纯虚函数派生类才能实例化出对象。纯虚函数规范了派生类必须重写。另外纯虚函数更体现出了接口继承。 在实践中一个类型在现实没有实体对象不想实例化出对象设计成抽象类。 class Car { public:virtual void Drive() 0; }; //当父类是纯虚函数派生类不想继承时要重写虚函数 class Benz :public Car { public:virtual void Drive()//重写虚函数{cout Benz-舒适 endl;} }; class BMW :public Car { public:virtual void Drive()//重写虚函数{cout BMW-操控 endl;} }; int main() {Benz bz;//不想继承父类的纯虚函数时在派生类中重写虚函数Car* pBenz new Benz;pBenz-Drive();Car* pBMW new BMW;pBMW-Drive();return 0; } 接口继承和实现继承 普通函数的继承是一种实现继承派生类继承了基类函数可以使用函数继承的是函数的实现。虚函数的继承是一种接口继承派生类继承的是基类虚函数的接口目的是为了重写达成多态继承的是接口。所以如果不实现多态不要把函数定义成虚函数。 四、多态的原理 虚函数表 class Base { public:virtual void Func1(){cout Func1() endl;} private:int _b 1;
};int main() {Base b;cout sizeof(Base) endl;return 0; } • 在x86的环境下通过观察测试我们发现 b 的对象是 8bytes 除了_b成员还多一个 _vfptr 放在对象的前面注意有些平台可能放在对象的最后面这跟平台有关对象中的这个 _vfptr 我们叫做虚函数表指针v为virtualf为 function。一个含有虚函数的类中至少都有一个虚函数表指针因为虚函数的地址要被放到对象的最后面这个跟平台有关。 • 虚函数表 本质是一个函数指针数组。 • 一个含有虚函数的类中都至少都有一个虚函数表指针因为虚函数的地址要被放到虚函数表中虚函数表也简称虚表。那派生类中这个表放了些什么呢咱接着往下看 多态的原理  // 针对上面的代码我们做出以下改造 // 1.我们增加一个派生类Derive去继承Base // 2.Derive中重写Func1 // 3.Base再增加一个虚函数Func2和一个普通函数Func3 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; };void Func1(Base* p) {//运行时绑定/动态绑定//运行时去虚表里面找到函数的地址确认函数的地址所以指向谁调用谁p-Func1();//有虚函数放进虚表里面//普通的调用//编译时绑定/静态绑定 //即编译时用函数名找如果只有声明就链接的时候找如果直接就有定义就在编译的时候用这个函数名在符号表里面找这个函数的地址p-Func3();//没有虚函数不放进虚表 }int main() {Base b;Derive d;Func1(b);Func1(d);//Func1(new Base);//Func1(new Derive);//指向的是子类当中父类的那一部分return 0; } •  派生类对象d中也有一个虚表指针d对象由两部分构成一部分是父类继承下来的成员虚表指针也就是存在部分的另一部分是自己的成员。  •  基类b对象和派生类d对象虚表是不一样的这里我们发现Func1完成了重写所以d的虚表中存的是重写的Derive::Func1所以虚函数的重写也叫作覆盖覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法覆盖是原理层的叫法。  •  另外Func2继承下来后是虚函数所以放进了虚表Func3也继承下来了但是不是虚函 数所以不会放进虚表。  •  虚函数表本质是一个存虚函数指针的指针数组一般情况这个数组最后面放了一个nullptr。  •  总结一下派生类的虚表生成     a.先将基类中的虚表内容拷贝一份到派生类虚表中     b.如果派生类重写了基类中某个虚函数用派生类自己的虚函数覆盖虚表中基类的虚函数       c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。 注意虚表存的是虚函数指针不是虚函数虚函数和普通函数一样的都是存在代码段的只是 他的指针又存到了虚表中。另外对象中存的不是虚表存的是虚表指针。 动态绑定与静态绑定 •  满足多态以后的函数调用不是在编译时确定的是运行起来以后到对象中去找的不满足多态的函数调用时编译时就确认好的。 •  静态绑定又称为前期绑定(早绑定)在程序编译期间确定了程序的行为也称为静态多态 比如函数重载 •  动态绑定又称后期绑定(晚绑定)是在程序运行期间根据具体拿到的类型确定程序的具体 行为调用具体的函数也称为动态多态。 小贴士 1指定类域后还能实现多态吗 不能。 原因编译器在编译时指定类域就不会识别成多态调用就不会按照运行时绑定的方式去生成指令此时就跟指向的对象没有关系 2为什么虚函数要放到虚表  运行的时候要去虚表里面找对应的虚函数如果完成了重写就可以达到指向那个对象调用哪个对象 3普通函数为什么不用放到虚表 虚表时运行时才用的普通函数是在编译的时候通过函数名去确定地址所以就支持了函数重载按照函数名修饰规则参数不同修饰出来的函数名就不相同就可以找到对应的地址 函数名的地址有可能是在编译的时候找也有可能是在链接的时候找 在编译的时候当前文件就用定义编译好的时候当前文件就有这个函数的地址编译就可以变成call的地址 在链接的时候只有声明定义在其他.cpp编译的时候时匹配的再链接时才会拿修饰的函数名去其他文件的符号表里面找找不到就会链接报错。 小贴士 1 inline函数可以是虚函数吗      可以不过编译器就忽略了inline属性这个函数就不再是Inline因为虚函数要放到虚表中去。 2 静态成员可以是虚函数吗        不能因为静态成员函数没有this指针使用 类型::成员函数 的调用方法无法访问虚函数表所以静态成员函数无法放进虚函数表。 3 构造函数可以是虚函数吗       不能因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。 4析构函数可以是虚函数吗什么场景下析构函数是虚函数       可以并且最好把基类的析构函数定义成虚函数。 5对象访问普通函数快还是虚函数快      首先如果是普通对象是一样快的。      如果是指针对象或者引用对象则调用普通函数快因为构成多态运行时调用虚函数需要到虚函数表中去查找。 6虚函数表是在什么阶段生成的存在哪       虚函数表是在编译阶段就生成的一般情况下存在代码段常量区的。 7什么是抽象类抽象类的作用       抽象类强制重写了虚函数另外抽象类体现了接口继承关系。 如若对你有帮助记得点赞、收藏、关注哦 若有误望各位在评论区留言或者私信我 指点迷津谢谢^ ^ ~