怎样向网站上传照片,电商网站话费充值怎么做,建站网站如何清理缓存,php网站屏蔽词怎么做本文章内容来源于C课堂上的听课笔记
多态基础
多态#xff08;Polymorphism#xff09;是面向对象编程中的一个重要概念#xff0c;它允许使用统一的接口来表示不同的对象和操作。多态性有两种主要形式#xff1a;静态多态性#xff08;编译时多态性#xff09;和动态多…本文章内容来源于C课堂上的听课笔记
多态基础
多态Polymorphism是面向对象编程中的一个重要概念它允许使用统一的接口来表示不同的对象和操作。多态性有两种主要形式静态多态性编译时多态性和动态多态性运行时多态性
多态性分为两类: 静态多态性和动态多态性
静态多态性编译时多态性 定义 在编译时确定方法的调用通常与函数重载overloading相关。 例子 方法重载是一种静态多态性的体现编译器在编译时能够根据方法的参数类型或个数来选择正确的方法
class StaticPolymorphism {
public:void display(int value) {// ...}void display(double value) {// ...}
};注意重载overload和重写override的区别下面是个重写的例子
#include iostream
using namespace std;
class CA
{ public:void f(int){ cout CA::f(int) endl; }};
class CB : public CA
{ public:void f(int) {cout CB::f(int) endl;} void f(int,int) {cout CB::f(int,int) endl; } int f(int,int,int) {cout CB::f(int,int,int) endl; }void test() {f(1); f(1,1); f(1,1,1); }};int main() { CB B;B.test();} 如果把上面的void f(int) {cout CB::f(int) endl;} 注释掉会发生什么错误 原因在调用一个类的成员函数时,编译器会沿着类的继承链逐级的向上查找函数的定义,如果找到了则停止查找 如果派生类CB和基类CA都有同一个同名函数f(不论参数是否相同),编译器最终将选择派生类中的函数,即派生类的成员函数“隐藏”了基类的成员函数, 它阻止了编译器继续向上查找函数的定义
所以如果修改成下面这样就可以运行了
#include iostream
using namespace std;
class CA
{ public:void f(int){ cout CA::f(int) endl; }};
class CB : public CA
{ public:void test() {f(1); }};int main() { CB B;B.test();} 动态多态性运行时多态性 定义 在运行时确定方法的调用通常与虚函数virtual function和继承相关。 例子 通过虚函数和基类指针实现动态多态性可以在运行时选择调用合适的函数。
#includeiostream
using namespace std;class Base {
public:virtual void display() {// ...cout1endl;}
};class Derived : public Base {
public:void display() override{// ...cout2endl;}
};int main() {Base* ptr new Derived();ptr-display(); // 在运行时调用 Derived 类的 display 方法delete ptr;return 0;
}静态多态性和动态多态性的区别 时机不同 静态多态性在编译时确定动态多态性在运行时确定。
实现机制不同 静态多态性通常与函数重载等相关而动态多态性通常与虚函数和继承相关。
使用场景不同 静态多态性适用于编译时能够确定的情况而动态多态性适用于在运行时确定的情况。
总的来说多态性是面向对象编程的一个强大特性它允许代码更加灵活、可扩展和易维护。
虚函数
在面向对象编程中我们有时候会有一系列的类它们可能会有一些相同的函数名但是具体的实现可能会因为类的不同而有所不同。虚函数就是为了解决这个问题而设计的。
想象一下你有一群动物比如猫、狗、鸟等它们都能发出声音。你可能会定义一个名为 makeSound 的函数来表示这个动作。但是猫“喵喵”叫狗“汪汪”叫鸟“啾啾”叫它们的叫声不同。这时候你就可以使用虚函数了
#includeiostream
using namespace std;
class Animal {
public:virtual void makeSound() {// 这里可以是一个默认的实现也可以是空的}
};
class Cat : public Animal {
public:void makeSound() override {std::cout 喵喵 std::endl;}
};class Dog : public Animal {
public:void makeSound() override {std::cout 汪汪 std::endl;}
};class Bird : public Animal {
public:void makeSound() override {std::cout 啾啾 std::endl;}
};
int main()
{Animal* myPet new Dog();myPet-makeSound(); // 输出汪汪Cat cat;Bird bird;myPet cat;myPet-makeSound();myPet bird;myPet-makeSound();return 0;
} 如果基类的函数不加关键字virtual会发生什么
1.所有派生类的相关函数不能添加override关键字以为实际上没有重写
2.函数最终调用的是基类的函数但可能使用对应派生类对象的数据
举个例子展示不用virtual时的情况
#include iostream
#include string
using namespace std;
class Student
{public:Student(int, string,float);void display( ); protected:int num;string name;float score;};
Student::Student(int n, string nam,float s) {numn;namenam;scores;}void Student::display( )
{coutnum:num\nname:
name\nscore:score\n\n;}
class Graduate: public Student
{public:Graduate(int, string, float, float); void display( );
private:float pay;
};void Graduate::display( ) {coutnum:num\nname:name\nscore:score\npaypayendl;}Graduate::Graduate(int n, string nam,float s,float p):Student(n,nam,s),pay(p){ }
int main(){ Student stud1(1001,Li,87.5); Graduate grad1(2001,Wang,98.5,563.5); Student *ptstud1; pt-display( );ptgrad1;pt-display( );return 0;}如果修改成虚函数即Student类中函数声明变为
virtual void display( );
运行结果 在派生类中重新定义此函数要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同当一个成员函数被声明为虚函数后其派生类中的同名函数都自动成为虚函数。派生类重新声明该虚函数时可以加virtual也可以不加
下面我们通过修改上述代码来证明这一点
添加类Test
class Test: public Graduate
{public:Test(int ,string,float,float,int);void display();private:int others;
};
void Test::display( ) {cout\n\nnum:num\nname:name\nscore:score\nothersothersendl;}Test::Test(int n, string nam,float s,float p,int o):Graduate(n,nam,s,p),others(o){ }
main中添加
Test t(9999,s,11,22,33);ptt;pt-display();
结果 说明Graduate派生类Test中的同名函数display已经成为虚函数如果不是虚函数应该输出下面的结果 num:1001 name:Li score:87.5
num:2001 name:Wang score:98.5 pay563.5 num:9999 name:s score:11 pay22
静态关联和动态关联
在C中静态关联和动态关联是面向对象编程中两个关键的概念通常与继承和多态性相关
静态关联Static Binding 概念 静态关联发生在编译时编译器在编译阶段就能够确定程序中各个函数或方法的调用关系。 实现 在静态关联中编译器根据函数或方法的声明类型来确定调用哪个函数或方法这种绑定在编译时期就已经确定因此称为静态关联。 优点 效率高因为在编译时已经确定了函数调用关系不需要在运行时进行额外的查找。 例子 C中的函数重载是一种静态关联的例子编译器在编译时根据参数的类型和数量确定调用哪个重载版本。
动态关联Dynamic Binding 概念 动态关联发生在运行时程序在执行过程中才能够确定调用哪个函数或方法。 实现 在动态关联中通常通过使用虚函数和指针或引用来实现。这种绑定在运行时根据实际对象的类型确定因此称为动态关联。 优点 提供了更高的灵活性和可扩展性允许在运行时根据实际情况改变调用关系。 例子 C中的虚函数和纯虚函数是动态关联的例子。当基类指针或引用指向派生类对象并调用虚函数时根据实际对象的类型来确定调用哪个版本的函数
什么情况下适合声明虚函数 1.基类预期被继承 如果你设计一个基类并且希望它能够作为其他类的基础支持多态性那么你应该在基类中声明虚函数。这样派生类就有机会覆盖这些虚函数实现自己的版本 2.需要运行时动态绑定 如果你希望在运行时根据对象的实际类型来调用函数而不是根据指针或引用的静态类型那么你应该使用虚函数。这种动态绑定提供了多态性的特性 3.需要覆盖基类函数 如果你希望派生类能够覆盖基类中的同名函数以提供特定于派生类的实现那么这个函数应该声明为虚函数 4.实现抽象类和接口 如果你想要创建一个抽象类包含至少一个纯虚函数或者接口那么你应该声明虚函数。这样的类不能被实例化但可以作为基类供其他类继承并实现虚函数
虚析构函数
在C中析构函数是用来在对象生命周期结束时进行清理工作的函数。而当我们在面向对象的程序设计中使用继承时有时候我们会遇到这样的情况基类指针指向派生类对象。
如果你使用了继承并且在基类和派生类中都定义了析构函数那么就有可能发生一个问题。当你使用基类指针指向派生类对象时如果你删除这个指针只会调用基类的析构函数而不会调用派生类的析构函数。这可能导致一些资源比如内存没有得到正确释放从而产生问题。
这时候虚析构函数就发挥作用了。在基类的析构函数前面加上virtual关键字就可以将它声明为虚析构函数。这样当你通过基类指针删除派生类对象时会正确地调用派生类的析构函数。
#includeiostream
using namespace std;
class Base {
public:virtual ~Base() {// 基类的清理工作coutBase Destructionendl;}
};class Derived : public Base {
public:~Derived() {// 派生类的清理工作coutDerived Destructionendl;}
};
int main()
{Base *ptnew Derived;delete pt;return 0;
}
这里~Base()是虚析构函数而~Derived()覆盖了基类的虚析构函数。当你使用基类指针指向派生类对象时通过这个基类指针删除对象时会正确地调用~Derived()来完成清理工作确保所有相关资源都被正确释放。
总的来说虚析构函数是为了在继承层次结构中正确地释放资源而引入的。通过使用虚析构函数可以确保在删除基类指针时正确调用派生类的析构函数从而实现正确的资源清理。
纯虚函数和抽象类
抽象类 首先想象一下你要画一只动物的图但是你不知道具体是什么动物只知道它是动物。你可能会画一些共性的特征比如四条腿、有尾巴等。这个画得很模糊的图就好比一个抽象类。 在编程中抽象类也是一种类但是它是一种不完整的类不能被实例化也就是不能创建对象。抽象类里面可能包含了一些方法函数但是这些方法没有具体的实现只是一个声明。抽象类的存在是为了给其他类提供一种共同的接口让这些类去继承它并实现这些方法。纯虚函数 再想象一下你画的那只动物有一些特征是必须由具体的动物来定义的比如各种动物的叫声。这时你可以把叫声这个特征标记为一个“待定”项告诉其他人这个特征必须由实际的动物类来具体实现。 在编程中这个“待定”项就是纯虚函数。一个纯虚函数是在抽象类中声明的虚函数但是没有具体的实现。它的目的是让派生类强制实现这个方法以使抽象类变得更加具体。 综合起来抽象类是一种不完整的类包含一些没有具体实现的方法其中可能包含了纯虚函数。而纯虚函数是为了强制派生类实现某些方法使得抽象类可以更具体、更有实际意义。在C中含有纯虚函数的类就被称为抽象类
注意 1.凡是包含纯虚函数的类都是抽象类包含纯虚函数的类是无法建立对象的 2.如果在抽象类所派生出的新类中对基类的所有纯虚函数进行了定义那么这些函数就被赋予了功能可以被调用。这个派生类就不是抽象类而是可以用来定义对象的具体类。如果在派生类中没有对所有纯虚函数进行定义则此派生类仍然是抽象类不能用来定义对象 3.虽然抽象类不能定义对象(或者说抽象类不能实例化)但是可以定义指向抽象类数据的指针变量 下面举一个综合性例子体现上面所说的一切
#include iostream// 抽象类 Animal
class Animal {
public:// 纯虚函数表示动物的叫声virtual void makeSound() const 0;// 普通方法表示动物的一般特征void sleep() const {std::cout Zzz... std::endl;}
};// 具体的动物类 Lion狮子
class Lion : public Animal {
public:// 实现了纯虚函数给出了狮子的叫声void makeSound() const override {std::cout Roar! std::endl;}
};// 具体的动物类 Elephant大象
class Elephant : public Animal {
public:// 实现了纯虚函数给出了大象的叫声void makeSound() const override {std::cout Trumpet! std::endl;}
};// 具体的动物类 Monkey猴子
class Monkey : public Animal {
public:// 猴子并没有实现纯虚函数 makeSound// 因此 Monkey 仍然是抽象类
};int main() {// 1. 尝试创建抽象类的对象编译错误// Animal animal; // 编译错误抽象类不能实例化// 2. 使用抽象类指针指向派生类对象Animal* lion new Lion();Animal* elephant new Elephant();// Animal* monkey new Monkey(); // 编译错误抽象类不能实例化// 调用纯虚函数实际上会调用相应派生类的实现lion-makeSound(); // 输出Roar!elephant-makeSound(); // 输出Trumpet!// 调用抽象类的普通方法lion-sleep();elephant-sleep();// 3. 定义指向抽象类的指针变量Animal* abstractAnimal nullptr; // 可以定义指针变量// 4. 尝试使用派生类 Monkey编译错误// Monkey monkeyObj; // 编译错误抽象类不能实例化delete lion;delete elephant;return 0;
}