[原链接] (http://blog.renren.com/blog/24467433/239252558)
这学期选了《数据结构》这门课,还算是真正接触了C/C++
语言。以前学的跟没学一样,一则只得多练得少,二则要求太低学得浅。
个人觉得这课刘利刚老师上得还是不错的,上课对知识点的讲解详略得当,旁带了不少深入的东西,如C++
自带的标准类STL
,MFC
编程(虽然这个只是例带过,只介绍了下)。
特别喜欢的是,他上课时对知识点直接举例编程,这样既巩固了知识点,又学到了老师编写代码时的思维方式。
当然要提的是老师的程序风格是相当值得学习的,真希望他也能出本讲义或教材,很喜欢他那样代码风格。
这次的HOMEWORK #2是写一个实现多项式加、减、乘、除法等运算的题目,要求用链表(单向链表或双向链表)实现,接口(Interfaces
)如下:
class CTerm
{
private:
int m_nDegree;
double m_dCoef;
CTerm *m_pNext;
};
class CPolynomial
{
private:
CTerm *m_pHead;
};
这个作业的主要难点是在对运算符(+、-、*、/)
的重载上,通过对默认运算符的重载,使它们支持用户自定义的多项式类的操作,比如定义两个多项式:
CPolynomial a, b, c;
c = a + b;
或者支持更复杂的混合运算:
c = a + b + a;
看似第一种实现了,第二种也自然能实现。其实不然,这要看你的操作符重载用的是什么方式,方式不同,效果也不同。大致分两类:一是成员操作符重载,二是非类成员操作符重载。至于这两种方式有什么不同,分析如下(这是我搜到的最清晰的解释):
用户定义的类型,如:字符串,日期,复数,联合体以及文件常常重载二元 +
操作符以实现对象的连接,附加或合并机制。
但是要正确实现 +
操作符会给设计,实现和性能带来一定的挑战。本文将概要性地介绍如何选择正确的策略来为用户定义类型重载这个操作符。
考虑如下的表达式: int x = 4 + 2;
内建的 +
操作符有两个类型相同的操作数,相加并返回右值 6
,然后被赋值给 x
。我们可以断定内建的 +
是一个二元的,对称的,可交换的操作符。它产生的结果的类型与其操作数类型相同。按照这个规测,当你为某个用户定义类型重载操作符时,也应该遵循相应内建操作符的特征。
为用户定义类型重载 +
操作符是很常见的编程任务。
尽管 C++
提供了几种实现方法,但是它们容易使人产生设计上的误解,这种误解常常影响代码的正确性,性能以及与标准库组件之间的兼容性。
下面我们就来分析内建操作符的特征并尝试模仿其相应的重载机制。
第一步:在成员函数和非成员函数之间选择
你可以用类成员函数的方式实现二元操作符如:+
、-
以及 ==
,例如:
class String
{
public:
bool operator==(const String & s); // 比较 *this 和 s
};
这个方法是有问题的。相对于其内建的操作符来说,重载的操作符在这里不具有对称性;
它的两个参数一个类型为:const String * const
(这个参数是隐含的),另一个类型为:const String &
。因此,一些 STL
算法和容器将无法正确处理这样的对象。
另外一个可选方法是把重载操作符 + 定义为一个外部(extern
)函数,该函数带两个类型相同的参数:
String operator + (const String & s1, const String s2);
这样一来,类 String
必须将该重载操作符声明为友元:
class String
{
public:
friend String operator+ (const String & s1, const String & s2);
};
第二步:返回值的两难选择
如前所述,内建操作符 +
返回右值,其类型与操作数相同。但是在调用者堆栈里返回一个对象效率很低,处理大型对象时尤其如此。那么能不能返回一个指针或引用呢?答案是不行。因为返回指针破坏参数类型与返回值类型应该相同的规则。更糟的是,链接多个表达式将成为不可能:
String s1,s2,s3;
String res = s1 + s2 + s3; // 不可能用 String* 作为返回值
虽然有一个办法可以定义额外的 + 操作符重载版本,但这个办法是我们不希望用的,因为返回的指针必须指向动态分配的对象。这样的话,如果调用者释放(delete
)返回的指针失败,那么将导致内存泄漏。显然,返回 String*
不是一个好主意。
那么返回 String &
好不好呢?返回的引用必须一定要是一个有效的 String
。它避免了使用动态对象分配,该方法返回的是一个本地静态对象的引用。
静态对象确实解决了内存泄漏问题,但这个方法的可行性仍然值得怀疑。
在一个多线程应用中,两个线程可能会并发调用 +
操作符,因此造成 String
对象的混乱。
而且,因为静态对象总是保留其调用前的状态,所以有必要针对每次 +
操作符的调用都清除该静态 String
对象。由此看来,在堆栈上返回结果仍然是最安全和最简单的解决方案。
百度知道:什么时候定义类成员操作符重载,什么时候定义非类成员操作符重载?
- 如果一个重载操作符是类成员,那么只有当跟它一起使用的左操作数是该类对象时,它才会被调用,如果该操作符的左操作数必须是其他类型,那么重载操作符必须是非类成员操作符重载。
- C++要求,赋值(
=
),下标([]
),调用(()
)和成员访问箭头(->
)操作符必须被指定为类成员操作符,否则错误。