引言
小伙伴们我们都知道了,什么是封装和继承,在有了这个的基础上我们接着来看什么是多态。多态从字面上意思我们就可以知道,大概就是一个函数的不同形态,而且,前边我们在学习函数重载的时候我们已经简单的了解了如何用一个函数来实现两种不一样的操作。那么本章就会告诉你有关多态性的所有知识,小伙伴们快拿起小本本记好哟。
好了,废话不多说,小伙伴们,开冲!!!!
多态
什么是多态:
多态(Polymorphism)是面向对象编程(OOP)中的一个核心概念,它指的是对象可以通过指向其派生类的引用来调用在基类中定义的函数,而且调用的是派生类中重写的方法。
在面向对象编程中,多态的定义可以概括:同一操作作用于不同的对象时,可以有不同的解释,产生不同的执行结果。 这意味着,同一个方法调用可以在不同的对象上产生不同的行为,这些对象虽然属于不同的类,但是都必须继承自同一个父类或者实现同一个接口。
我们可以把多态大体分为两种不同的形式:
那就是静态多态和动态多态
静态多态
静态多态是通过函数重载和运算符重载实现,其多态性是编译时已经决定的。静态多态的优点是可以提供更好的性能,因为所有的决策都在编译时做出,减少了运行时的开销。
函数重载
有关函数没学明白,没看懂的去看函数专项 》》》点这里看函数专项《《《
函数重载(Function Overloading)是C++中的一种特性,允许在同一作用域内存在多个同名函数,只要它们的参数列表不同即可。函数的重载可以根据参数的数量、类型或者顺序来区分。
当调用重载函数时,编译器会根据传递给函数的参数来决定调用哪个函数版本,这个过程称为函数重载解析
函数重载的条件:
- 相同的函数名:重载的函数必须具有相同的名字。
- 不同的参数列表:参数列表的不同可以体现在参数的数量、类型或者顺序上。
- 在同一作用域内:重载的函数必须在同一作用域内,例如同一个类或者同一个全局作用域。
- 代码示例:
#include <iostream>
using namespace std;
int myAdd(int a, int b);
float myAdd(int a, float b);
int main()
{
int a1 = 10, a2 = 20;
float b1 = 11.1, b2 = 22.2;
cout << myAdd(a1, a2) << endl;
cout << myAdd(a1, b1) << endl;
return 0;
}
int myAdd(int a, int b)
{
cout << "myAdd(int a, int b)" << endl;
return a + b;
}
float myAdd(int a, float b)
{
cout << "myAdd(int a, float b)" << endl;
return a + b;
}
运算符重载
运算符重载(Operator Overloading)是面向对象编程中的一种特性,它允许开发者对已有的运算符赋予额外的含义,以适应不同的数据类型。这样,用户可以自定义运算符对自定义类型的操作行为。运算符重载使得对象和内置数据类型之间的运算更加自然和直观。
运算符重载的基本原则是保持原有运算符的基本语义不变。例如,加法运算符 + 通常用于两个数值的相加,重载后也应该表示某种意义上的“相加”操作,而不是彻底改变其行为。
运算符重载可以通过在类中定义特殊的成员函数来实现,这些函数被称为运算符重载函数。运算符重载函数的名称是由关键字 operator 后跟要重载的运算符符号组成的。
- 代码示例:
class Complex {
public:
double real;
double image;
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
// 重载加法运算符 +
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
};
int main() {
Complex c1(1.0, 2.0);
Complex c2(2.0, 3.0);
Complex c3 = c1 + c2; // 使用重载的加法运算符
return 0;
}
静态多态代码示例:
#include <iostream>
using namespace std;
class Cat
{
private:
char* m_name;
int m_full;
public:
Cat(const char* name, int full);
~Cat();
void eat();
// 函数重载产生静态多态
void eat(int n);
void play();
void play(int n);
// 运算符重载产生静态多态
Cat& operator=(const Cat& cat);
void show();
};
Cat::Cat(const char* name, int full)
{
int len = strlen(name) + 1;
m_name = new char[len];
strcpy_s(m_name, len, name);
m_full = full;
}
Cat::~Cat()
{
delete[]m_name;
}
void Cat::eat()
{
cout << m_name << "吃了1条鱼" << endl;
m_full++;
}
void Cat::eat(int n)
{
cout << m_name << "吃了" << n <<"条鱼" << endl;
m_full+=n;
}
void Cat::play()
{
cout << m_name << "抓了1只蝴蝶" << endl;
m_full--;
}
void Cat::play(int n)
{
cout << m_name << "抓了" << n << "只蝴蝶" << endl;
m_full -= n;
}
Cat& Cat::operator=(const Cat& cat)
{
if (this != &cat)
{
delete[]m_name;
int len = strlen(cat.m_name) + 1;
m_name = new char[len];
strcpy_s(m_name, len, cat.m_name);
m_full = cat.m_full;
}
return *this;
}
void Cat::show()
{
cout << m_name << ", " << m_full << endl;
}
int main()
{
Cat c1("大花", 50);
c1.show();
c1.play();
c1.play(10);
c1.show();
c1.eat(5);
c1.eat();
c1.show();
Cat c2("二花", 10);
c2.show();
c2 = c1;
c2.show();
c1.eat(100);
c1.show();
c2.show();
return 0;
}
虽然静态多态有这样的优点:可以提供更好的性能,因为所有的决策都在编译时做出,减少了运行时的开销。但同时,它也提供了类型安全,因为编译器会检查所有的类型是否匹配。然而,静态多态也缺乏动态性,它不能在运行时根据对象的实际类型来改变行为,这就需要动态多态来实现。
动态多态
- 在程序运行时,根据指针指向具体对象来调用各自的虚函数,称为动态多态。
- 成员函数声明时使用virtual关键字修饰的,称为虚函数。
- 当一个派生类的对象通过基类的指针或引用调用一个虚函数时,会调用与对象类型相对应的函数版本,而不是指针或引用的类型。
- 动态多态需要满足的三个条件:
- 基类中定义了虚函数。
- 子类中重写了基类的虚函数。
- 基类指针指向子类对象并调用了该虚函数。
- 为了实现动态多态,编译器为每个包含虚函数的类创建一个虚函数表,并为每个对象设置一个虚函数指针(虚指针)指向这个虚函数表。
- 使用基类指针对虚函数调用时,通过这个虚函数指针,在虚函数表中查找虚函数的地址,从而调用不同的虚函数。
- 由于包含虚函数的对象有一个虚指针,与没有虚函数的对象相比,含有虚函数的对象指针所占用的内存空间要多。
- 代码示例:
#include <iostream>
using namespace std;
class Person
{
public:
virtual void buyTicket()
{
cout << "普通人买全票" << endl;
}
};
class Student : public Person
{
public:
void buyTicket()
{
cout << "学生可以买学生票" << endl;
}
};
class Children : public Person
{
public:
void buyTicket()
{
cout << "儿童可以买儿童票" << endl;
}
};
int main()
{
/*
父类函数成员声明为虚函数,子类中实现了和父类同名的函数,且父类指针指向子类对象时,通过父类指针调用子类的函数。
*/
Person person;
Student student;
Children children;
Person* p = NULL;
p = &person;
p->buyTicket();
p = &student;
p->buyTicket();
p = &children;
p->buyTicket();
Person* p1[3] = { &person, &student, &children};
for (int i = 0; i < 3; i++)
{
p1[i]->buyTicket();
}
return 0;
}
虚析构函数
析构函数可以定义为虚函数,如果基类的析构函数定义为虚函数,则派生类的析构函数就会自动称为虚析构函数。
- 代码示例:
#include <iostream>
using namespace std;
class Employee
{
protected:
char* m_name;
public:
Employee(const char* name)
{
cout << "Employee(const char* name) " << name << endl;
int len = strlen(name) + 1;
m_name = new char[len];
strcpy_s(m_name, len, name);
}
virtual ~Employee()
{
cout << "~Employee() " << m_name << endl;
delete[]m_name;
}
};
class Teacher : public Employee
{
protected:
char* m_course;
public:
Teacher(const char* name, const char* course) : Employee(name)
{
cout << "Teacher(const char* name, const char* course) : Employee(name)) " << name << ", " << course << endl;
int len = strlen(course) + 1;
m_course = new char[len];
strcpy_s(m_course, len, course);
}
~Teacher()
{
cout << "~Teacher() " << m_name << ", " << m_course << endl;
delete[] m_course;
}
};
int main()
{
Teacher* t = new Teacher("王老师", "C++");
delete t;
/*
不使用虚析构函数时,当通过父类指针释放子类对象时,仅调用父类析构函数。
*/
Employee* e = new Teacher("杨老师", "Python");
delete e;
}
纯虚函数函数和抽象类
-
抽象类就是包含纯虚函数的类。
-
抽象类不允许实例化,抽象类的指针可以指向实现纯虚函数的子类实例。
-
如果子类没有实现纯虚函数,子类会继承纯虚函数,并成为一个抽象类。
-
代码示例:
#include <iostream>
using namespace std;
// Person是抽象类,因为包含纯虚函数buyTicket。
class Person
{
public:
// 定义纯虚函数
virtual void buyTicket() = 0;
};
class Student : public Person
{
public:
void buyTicket()
{
cout << "学生可以买学生票" << endl;
}
};
class Children : public Person
{
public:
void buyTicket()
{
cout << "儿童可以买儿童票" << endl;
}
};
int main()
{
// Person person; 抽象类不能实例化
// Student是Person的子类,实现了Person的纯虚函数buyTicket.
Student student;
Children children;
// 抽象类的指针可以指向其子类对象
Person* p = &student;
p->buyTicket();
p = &children;
p->buyTicket();
return 0;
}
结语
小伙伴们,当你看到这里就已经把面向对象的所有知识就全部都学完了,为自己鼓鼓掌,你们是最棒的 ,小杨也是最棒的,小杨会持续不间断的为大家更新新的知识,但同时,大家一定要好好的把前边的好好的复习一遍,而且现在的知识已经够大家在牛客和力扣上边刷相关的题了,大家可以试着去做一做,稍后小杨也会专门开个专题刷题的,内容就是C++相关内容。