一 友元
1 友元
友元是一种允许非类成员函数访问类的非公有成员的一种机制。可以把一个函数指定为类的友元,也可以把整个类指定为另一个类的友元。
2 友元函数
1)友元函数不是类的成员函数,在函数体中访问对象的成员,必须用对象名加运算符“.”加对象成员名。(友元函数的参数一定是与类对象有关的,通过该类对象直接访问类的数据成员。友元类同理。)
2)友元函数不受类中的访问权限关键字限制,可以把它放在类的公有、私有、保护部分,效果一样。
3)友元函数破坏了面向对象程序设计类的封装性,所以友元函数如不是必须使用,则尽可能少用。
4)友元函数在类体内声明,在类体外定义,且定义时不能再加friend
3 友元类
1)友元关系是单向的:A是B的友元类,但B不是A 的友元类。
2)友元关系不能被传递:A是B的友元类,B是C的友元类,但A不是C的友元类。
3)友元关系不能被继承
二 运算符重载
1 成员函数的方式重载
类体内声明: 返回值类型 operator 运算符(参数表);
以成员函数的方式重载,则调用运算符的对象会被默认为运算符函数的一个左侧参数。例如:Test& operator+(Test& other); 执行:Test t=t1+t2;时,实际上是t1.operator+(t2)。这决定了流运算符只能以友元的方式重载,因为我们调运流运算符时是这样调用的:cout<<t<<endl;如果把<<重载为成员函数,则使用时就成了t<<cout,且无法链式的使用<<。
2 友元函数的方式重载
类体内声明: friend 返回值类型 operator 运算符(参数表);
由于友元函数不是类的成员函数,所以相比于成员函数的方式重载,友元函数多了一个参数。
C++规定有四个运算符 =, ->, [], ()不可以是全局域中的重载(即不能重载为友元函数)。关于这一点不太明白。感觉上友元函数能应该做到成员函数做到的一切,有的文章解释说是怕引起矛盾。但无论如何,正如前面所说,友元会破坏类的封装性,所以,只需记住能用成员函数就用成员函数,类似于流运算符那样要把对象放在运算符右边时才使用友元的方式重载。
三 ++运算符的重载
++运算符分前置++和后置++。关于前置++,我本以为对象位于运算符右侧,所以应该重载为友元函数,但如何与后置++区分呢?原来C++中都是用成员函数来重载的,为了区分两者,前置++不带参数,后置++带一个不使用的int参数。关于这样区分,我认为是有一定道理的。假如让二者反过来,那么前置++带一个参数,而隐含的对象参数就必须位于运算符左侧,不使用的int参数位于右侧,这显然不是前置++的形式,而是后置++的形式。或许不带参数的前置++运算符允许隐含的对象在运算符右侧?(更新:感觉之前理解有误,成员函数重载是默认第一个参数是对象,那么对于一个二元运算符,左侧的自然要求是对象;但对于前置++运算符,他只有一个参数,参数是对象,自然没错。就像[]运算符,对象总不可能出现在其左侧或右侧吧。因此,只需记住成员函数重载默认第一个参数是对象。)
#ifndef _INTEGER_H #define _INTEGER_H class Integer { public: Integer() :num_(0) { } Integer(int num) :num_(num) { } void Display(); Integer& operator++(); Integer operator++(int); private: int num_; }; #endif _INTEGER_H
#include "Integer.h" #include <iostream> using namespace std; void Integer::Display() { cout << num_ << endl; } Integer& Integer::operator++() { num_++; return *this; } Integer Integer::operator++(int) { Integer a(num_); ++*this; return a; }
#include <iostream> #include "Integer.h" using namespace std; int main() { Integer a(100); Integer b = ++a; a.Display(); b.Display(); Integer c = a++; a.Display(); c.Display(); return 0; }
运行结果:
前置++返回的是引用,因为对象是先+后用的;而后置++是先用后+,那么就需要把对象之前的值用临时对象保存之后在对对象+,因为返回的是临时对象,所以函数返回值不能是引用!
四 赋值运算符的重载
假设有一个自定义的String类,赋值运算符的重载可以简述如下:
String& operator=(const String& other) { if (this==&other) return; 分配空间,并将other的数据拷贝过来。 return *this; }
正如在构造函数的那一节提到的,s1和s2是两个已经初始化的String对象,s1=s2;时会调用赋值运算符函数。假如执行:
s1="abc";
如果有一个转换构造函数,则先隐式的调用转换构造函数构造一个临时对象,再调用赋值运算符将其赋给s1;如果重载有参数为char*的赋值运算符函数:
String& operator=(const char* other)
则直接调用该赋值运算符函数。如果都没有(转换构造函数如果生命为explicit,则不可隐式调用,等同于没有),则报错。