第七层:多态(上)

简介: 第七层:多态(上)

前情回顾


在第六层中,我遇到了继承,它是面向对象三大特性之一,它也是我遇到的第二个面向对象的特性,因为继承,C++中的类被分成子类和父类,还有虚继承等强大的力量,但是,我还是掌握,走向了第七层…


🚄上章地址:第六层:继承


多态


“你来了啊,这层有着面向对象的最后一种特性——多态,它是每一个C++程序员都必须掌握的核心技术,希望你也可以掌握…”“面向对象的最后一中特性了吗?看起来是一场恶战。”


多态的基本概念


多态是C++面向对象的三大特性之一,多态分为两类:


静态多态:两种重载(函数重载和运算符重载)属于静态多态,复用函数名

动态多态:子类和虚函数实现运行时发生的多态

那这两种多态有什么区别呢?


静态多态的函数地址早绑定(在编译阶段确定函数地址)

动态多态的函数地址晚绑定(运行阶段确定函数地址)

那什么是晚绑定,什么是早绑定?下面就是早绑定的案例:


现在有一个函数,它的参数是父类引用,里面调用父类和子类当中都有的函数,现在传过去一个子类,可以调用吗?可以的话,是调用子类还是父类?

#include<iostream>
using namespace std;
class A
{
public:
  void a1()
  {
  cout << "A在调用" << endl;
  }
};
class A1 :public A
{
public:
  void a1()
  {
  cout << "A1在调用" << endl;
  }
};
void polym(A& a)
{
  a.a1();
}
void test1()
{
  A1 a;
  polym(a);
}
int main()
{
  test1();
  return 0;
}

0a2653c851af460fa595bd959398a8f1.png

是可以调用的,因为在C++中,允许父子之间的类型转换,不需要做强制类型转换,父类的引用可以直接指向子类,但是反省调用的是父类中的函数,那为什么传过去一个子类却调用的是父类呢?因为在底层,是调用父类,这个时候就是因为地址的早绑定,在编译阶段就确定了函数地址,所以不管传子还是父,都会指向父类中的函数地址,调用父类中的函数地址,如果要执行子类,那便不能让编译器就编译阶段就确定函数地址,不能进行早绑定,需要在运行时在进行绑定,这个时候就叫做地址晚绑定,需要在父类成员函数前加:


virtual

这个时候成员函数就变成了虚函数,也就实现晚绑定了:


#include<iostream>
using namespace std;
class A
{
public:
  virtual void a1()
  {
    cout << "A在调用" << endl;
  }
};
class A1 :public A
{
public:
  void a1()
  {
    cout << "A1在调用" << endl;
  }
};
void polym(A& a)
{
  a.a1();
}
void test1()
{
  A1 a;
  polym(a);
}
int main()
{
  test1();
  return 0;
}

0eacb84100b54626af849e6b562bf92a.png


动态多态的满足条件


在上面所提到的地址晚绑定,就是动态多态,那要实现动态多态,有一些需要满足的条件:


有继承关系

子类中要重写父类中虚函数(返回类型、函数名、参数列表都要一致),对于子类中的函数重写,virtual可加可不加


动态多态的使用


使用父类指针或者引用去执行子类对象

那具体为什么会这样呢?就是因为虚函数的作用。


虚函数


在第三层:C++中的对象和this指针中提到了,在类内没有非静态成员时的大小为一,那类内只有有虚函数的时候,大小是多少?


#include<iostream>
using namespace std;
class A
{
public:
  virtual void a1()
  {
  cout << "A在调用" << endl;
  }
};
class A1 :public A
{
public:
  void a1()
  {
  cout << "A1在调用" << endl;
  }
};
void test1()
{
  cout << sizeof(A) << endl;
}
int main()
{
  test1();
  return 0;
}


0a2653c851af460fa595bd959398a8f1.png

0a2653c851af460fa595bd959398a8f1.png

是8,那它的本质其实是指针,因为我编译器的环境是x64,所以是8,那虚函数的内部指向的其实和虚继承一样,内部也只一个指针,但是是vfptr,叫做虚函数指针,会指向vftable,叫做虚函数表,这个虚函数表内部是记录的是虚函数地址,当父类的指针指向的是指针或者引用的时候,发生多态,当子类通过父类引用调用函数的时候,就会去子类的虚函数表内调用子类当中的函数,因为重写,子类的函数就会覆盖掉自己虚函数表中的父类函数,这个时候就会调用子类函数。

0eacb84100b54626af849e6b562bf92a.png


多态的优点


代码组织结构清晰

可读性强

利于前期和后期的扩展以及维护

可以用代码实现一个不用多态的和一个使用多态的来对比验证:


现在处于一个麦饮料的地方,需要你选择饮料

普通方法:


#include<string>
#include<iostream>
using namespace std;
class drink
{
public:
  void dele(string s)
  {
  if (s == "mike")
  {
    cout << "牛奶来咯" << endl;
  }
  else if (s == "orange")
  {
    cout << "橙汁来咯" << endl;
  }
  else if (s == "coke")
  {
    cout << "可乐来咯" << endl;
  }
  } 
  string _d;
};
void test1()
{
  drink d;
  cin >> d._d;
  d.dele(d._d);
}
int main()
{
  test1();
  return 0;
}


0a2653c851af460fa595bd959398a8f1.png0a2653c851af460fa595bd959398a8f1.png

0a2653c851af460fa595bd959398a8f1.png

多态实现:


#include<string>
#include<iostream>
using namespace std;
class drink
{
public:
  virtual void dele()
  {
  } 
};
class mike :public drink
{
public:
  void dele()
  {
  cout << "牛奶来咯" << endl;
  }
};
class orange :public drink
{
public:
  void dele()
  {
  cout << "橙汁来咯" << endl;
  }
};
class coke :public drink
{
public:
  void dele()
  {
  cout << "可乐来咯" << endl;
  }
};
void test1()
{
  drink *d = new coke;
  d->dele();
  delete d;
  d=NULL;
}
int main()
{
  test1();
  return 0;
}

0eacb84100b54626af849e6b562bf92a.png

可以发现,多态的代码量要远远超于普通的写法,那为什么还要使用多态?就是因为多态的三个优点,并且,对于第三个优点来说,对于普通写法,要加入什么饮品,需要修改原码,而在真实的开发中,是不建议去修改的,提倡开闭原则1。


纯虚函数和抽象类


上面例子中,可以看到对于父类中的虚函数是基本不会调用的,用的多的是子类中的同名函数,这个时候,可以将父类中的虚函数变成纯虚函数,语法:


virtual 返回类型 函数名 (参数) =0;(大括号不用写)

这个时候,当类内有了纯虚函数,这个类也就被称为抽象类。


抽象类特点


抽象类无法实例化对象

子类必须重写抽象类函数中的纯虚函数,否则子类也会成为抽象类

验证抽象类无法实例化对象:


#include<string>
#include<iostream>
using namespace std;
class drink
{
public:
  virtual void dele() = 0;
};
class mike :public drink
{
public:
  void dele()
  {
  cout << "牛奶来咯" << endl;
  }
};
class orange :public drink
{
public:
  void dele()
  {
  cout << "橙汁来咯" << endl;
  }
};
class coke :public drink
{
public:
  void dele()
  {
  cout << "可乐来咯" << endl;
  }
};
void test1()
{
  drink d;
}
int main()
{
  test1();
  return 0;
}


0a2653c851af460fa595bd959398a8f1.png

0a2653c851af460fa595bd959398a8f1.png

相关文章
|
5天前
|
存储 C++
C++中的多态
C++中的多态
8 0
|
7月前
多态
对于方法而言 优先使 用子类方法 对于成 员变量而言 优先使用 父类变量 访问成员变量的两种方式: 1.直接通过对象名称访问成员变量,看等号左边是谁,优先用谁,没有向上寻找 2.间接通过成员方法访问成员变量,看该方法属于谁。优先用谁,没有向上寻找
27 0
|
5天前
深入理解多态
深入理解多态
11 0
|
5天前
|
C++
|
10月前
多态你真的了解吗?
多态你真的了解吗?
45 0
|
5天前
|
存储 编译器 C++
C++【多态】
C++【多态】
44 0
|
5天前
|
编译器 C++
【C++】:多态
【C++】:多态
42 0
|
7月前
|
编译器 对象存储 C++
【C++】多态
【C++】多态
13 0
|
7月前
|
大数据 编译器 C++
|
7月前
|
C++