网站注册转化率,wordpress字体大小插件,北京红酒网站建设,任城网络推广教程http://www.cnblogs.com/zpcdbky/p/5027481.html在前面#xff1a;关于C的赋值运算符重载函数(operator)#xff0c;网络以及各种教材上都有很多介绍#xff0c;但可惜的是#xff0c;内容大多雷同且不全面。面对这一局面#xff0c;在下在整合各种资源及融入个人理解的基…http://www.cnblogs.com/zpcdbky/p/5027481.html在前面关于C的赋值运算符重载函数(operator)网络以及各种教材上都有很多介绍但可惜的是内容大多雷同且不全面。面对这一局面在下在整合各种资源及融入个人理解的基础上整理出一篇较为全面/详尽的文章以飨读者。
正文
Ⅰ.举例
例1
#includeiostream
#includestring
using namespace std;class MyStr
{
private:char *name;int id;
public:MyStr() {}MyStr(int _id, char *_name) //constructor{cout constructor endl;id _id;name new char[strlen(_name) 1];strcpy_s(name, strlen(_name) 1, _name);}MyStr(const MyStr str){cout copy constructor endl;id str.id;if (name ! NULL)delete name;name new char[strlen(str.name) 1];strcpy_s(name, strlen(str.name) 1, str.name);}MyStr operator (const MyStr str)//赋值运算符{cout operator endl;if (this ! str){if (name ! NULL)delete name;this-id str.id;int len strlen(str.name);name new char[len 1];strcpy_s(name, strlen(str.name) 1, str.name);}return *this;}~MyStr(){delete name;}
};int main()
{MyStr str1(1, hhxx);cout endl;MyStr str2;str2 str1;cout endl;MyStr str3 str2;return 0;
}结果Ⅱ.参数
一般地赋值运算符重载函数的参数是函数所在类的const类型的引用如上面例1加const是因为
①我们不希望在这个函数中对用来进行赋值的“原版”做任何修改。
②加上const对于const的和非const的实参函数就能接受如果不加就只能接受非const的实参。
用引用是因为
这样可以避免在函数调用时对实参的一次拷贝提高了效率。
注意
上面的规定都不是强制的可以不加const也可以没有引用甚至参数可以不是函数所在的对象正如后面例2中的那样。
Ⅲ.返回值
一般地返回值是被赋值者的引用即*this如上面例1原因是
①这样在函数返回时避免一次拷贝提高了效率。
②更重要的这样可以实现连续赋值即类似abc这样。如果不是返回引用而是返回值类型那么执行ab时调用赋值运算符重载函数在函数返回时由于返回的是值类型所以要对return后边的“东西”进行一次拷贝得到一个未命名的副本有些资料上称之为“匿名对象”然后将这个副本返回而这个副本是右值所以执行ab后得到的是一个右值再执行c就会出错。
注意
这也不是强制的我们可以将函数返回值声明为void然后什么也不返回只不过这样就不能够连续赋值了。
Ⅳ.调用时机当为一个类对象赋值注意可以用本类对象为其赋值如上面例1也可以用其它类型如内置类型的值为其赋值关于这一点见后面的例2时会由该对象调用该类的赋值运算符重载函数。
如上边代码中
str2 str1;
一句用str1为str2赋值会由str2调用MyStr类的赋值运算符重载函数。
需要注意的是
MyStr str2;
str2 str1;
和
MyStr str3 str2;
在调用函数上是有区别的。正如我们在上面结果中看到的那样。前者MyStr str2;一句是str2的声明加定义调用无参构造函数所以str2 str1;一句是在str2已经存在的情况下用str1来为str2赋值调用的是拷贝赋值运算符重载函数而后者是用str2来初始化str3调用的是拷贝构造函数。
Ⅴ.提供默认赋值运算符重载函数的时机当程序没有显式地提供一个以本类或本类的引用为参数的赋值运算符重载函数时编译器会自动生成这样一个赋值运算符重载函数。注意我们的限定条件不是说只要程序中有了显式的赋值运算符重载函数编译器就一定不再提供默认的版本而是说只有程序显式提供了以本类或本类的引用为参数的赋值运算符重载函数时编译器才不会提供默认的版本。可见所谓默认就是“以本类或本类的引用为参数”的意思。
见下面的例2
#includeiostream
#includestring
using namespace std;class Data
{
private:int data;
public:Data() {};Data(int _data):data(_data){cout constructor endl;}Data operator(const int _data){cout operator(int _data) endl;data _data;return *this;}
};int main()
{Data data1(1);Data data2,data3;cout endl;data2 1;cout endl;data3 data2;return 0;
}结果上面的例子中我们提供了一个带int型参数的赋值运算符重载函数data2 1;一句调用了该函数如果编译器不再提供默认的赋值运算符重载函数那么data3 data2;一句将不会编译通过但我们看到事实并非如此。所以这个例子有力地证明了我们的结论。
Ⅵ.构造函数还是赋值运算符重载函数如果我们将上面例子中的赋值运算符重载函数注释掉main函数中的代码依然可以编译通过。只不过结论变成了可见当用一个非类A的值如上面的int型值为类A的对象赋值时
①如果匹配的构造函数和赋值运算符重载函数同时存在如例2会调用赋值运算符重载函数。
②如果只有匹配的构造函数存在就会调用这个构造函数。
Ⅶ.显式提供赋值运算符重载函数的时机
①用非类A类型的值为类A的对象赋值时当然从Ⅵ中可以看出这种情况下我们可以不提供相应的赋值运算符重载函数而只提供相应的构造函数来完成任务。
②当用类A类型的值为类A的对象赋值且类A的成员变量中含有指针时为避免浅拷贝关于浅拷贝和深拷贝下面会讲到必须显式提供赋值运算符重载函数如例1。
Ⅷ.浅拷贝和深拷贝拷贝构造函数和赋值运算符重载函数都会涉及到这个问题。所谓浅拷贝就是说编译器提供的默认的拷贝构造函数和赋值运算符重载函数仅仅是将对象a中各个数据成员的值拷贝给对象b中对应的数据成员这里假设a、b为同一个类的两个对象且用a拷贝出b或用a来给b赋值而不做其它任何事。假设我们将例1中显式提供的拷贝构造函数注释掉然后同样执行MyStr str3 str2;语句此时调用默认的拷贝构造函数它只是将str2的id值和nane值拷贝到str3这样str2和str3中的name值是相同的即它们指向内存中的同一区域在例1中是字符串”hhxx”。如下图这样会有两个致命的错误
①当我们通过str2修改它的name时str3的name也会被修改
②当执行str2和str3的析构函数时会导致同一内存区域释放两次程序崩溃这是万万不可行的所以我们必须通过显式提供拷贝构造函数以避免这样的问题。就像我们在例1中做的那样先判断被拷贝者的name是否为空若否dalete name后面会解释为什么要这么做然后为name重新申请空间再将拷贝者name中的数据拷贝到被拷贝者的name中。执行后如图这样str2.name和str3.name各自独立避免了上面两个致命错误。我们是以拷贝构造函数为例说明的赋值运算符重载函数也是同样的道理。
Ⅸ.赋值运算符重载函数只能是类的非静态的成员函数C规定赋值运算符重载函数只能是类的非静态的成员函数不能是静态成员函数也不能是友元函数。关于原因有人说赋值运算符重载函数往往要返回*this而无论是静态成员函数还是友元函数都没有this指针。这乍看起来很有道理但仔细一想我们完全可以写出这样的代码
static friend MyStr operator(const MyStr str1,const MyStr str2)
{……return str1;
}可见这种说法并不能揭露C这么规定的原因。其实之所以不是静态成员函数是因为静态成员函数只能操作类的静态成员不能操作非静态成员。如果我们将赋值运算符重载函数定义为静态成员函数那么该函数将无法操作类的非静态成员这显然是不可行的。在前面的讲述中我们说过当程序没有显式地提供一个以本类或本类的引用为参数的赋值运算符重载函数时编译器会自动提供一个。现在假设C允许将赋值运算符重载函数定义为友元函数并且我们也确实这么做了而且以类的引用为参数。与此同时我们在类内却没有显式提供一个以本类或本类的引用为参数的赋值运算符重载函数。由于友元函数并不属于这个类所以此时编译器一看类内并没有一个以本类或本类的引用为参数的赋值运算符重载函数所以会自动提供一个。此时我们再执行类似于str2str1这样的代码那么编译器是该执行它提供的默认版本呢还是执行我们定义的友元函数版本呢为了避免这样的二义性C强制规定赋值运算符重载函数只能定义为类的成员函数这样编译器就能够判定是否要提供默认版本了也不会再出现二义性。
Ⅹ. 赋值运算符重载函数不能被继承
见下面的例3
#includeiostream
#includestring
using namespace std;class A
{
public:int X;A() {}A operator (const int x){X x;return *this;}
};
class B :public A
{
public:B(void) :A() {}
};int main()
{A a;B b;a 45;//b 67;(A)b 67;return 0;
}注释掉的一句无法编译通过。报错提示没有与这些操作数匹配的””运算符。对于b 67;一句首先没有可供调用的构造函数前面说过在没有匹配的赋值运算符重载函数时类似于该句的代码可以调用匹配的构造函数此时代码不能编译通过说明父类的operator 函数并没有被子类继承。为什么赋值运算符重载函数不能被继承呢因为相较于基类派生类往往要添加一些自己的数据成员和成员函数如果允许派生类继承基类的赋值运算符重载函数那么在派生类不提供自己的赋值运算符重载函数时就只能调用基类的但基类版本只能处理基类的数据成员在这种情况下派生类自己的数据成员怎么办所以C规定赋值运算符重载函数不能被继承。上面代码中 (A)b 67; 一句可以编译通过原因是我们将B类对象b强制转换成了A类对象。
Ⅺ.赋值运算符重载函数要避免自赋值对于赋值运算符重载函数我们要避免自赋值情况即自己给自己赋值的发生一般地我们通过比较赋值者与被赋值者的地址是否相同来判断两者是否是同一对象正如例1中的if (this ! str)一句。为什么要避免自赋值呢①为了效率。显然自己给自己赋值完全是毫无意义的无用功特别地对于基类数据成员间的赋值还会调用基类的赋值运算符重载函数开销是很大的。如果我们一旦判定是自赋值就立即return *this会避免对其它函数的调用。
②如果类的数据成员中含有指针自赋值有时会导致灾难性的后果。对于指针间的赋值注意这里指的是指针所指内容间的赋值这里假设用_p给p赋值先要将p所指向的空间delete掉为什么要这么做呢因为指针p所指的空间通常是new来的如果在为p重新分配空间前没有将p原来的空间delete掉会造成内存泄露然后再为p重新分配空间将_p所指的内容拷贝到p所指的空间。如果是自赋值那么p和_p是同一指针在赋值操作前对p的delete操作将导致p所指的数据同时被销毁。那么重新赋值时拿什么来赋所以对于赋值运算符重载函数一定要先检查是否是自赋值如果是直接return *this。
结束语至此本文的所有内容都介绍完了。由于在下才疏学浅错误纰漏之处在所难免如果您在阅读的过程中发现了在下的错误和不足请您务必指出。您的批评指正就是在下前进的不竭动力