山东省城乡住房和建设厅网站首页,wordpress回收站在哪里,网站导航栏怎么做简单,wordpress前端页面模板目录 前言
1.多态的概念
2.多态的定义及实现
2.1多态的构成条件
2.1.1重要条件
2.1.2 虚函数
2.1.3 虚函数的重写/覆盖
2.1.4 选择题
2.1.5 虚函数其他知识
协变#xff08;了解#xff09; 析构函数的重写
override 和 final关键字
3. 重载#xff0c;重写…目录 前言
1.多态的概念
2.多态的定义及实现
2.1多态的构成条件
2.1.1重要条件
2.1.2 虚函数
2.1.3 虚函数的重写/覆盖
2.1.4 选择题
2.1.5 虚函数其他知识
协变了解 析构函数的重写
override 和 final关键字
3. 重载重写隐藏的对比 4.纯虚函数和抽象类
结束语 前言 在前面我们对C的封装继承等特性都有了了解和学习接下来我们将对C的第三大特性-多态进行认识和掌握。内容分为来两大部分第一个是对多态的认识和运用第二大部分是对多态原理的了解和扩展。 1.多态的概念
多态Polymorphism是面向对象编程OOP中的一个核心概念它指的是同一个行为具有多个不同表现形式或形态的能力。在编程中多态通常通过继承inheritance和接interfaces来实现。
以下是多态的几个主要方面 编译时多态静态多态这是在编译时确定的多态性通常通过函数重载function overloading和模板templates来实现。编译器根据函数的参数类型或数量来决定调用哪个函数。 运行时多态动态多态这是在程序运行时确定的多态性主要通过虚函数virtual functions和继承来实现。在运行时根据对象的实际类型来调用相应的成员函数。
之所以叫编译时多态是 因为他们实参传给形参的参数匹配是在编译时完成的我们把编译时一般归为静态运行时归为动态。 运行时多态具体点就是去完成某个行为(函数)可以传不同的对象就会完成不同的行为就达到多种 形态。比如买票这个行为当普通人买票时是全价买票学生买票时是优惠买票(5折或75折)军人买票时是优先买票。再比如同样是动物叫的一个行为(函数)传猫对象过去就是”(^ω^)喵“传狗对象过去就是汪汪。 多态的关键特性包括
继承子类继承父类的属性和行为可以对这些行为进行重写override。虚函数在基类中声明为虚的成员函数可以在派生类中被重写使得通过基类指针或引用调用函数时能够根据对象的实际类型来调用相应的函数版本。虚函数表用于实现运行时多态的数据结构它存储了虚函数的地址使得程序能够在运行时确定调用哪个函数。向上转型将派生类对象的引用或指针转换为基类类型的引用或指针这是多态实现的基础。 2.多态的定义及实现 2.1多态的构成条件 多态是一个继承关系的下的类对象去调用同一函数产生了不同的行为。比如Student继承了 Person。Person对象买票全价Student对象优惠买票。 2.1.1重要条件 被调用的函数必须是虚函数 指针或者引用调用虚函数 说明要实现多态效果第一必须是基类的指针或引用因为只有基类的指针或引用才能既指向派生 类对象第二派生类必须对基类的虚函数重写/覆盖重写或者覆盖了派生类才能有不同的函数多 态的不同形态效果才能达到。 2.1.2 虚函数 类成员函数前面加virtual修饰那么这个成员函数被称为虚函数。注意⾮成员函数不能加virtual修 饰。 class Person {
public:virtual void BuyTicket() {cout 买票全额 endl;}
}; 2.1.3 虚函数的重写/覆盖 虚函数的重写/覆盖 派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表类型数量完全相同)称派生类的虚函数重写了基类的虚函数。 注意在重写基类虚函数时派生类的虚函数在不加virtual关键字时虽然也可以构成重写(因为继承 后基类的虚函数被继承下来了在派生类依旧保持虚函数属性)但是该种写法不是很规范不建议这样 使用不过在考试选择题中经常会故意买这个坑让判断是否构成多态。 #define _CRT_SECURE_NO_WARNINGS
#include iostream
using 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();
}
//指针调用
void func1(Person* p) {p-BuyTicket();// 这⾥可以看到虽然都是Person指针Ptr在调⽤BuyTicket// 但是跟ptr没关系⽽是由ptr指向的对象决定的。
}
int main() {Person p1;Student s1;Person* p2 new Person();Student* s2 new Student();func(p1);func(s1);p1.BuyTicket();s1.BuyTicket();func1(p1);func1(s1);p2-BuyTicket();s2-BuyTicket();return 0;
} void func(Student p) { p.BuyTicket(); } //指针调用 void func1(Student* p) { p-BuyTicket(); } 如果改成Student,就会出问题就不是多态了也就不能传Person对象了。
#include iostream
using namespace std;class Pet {
public:virtual void eat() const{cout Eat food endl;}
};
class Dog : public Pet{
public:virtual void eat() const {cout Dog eats meat! endl;}
};
class Cat :public Pet {
public:virtual void eat()const {cout Cat eats fish! endl;}
};
void func(const Pet p) {p.eat();
}
int main() {Pet p;Dog g;Cat c;func(p);func(g);func(c);return 0;
} 上述是宠物的一个多态实现。
这里我们测试一下基类函数不加virtual会怎样 class Pet { public: void eat() const{ cout Eat food endl; } }; 我们会发现多态效果没有实现所以一定要加上virtual.
2.1.4 选择题
下面程序输出结果是什么B
A: A-0 B: B-1 C: A-1 D: B-0 E: 编译出错 F: 以上都不正确 class A { public: virtual void func(int val 1){ std::coutA- val std::endl;} virtual void test(){ func();} }; class B : public A { public: void func(int val 0){ std::coutB- val std::endl; } }; int main(int argc ,char* argv[]) { B*p new B; p-test(); return 0; } B* p new B; 创建了一个 B 类型的对象并通过基类指针 p 指向它。p-test(); 调用了 A 类的 test 方法因为 B 类没有重写 test 方法。在 A 类的 test 方法中func(val) 被调用没有指定 val 的值因此它使用 A 类 func 方法的默认参数 1。由于 func 是虚函数并且 p 指向一个 B 类型的对象所以 B 类的 func 方法被调用接收到的参数是 1。
2.1.5 虚函数其他知识
协变了解
派生类重写基类虚函数时与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用派生类虚函数返回派生类对象的指针或者引用时称为协变。 #define _CRT_SECURE_NO_WARNINGS
#include iostream
using namespace std;class A {};
class B : public A {};
class Person {
public:virtual A* BuyTicket(){cout 买票-全价 endl;return nullptr;}
};
class Student : public Person {
public:virtual B* BuyTicket(){cout 买票-打折 endl;return nullptr;}
};
void Func(Person* ptr)
{ptr-BuyTicket();
}
int main()
{Person ps;Student st;Func(ps);Func(st);return 0;
} 析构函数的重写
基类的析构函数为虚函数此时派生类析构函数只要定义无论是否加virtual关键字都与基类的析构函数构成重写虽然基类与派生类析构函数名字不同看起来不符合重写的规则实际上编译器对析构函数的名称做了特殊处理编译后析构函数的名称统一处理成destructor 所以基类的析构函数加了vialtual修饰派生类的析构函数就构成重写。 故在C中当一个基类的析构函数被声明为虚函数时它确保了当通过基类指针或引用删除派生类对象时会调用正确的析构函数即派生类的析构函数然后再调用基类的析构函数。这是因为虚析构函数允许动态绑定确保了派生类对象被正确地销毁。
#include iostream
using namespace std;
class A {
public:virtual ~A() {cout delete A endl;}
};
class B :public A {public:~B() {cout ~B()-delete: _p endl;delete _p;}
protected:int* _p new int[10];
};
int main() {A* a new A;A* b new B;delete a;delete b;return 0;
} 当我们不把基类析构函数设置成virtual时 会发现没有调用B的析构该释放的资源没有释放掉。
public:~A() {cout delete A endl;}
}; 故基类的析构函数我们要设置成虚函数。
override 和 final关键字 从上面可以看出C对函数重写的要求比较严格但是有些情况下由于疏忽比如函数名写错参数写错等导致无法构成重载而这种错误在编译期间是不会报出的只有在程序运行时没有得到预期结果才来debug会得不偿失。 如果不想让派生类重写这个虚函数那么可以用final去修饰。 在C中override 和 final 关键字是C11标准引入的用于增强类继承和虚函数的声明。 override 关键字用于明确指出一个成员函数旨在重写覆盖其基类中的一个虚函数。如果该函数没有正确地重写基类中的任何虚函数编译器将报错。这有助于避免因拼写错误或参数列表不匹配而意外地没有重写虚函数的情况。 class Car {public:virtual void Dirve(){}};class Benz :public Car {public:virtual void Drive() override { cout Benz-舒适 endl; }};
比如上面这个例子函数名写错了重写失败编译报错。 final 关键字用于防止类被进一步派生或者防止虚函数被重写。当应用于类时它表示这个类不能被继承。当应用于虚函数时它表示这个虚函数不能在派生类中被重写。
class Car {
public:virtual void Dirve() final{}
};
class Benz :public Car {
public:virtual void Dirve(){ cout Benz-舒适 endl; }
}; class Base final { // 不能从这个类派生其他类 public: virtual void doSomething() const final {} // 这个虚函数不能被重写 }; // 下面的类声明会导致编译错误因为 Base 是 final 的 // class Derived : public Base {}; // 下面的函数声明也会导致编译错误因为 doSomething 是 final 的 // class Derived : public Base { // public: // void doSomething() const override {} // 错误不能重写 final 函数 // }; 使用 final 关键字可以确保类或虚函数的行为不会被意外的继承或重写改变这对于设计那些不打算被扩展的类或函数非常有用。
3. 重载重写隐藏的对比
重载Overloading
定义在同一作用域内可以定义多个同名函数只要它们的参数列表参数的数量、类型或顺序不同。特点 发生在同一类中。参数列表必须不同。返回类型可以不同但不是区分重载的主要因素。
重写Overriding
定义在派生类中提供一个与基类中虚函数同名、参数列表和返回类型相同的函数以实现多态。特点 发生在基类和派生类之间。参数列表和返回类型必须相同。基类函数必须是虚函数。使用 override 关键字可以明确指出重写意图。 隐藏Hiding
定义在派生类中定义一个与基类中成员非虚函数或非静态成员变量同名的成员导致基类中的同名成员在派生类中不可见。特点 发生在基类和派生类之间。可以是函数或变量。如果是函数参数列表不必相同。如果派生类中的成员与基类中的成员具有相同的名称但不同的参数列表则基类成员被隐藏而不是重载或重写。 4.纯虚函数和抽象类
在虚函数的后面写上 0 则这个函数为纯虚函数纯虚函数不需要定义实现(实现没啥意义因为要被派生类重写但是语法上可以实现)只要声明即可。 包含纯虚函数的类叫做抽象类抽象类不能实例化出对象如果派生类继承后不重写纯虚函数那么派生类也是抽象类。纯虚函数某种程度上强制了派生类重写虚函数因为不重写实例化不出对象。 #include iostream
using namespace std;
class Car {
public:virtual void Drive() 0;};
class Benchi :public Car {
public:virtual void Drive() {cout Benchi-舒适 endl;}
};class Baoma :public Car {
public:virtual void Drive() {cout Baoma-上手 endl;}
};
int main() {Car car;Car* b new Benchi();b-Drive();Car* m new Baoma();m-Drive();return 0;
} 这里Car是抽象类所以无法实例化对象。 结束语 本期内容就到此结束了内容有点多下节我们将对多态的原理进行补充讲解。 最后感谢各位友友的支持