函数对象与普通函数区别

出处:C++ - [ ]操作符使用 、函数对象与普通函数区别(详解)

函数对象
  • 函数对象是指该对象具备函数的行为
  • 函数对象,是通过()调用操作符声明得到的,然后便能通过函数方式来调用该对象了.
  • ()调用操作符可以定义不同参数的多个重载函数
  • ()调用操作符只能通过类的成员函数重载(不能通过全局函数)
  • 函数对象用于在工程中取代函数指针

比如,定义一个函数对象t:

class Test{
public:
      void operator () (void)  //通过()重载操作符,来使对象具备函数的行为
     {
              cout<<"hello"<<endl;
     }
};

int main()
{
       Test t;
       t();          //打印"hello"
} 
函数对象与普通函数区别
函数对象

可以封装自己的成员以及其它函数,所以能够更好的面向对象.

普通函数

往往只具备逻辑关系,并且没有固定的成员,因为普通函数一被调用完后,里面的内容便被摧毁了,除非使用全局变量,但是全局变量又不具备封装性.

接下来,我们来个普通函数和函数对象的示例,便知道两者的区别了.

需求如下:

  • 通过一个函数,来获取斐波那契数列每项的值
  • 每调用一次函数,便返回一个值
  • 可以重复使用

普通函数实例:

#include <iostream>           
using namespace std;

int cnt0=0;
int cnt1=1;

void fib_set(int n)          //设置斐波那契数列为哪一项,使fib()能重复使用
{
     cnt0=0;
     cnt1=1;
     for(int i=0;i<n;i++)
     {
            int tmp=cnt1;
            cnt1=cnt0+cnt1;
            cnt0=tmp;
     }
}

int fib()              //计算出一项值
{
  int tmp=cnt1;
  cnt1=cnt0+cnt1;
  cnt0=tmp;

  return tmp;
}

int main()
{
  for(int i=0;i<5;i++)
  cout<<fib()<<endl;    //打印1~5项值

  fib_set(0);                  //从新设置项数位0

  for(int i=0;i<5;i++)
  cout<<fib()<<endl;    //再次打印1~5项值,使它能重复使用

  return 0;     
} 

运行打印:

1
1
2
3
5
1
1
2
3
5

从上面代码可以看到,通过普通函数实现的需求,还需要两个全局变量才行,这在大项目里,完全不可取的,若项目里,像这样的模块多的话,那得有多少个全局变量啊?并且这些全局变量能够随意被破坏,没有一点封装性.

接下来,通过函数对象来完成这个需求:

#include <iostream>           
using namespace std;

class Fib{
private:
       int cnt0;
       int cnt1;
public:
       Fib(int n=0)
       {
          cnt0=0;
          cnt1=1;
       }

       void operator =(int n)
       {
          cnt0=0;
          cnt1=1;      
          for(int i=0;i<n;i++)
          {
             int tmp=cnt1;
             cnt1+=cnt0;
             cnt0=tmp;
          }
       }

       int operator () ()
       {
             int tmp=cnt1;
             cnt1+=cnt0;
             cnt0=tmp;

              return cnt0;
       }

};

int main()
{
  Fib  fib;
  for(int i=0;i<5;i++)
  cout<<fib()<<endl;    //打印1~5项值

  fib=0;                        //从新设置项数为0

  for(int i=0;i<5;i++)
  cout<<fib()<<endl;    //打印1~5项值

  return 0;     
} 

运行打印:

1
1
2
3
5
1
1
2
3
5

从上面代码看到,使用函数对象后,便不需要使用全局变量了.

<think>首先,用户的问题是关于C++中虚函数普通成员函数区别。我需要基于系统指令和提供的引用来回答。 关键系统指令: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 提供的引用: - [^1]: 解释了虚函数的核心目的是通过基类访问派生类定义的函数,提高可读性。 - [^2]: 讨论了静态成员函数普通成员函数区别普通成员函数在代码区创建,只通过对象调用。 - [^3]: 强调了多态需要基类指针、虚函数和子类重写;基类指针只能调用继承的公共函数或重写的虚函数。 - [^4]: 给出了一个代码示例,展示虚函数如何通过基类指针调用派生类函数。 我的回答结构: 1. **介绍主题**:简要解释虚函数普通成员函数。 2. **关键区别**:列出主要区别点,基于引用。 3. **详细解释**:逐步解释每个区别,包括代码示例。 4. **总结**:概括核心差异。 5. **相关问题**:生成相关问题。 使用中文回答,并确保LaTeX语法正确。由于问题不涉及数学表达式,可能不需要数学格式,但如果提到如函数调用等,可以适当使用行内格式。 核心区别基于引用: - **虚函数**:用于实现多态。通过基类指针或引用调用时,会根据实际对象类型调用派生类的函数。必须使用virtual关键字声明,可以被派生类重写。 - **普通成员函数**:在编译时绑定。调用基于对象的静态类型,不能实现运行时多态。普通成员函数在类实例化时在代码区创建,但只通过对象调用。 从引用中提取要点: - [^1]:虚函数允许通过基类访问派生类函数。 - [^2]:普通成员函数在代码区创建,通过对象调用。 - [^3]:多态需要基类指针、虚函数和重写;基类指针只能调用公共函数或重写的虚函数。 - [^4]:代码示例显示虚函数的动态绑定。 回答结构: 1. **引言**:定义虚函数普通成员函数。 2. **区别1:绑定时间**:虚函数是动态绑定(运行时),普通函数是静态绑定(编译时)。 3. **区别2:多态支持**:虚函数支持多态,普通函数不支持。 4. **区别3:声明和使用**:虚函数需要virtual关键字,可以被覆盖;普通函数不需要。 5. **区别4:调用方式**:虚函数可以通过基类指针调用派生类实现;普通函数只能基于对象类型调用。 6. **代码示例**:提供简单代码展示区别。 7. **总结**。 8. **相关问题**。 确保引用自然添加:在解释时,如果基于特定引用,在句末添加[^n]。 生成相关问题:基于主题,如虚函数的应用、性能影响等。</think>### C++虚函数普通成员函数区别 在C++中,虚函数(virtual function)和普通成员函数(non-virtual member function)是面向对象编程的核心概念,它们的主要区别在于多态性、绑定机制和使用场景。下面我将逐步解释这些区别,帮助你深入理解。回答基于C++标准(如C++11及以上),并结合实际代码示例说明。 #### 1. **核心概念介绍** - **虚函数**:使用`virtual`关键字声明的成员函数,允许在派生类中被重写(override)。其核心目的是实现运行时多态,即通过基类指针或引用调用函数时,实际执行的是派生类的实现[^1]。例如: ```cpp class Base { public: virtual void print() { std::cout << "Base" << std::endl; } // 虚函数 }; ``` - **普通成员函数**:未使用`virtual`关键字的成员函数,在编译时绑定到具体类。它不支持多态,调用基于对象的静态类型[^2]。例如: ```cpp class Base { public: void display() { std::cout << "Base Display" << std::endl; } // 普通成员函数 }; ``` #### 2. **关键区别详解** 下面从多个维度对比两者的差异,确保解释清晰可靠。 - **区别1:绑定机制(静态 vs 动态)** - **普通成员函数**:使用静态绑定(编译时绑定)。函数调用在编译时确定,基于对象的声明类型。例如,如果对象是`Base`类型,则始终调用`Base`的函数实现,无法根据实际对象类型调整[^2]。 - **虚函数**:使用动态绑定(运行时绑定)。函数调用在运行时通过虚函数表(vtable)解析,根据对象的实际类型(如派生类)决定执行哪个函数。这实现了多态性,允许基类指针调用派生类函数[^1][^3]。 代码示例说明: ```cpp #include <iostream> class Base { public: virtual void print() { std::cout << "Base" << std::endl; } // 虚函数 void display() { std::cout << "Base Display" << std::endl; } // 普通函数 }; class Derived : public Base { public: void print() override { std::cout << "Derived" << std::endl; } // 重写虚函数 void display() { std::cout << "Derived Display" << std::endl; } // 普通函数(隐藏基类版本) }; int main() { Derived d; Base* ptr = &d; // 基类指针指向派生类对象 ptr->print(); // 输出 "Derived":虚函数,动态绑定 ptr->display(); // 输出 "Base Display":普通函数,静态绑定(基于Base类型) } ``` 在这个例子中,`print()`是虚函数,通过基类指针调用时执行派生类版本;`display()`是普通函数,始终调用基类版本[^3][^4]。 - **区别2:多态性支持** - **普通成员函数**:不支持多态。派生类可以定义同名函数,但这会隐藏(hide)基类函数而非重写。调用时完全依赖对象的静态类型,无法通过基类接口访问派生类特定行为[^2][^3]。 - **虚函数**:支持运行时多态。派生类通过`override`关键字重写虚函数,基类指针或引用可以透明地调用派生类实现。这是实现“一个接口,多种实现”的关键[^1][^4]。引用[^3]强调:多态需要三个条件——基类指针、虚函数声明和派生类重写。 - **区别3:内存和生命周期** - **普通成员函数**:在类首次实例化时,函数代码在内存的代码区创建一次。之后所有对象共享同一份代码,即使对象被销毁,函数代码也不会释放(参代码区生命周期)。调用必须通过具体对象实例[^2]。 - **虚函数**:每个包含虚函数的类都有一个虚函数表(vtable),存储函数指针。这增加了少量内存开销(vtable指针),但允许动态派发。虚函数本身也在代码区,但绑定机制更灵活[^1]。 - **区别4:声明和使用约束** - **普通成员函数**:不需要特殊关键字,派生类可以定义同名函数,但这不是重写而是隐藏。基类指针只能调用基类自身定义的函数,无法访问派生类新增或隐藏的函数[^3]。 - **虚函数**:必须用`virtual`声明(在派生类可省略,但建议用`override`显式标记)。基类指针可以调用任何重写的虚函数,但不能调用派生类独有的非虚函数[^4]。 #### 3. **总结** - **核心差异**:虚函数通过动态绑定实现多态,允许基类接口调用派生类功能;普通成员函数通过静态绑定提供高效但固定的行为。 - **何时使用**: - 用虚函数当需要运行时多态(如框架设计、插件系统)。 - 用普通成员函数函数行为固定且无需派生类定制(性能更高,无vtable开销)。 - **性能注意**:虚函数有轻微运行时开销(vtable查找),普通函数更高效但缺乏灵活性[^1][^2]。 理解这些区别有助于设计可扩展的C++程序。如果你有具体代码场景,我可以进一步分析!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值