cpp_5_3_多态_0_虚函数 Virtual Functions

  1. Oviewview
  2. Pointers and references to the base class of derived objects 指向派生对象的基类的指针/引用
  3. Virtual Function 虚函数
  4. Polymorphism 多态
  5. Reference

Oviewview

1.Pointers and references to the base class of derived objects 指向派生对象的基类的指针/引用
2.Virtual Function 虚函数
3.Polymorphism 多态

Pointers and references to the base class of derived objects 指向派生对象的基类的指针/引用

1.当我们构造一个派生对象的时候, 其实包括了一个基类的部分 (优先通过调用基类的构造函数), 和一个派生类的部分 (然后调用派生类的构造函数完成构造)
2.我们构建一个基础类和派生类, 然后分别用一个派生类的 rDerived引用 和一个指针 pDerived 指向派生类的对象

#include <iostream>
#include <cstring>

class Base {
 protected:
  int m_value{};
 public:
  Base(int value): m_value{value} {
  }
  std::string getName() const {
    return "Base";
  }
  int getValue() const {
    return m_value;
  }
};

class Derived : public Base {
 public:
  Derived(int value): Base{value} {
  }
  std::string getName() const {
    return "Derived";
  }
  int getValueDoubled() const {
    return m_value * 2;
  }
};

int main() {
  Derived derived{5};
  std::cout << "derived is a " << derived.getName() << " and has value " << derived.getValue() << '\n';

  Derived& rDerived{derived};
  std::cout << "rDerived is a " << rDerived.getName() << " and has value " << rDerived.getValue() << '\n';

  Derived* pDerived{&derived};
  std::cout << "pDerived is a " << pDerived->getName() << " and has value " << pDerived->getValue() << '\n';
  return 0;
}

输出结果为

derived is a Derived and has value 5
rDerived is a Derived and has value 5
pDerived is a Derived and has value 5

和上述在派生类构建引用/指针不同, 这里我们构建一个基类的引用 (rBase) / 指针(pBase), 指向同一个派生类的对象;

#include <iostream>

int main() {
  Derived derived{5};

  Base& rBase{derived};
  Base* pBase{&derived};

  std::cout << "derived is a " << derived.getName() << " and has value " << derived.getValue() << '\n';
  std::cout << "rBase is a "   << rBase.getName() << " and has value " << rBase.getValue() << '\n';
  std::cout << "pBase is a "   << pBase->getName() << " and has value " << pBase->getValue() << '\n';
  return 0;
}

输出结果如下, 想想为什么是这个输出结果?

derived is a Derived and has value 5
rBase is a Base and has value 5
pBase is a Base and has value 5

原因分析:
1.因为 rBase 和 pBase 是 Base 的引用和指针, 所以它们只能看到 Base 的成员; 因此, 即使 Derived::getName() 遮盖 (隐藏) Derived 对象的 Base::getName(), Base指针/引用也看不到 Derived::getName(), 因此他们嗲用的是Base::getName(); 因此这就是为什么结果输出的是 Base 而不是 Derived.

我们再看一个稍微复杂一点的例子, 体会一下这种指向派生对象基类的的指针和引用: Animal作为基类, cat 和 dog 分别继承 Animal 类, 然后构建 Animal 的引用和指针指向 cat 和 dog 对象;

#include <iostream>
#include <string>
#include <cstring>

class Animal {
 protected:
  std::string m_name;
  Animal(std::string name): m_name{name} {}
  Animal(const Animal&) = default;
  Animal& operator=(const Animal&) = default;
 public:
  std::string getName() const {
    return m_name;
  }
  std::string speak() const {
    return "???";
  }
};

class Cat : public Animal {
 public:
  Cat(std::string name): Animal{name} {}
  std::string speak() const {
    return "Meow";
  }
};

class Dog : public Animal {
 public:
  Dog(std::string name): Animal{name}{}
  std::string speak() const {
    return "Woof";
  }
};

int main() {
  const Cat cat{"Fred"};
  std::cout << "cat is named " << cat.getName() << ", and it says " << cat.speak() << '\n';
  const Dog dog{"Garbo"};
  std::cout << "dog is named " << dog.getName() << ", and it says " << dog.speak() << '\n';
  const Animal* pAnimal{&cat};
  std::cout << "pAnimal is named " << pAnimal->getName() << ", and it says " << pAnimal->speak() << '\n';
  pAnimal = &dog;
  std::cout << "pAnimal is named " << pAnimal->getName() << ", and it says " << pAnimal->speak() << '\n';
  return 0;
}

输出的结果如下, 我们想想这种基类指向派生类对象的指针的设定有什么问题 ? 先分析下结果, pAnimal 是一个基类的指针, 基类的指针只能看到 Animal类这部分的内容, 当我们调用 pAnimal -> speark() 的时候, 调用的是 Animal()->speark() 而不是 Dog::Speak() 也不是 Cat::Speark() 这样的;

cat is named Fred, and it says Meow
dog is named Garbo, and it says Woof
pAnimal is named Fred, and it says ???
pAnimal is named Garbo, and it says ???

这里停一下回到我们本能的问题, 为什么我们非要用基类指针/引用指向子类的对象呢 ? 有两点原因
(i). 如果我们希望在上面的例子上写一个函数, 实现打印猫和狗的 name 和 speak(), 那么就需要增加 2 个函数

void report(const Cat& cat) {
  std::cout << cat.getName() << " says " << cat.speak() << '\n';
}
void report(const Dog& dog) {
  std::cout << dog.getName() << " says " << dog.speak() << '\n';
}

如果我们在此基础上想增加30个动物呢 ? 那么我们需要增加 30 个函数, 显然增加的 30个动物这个函数写在基类不是很好吗? 所以这个函数一定是建立在基类之上的一个函数, 比如说如下的函数;

void report(const Animal& rAnimal) {
  std::cout << rAnimal.getName() << " says " << rAnimal.speak() << '\n';
}

修改一下代码

#include <iostream>
#include <string>
#include <cstring>

class Animal {
 protected:
  std::string m_name;
  Animal(std::string name): m_name{name} {}
  Animal(const Animal&) = default;
  Animal& operator=(const Animal&) = default;
 public:
  std::string getName() const {
    return m_name;
  }
  std::string speak() const {
    return "???";
  }
};

class Cat : public Animal {
 public:
  Cat(std::string name): Animal{name} {}
  std::string speak() const {
    return "Meow";
  }
};

class Dog : public Animal {
 public:
  Dog(std::string name): Animal{name}{}
  std::string speak() const {
    return "Woof";
  }
};

void report(const Animal& animal) {
  std::cout << animal.getName() << " says " << animal.speak() << '\n';
}

int main() {
  Cat cat{"Fred"};
  Dog dog{"Garbo"};

  report(cat);
  report(dog);
  return 0;
}

输出结果如下, 显然这个不能满足我们的需求, 这时候就要引入一种新的机制 virtual function;

Fred says ???
Garbo says ???

Virtual Function 虚函数

A virtual function is a special type of member function that, when called, resolves to the most-derived version of the function for the actual type of the object being referenced or pointed to.

A derived function is considered a match if it has the same signature (name, parameter types, and whether it is const) and return type as the base version of the function. Such functions are called overrides.

To make a function virtual, simply place the “virtual” keyword before the function declaration.

intuitively,
1.虚函数是一种特殊的成员函数, 在函数调用时, 它会解析为引用或指向的对象的函数的”最派生的版本”, 也就是”最子类的版本”, 可以理解为一种运行期间的动态的函数重写
2.派生函数根据基类函数在有着相同的函数签名 (函数名称, 参数类型, 是否const) 的条件下进行匹配, 然后调用一个覆盖基的函数版本
3.cpp中, 如果想声明一个函数虚函数, 只需要在函数前面增加 virtual 关键字

#include <iostream>
#include <string_view>

class Base {
 public:
  virtual std::string_view getName() const { 
    return "Base";
  }
};

class Derived : public Base {
 public:
  std::string_view getName() const {
    return "Derived";
  }
};

int main() {
  Derived derived{};
  Base& rBase{derived};
  std::cout << "rBase is a " << rBase.getName() << '\n';
  return 0;
}

再看一个比较复杂的例子

#include <iostream>
#include <cstring>

class A {
 public:
  virtual std::string getName() const { 
    return "A";
  }
};

class B : public A {
 public:
  virtual std::string getName() const {
    return "B";
  }
};

class C : public B {
 public:
  virtual std::string getName() const { 
    return "C";
  }
};

class D : public C {
 public:
  virtual std::string getName() const {
    return "D";
  }
};

int main() {
  C c{};
  A& rBase{c};
  std::cout << "rBase is a " << rBase.getName() << '\n';
  return 0;
}

输出结果为

rBase is a C

原因分析:
(i). 首先我们实例化一个 C 类对象.
(ii). 声明 rBase , 它是一个 A 引用, 我们将其设置为引用 C 对象的 A 部分.
(iii). 最后, 我们调用 rBase.getName(). rBase.getName() 计算结果为 A::getName(). 然而, A::getName() 是虚拟的, 因此编译器将调用 A 和 C 之间”最派生”的匹配. 在本例中, 即 C::getName().
(iv). 注意, 它不会调用 D::getName(), 因为我们的原始对象是 C, 而不是 D, 因此只考虑 A 和 C 之间的函数

Polymorphism 多态

Polymorphism refers to the ability of an entity to have multiple forms (the term “polymorphism” literally means “many forms”).

Polimorphsim Definition from wiki: In programming language theory and type theory, polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types. The concept is borrowed from a principle in biology where an organism or species can have many different forms or stages.

Compile-time polymorphism refers to forms of polymorphism that are resolved by the compiler. These include function overload resolution, as well as template resolution.

Runtime polymorphism refers to forms of polymorphism that are resolved at runtime. This includes virtual function resolution.

intuitively
1.多态指的是实体具备多种形态; Polimorphsim 来自 wiki 的定义:在编程语言理论和类型理论中,多态性是为不同类型的实体提供单一接口或使用单一符号来表示多个不同类型. 这个概念借用了生物学的原理,有机体或物种可以有许多不同的形式或阶段.
2.多态分为两种, 一种是编译器的多态, 一种是运行期的多态;
3.编译期的多态: 通过编译器解析的一种多态, 其中典型的例子是函数重载的解析和模板的解析;
4.运行期的多态: 运行时解析的一种多态, 虚函数就是一种典型的运行期多态;

我们理解了多态的含义, 再看下之前猫狗继承动物调用 speak() 的这个例子, 理解如何使用多态去更灵活地组织代码:

#include <iostream>
#include <string>
#include <cstring>

class Animal {
 protected:
  std::string m_name;
  Animal(std::string name): m_name{name} {}
  Animal(const Animal&) = default;
  Animal& operator=(const Animal&) = default;
 public:
  std::string getName() const {
    return m_name;
  }
  // 这里将 speak() 声明成了虚函数
  virtual std::string speak() const {
    return "???";
  }
};

class Cat : public Animal {
 public:
  Cat(std::string name): Animal{name} {}
  std::string speak() const {
    return "Meow";
  }
};

class Dog : public Animal {
 public:
  Dog(std::string name): Animal{name}{}
  std::string speak() const {
    return "Woof";
  }
};

void report(const Animal& animal) {
  std::cout << animal.getName() << " says " << animal.speak() << '\n';
}

int main() {
  Cat cat{"Fred"};
  Dog dog{"Garbo"};

  report(cat);
  report(dog);
  return 0;
}

输出结果如下, 多态性的存在帮我们更好地组织了代码;

Fred says Meow
Garbo says Woof

我们再看一个利用多态计算图形面积的的例子, 一个基类是 shape , 子类是 rectangle 和 triangle , 基类的指针可以在运行期间动态地绑定各种子类的对象, 然后输出 shape 指针的当前的面积值;

#include <iostream>

class Shape {
 protected:
  int width;
  int height;
 public:
  Shape(int a, int b): width(a), height(b) {}
  // 声明成虚函数
  virtual int area() {
    std::cout << "Shape width:" << width << ", height:" << height << std::endl;
    return 0;
  }
  // 定义成纯虚函数
  // virtual int area() = 0;
};

class Rectangle : public Shape {
 public:
  Rectangle(int a, int b): Shape(a, b) {}
  int area() {
    std::cout << "Rectangle area: " << (width * height) << std::endl;
    return (width * height);
  }
};

class Triangle : public Shape {
 public:
  Triangle(int a, int b): Shape(a, b) {}
  int area() {
    std::cout << "triangle area: " << (width * height / 2) << std::endl;
    return (width * height / 2);
  }
};

int main() {
  Shape* shape;
  Rectangle rec(10, 8);
  Triangle tri(10, 4);

  shape = &rec;
  shape->area();

  shape = &tri;
  shape->area();
  return 0;
}

Reference

  1. https://en.wikipedia.org/wiki/Polymorphism_(computer_science)

转载请注明来源, from goldandrabbit.github.io

💰

×

Help us with donation