C++【多态】

多态的概念:

多态即多种形态,在生活中不同的人去做一件事可能是不同的结果。比如学生买票和成人买票,学生买票为半价,而成人则是全价票。

多态的定义

多态的构成条件

多态为在继承关系中不同的类对象去调用同一函数,产生了不同的结果。比如现在有两个类为Person和Student,Student继承了Person。Person买票为全价,Student买票为半价。

继承中构成多态需要两个条件:

1.必须通过基类的指针或者引用调用虚函数

2.被调用的函数必须是虚函数,且派生类要对基类的虚函数进行重写(注意区分重写与重定义,重定义为隐藏

class Person
{
public:
	virtual void buy_ticket()
	{
		cout << "成人买票全价" << endl;
	}
};

class Student:public Person
{
public:
	virtual void buy_ticket()
	{
		cout << "学生买票半价" << endl;
	}
};

void func(Person& p)
{
	p.buy_ticket();
}

void test1()
{
	Person p1;
	Student s1;
	func(p1);
	func(s1);
}

其运行结果为:

可以看到子类与父类对象调用func函数时,其产生了不同的结果

虚函数

虚函数即为被virtual修饰的类成员函数

虚函数的重写

虚函数的重写(覆盖):子类中有一个与父类完全相同的虚函数(返回值类型,函数名,参数列表相同),称子类的虚函数重写了父类的虚函数。

上面的代码例子中buy_ticket即为虚函数重写

虚函数重写的例外

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:
	~Person()
	{
		cout << "~Person()" << endl;
	}
};

class Student:public Person
{
public:
	~Student()
	{
		cout << "~Student()" << endl;
	}
};

void test2()
{
	Person* p = new Person;
	Student* s = new Student;

	delete p;
	cout << "---------------------------------" << endl;
	delete s;

}

执行结果为:

 

可以看到父类的对象析构调用的是父类的析构函数,而子类的析构也调用了父类的析构,如果子类没有需要额外自行清理的资源(如开辟了空间需要释放等,则其可以只定义析构函数,然后直接使用父类的析构函数)

class Person
{
public:
	~Person()
	{
		cout << "~Person()" << endl;
	}
};

class Student:public Person
{
public:
	~Student()
	{
	}
};

void test2()
{
	Person* p = new Person;
	Student* s = new Student;

	delete p;
	cout << "---------------------------------" << endl;
	delete s;

}

如上,没有需要额外清理的空间,直接使用父类的析构函数

补充final与override

1.final:修饰虚函数,表示该虚函数不能被重写,如果重写会报错

2.override:检查子类虚函数是否重写了某个父类的某个虚函数,如果没有重写则编译报错

class A
{
public:
	virtual void fuc1()
	{}
};

class B:public A
{
public:
	virtual void fuc1() override
	{
		cout << "b" << endl;
	}
};

函数重载,重写,重定义(隐藏)

抽象类 

虚函数的后面写上 =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;
		}
	};

	void Test()
	{
		Car* pBenz = new Benz;
		pBenz->Drive();

		Car* pBMW = new BMW;
		pBMW->Drive();
	}

多态的原理

虚函数表

这里的sizeof(Base)为多少?

class Base
{
public:
virtual void Func1()
{
cout<<"Func1()"<<endl;
}
private:
int _b = 1;
};

通过窗口可以看到sizeof(Base)为8bytes(x86)环境下。

除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表

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:
//重写了Func1
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下是存在代码段

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的多(Polymorphism)是指在父类和子类之间的相互转换,以及在不同对象之间的相互转换。 C++中的多性有两种:静和动。 1. 静是指在编译时就已经确定了函数的调用,也称为编译时多。C++中实现静的方式主要有函数重载和运算符重载。 函数重载是指在同一作用域内定义多个同名函数,但它们的参数列表不同。编译器根据传递给函数的参数类型和数量来确定调用哪个函数。例如: ```c++ void print(int num) { std::cout << "This is an integer: " << num << std::endl; } void print(double num) { std::cout << "This is a double: " << num << std::endl; } int main() { int a = 10; double b = 3.14; print(a); // 调用第一个print函数 print(b); // 调用第二个print函数 } ``` 运算符重载是指对C++中的运算符进行重新定义,使其能够用于自定义的数据类型。例如: ```c++ class Complex { public: Complex(double real, double imag) : m_real(real), m_imag(imag) {} Complex operator+(const Complex& other) const { return Complex(m_real + other.m_real, m_imag + other.m_imag); } private: double m_real; double m_imag; }; int main() { Complex a(1.0, 2.0); Complex b(3.0, 4.0); Complex c = a + b; // 调用Complex类中重载的+运算符 } ``` 2. 动是指在运行时根据对象的实际类型来确定调用哪个函数,也称为运行时多。C++中实现动的方式主要有虚函数和纯虚函数。 虚函数是在父类中定义的可以被子类重写的函数,使用virtual关键字声明。当一个对象的指针或引用指向一个子类对象时,调用虚函数时会根据实际的对象类型来确定调用哪个函数。例如: ```c++ class Shape { public: virtual void draw() { std::cout << "Drawing a shape." << std::endl; } }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle." << std::endl; } }; int main() { Shape* shape_ptr = new Circle(); shape_ptr->draw(); // 调用Circle类中重写的draw函数 } ``` 纯虚函数是在父类中定义的没有实现的虚函数,使用纯虚函数声明(如virtual void func() = 0;)。父类中包含纯虚函数的类称为抽象类,抽象类不能被实例化,只能作为基类来派生子类。子类必须实现父类的纯虚函数才能实例化。例如: ```c++ class Shape { public: virtual void draw() = 0; }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle." << std::endl; } }; int main() { Shape* shape_ptr = new Circle(); shape_ptr->draw(); // 调用Circle类中重写的draw函数 } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值