C++ operator overload -- 操作符重载 2011-12-13 14:18:29
分类: C/C++
操作符重载有两种方式,一是以成员函数方式重载,另一种是全局函数。
先看例子
- #include <iostream>
- #include <string>
- using namespace std;
- /* defualt operator= differ from my own one.
- * assign 0 or 1 to TEST_EQ, look the diference*/
- #define TEST_EQ 1
- class person{
- private:
- int age;
- string name;
- public:
- person(string nm, int a=0):name(nm),age(a){}
- int getAge(void){ return age; }
- void setAge(int age){ this->age = age; }
- void print(void){
- cout << name << ": age :" << age << std::endl;
- }
- inline bool operator == (const person &ps) const;
- #if TEST_EQ == 1
- person & operator = (const person &ps);
- #endif
- };
- inline bool person::operator == (const person &ps) const
- {
- return this->age == ps.age;
- }
- person &operator += (person &ps, int age)
- {
- ps.setAge( ps.getAge() + age);
- return ps;
- }
- person &operator += (person &p1, person &p2)
- {
- p1.setAge( p1.getAge() + p2.getAge());
- return p1;
- }
- person & operator - ( person &p1, person &p2)
- {
- p1.setAge( p1.getAge() - p2.getAge());
- return p1;
- }
- #if TEST_EQ == 1
- person & person::operator = (const person &ps)
- {
- this->age = ps.age;
- return *this;
- }
- #endif
- int main()
- {
- person p1("p1",10);
- person p2("p2",10);
- person p3("p3",30);
- person p4("p4",40);
- tes1:
- cout << "p1 and p2‘age is " << ( p1==p2 ? "equal" : "unequal") << endl;
- test2:
- cout << "p1 and p2‘age is " << ( p1.operator==(p2) ? "equal" : "unequal") << endl;
- test3:
- cout << "p1 and p3‘age is " << ( p1==p3 ? "equal" : "unequal") << endl;
- p1.print();
- p1 += 10;
- p1.print();
- (p1 += p2).print();
- p1.print();
- p2 = p3;
- p2.print();
- p4.print();
- (p4 - p3).print();
- p4.print();
- person p5 = person("p5",50);
- p5.print();
- p5 = p4 += 10;
- p5.print();
- return 0;
- }
结果
- p1 and p2‘age is equal
- p1 and p2‘age is equal
- p1 and p3‘age is unequal
- p1: age :10
- p1: age :20
- p1: age :30
- p1: age :30
- p2: age :30
- p4: age :40
- p4: age :10
- p4: age :10
- p5: age :50
- p5: age :20
文章:
==============================
C++ operator关键字
operator是C++的关键字,它和运算符一起使用,表示一个运算符函数,理解时应将operator=整体上视为一个函数名。
这是C++扩展运算符功能的方法,虽然样子古怪,但也可以理解:一方面要使运算符的使用方法与其原来一致,另一方面扩展其功能只能通过函数的方式(c++中,“功能”都是由函数实现的)。
一、为什么使用操作符重载?
对
于系统的所有操作符,一般情况下,只支持基本数据类型和标准库中提供的class,对于用户自己定义的class,如果想支持基本操作,比如比较大小,判
断是否相等,等等,则需要用户自己来定义关于这个操作符的具体实现。比如,判断两个人是否一样大,我们默认的规则是按照其年龄来比较,所以,在设计
person
这个class的时候,我们需要考虑操作符==,而且,根据刚才的分析,比较的依据应该是age。那么为什么叫重载呢?这是因为,在编译器实现的时候,已
经为我们提供了这个操作符的基本数据类型实现版本,但是现在他的操作数变成了用户定义的数据类型class,所以,需要用户自己来提供该参数版本的实现。
二、如何声明一个重载的操作符?
A: 操作符重载实现为类成员函数
重载的操作符在类体中被声明,声明方式如同普通成员函数一样,只不过他的名字包含关键字operator,以及紧跟其后的一个c++预定义的操作符。
可以用如下的方式来声明一个预定义的==操作符:
class person{
private:
int age;
public:
person(int a){
this->age=a;
}
inline bool operator == (const person &ps) const;
};
实现方式如下:
inline bool person::operator==(const person &ps) const
{
if (this->age==ps.age)
return true;
return false;
}
调用方式如下:
#include
using namespace std;
int main()
{
person p1(10);
person p2(20);
if(p1==p2) cout<<”the age is equal!”< return 0;
}
这里,因为operator ==是class person的一个成员函数,所以对象p1,p2都可以调用该函数,上面的if语句中,相当于p1调用函数==,把p2作为该函数的一个参数传递给该函数,从而实现了两个对象的比较。
B:操作符重载实现为非类成员函数(全局函数)
对于全局重载操作符,代表左操作数的参数必须被显式指定。例如:
#include
#include
using namespace std;
class person
{
public:
int age;
public:
};
bool operator==(person const &p1 ,person const & p2)
//满足要求,做操作数的类型被显示指定
{
if(p1.age==p2.age)
return true;
return false;
}
int main()
{
person rose;
person jack;
rose.age=18;
jack.age=23;
if(rose==jack)
cout<<"ok"< return 0;
}
C:如何决定把一个操作符重载为类成员函数还是全局名字空间的成员呢?
①如果一个重载操作符是类成员,那么只有当与他一起使用的左操作数是该类的对象时,该操作符才会被调用。如果该操作符的左操作数必须是其他的类型,则操作符必须被重载为全局名字空间的成员。
②C++要求赋值=,下标[],调用(), 和成员指向-> 操作符必须被定义为类成员操作符。任何把这些操作符定义为名字空间成员的定义都会被标记为编译时刻错误。
③如果有一个操作数是类类型如string类的情形那么对于对称操作符比如等于操作符最好定义为全局名字空间成员。
D:重载操作符具有以下限制:
(1) 只有C++预定义的操作符集中的操作符才可以被重载;
(2)对于内置类型的操作符,它的预定义不能被改变,应不能为内置类型重载操作符,如,不能改变int型的操作符+的含义;
(3) 也不能为内置的数据类型定义其它的操作符;
(4) 只能重载类类型或枚举类型的操作符;
(5) 重载操作符不能改变它们的操作符优先级;
(6) 重载操作符不能改变操作数的个数;
(7) 除了对( )操作符外,对其他重载操作符提供缺省实参都是非法的;
E: 注意点
(1)后果载操操作符首先要确定它的返回值是左值,还是右值,如果是左值最返回引用,如果是右值那就直接返回值;
(2)
+号等这样的操作符没有对象可以容纳改变后值,对于这样的情况最好返回数值,否则只能要操作符体内创建临时对象用于容纳改变后的值,如果在堆中创建临时对
象返回指针或者引用,在操作符函数体外还需要释放它,如果返回的对象而不是引用或者指针,那么效率是比较低的。如果返回的是数值,最好在该类的构造函数中
增加对该类型数值的转换函数,如:返回值是int类型,那么最好有一个int类型作为参数的构造函数。
(3)在增量运算符中,放上一个整数形参,就是后增量运行符,它是值返回,对于前增量没有形参,而且是引用返回,示例:
class Test
{
public:
Test(x=3){ m_value = x}
Test &operator ++(); //前增量
Test &operator ++(int);//后增量
private:
Int m_value:
};
Test &Test::operator ++()
{
m_value ++; //先增量
return *this; //返回当前对象
}
Test Test::operator ++(int)
{
Test tmp(*this); //创建临时对象
m_value ++; //再增量
return temp; //返回临时对象
}
(4)因为强制转换是针对基本数据类型的,所以对类类型的转换需自定义;
(5) 转换运行符重载声明形式:operator 类型名();它没有返回类型,因为类型名就代表了它的返回类型,所以返回类型显得多余。
(6)
一般来说,转换运算符与转换构造函数(即带一个参数的构造函数)是互逆的,如有了构造函数Test(int),那么最好有一个转换运算符int()。这样
就不必提供对象参数重载运算符了,如Test a1(1);Test a2(2); Test a3; a3 =
a1+a2;就不需要重载+号操作符了,因为对于a1+a2的运算,系统可能会先找有没有定义针对Test的+号操作符,如果没有,它就会找有没有针对
Test类转换函数参数类型的+号操作符(因为可以将+号运行结果的类型通过转换函数转换为Test对象),因为Test类有个int类型的参数,对于
int类型有+操作符,所以a1+a2真正执行的是Test(int(a1) + int(a2));即Test(3);
(7)对于转换运算符,还有一个需要注意的地方就是,如果A类中有以B为参数的转换函数(构造函数),那B中不能有A的转换运算符,不然就存在转换的二义性,如:
class A{A(B&){…}}; class B{ operator A(){…}};那么以下语句就会有问题:
B b; A(b);//A(b)有就可能是A的构造函数,也可以是B的转换运算符
=================================
C++ Operator Overloading Guidelines
One of the nice features of C++ is that you can give special meanings to
operators, when they are used with user-defined classes. This is called
operator overloading. You can implement C++ operator overloads by
providing special member-functions on your classes that follow a particular
naming convention. For example, to overload the + operator for
your class, you would provide a member-function named operator+ on
your class.
The following set of operators is commonly overloaded for user-defined
classes:
- = (assignment operator)
- + - * (binary arithmetic operators)
- += -= *= (compound assignment operators)
- == != (comparison operators)
Here are some guidelines for implementing these operators. These guidelines
are very important to follow, so definitely get in the habit early.
Assignment Operator =
The assignment operator has a signature like this:
class MyClass {
public:
...
MyClass & operator=(const MyClass &rhs);
...
}
MyClass a, b;
...
b = a; // Same as b.operator=(a);
Notice that the = operator takes a const-reference to the right hand
side of the assignment. The reason for this should be obvious, since we don‘t
want to change that value; we only want to change what‘s on the left hand
side.
Also, you will notice that a reference is returned by the assignment operator.
This is to allow operator chaining. You typically see it with
primitive types, like this:
int a, b, c, d, e;
a = b = c = d = e = 42;
This is interpreted by the compiler as:
a = (b = (c = (d = (e = 42))));
In other words, assignment is right-associative. The last assignment
operation is evaluated first, and is propagated leftward through the series of
assignments. Specifically:
- e = 42 assigns 42 to e, then returns e as the
result - The value of e is then assigned to d, and then
d is returned as the result - The value of d is then assigned to c, and then
c is returned as the result - etc.
Now, in order to support operator chaining, the assignment operator must
return some value. The value that should be returned is a reference to
the left-hand side of the assignment.
Notice that the returned reference is not declared const.
This can be a bit confusing, because it allows you to write crazy stuff like
this:
MyClass a, b, c;
...
(a = b) = c; // What??
At first glance, you might want to prevent situations like this, by having
operator= return a const reference. However, statements
like this will work with primitive types. And, even worse, some
tools actually rely on this behavior. Therefore, it is important to return a
non-const reference from your operator=. The rule of
thumb is, "If it‘s good enough for ints, it‘s good enough for
user-defined data-types."
So, for the hypothetical MyClass assignment operator, you would do
something like this:
// Take a const-reference to the right-hand side of the assignment.
// Return a non-const reference to the left-hand side.
MyClass& MyClass::operator=(const MyClass &rhs) {
... // Do the assignment operation!
return *this; // Return a reference to myself.
}
Remember, this is a pointer to the object that the member function is
being called on. Since a = b is treated as a.operator=(b),
you can see why it makes sense to return the object that the function is
called on; object a is the left-hand side.
But, the member function needs to return a reference to the object, not a
pointer to the object. So, it returns *this, which returns what
this points at (i.e. the object), not the pointer itself. (In C++,
instances are turned into references, and vice versa, pretty much
automatically, so even though *this is an instance, C++ implicitly
converts it into a reference to the instance.)
Now, one more very important point about the assignment operator:
YOU MUST CHECK FOR SELF-ASSIGNMENT!
This is especially important when your class does its own memory allocation.
Here is why: The typical sequence of operations within an assignment operator
is usually something like this:
MyClass& MyClass::operator=(const MyClass &rhs) {
// 1. Deallocate any memory that MyClass is using internally
// 2. Allocate some memory to hold the contents of rhs
// 3. Copy the values from rhs into this instance
// 4. Return *this
}
Now, what happens when you do something like this:
MyClass mc;
...
mc = mc; // BLAMMO.
You can hopefully see that this would wreak havoc on your program. Because
mc is on the left-hand side and on the right-hand side, the
first thing that happens is that mc releases any memory it holds
internally. But, this is where the values were going to be copied from, since
mc is also on the right-hand side! So, you can see that this
completely messes up the rest of the assignment operator‘s internals.
The easy way to avoid this is to CHECK FOR SELF-ASSIGNMENT. There are
many ways to answer the question, "Are these two instances the same?" But,
for our purposes, just compare the two objects‘ addresses. If they are the
same, then don‘t do assignment. If they are different, then do the
assignment.
So, the correct and safe version of the MyClass assignment operator
would be this:
MyClass& MyClass::operator=(const MyClass &rhs) {
// Check for self-assignment!
if (this == &rhs) // Same object?
return *this; // Yes, so skip assignment, and just return *this.
... // Deallocate, allocate new space, copy values...
return *this;
}
Or, you can simplify this a bit by doing:
MyClass& MyClass::operator=(const MyClass &rhs) {
// Only do assignment if RHS is a different object from this.
if (this != &rhs) {
... // Deallocate, allocate new space, copy values...
}
return *this;
}
Remember that in the comparison, this is a pointer to the object
being called, and &rhs is a pointer to the object being passed
in as the argument. So, you can see that we avoid the dangers of
self-assignment with this check.
In summary, the guidelines for the assignment operator are:
- Take a const-reference for the argument (the right-hand side of the
assignment). - Return a reference to the left-hand side, to support safe and reasonable
operator chaining. (Do this by returning *this.) - Check for self-assignment, by comparing the pointers (this to
&rhs).
Compound Assignment Operators += -= *=
I discuss these before the arithmetic operators for a very specific reason,
but we will get to that in a moment. The important point is that these are
destructive operators, because they update or replace the values on
the left-hand side of the assignment. So, you write:
MyClass a, b;
...
a += b; // Same as a.operator+=(b)
In this case, the values within a are modified by the
+= operator.
How those values are modified isn‘t very important - obviously, what
MyClass represents will dictate what these operators mean.
The member function signature for such an operator should be like this:
MyClass & MyClass::operator+=(const MyClass &rhs) {
...
}
We have already covered the reason why rhs is a const-reference.
And, the implementation of such an operation should also be straightforward.
But, you will notice that the operator returns a MyClass-reference,
and a non-const one at that. This is so you can do things like this:
MyClass mc;
...
(mc += 5) += 3;
Don‘t ask me why somebody would want to do this, but just like the normal
assignment operator, this is allowed by the primitive data types. Our
user-defined datatypes should match the same general characteristics of the
primitive data types when it comes to operators, to make sure that everything
works as expected.
This is very straightforward to do. Just write your compound assignment
operator implementation, and return *this at the end, just like for
the regular assignment operator. So, you would end up with something like
this:
MyClass & MyClass::operator+=(const MyClass &rhs) {
... // Do the compound assignment work.
return *this;
}
As one last note, in general you should beware of self-assignment
with compound assignment operators as well. Fortunately, none of the C++
track‘s labs require you to worry about this, but you should always give it
some thought when you are working on your own classes.
Binary Arithmetic Operators + - *
The binary arithmetic operators are interesting because they don‘t modify
either operand - they actually return a new value from the two arguments.
You might think this is going to be an annoying bit of extra work, but here is
the secret:
Define your binary arithmetic operators using your compound assignment
operators.
There, I just saved you a bunch of time on your homeworks.
So, you have implemented your += operator, and now you want to
implement the + operator. The function signature should be like
this:
// Add this instance‘s value to other, and return a new instance
// with the result.
const MyClass MyClass::operator+(const MyClass &other) const {
MyClass result = *this; // Make a copy of myself. Same as MyClass result(*this);
result += other; // Use += to add other to the copy.
return result; // All done!
}
Simple!
Actually, this explicitly spells out all of the steps, and if you want, you
can combine them all into a single statement, like so:
// Add this instance‘s value to other, and return a new instance
// with the result.
const MyClass MyClass::operator+(const MyClass &other) const {
return MyClass(*this) += other;
}
This creates an unnamed instance of MyClass, which is a copy
of *this. Then, the += operator is called on the temporary
value, and then returns it.
If that last statement doesn‘t make sense to you yet, then stick with the
other way, which spells out all of the steps. But, if you understand exactly
what is going on, then you can use that approach.
You will notice that the + operator returns a const
instance, not a const reference. This is so that people
can‘t write strange statements like this:
MyClass a, b, c;
...
(a + b) = c; // Wuh...?
This statement would basically do nothing, but if the + operator
returns a non-const value, it will compile! So, we want to
return a const instance, so that such madness will not even be
allowed to compile.
To summarize, the guidelines for the binary arithmetic operators are:
- Implement the compound assignment operators from scratch, and then define
the binary arithmetic operators in terms of the corresponding compound
assignment operators. - Return a const instance, to prevent worthless and confusing
assignment operations that shouldn‘t be allowed.
Comparison Operators == and !=
The comparison operators are very simple. Define == first, using a
function signature like this:
bool MyClass::operator==(const MyClass &other) const {
... // Compare the values, and return a bool result.
}
The internals are very obvious and straightforward, and the bool
return-value is also very obvious.
The important point here is that the != operator can also be defined
in terms of the == operator, and you should do this to save effort.
You can do something like this:
bool MyClass::operator!=(const MyClass &other) const {
return !(*this == other);
}
That way you get to reuse the hard work you did on implementing your
== operator. Also, your code is far less likely to exhibit
inconsistencies between == and !=, since one is implemented
in terms of the other.