赋值运算,拷贝运算,运算符重载,函数调用入栈,寄存器

时间:2014-12-20 15:33:02   收藏:0   阅读:364

赋值运算与拷贝运算的区别

如果对象在申明之后进行赋值运算,我们称之为赋值运算。例如:
class1 A("af"); class1 B;
B=A;
此时实际调用的类的缺省赋值函数B.operator=(A);

 

如果对象在申明的同时马上进行初始化操作,则称之为拷贝运算。例如:
class1 A("af"); class1 B=A;
此时其实际调用的是B(A)这样的浅拷贝操作。

 

C++与C#对象的内存分配方式的不同

在C++中,对象的实例在编译的时候,就需要为其分配内存大小,因此,系统都是在stack上为其分配内存的。

在C#中,所有类都是reference type,要创建类的实体,必须通过new在heap上为其分配空间,同时返回在stack上指向其地址的reference.

 

C++中赋值操作的内存情况

class A
{
public:
    A()
    {
    }
    A(int id,char *t_name)
    {
    _id=id;
    name=new char[strlen(t_name)+1];
    strcpy(name,t_name);
    }
    private:
        char *username;
        int _id;
}

int main()
{
A a(1,"herengang");
A b;
b=a; }

b=a执行的是缺省的赋值运算。所谓缺省的赋值运算,是指对象中的所有位于stack中的域,进行相应的复制。但是,如果对象有位于heap上的域的话,其不会为拷贝对象分配heap上的空间,而只是指向相同的heap上的同一个地址。
执行b=a这样的缺省的赋值运算后,其内存分配如下

技术分享

因此,对于缺省的赋值运算,如果对象域内没有heap上的空间,其不会产生任何问题。但是,如果对象域内需要申请heap上的空间,那么在析构对象的时候,就会连续两次释放heap上的同一块内存区域,从而导致异常。

解决办法--重载(overload)赋值运算符

class A
{
public:

    A()
    {
    }
    A(int id,char *t_name)
    {
        _id=id;
        name=new char[strlen(t_name)+1];
        strcpy(name,t_name);
    }
    
    A& operator =(A& a)
//注意:此处一定要返回对象的引用,否则返回后其值立即消失!
    {
            if(name!=NULL)
                delete name;
        this->_id=a._id;
        int len=strlen(a.name);
        name=new char[len+1];
        strcpy(name,a.name);
        return *this;
    }

    ~A()
    {
        cout<<"~destructor"<<endl;
        delete name;
    }

    int _id;
    char *name;
};

int main()
{
 A a(1,"herengang");
 A b;
 b=a;
}

技术分享

如何重载赋值运算符

法I:返回类对象的引用

A& A::operaton=(A &a)
{
        if(this==&a)//  考虑a=a这样的操作。
            return *this;
        if(username!=NULL) //释放自身的堆空间
            delete username;
        _id=a._id;
        username=new char[strlen(a.username)+1];
        if(username!=NULL)
            strcpy(username,a.usernam);
        return *this;    
}

其过程如下:
                   1 释放掉目标对象原来占有的堆空间
                   2 申请一块新的堆内存
                   3 将源对象的堆内存的值深度拷贝给新的堆内存
                   4 返回目标对象的引用
                   5 结束。

法II:返回类对象本身

其过程是这样的:
                       1 释放目标对象原来的堆资源
                       2 重新申请堆空间
                       3 拷贝源的值到目标对象的堆空间
                       4 调用临时对象拷贝构造函数创建临时对象,将临时对象返回(因为函数返回时,会清空栈,在栈中的目标对象也会被清,所以需要临时对象)
                       5.临时对象结束,调用临时对象析构函数,释放临时对象堆内存
如果第4步,我们没有overload 拷贝函数,也就是没有进行深拷贝。那么在进行第5步释放临时对象的heap 空间时,将释放掉的是和目标对象同一块的heap空间。这样当目标对象B作用域结束调用析构函数时,就会产生错误!
因此,如果赋值运算符返回的是类对象本身,那么一定要overload 类的拷贝函数(进行深拷贝)!

 

法III:返回void

如果这样的话,他将不支持客户代买中的链式赋值 ,例如a=b=c will be prohibited!

 

拷贝构造函数

赋值函数最好是对象的引用, 而拷贝函数不需要返回任何。

同时,赋值函数首先要释放掉对象自身的堆空间,然后进行其他的operation。而拷贝函数不需要如此,因为对象此时还没有分配堆空间。

A::A(A &a)
{

    int len=strlen(a.m_username);
    this->m_username=new char[len+2];
    strcpy(m_username,a.m_username);
    strcat(m_username,"f");
    printf("\ndeep copy function");
}

 

运算符的重载

返回对象本身。因为如果返回引用,会导致擦操作数被意外修改,比如a+b=c,会将c赋给a

class Complex //复数类
{
private://私有
double real;//实数
double imag;//虚数
public:
        Complex(double real=0,double imag=0)
        {
this->real=real;
this->imag=imag;
        }
        Complex operator+(int x);
};

Complex Complex::operator+(int x)
{
return Complex(real+x,imag);
}

int main()
{
    Complex com1(5,10),total;
    total=com1+5;

return0;
}

函数调用时的入栈情况

当调用(call)一个函数时,主调函数将声明中的参数表以逆序压栈,然后将当前的代码执行指针(eip)压栈,跳转到被调函数的入口点。

VarType Func (Arg1, Arg2, Arg3, ... ArgN) 
{ 
    VarType Var1, Var2, Var3, ...VarN;
    //... 
    return VarN; 
}

假设sizeof(VarType) = 4(DWORD), 则一次函数调用汇编代码示例为:

调用方代码:

push ArgN ; 依次逆序压入调用参数
push ...
push Arg1
call Func_Address ; 压入当前EIP后跳转(EIP:指令寄存器,extended instruction pointer,指向下一条等待执行的指令地址)

跳转至被调方代码:

push ebp ; 备份调用方EBP指针(EBP:基址指针,base pointer,指向系统栈最上面一个栈帧的底部)
mov ebp, esp ; 建立被调方栈底(ESP:堆栈指针,stack pointer,指向系统栈最上面一个栈帧的栈顶)
sub esp, N * 4; 为局部变量分配空间(栈是从高地址向低地址拓展,即栈顶在低地址处)

                      内存低地址 | ESP - - - - - - - - - - - - - - - - EBP - - - - - - - - - - - - - - - - - - - - - >| 内存高地址

mov dword ptr[esp - 4 * 1 ], 0 ; 初始化各个局部变量 = 0 这里假定VarType不是类
mov dword ptr[esp - 4 * ... ], 0
mov dword ptr[esp - 4 * N ], 0
. . . . . . ; 这里执行一些函数功能语句(比如将第N个参数[ebp + N * 4]存入局部变量), 功能完成后将函数返回值存至eax
add esp, N * 4 ; 销毁局部变量
mov esp, ebp ; 恢复主调方栈顶
pop ebp ; 恢复主调方栈底
ret ; 弹出EIP 返回主调方代码

接上面调用方代码:
add esp, N * 4 ; 释放参数空间, 恢复调用前的栈
mov dword ptr[ebp - 4], eax ; 将返回值保存进调用方的某个VarType型局部变量

(eax:4个字节;AX:eax的低2个字节;AH:AX的高字节;AL:AX的低字节)

8086中的寄存器

AX,可存放一般数据,而且可作为累加器使用;
BX,可存放一般数据,而且可用来存放数据的指针(偏移地址),常常和DS寄存器连用;
CX,可存放一般数据,而且可用来做计数器,常常将循环次数用它来存放;
DX,可存放一般数据,而且可用来存放乘法运算产生的部分积,或用来存放输入输出的端口地址(指针);
SP,用于寻址一个称为堆栈的存储区,通过它来访问堆栈数据;
BP,可存放一般数据,用来存放访问堆栈段的一个数据区,作为基地址;
SI,可存放一般数据,还可用于串操作中,存放源地址,对一串数据访问;
DI,可存放一般数据,还可用于串操作中,存放目的地址,对一串数据访问;
IP,用于寻址当前需要取出的指令字节,程序员不能对它直接操作;
FLAGS,用于指示微处理器的状态并控制它的操作;
CS,代码段寄存器,代码段是一个存储区域,存放的是CPU要使用的代码,CS存放代码段的段基地址;
DS,数据段寄存器,数据段是包含程序使用的大部分数据的存储区,DS中存放数据段的段基地址;
ES,附加段寄存器,附加段是为某些串操作指令存放目的操作数而附近的一个数据段,ES中存放该数据段的段基地址;
SS,堆栈段寄存器,堆栈段是内存中一个特殊的存储区,用于暂时存放程序运行时所需的数据或地址信息。SS中存放该存储区的段基地址。

技术分享

技术分享

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!