多态

多态

允许不同类的对象对同一消息做出不同的响应

  • 编译时多态:通过函数重载运算符重载实现,编译时确定调用。
  • 运行时多态:通过虚函数继承实现,运行时确定调用。
  • 抽象类:包含纯虚函数的类,不能实例化,派生类必须实现纯虚函数。

编译时多态(静态多态)

编译时多态主要通过函数重载运算符重载实现,编译器在编译阶段就能确定调用哪个函数。

函数重载

void print(int i) {
    std::cout << "Integer: " << i << std::endl;
}

void print(double d) {
    std::cout << "Double: " << d << std::endl;
}

int main() {
    print(5);    // 调用 print(int)
    print(3.14); // 调用 print(double)
    return 0;
}

编译器根据参数类型决定调用哪个print函数。


运行时多态(动态多态)

运行时多态通过虚函数继承实现,具体调用哪个函数在运行时决定。

虚函数

class Base {
public:
    virtual void show() {
        std::cout << "Base class show" << std::endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        std::cout << "Derived class show" << std::endl;
    }
};

int main() {
    Base* b = new Derived();
    b->show(); // 调用 Derived::show()
    delete b;
    return 0;
}

Base类中的show函数声明为virtualDerived类重写了show。通过基类指针调用show时,实际调用的是派生类的show函数。

class Animal {
public:
	virtual void eat() {//加上virtual关键字 成为虚函数
		cout << "动物在吃东西" << endl;
	}
};
class Cat:public Animal {
public:
	 void eat() {
		cout << "猫在吃东西" << endl;
	}
};
class Pig :public Animal {
public:
	void eat() {
		cout << "猪在吃东西" << endl;
	}
};

//调用链 
//main -> Test -> eat -> Animal::eat
//Animal::eat 是个虚函数,函数传参是个动物,但是传入不同的动物,会重写虚函数产生不同的行为,这就叫多态
void Test(Animal& a) {
	a.eat();
}

int main() {
	Cat c;
	Pig p;

	Test(c);
	Test(p);

	return 0;
}

类里有虚函数,编译器会创建一个虚函数指针,虚函数指针指向一个数组,数组里保存的是所有虚函数的地址,在子类继承时,同名函数会覆盖掉数组里对应的虚函数。

class Animal {
public:
	virtual void eat() {
		cout << "动物在吃东西" << endl;
	}
	virtual void run() {
		cout << "动物在跑" << endl;
	}
};
class Cat:public Animal {
public:
	 void eat() {
		cout << "猫在吃东西" << endl;
	}
};

void Test(Animal& a) {
	Animal b;
	a.eat();
}

int main() {
	Cat c;
	Test(c);
	return 0;
}


C++中多态的原理主要依赖于虚函数表(vtable)虚函数表指针(vptr)。这些机制使得程序能够在运行时动态决定调用哪个函数,从而实现运行时多态。

1. 虚函数表(vtable)

  • 虚函数表是一个存储虚函数地址的数组,每个包含虚函数的类都有一个对应的虚函数表。
  • 表中按声明顺序存储类的虚函数地址。如果派生类重写了基类的虚函数,表中会更新为派生类的函数地址。

2. 虚函数表指针(__vptr)

  • 虚函数表指针是一个隐藏的指针,存在于每个包含虚函数的对象中,指向该类的虚函数表。
  • 当对象调用虚函数时,通过__vptr找到虚函数表,再通过表中的函数地址调用正确的函数。

3. 多态的实现过程

  1. 编译阶段
    • 编译器为每个包含虚函数的类生成一个虚函数表。
    • 每个对象在构造时,其__vptr会被初始化为指向该类的虚函数表。
  2. 运行时
    • 当通过基类指针或引用调用虚函数时,程序通过对象的__vptr找到虚函数表。
    • 通过虚函数表中的函数地址调用正确的函数。

纯虚函数与抽象类

纯虚函数在基类中声明但不实现,派生类必须重写。包含纯虚函数的类称为抽象类,不能实例化。

class Shape {
public:
    virtual void draw() = 0; // 纯虚函数
};

class Circle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing Circle" << std::endl;
    }
};

int main() {
    Shape* s = new Circle();
    s->draw(); // 调用 Circle::draw()
    delete s;
    return 0;
}

Shape类是抽象类,Circle类实现了draw函数。

虚析构和纯虚析构

用于解决内存泄漏问题

如:父类指针指向子类对象,在对它进行销毁时,如果父类的析构函数不是虚析构,就会无法调用子类的析构函数,从而导致内存泄漏

class BaseA {
public:
	BaseA() {}
	~BaseA() {
		cout << "BaseA销毁了" << endl;
	}
};
class SonA:public BaseA {
public:
	SonA():m_Data(NULL){
		m_Data = new int(10);
	}
	~SonA() {
		cout << "SonA销毁了" << endl;
		delete m_Data;
	}
	int* m_Data;
};

class BaseB {
public:
	BaseB() {}
	virtual ~BaseB() = 0;
	/*virtual ~BaseB() {
		cout << "BaseB销毁了" << endl;
	}
	*/
};

BaseB::~BaseB() {
		cout << "BaseB销毁了" << endl;
}

class SonB :public BaseB {
public:
	SonB() :m_Data(NULL) {
		m_Data = new int(10);
	}
	~SonB() {
		cout << "SonB销毁了" << endl;
		delete m_Data;
	}
	int* m_Data;
};
int main() {
	BaseA* a = new SonA();
	delete a;
	BaseB* b = new SonB();
	delete b;

	//BaseB x;//报错,纯虚函数无法实例化

	return 0;
}
posted @ 2025-02-27 17:22  十四2001  阅读(22)  评论(0)    收藏  举报