标签:
赋值运算与拷贝运算的区别
如果对象在申明之后进行赋值运算,我们称之为赋值运算。例如:
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中存放该存储区的段基地址。
标签:
原文地址:http://www.cnblogs.com/qionglouyuyu/p/4175444.html