c++ 临时对象的来源

 更新时间:2013年01月02日 17:56:54   作者:  
大家可能对这个临时对象这个概念还不是很清楚,那么首先我们花一些时间来理解临时对象

首先看下面一端代码:

复制代码 代码如下:

 #include <iostream>
 void swap( int &a,int &b)
 {
     int temp;
     temp=a;
     a=b;
     b=temp;
 }

 int main(int argc,char** argv)
 {
     int a=1,b=2;
     swap(a,b);
     std::cout<<a<<"-----"<<b<<std::endl;
     return 0;
 }

 结果为
 2-----1  
 可能大多数园友,认为"int temp"是"临时对象",但是其实不然,"int temp"仅仅是swap函数的局部变量。

临时对象是代码中看不到的,但是实际程序中确实存在的对象。临时对象是可以被编译器感知的。


为什么研究临时对象?
主要是为了提高程序的性能以及效率,因为临时对象的构造与析构对系统开销也是不小的,所以我们应该去了解它们,知道它们如何造成,从而尽可能去避免它们。

临时对象建立一个没有命名的非堆对象会产生临时对象。(不了解什么是堆对象和非堆对象,可以参考C++你最好不要做的这一博文,这里面有介绍。)这种未命名的对象通常在三种条件下产生:为了使函数成功调用而进行隐式类型转换时候、传递函数参数和函数返回对象时候。

那么首先看看为了使函数成功调用而进行隐式类型转换。

复制代码 代码如下:

 #include <iostream>
  int countChar(const std::string & s,const char c)
  {
      int count=0;
      for(int i=0;i<s.length( );i++)
      {
          if(*(s.c_str( )+i) == c)
          {
              count++;
         }
    }
     return count;
 }

 int main(int argc,char** argv)
 {
     char buffer[200];
     char c;
     std::cout<<"please input the string:";
     std::cin>>buffer;
     std::cout<<"please input the char which you want to chount:";
     std::cin>>c;
     int count=countChar(buffer,c);
     std::count<<"the count is:"<<count<<std::endl;
     return 0;
 }

结果为:



这里调用函数countChar(const std::string& s,const char& c),那么我们看看这个函数的形参是const std::string &s,形参类型为const std::string,但是实际上传递的是char buffer[200]这个数组。其实这里编译器为了使函数调用成功做了类型转换,char *类型转换为了std::string类型,这个转换是通过一个赋值构造函数进行的,以buffer做为参数构建一个std::string类型的临时对象。当constChar返回时,即函数撤销,那么这个std::string临时对象也就释放了。但是其实从整个程序上来说临时对象的构造与释放是不必要的开销,我们可以提高代码的效率修改一下代码避免无所谓的转换。所以知道临时对象的来源,可以对程序性能上有一个小小提升。

   注意仅当通过传值方式传递对象或者传递常量引用参数,才会发生这类型的转换,当传递非常量引用的参数对象就不会发生。因为传递非常量的引用参数的意图就是想通过函数来改变其传递参数的值,但是函数其实是改变的类型转换建立的临时对象,所以意图无法实现,编译器干脆就直接拒绝。

第二种情况是大家熟悉的函数传递参数的时候,会构造对应的临时对象。看下面一段代码运行的结果想必就一清二楚了。

复制代码 代码如下:

 #include<iostream>
 class People
 {
     public:
         People(std::string n,int a)
         :name(n),age(a)
         {
             std::count<<"h2"<<std::endl;
         }
        People( )
        {
            std::count<<"h1"<<std::endl;
        }
        People(const People& P)
        {
            name=p.name;
            age=p.age;
            std::cout<<"h3"<<std::endl;
        }
        std::string name;
        int age;
};

void swap(People p1,People p2)
{
    People temp;
    temp.age=p1.age;
    temp.name=p1.name;
    p1.age=p2.age;
    p1.name=p2.name;
    p2.age=temp.age;
    p2.name=temp.name;
}

int main(int argc, char ** argv)
{
    People p1("tom",18),p2("sam",19);
    swap(p1,p2);
    return 0;
}


结果为:



这里分析下前面两个"h2"是通过调用构造函数People(std::string n,int a)打印出来的,而"h3"就是通过调用复制构造函数People(const People&)而建立临时对象打印出来的,h1是调用默认构造函数People( )打印出来的。那么怎么避免临时对象的建立呢?很简单,我们通过引用实参而达到目的

void swap(People &p1,People &p2)
第三种情景就是函数返回对象时候。这里要注意临时对象的创建是通过复制构造函数构造出来的。

例如   const Rationanl operator+(Rationanl a,Rationanl b)该函数的返回值的临时的,因为它没有被命名,它只是函数的返回值。每回必须为调用add构造和释放这个对象而付出代价。

复制代码 代码如下:

 #include <iostream>
 class Rationanl
 {
     public:
         Rationanl(int e,int d)
         :_elemem(e),_denom(d)
         {
             std::cout<<"h2"<<std::endl;
         }
         void show( ) const;
         int elemem() const {return _elemem;}
         int denom() const {return _denom;}
         void  setElemon(int e){_elemon=e;}
         void  setDenom(int d) {_denom=d;}
         Rationanl(const Rationanl &r);
         Rationanl & operator=(const Rationanl &r);
     private:
         int _elemem;
         int _denom;
 };
 Rationanl::Rationanl(const Rationanl &r)
 {
     setElemon(r.elemon( ));
     setDenom(r.denom( ) );
     std::cout<<"h3"<<std::endl;
 }
 Rationanl & Rationanl::operator=(const Rationanl &r)
 {
     setElemon(r.elemon( ));
     setDenom(r.denom( ) );
     std::cout<<"h4"<<std::endl;
     return *this;
 }

 void Rationanl::show( )
 {
         std::cout<<_elemen<<"/"<<_denom<<std::endl;
 }
 const Rationanl operator*(const Rationanl lhs,const Rationanl rhs)
 {
     return Rational result(lhs.elemen*rhs.elemen,rhs.denom*rhs.denom);
 }

 int main(int argc,char **argv)
 {
     Rationanl r1(1,2),r2(1,3)
     Rationanl r3=r1*r2;    //GCC做了优化,没有看到临时变量。编译器直接跳过建立r3,使用赋值符号
     r3.show( );
     //相当于  (r1*r2).show( );
     return 0;
 }

结果为:



这里很可惜没有看到我们想到看到的结果,结果应该为h2,h2,h2,h3,h4,应该是在返回值的时候有一个赋值构造函数,建立临时变量的,后来经笔者网上查找资料证实GCC做了优化。

相关文章

  • 用C++的odeint库求解微分方程

    用C++的odeint库求解微分方程

    求解微分方程的数值解一般使用MATLAB等数值计算软件,其实C++也可以求解微分方程,需要用到odeint库,它是boost库的一部分。官方教程和示例比较晦涩,本文力求用较短的篇幅介绍它的基本用法,需要的朋友可以参考下面文章的具体内容
    2021-09-09
  • 关于c语言的一个小bug详解

    关于c语言的一个小bug详解

    以下是对c语言中的一个小bug进行了详细的分析介绍。需要的朋友可以过来参考下
    2013-08-08
  • 2~62位任意进制转换方法(c++)

    2~62位任意进制转换方法(c++)

    下面小编就为大家带来一篇2~62位任意进制转换方法(c++)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • QT Creator+OpenCV实现图像灰度化的示例代码

    QT Creator+OpenCV实现图像灰度化的示例代码

    这篇文章主要为大家详细介绍了QT如何利用Creator和OpenCV实现图像灰度化效果,文中的示例代码讲解详细,感兴趣的小伙伴可以尝试一下
    2022-12-12
  • 通过GDB学习C语言的讲解

    通过GDB学习C语言的讲解

    今天小编就为大家分享一篇关于通过GDB学习C语言的讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • C语言实现猜数游戏

    C语言实现猜数游戏

    这篇文章主要为大家详细介绍了C语言实现猜数游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-12-12
  • Qt实现数据进行加密、解密的步骤

    Qt实现数据进行加密、解密的步骤

    本文主要介绍了Qt实现数据进行加密、解密的步骤,包含QCryptographicHash和Qt-AES两种库的实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • C语言利用EasyX绘制小企鹅表情包

    C语言利用EasyX绘制小企鹅表情包

    这篇文章主要为大家详细介绍了C语言如何利用EasyX绘图库实现绘制可爱的小企鹅表情包,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2022-12-12
  • C语言中的5种简单排序算法(适合小白)

    C语言中的5种简单排序算法(适合小白)

    在编程练习时我们经常会遇到一些将一串乱序的数字排列成有序的数列(递增,递减)的问题,以此起到解决问题的效果,下面这篇文章主要给大家介绍了关于C语言中的5种简单排序算法的相关资料,需要的朋友可以参考下
    2023-03-03
  • C语言实现班级档案管理系统课程设计

    C语言实现班级档案管理系统课程设计

    这篇文章主要为大家详细介绍了C语言实现班级档案管理系统课程设计,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-12-12

最新评论