C++ 匿名变量的一些理解


想总结一下,但是总是不断否定自己的理解,迟迟不能下笔。终于今天还是写下点记录和理解。

运行环境:

OS : Ubuntu 16.04 Desktop

G++ Versiongcc version 5.4.0 20160609(Ubuntu 5.4.0-6ubuntu1~16.04.9)

匿名变量:这种变量其实是程序员看不见的,是编译器为了代码正确实行,自动会在代码里面添加的内容(匿名变量)。在C语言里面经常使用return这条语句,但是在C+中,简单的return确实比想象中的复杂。

赋值运算:赋值是在已有的空间中操作,会调用operator = 函数。

类初始化:初始化是分配空间并且赋值,会调用copy construct函数(依据情况)。

假设一个函数或者执行体返回一个变量,这个变量又不是int char double等基本类型,是一个复杂类型classstruct,如何把“数据块”有效的返回给使用者,又如何把数据块传递给函数。这些问题都和匿名变量相关。在这里不想普及右值,左值将亡值等概念只是从函数的语法结合一点语义去理解。

从一个重载说起:

T & operator=T &obj{

}

当在类中重载了=那么当编译器遇到

T ta

T tb

ta  = tb

这样的代码后会使用用户自己定义的重载赋值运算 = 完成运算。编译器会这样处理:

ta.operator(&tb);ta是一个类的实体。会调用本类中的赋值方法。即=号。我们可以理解为一个函数,这个函数的作用就是把用参数来完成目标类的成员或这内容。一个再简单不过的运算符号重载怎么可以和匿名变量联系在一起?

假设我们定义了重载操作函数:

T  operator=T &obj{

}

会发生什么事情?

执行ta=tb;后会发现产生了一个匿名的变量,在operator=执行后立即析构掉。那为什么在返回引用和返回值得情况下会不一样?这里要说明一下,不要和函数返回和返回“引用“混淆。应为在函数返回值这样的语法下,编译器是会给代码做优化。所谓的优化就是减少不必要的匿名变量,减少不必要的拷贝函数。

这是个人理解:既然函数返回了,而且是返回值,一定有个临时变量与之对应。这样就是右值或者叫将亡值。执行后语句后立即消失(析构)。但是返回引用却不一样,应为返回的是一个别名。可以理解为一个临时变量的别名,临时变量的生命周期很短,所以这个别名也没有必要与之对应生成一个临时变量。至于使用“值”返回后,这个变量到底有没有作用,我觉的最好的例子就是:

或者有这样的表达式:

class t{

tclass t &obj{}

}

t b

t ab);

假设可以变形为:a.copy_construct(&b);

这样是不是会有临时变量?我觉的会有,但是这个临时变量就是a本生。也就是说用b来初始化a。在a退出具体作用区域后也会被析构,这个a才是临时变量。而匿名变量是看不着摸不到的。直接以相同参数调用相关构造函数构或者直接调用拷贝构造函数到目标对象。C++编译器又这样的优化能力。

两个表达式都是初始化另外一个变量,但是C++真的很拗口,很晦涩。返回值,返回的是什么,可以是类,可以是基本类型。在基本类型中我们只要简单的处理压栈的数据,只要保证数据在函数出栈以后能继续访问就可以,似乎从来不考虑效率的问题,但是C++不一样,More than C。而且这些问题在使用C语言时候也从没有考虑过。

接着在聊聊函数返回的情况:

T func()或者

T & func()

T a = func()

或者这样的表达式

T a

a = func();


      以上一共有若干种表达式可以组合来完成函数返回值和返回引用的逻辑。但是在C++编译器看来是完全不一样的。初始化和赋值操作是两种概念的操作。初始化是分配空间并且赋值,而赋值是在已有的空间中操作。

这里参考一下编写的test.cpp

#include <iostream>
using namespace std;

class test
{
        int age;
public:
        test():age(0){
                cout<<"test construct "<<this<<endl;
        }
        test(int age_)
        {
                cout<<"test construct parm "<<this<<endl;
                age = age_;
        }
        ~test(){
                cout<<"test destruct "<<this<<endl;
        }
        test(const test& obj)
        {
                cout<<&obj<<" from copy construct to "<<this<<endl;
                this->age = obj.age;
        }
        test(const test && obj)
        {
                cout<<&obj<<" from rvalue construct to "<<this<<endl;
        }
        test &operator = (const test & obj)
        {
                cout<<this<<" operator = "<<&obj<<endl;
                this->age = obj.age;
                //return move(*this);
                return *this;
        }
        void get_age(void)
        {
                cout<<age<<endl;
        }
};
//test te(4);
test & testfunc()
{
        test te(3);
        cout<<"in function "<<&te<<endl;
        return te;
}

int main()
{
        test t1(1);
        cout<<"==============="<<endl;
        test t = testfunc();
        return 0;
}

分别使用局部变量和全局变量来说名问题。在局部变量编译后运行过过程中程序会发生错误,但是先搁置一下,主要是看看编译器有没有分配匿名变量。并且如何对待初始化和赋值操作。

1)初始化类通过局部变量返回值:
test testfunc()

{

        test te(3);
        cout<<"in function "<<&te<<endl;
        return te;

}

执行过程:

===============
test construct parm 0x7ffc15570ad0
in function 0x7ffc15570ad0
test destruct 0x7ffc15570ad0
似乎没有匿名变量,这与我们的预期不都符合,后来百度了一下,又个优化叫ROV。并且还有一个有趣的现象:函数内使用的临时变量(te(3))再函数返回后尽然还有效,随后使用t.get_age()任然输出了3。这个地放不明白编译器是不是再解析这种表达式都是按照这样的逻辑处理?

2)初始化类通过局部变量返回引用:
test & testfunc()
{

        test te(3);

        cout<<"in function "<<&te<<endl;

        return te;
}

执行过程:

testconstruct parm 0x7ffff0160750

===============
testconstruct parm 0x7ffff0160720
in function0x7ffff0160720
testdestruct 0x7ffff0160720
0 from copyconstruct to 0x7ffff0160760 这个地方就是临时变量的产生地址就是:0x7ffff160760,把一个析构掉的类用来拷贝初始化一个临时变量。
Segmentationfault (core dumped)


3)初始化类通过全局变量返回值:

test te(4);
test  testfunc()
{
        cout<<"in function "<<&te<<endl;
        return te;
}
执行过程:
test construct parm 0x602194 这个地方开始觉的很奇怪,在main函数之前就又函数别调用,原来是全局变量。C++还是比C多了很多特点。都是编译器的行为。在C中放在data section中,但是在C++中还再需要执行一次构造函数。
===============
in function 0x602194
0x602194 from copy construct to 0x7fff27e2cbe0 匿名变量
test destruct 0x7fff27e2cbe0
test destruct 0x602194

4)初始化类通过全局变量返回引用:

test te(4);
test & testfunc()
{
        cout<<"in function "<<&te<<endl;
        return te;
}
执行过程:
test construct parm 0x602194
===============
in function 0x602194
0x602194 from copy construct to 0x7ffe1d01fbf0
test destruct 0x7ffe1d01fbf0
test destruct 0x602194

返回引用的执行过程和返回“值”是一样的逻辑。这里在没有能力阅读汇编的前提下,只能证明编译器把两种类型的初始化方式一样优化了。
        以上介绍了4种不同的表达方式,4种不同的效果。目的不是为了探究编译器背后的工作机制,和语言语义的分析。其目的就是总结一下差异,在不同的编译环境下也知道C++的代码无时无刻的在和匿名变量打交道。通过这一阶段的思考和分析,也随着调试和尝试,慢慢理解了C++匿名变量的作用。这大概就是对象编程?因为所有的都是“类”。类多数情况下式复杂数据结构,不可能通过简单的return满足所有需求。 很多知识点还是值得思考的,至少在编写C代码的时候从来没有考虑过这些问题。只是使用return,不要返回局部变量等等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值