前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >60秒问答:多态和函数重载的关系?

60秒问答:多态和函数重载的关系?

作者头像
程序员小王
发布2021-07-22 10:46:49
1.2K0
发布2021-07-22 10:46:49
举报
文章被收录于专栏:架构说架构说

目录:阅读该文章将获得如下收益

  • 什么是多态,与重载,重写,隐藏什么关系?
  • 名词隐藏机制和重载 new 函数 例子(liunx api返回值设计 1返回错误 2 抛异常3 抛信号)
  • 隐藏可以避免吗,在c++11中呢?
  • 函数重载与stl萃取机制结合 实现编译时多态
  • 汇编查看虚函数指针与构造 和析构函数关系。

60秒问答

一、 问:重载,重写 ,隐藏区别?

答:

  1. 重载 相同作用域内,函数名字相同,参数不同。
  2. 重写 不用作用域 , 函数名字相同,参数相同。
  3. 隐藏:不用作用域, 通过派生类访问:派生类同名函数,隐藏基类函数 或者通过基类指针访问,基类函数隐藏派生类。

二、 问题:如何解隐藏问题?

答:隐藏分为2个情况,同名函数查找过程 派生类 基类 全局

情况1 如果是通过派生类访问一个函数,派生类局部作用域隐藏上层 base同名函数。

  1. 为了让隐藏起来的名字重见天日,使用using声明
  2. 通过base类指针或者引用访问 (这个和虚函数无关)

情况2 如果是通过通过base类指针或者引用访问 隐藏派生类同名函数。

通过虚函数解决

三、 问 什么是多态?怎么实现的

答:

多态:同一个函数,不同的行为。具体选择那个行为 2个情况。

编译时的多态:函数重载和运算符重载(根据参数不同选择具体函数 )

运行时的多态:通过类继承和虚函数实现的(根据虚表指针 指向 派生类的函数,还是基类的函数)

四、 类型转换有几种情况,有什么区别?

答:

  1. 自动类型转换,缺点有可能丢失精度(派生类转换base类,3.14--3)
  2. 静态转换 动态转换 强制转换 和解释转换 3、 区别:静态转换 任何类型

解释

到底什么是多态?这个概念很模糊,不清楚,

就是具体执行那个函数吗?

  • case1. if a if b, if c 是多态吗?表现不一样。

整理这个文章之后,依然不清楚,有了解的可以告诉我

我知道的

面向对象的三大特征:

1.封装:保证对象自身数据的完整性、安全性

2.继承:建立类之间的关系,实现代码复用、方便系统的扩展

3.多态:相同的方法调用可实现不同的实现方式。【定义】

多态是指两个或多个属于不同类的对象,对于同一个消息(方法调用)作出不同响应的方式。

、、、、、、、、、

实现多态的方式【为什么3个情况,不是一个情况】

  1. 函数重载;
  2. 运算符重载;
  3. 虚函数

、、、、、、、、、

  • 多态性指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。[定义]

C++支持两种多态性:编译时多态性,运行时多态性。

1.编译时的多态:函数重载和运算符重载,在编译时就决定调用哪个函数,先期联编 early binding

2.运行时的多态:通过类继承和虚函数实现的

C++运行时多态性是通过虚函数来实现的,

虚函数允许子类重新定义成员函数,

而子类重新定义父类的做法称为覆盖(Override),或者称为重写。

  • 多态与非多态的实质区别就是函数地址是早绑定

1.2 运算符 operator= 重载例子 【STL源码剖析简体】

  • STL—Iterator的分类和copy的重载及其使用
  • https://www.cplusplus.com/reference/iterator/insert_iterator/
代码语言:javascript
复制
template <class _Container>
class insert_iterator {
protected:
  _Container* container;
  typename _Container::iterator iter;
public:

  operator=(const typename _Container::value_type& __value) { 
    iter = container->insert(iter, __value);
    ++iter;
    return *this;
  }
  
};

// insert_iterator example
#include <iostream>     // std::cout
#include <iterator>     // std::insert_iterator
#include <list>         // std::list
#include <algorithm>    // std::copy

int main () {
  std::list<int> foo, bar;
  for (int i=1; i<=5; i++)
  { foo.push_back(i); bar.push_back(i*10); }

  std::list<int>::iterator it = foo.begin();
  advance(it,3);

  std::insert_iterator< std::list<int> > insert_it (foo,it);

  std::copy (bar.begin(),bar.end(),insert_it);

  std::cout << "foo:";
  for ( std::list<int>::iterator it = foo.begin(); it!= foo.end(); ++it )
   std::cout << ' ' << *it;
  std::cout << '\n';

  return 0;
}

Output:

1 2 3 10 20 30 40 50 4 5

我不知道的:什么是可扩展的多态

多态性机制不仅增加了面向对象软件系统的灵活性,进一步减少了冗余信息, 而且显著提高了软件的可重用性和可扩充性。

从实现的角度来讲,c++多态性可以划分为两类

  1. 编译时的多态:函数重载和运算符重载,在编译时就决定调用哪个函数
    • 重载 编译时的多态
    • 重载指允许【在相同作用域中】存在多个同名的函数,这些函数的参数表不同
  2. 运行时的多态:通过类继承和虚函数实现的,在运行时就就决定调用哪个函数
    • 重写(override)就是一种运行时多态
    • 覆盖是存在类中,【子类重写】从基类继承过来的函数,函数名、返回值、参数列表都必须和基类相同

重载,重写都出现了,隐藏呢:

  • 是指派生类的函数屏蔽了与其同名的基类函数, 注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。

作用域与名字隐藏机制

代码语言:javascript
复制

数据隐藏

int n = 1;//全局作用域
int main()
{
 int n = 2;//局部作用域
 {
  int n = 3;//块作用域
 }
}

转载自《C++ exceptional style》第22条

隐藏经常发生在继承关系中,派生类重新定义了基类的非virtual函数【虚函数也隐藏】,当发生隐藏时,编译器名字隐藏机制如下:

1. 编译器会从当前域开始查找(比如派生类对象调用,会在派生类的定义内查找),查找需要的名字;

2. 如果在当前域没有找到,编译器会在外围作用域继续查找,先是基类的定义内,然后是全局名字空间;

3. 一旦在某个作用域内包含需要的名字就会停下来,并就该作用域内的名字进行决议
,这意味着往外层的作用域就不予考虑了,从而将外层作用域的同名函数隐藏;[不在去寻找更合适的]

4.编译器在当前的名字空间中找到与所求名字同名的实体之间进行决议(函数重载),如果选不出最优,就产生二义性错误;[寻找不到,然后报错]

5. 选出最优的函数,查看函数是否可以访问/调用




IF 子类的函数与父类的名称相同,但是参数不同

父类函数被隐藏

ELSE IF 子类函数与父类函数的名称相同&&参数也相同&&但是父类函数没有virtual

父类函数被隐藏

ELSE IF 子类函数与父类函数的名称相同&&参数也相同&&但是父类函数有virtual

父类函数被覆盖

C++名字隐藏机制例子1

全局:重载3个operator new

new 是可以被重载的

  • void* operator new (std::size_t size)
  • void* operator new (std::size_t size, const std::nothrow_t& nothrow_value)
  • void* operator new (std::size_t size, void* ptr)
代码语言:javascript
复制

https://www.cplusplus.com/reference/new/operator%20new/

throwing (1) void* operator new (std::size_t size);

nothrow (2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;

placement (3) 
void* operator new (std::size_t size, void* ptr) noexcept;


// operator new example
#include <iostream>     // std::cout
#include <new>          // ::operator new
struct MyClass {
  int data[100];
  MyClass() {std::cout << "constructed [" << this << "]\n";}
};

int main () {
  std::cout << "1: ";
  MyClass * p1 = new MyClass;
      // allocates memory by calling: operator new (sizeof(MyClass)) 
      //1 计算大小 
      //2 调用 operator new
      // and then constructs an object at the newly allocated space 
      //3 执行构造函数

  std::cout << "2: ";
  
  MyClass * p2 = new (std::nothrow) MyClass; 
      // allocates memory by calling: operator new (sizeof(MyClass),std::nothrow)
      // and then constructs an object at the newly allocated space
      // 返回值为0 ,不抛异常。

  std::cout << "3: ";
  new (p2) MyClass;
      // does not allocate memory -- calls: operator new (sizeof(MyClass),p2)
      // but constructs an object at p2

  // Notice though that calling this function directly does not construct an object:
  std::cout << "4: ";
  MyClass * p3 = (MyClass*) ::operator new (sizeof(MyClass));
      // allocates memory by calling: operator new (sizeof(MyClass))
      // but does not call MyClass's constructor
  return 0;
}

局部只重载placement new,遗忘另外2个情况(不抛出异常和抛异常)
继承体系中的名字是如何被隐藏的
  • 首先编译器在Derived类内查找new函数,没有找到名字;
  • 编译器往外一层查找new,在基类Base定义内查找,找到了new函数,就不继续考虑全局空间的定义了
  • 基类只定义了一个new运算符,把简单new,定位new和nothrow new都隐藏了,导致无匹配的版本可使用
代码语言:javascript
复制
//例子 名字隐藏了new
 
 class Base
 {
 public:
     static void* operator new(std::size_t, const FastMemory&);
 };
 
 class Derived: public Base
 {
     Derived *p1 = new Derived;              //错误,无可用的匹配,因为简单new被隐藏
     Derived *p2 = new(std::nothrow) Derived;     //错误,无可用的匹配,因为nothrow new被隐藏
 
     void *p3 = /* 足够放下一个Derived对象 */        
     new(p3) Derived;                                //错误,无可用的匹配,因为定位new被隐藏
     
     FastMemroy f;
     Derived *p4 = new(f) Derived;                   //调用Base::operator new()
}

普通函数的隐藏

(1)函数Derived::f(float)覆盖了Base::f(float)。父类函数被覆盖

(2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。不相同的参数

(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。相同的参数

  • 特点:IF 子类的函数与父类的名称相同,但是参数不同

父类函数被隐藏

ELSE IF 子类函数与父类函数的名称相同&&参数也相同&&但是父类函数没有virtual

父类函数被隐藏

ELSE IF 子类函数与父类函数的名称相同&&参数也相同&&但是父类函数有virtual

父类函数被覆盖

代码语言:javascript
复制
#include <iostream>

using namespace std;

class Base
{
public:
    virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
    void g(float x){ cout << "Base::g(float) " << x << endl; }
    void h(float x){ cout << "Base::h(float) " << x << endl; }
};

class Derived : public Base
{
public:
    virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }
    void g(int x){ cout << "Derived::g(int) " << x << endl; }
    void h(float x){ cout << "Derived::h(float) " << x << endl; }
};

int main(void)
{
    Derived d;
    Base *pb = &d;
    Derived *pd = &d;
    // Good : behavior depends solely on type of the object
    pb->f(3.14f); //Derived::f(float) 3.14  
   // 函数Derived::f(float)覆盖了Base::f(float) 多态
    pd->f(3.14f); //Derived::f(float) 3.14  非多态

    // Bad : behavior depends on type of the pointer
    pb->g(3.14f); //Base::g(float) 3.14
    pd->g(3.14f); //Derived::g(int) 3  
    //函数Derived::g(int)隐藏了Base::g(float),不相同的参数

    // Bad : behavior depends on type of the pointer
    pb->h(3.14f); //Base::h(float) 3.14
    pd->h(3.14f); //Derived::h(float) 3.14
    // 函数Derived::h(float)隐藏了Base::h(float),相同的参数
   


    return 0;
}


[root@VM-0-10-centos work]# ./a.out
Derived::f(float) 3.14
Derived::f(float) 3.14

Base::g(float) 3.14
Derived::g(int) 3

Base::h(float) 3.14
Derived::h(float) 3.14

错误理解:

  • 直接回答隐藏无法避免是错误的,没有区分 参数相同,参数不同。

三、如何将隐藏行为覆盖掉

情况1 如果是通过派生类访问一个函数,派生类局部作用域隐藏上层 base类函数

  1. 为了让隐藏起来的名字重见天日,使用using声明
  2. 通过base类指针或者引用访问 (这个和虚函数无关)

情况2 如果是通过通过base类指针或者引用访问 隐藏派生类同名函数。

通过虚函数解决

代码语言:javascript
复制
   // Good : behavior depends solely on type of the object
    pb->f(3.14f); //Derived::f(float) 3.14  
   // 函数Derived::f(float)覆盖了Base::f(float) 多态
    pd->f(3.14f); //Derived::f(float) 3.14  非多态

3.1 C++核心准则C.138:使用using为派生类生成重载函数集

C.138: Create an overload set for a derived class and its bases with using

原文链接:

  • https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c138-create-an-overload-set-for-a-derived-class-and-its-bases-with-using
  • http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#c138-create-an-overload-set-for-a-derived-class-and-its-bases-with-using
  • https://www.cnblogs.com/harlanc/p/6556371.html
代码语言:javascript
复制
C.138: Create an overload set for a derived class and its bases with using

Reason


Without a using declaration, member functions in the derived class hide the entire inherited overload sets.

如果没有using声明,派生类中的成员函数将隐藏整个继承的重载集。


Example, bad
#include <iostream>
class B {
public:
    virtual int f(int i) { std::cout << "f(int): "; return i; }
    virtual double f(double d) { std::cout << "f(double): "; return d; }
    virtual ~B() = default;
};
class D: public B {
public:
    int f(int i) override { std::cout << "f(int): "; return i + 1; }
};
int main()
{
    D d;
    std::cout << d.f(2) << '\n';   // prints "f(int): 3"
    std::cout << d.f(2.3) << '\n'; // prints "f(int): 3"
}

//在此证明 虚函数无法解决参数不相同的隐藏问题


Example, good
class D: public B {
public:
    int f(int i) override { std::cout << "f(int): "; return i + 1; }
    using B::f; // exposes f(double)
};
Note This issue affects both virtual and non-virtual member functions
For variadic bases, C++17 introduced a variadic form of the using-declaration,

template<class... Ts>
struct Overloader : Ts... {
    using Ts::operator()...; // exposes operator() from every base
};

Enforcement 

Diagnose 
name hiding
名称隐藏


C.140: Do not provide different default arguments for a virtual function and an overrider
Reason That can cause confusion: An overrider does not inherit default arguments.Example, bad
class Base {
public:
    virtual int multiply(int value, int factor = 2) = 0;
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    int multiply(int value, int factor = 10) override;
};

Derived d;
Base& b = d;

b.multiply(10);  // these two calls will call the same function but
d.multiply(10);  // with different arguments and so different results

  • 警告:

青铜回答:用override解决隐藏?

点评:

  • 这个想法是错误的,一看就没有经过验证,这个认知要纠正
  • C++11 中的 override 关键字,可以显式的在派生类中声明,哪些成员函数需要被重写,如果没被重写,则编译器会报错。[表示这个函数一定是base出现过的。]

3.2 普通函数

代码语言:javascript
复制
--example 1--
#include<iostream>
using namespace std;
namespace Lib
{
    void print(int x)
    {
        cout<<"int"<<x<<endl;
    }
}
void print(double y){
        cout<<"double"<<y<<endl;
    }
int main()
{
    using Lib::print;//example 1 : main作用域中嵌套了Lib命名空间
    print(1.3);
    print(3);
    getchar();
    return 1;
}
//输出:
int 1
int 3
1.3 函数参数传递 与类型转换
  • C++ 函数类型自动转化 与二义性【成功就匹配,不然就是错误】

函数传递过程中, 参数 如果是基本类似,发生自动转换。

问题来源:4.编译器在当前的名字空间中找到与所求名字同名的实体之间进行决议(函数重载),如果选不出最优,就产生二义性错误

代码语言:javascript
复制
https://blog.nowcoder.net/n/bb65a484a87d4a7fab967d0555f6a152
1 int get(int m){
2     return m;
3 }
4  
5 long get(long m){
6     return m;
7 }
//double d = 1.234;
//调用get(d);double既可以隐式转换未long,也可以是int,
//或者说一般的数值类型之间都可以进行隐式类型转换,
//故无法确定那一个更加匹配。

函数传递过程中, 参数 如果是类 发生切割转换

  • C.145: Access polymorphic objects through pointers and references C.146: Use dynamic_cast where class hierarchy navigation is unavoidable
代码语言:javascript
复制
C.hier-access: Accessing objects in a hierarchy

Reason 

If you have a class with a virtual function, 

you don’t (in general) know which class provided the function to be used.



Example
struct B { 
   int a; virtual int f(); 
  virtual ~B() = default 
};

struct D : B { int b; int f() override; };

void use(B b)
{
    D d;
    B b2 = d;   // slice ??? 这地方怎么发生切换了呢
    B b3 = b;
}

void use2()
{
    D d;
    use(d);   // slice
}
Both ds are sliced.

Exception 


You can safely access a named polymorphic object in the scope of its definition, just don’t slice it.

void use3()
{
    D d;
    d.f();   // OK
}
See also 

A polymorphic class should suppress copying Enforcement Flag all slicing.




C.130: For making deep copies of polymorphic classes prefer a virtual clone function instead of public copy construction/assignment

Reason

Copying a polymorphic class is discouraged due to the slicing problem, see C.67. If you really need copy semantics, copy deeply: Provide a virtual clone function that will copy the actual most-derived type and return an owning pointer to the new object, and then in derived classes return the derived type (use a covariant return type)

代码语言:javascript
复制
.Example
class B {
public:
    virtual owner<B*> clone() = 0;
    
    B() = default;
    virtual ~B() = default;
    B(const B&) = delete; //禁用 
    
    B& operator=(const B&) = delete; //禁用 
};

class D : public B {
public:
    owner<D*> clone() override;
    ~D() override;
};

Generally, it is recommended to use smart pointers to represent ownership (see R.20). However, because of language rules, the covariant return type cannot be a smart pointer: D::clone can’t return a unique_ptrwhile B::clone returns unique_ptr. Therefore, you either need to consistently return unique_ptr in all overrides, or use owner<> utility from the Guidelines Support Library.

二、我不清楚的:重载 参数不一样呀?

2.1 疑问

多态:相同的方法调用可实现不同的实现方式,定义 重载 参数不一样呀?怎么算

多态分为四种:重载多态、强制多态、包含多态和参数多态。

重载多态分为两种:函数重载和运算符重载。

可以说,函数重载只是多态这个概念中非常小的一部分。

2.2 例子

代码语言:javascript
复制

//求距离 函数相同,参数相同 
template <class _InputIterator, class _Distance>
inline void distance(_InputIterator __first, 
                     _InputIterator __last, _Distance& __n)
{
  __STL_REQUIRES(_InputIterator, _InputIterator);
  __distance(__first, __last, __n, iterator_category(__first));
}


//参数 input_iterator_tag 不同
template <class _InputIterator, class _Distance>

inline void __distance(_InputIterator __first, _InputIterator __last,
                       _Distance& __n, input_iterator_tag)
{
  while (__first != __last) { ++__first; ++__n; }
}
//参数 random_access_iterator_tag 不同
template <class _RandomAccessIterator, class _Distance>
inline void __distance(_RandomAccessIterator __first, 
                       _RandomAccessIterator __last, 
                       _Distance& __n, random_access_iterator_tag)
{
  __STL_REQUIRES(_RandomAccessIterator, _RandomAccessIterator);
  __n += __last - __first;
}



- 类型:
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};

- iterator_traits

// The overloaded functions iterator_category, distance_type, and
// value_type are not part of the C++ standard.  (They have been
// replaced by struct iterator_traits.)  They are included for
// backward compatibility with the HP STL.

// We introduce internal names for these functions.

template <class _Iter>
inline typename iterator_traits<_Iter>::iterator_category
__iterator_category(const _Iter&)
{
  typedef typename iterator_traits<_Iter>::iterator_category _Category;
  return _Category();
}


// 特化版本一,针对原生指针类型
template <class _Tp>
struct iterator_traits<const _Tp*> {
  typedef random_access_iterator_tag iterator_category;
  typedef _Tp                         value_type;
  typedef ptrdiff_t                   difference_type;
  typedef const _Tp*                  pointer;
  typedef const _Tp&                  reference;
};


template <class _Iterator>
struct iterator_traits {
  typedef typename _Iterator::iterator_category iterator_category;
  typedef typename _Iterator::value_type        value_type;
  typedef typename _Iterator::difference_type   difference_type;
  typedef typename _Iterator::pointer           pointer;
  typedef typename _Iterator::reference         reference;
};


template <class _Container>
class insert_iterator {
protected:
  _Container* container;
  typename _Container::iterator iter;
public:
  typedef _Container          container_type;
  typedef output_iterator_tag iterator_category;
  typedef void                value_type;
  typedef void                difference_type;
  typedef void                pointer;
  typedef void                reference;
  

60秒总结

一、 问:重载,重写 ,隐藏区别?

答:

  1. 重载 相同作用域内,函数名字相同,参数不同。
  2. 重写 不用作用域 , 函数名字相同,参数相同。
  3. 隐藏:不用作用域, 通过派生类访问:派生类同名函数,隐藏基类函数 或者通过基类指针访问,基类函数隐藏派生类。

二、 问题:如何解隐藏问题?

答:隐藏分为2个情况,同名函数查找过程 派生类 基类 全局

情况1 如果是通过派生类访问一个函数,派生类局部作用域隐藏上层 base同名函数。

  1. 为了让隐藏起来的名字重见天日,使用using声明
  2. 通过base类指针或者引用访问 (这个和虚函数无关)

情况2 如果是通过通过base类指针或者引用访问 隐藏派生类同名函数。

通过虚函数解决

三、 问 什么是多态?怎么实现的

答:

多态:同一个函数,不同的行为。具体选择那个行为 2个情况。

编译时的多态:函数重载和运算符重载(根据参数不同选择具体函数 )

运行时的多态:通过类继承和虚函数实现的(根据虚表指针 指向 派生类的函数,还是基类的函数)

四、 类型转换有几种情况,有什么区别?

答:

  1. 自动类型转换,缺点有可能丢失精度(派生类转换base类,3.14--3)
  2. 静态转换 动态转换 强制转换 和解释转换 3、 区别:转换类型 ,释放必须虚函数 ,安全与效率。
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Offer多多 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录:阅读该文章将获得如下收益
  • 60秒问答
  • 解释
  • 我知道的
    • 1.2 运算符 operator= 重载例子 【STL源码剖析简体】
    • 我不知道的:什么是可扩展的多态
      • C++名字隐藏机制例子1
        • 全局:重载3个operator new
        • 局部只重载placement new,遗忘另外2个情况(不抛出异常和抛异常)
        • 普通函数的隐藏
      • 三、如何将隐藏行为覆盖掉
        • 3.1 C++核心准则C.138:使用using为派生类生成重载函数集
          • 3.2 普通函数
            • 1.3 函数参数传递 与类型转换
          • 函数传递过程中, 参数 如果是基本类似,发生自动转换。
            • 函数传递过程中, 参数 如果是类 发生切割转换
            • 二、我不清楚的:重载 参数不一样呀?
              • 2.1 疑问
                • 2.2 例子
                • 60秒总结
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档