标签:erro class类 申请 需要 复杂 ack tle 检查 表示
重载运算符实质是编写一个执行相应操作的函数,当运算符被使用时,实质是函数被调用,这是编译器完成的转换工作。
重载的运算符函数,都有个特殊的函数名:operator【运算符】。operator为关键字。
如重载加法运算符: operator+
乘法运算符: operator*
C++支持运算符重载,使得类被封装得更加完美,但是这也增加了其复杂性。正因为如此,Java没有支持运算符重载。
有趣的是,当我在使用Java的时候,并没有觉得Java不支持运算符重载有多么不方便,但当我使用C++的时候,又觉得支持运算符重载多么的酷。显然,我们常说的语言信仰,只不过是被语言本身洗脑 了 - -!
废话完了。开始。
备注 | ||||||||
---|---|---|---|---|---|---|---|---|
与比较相关,最好成对重载,或者全部重载 | > | < | >= | <= | == | != | ||
与赋值相关,重载的函数要返回当前对象的引用 ,和内置类型的赋值保持一致的效果 |
= | += | -= | /= | *= | %= |
&= |= ^= |
>>= <<= |
需要区分前后缀 | ++ | -- | ||||||
只能重载为对象的成员的运算符(还有=) | [] | () | -> | |||||
逻辑相关,不建议重载,因为他们不满足短路求值 | && | || | ! | |||||
加和减的 一元版本,前缀。很少使用 | + | - | ||||||
二元常规运算符 | + | - | * | / | % | |||
位运算相关,很少使用 | & | | | ~ | ^ | >> | << | ||
其他运算符 | ->* | new | new[] | delete | delete[] | , |
一些规定:
一些建议:1、不要改变运算符原有的意义。
2、不要重载的运算符 :
& 和 * 的 一 元 版 本,他们对于所有的数据对象都有固有的操作语义:取地址 和 解地址。
|| 和 && 无法满足内置的短路求值性质。因为他们实质是函数,只要出现了调用,就一定会被执行。
, 逗号运算符,有固定的语义。这个基本不会用到。
3、不要为了获得微弱的使用方便性,而不假思索的重载运算符,有时候使用一个有意义名称的普通函数会更加安全,合适。
二元运算符 既可以定义为类的辅助函数(全局函数),也可以定义为对象的成员函数。
当一个二元运算符 @ 定义为对象的成员函数时: a @ b 实质是 a.operator@(b)
当一个二元运算符 @ 定义为类的辅助函数时 : a @ b 实质是 operator@(a,b)
可以发现,当定义为对象的成员函数时,运算符的第一个操作数默认就是本类当前对象,所以函数参数只需提供第二个操作数。而当重载为全局函数时,运算符的操作数必须都以函数参数的形式出现。
一元运算符 则有几个特例,他们必须重载为对象的成员函数。原因是:当前类对象只能作为运算符的第一个操作数。
[ ] 下标运算符 ,一般用于容器或者序列。
( ) 函数调用运算符 ,一般用于自定义函数类对象。这些对象是可调用的。
= 赋值运算符
-> 通过指针访问对象的成员的运算符。一般用于 自定义指 针 类对象。
一些常用的运算符的重载。
/*studnet.h*/ #ifndef _STUDENT_H__ #define _STUDENT_H__ #include<string> #include<iostream> class Student {
private: std::string _name; int _age; double _score; public: Student(); Student(const std::string &name, const int &age, const double&score);
/*一般将非成员运算符重载 声明为类的友元,这样方便访问对象的数据*/
friend std::ostream& operator<<(std::ostream& out, const Student& stu);
friend std::istream& operator>>(std::istream& in, Student& stu);
}; std::ostream& operator<<(std::ostream& out, const Student& stu); std::istream& operator>>(std::ostream& in, Student& stu);
/*studnet.cpp*/ #include"student.h" Student::Student() { _name = ""; _score = 0.0; _age = 0; } Student::Student(const std::string &name, const int &age, const double&score) :_name(name), _age(age), _score(score) { } //参数 out不能修饰为const,因为通过out流对象 输出 stu时,就是更改out流状态的过程。 //参数stu被输出,不会改变对象状态,修饰为const最好 //返回out本身,以便连续输出 std::ostream& operator<<(std::ostream& out, const Student& stu) { out << "age:" << stu._age << ‘\n‘ << "name:" << stu._name << ‘\n‘ << "score:" << stu._score; return out; } std::istream& operator>>(std::istream& in, Student& stu) { in >> stu._name >> stu._age >> stu._score; return in; }
int main() { Student s; cin >> s; cout << s << endl; return 0; }
以 ++ 运算符为例,-- 运算与之符同理。
++ 有前缀和后缀版本。当仅仅使用 ++ 的副作用,使操作对象自增1时,++a 和 a++都可以达到相同的效果,但是优选使用 ++ a,为什么,请往后看。
++ a 整个表达式的值是 a +1 之后的值, a++ 整个表达式的值是 a原本的值,这是二者表明上的区别。
下面将一个Student类对象重载 ++ 运算符,表示增加对象的_age属性。
/*studnet.h*/ #ifndef _STUDENT_H__ #define _STUDENT_H__ #include<string> #include<iostream> class Student { private: std::string _name; int _age; double _score; public: Student(); Student(const std::string &name, const int &age, const double&score); Student& operator++(); //前缀版本 Student operator++(int); //后缀版本
friend std::ostream& operator<<(std::ostream& out, const Student& stu);
}; std::ostream& operator<<(std::ostream& out, const Student& stu);
/*studnet.cpp*/ #include"student.h" Student::Student() { _name = ""; _score = 0.0; _age = 0; } Student::Student(const std::string &name, const int &age, const double&score) :_name(name), _age(age), _score(score) { } /*使用一个int参数类型占位符来区别 前缀 和后缀版本,它只用来占位,区分,并无它用*/ /*后缀版本需要临时保存对象增1前的状态,以便返回,这就是我为什么说优先使用前缀版本的缘故了*/ Student& Student::operator++() //前缀,返回值是增1后的 值,返回的是当前对象 { ++_age; return *this; } Student Student::operator++(int) //后缀,返回的值当前对象增1 前 的值。 { //由于返回的是局部对象,所以函数的返回类型不能是引用类型。 Student re = *this; ++_age; return re; } std::ostream& operator<<(std::ostream& out, const Student& stu) { out << "age:" << stu._age << ‘\n‘ << "name:" << stu._name << ‘\n‘ << "score:" << stu._score; return out; }
默认,编译器会帮我们提供一个默认的赋值算 符 函 数 ,其默认的行为是:
对对象字段做如下操作:
字段是class类型,struct,则调用字段的赋值运算符。
字段是基本类型则直接赋值。
字段 是数组,则一 一 执行数组元素的赋值运算符,复制到另一个数组中。
很多时候这样并不能正确的执行我们需要的效果。所以需要自定义。
固定格式形如:Student & operator=(cosnt Student& other);
注意点:
1、如果赋值参数是同类对象,则应该有防止自赋值代码,以提高函数效率。
2、所有的赋值运算符,组合赋值运算符都应该返回当前对的引用。
3、由于赋值是原有数据的覆盖,所以应在赋值数据前,做必要的清理工作,如delete原对象申请的内存。
总结就是4个步骤:
①如果参数是同类对象,则要防止自赋值
②清理当前对象原有的资源
③ 一 一拷贝数据
④返回当前对象引用
下面是一个简单的 存储int类型元素的Stack 的赋值运算符。
Stack& Stack::operator = (const Stack& that)
{
if (that == this) //防止自赋值
return *this;
delete[] _innerArr; //清理源有内存
_len = that._len;
_innerArr = new int[len]; //分配新内存
for (std::size_t i = 0; i < _len; ++i)
{
_innerArr[i] = that._innerArr[i];
}
return *this; //返回当前对象
}
何时调用?
将一个对象 赋值给一个已存在的对象时,会调用赋值运算符。
下面,为Student重载 + 运算符,表示返回一个_age 加上 某个参数后的 新 的Student类对象。
/*student.h(部分)*/
class Student { //... public: //... //返回一个student镀锡 的_age 加上 add岁后的新的student对象 Student operator+(int add) const; };
/*student.cpp中的实现(部分)*/
Student Student::operator+(int add) const { Student re = *this; re._age += add; return re; }
/*main.cpp*/
int main() { Student s("Bob", 19, 90.0); cout << s+3 << endl; //OK cout << 3+s<< endl; //error 匹配不到相应的运算符,因为我们没有考虑到前操作数是int 的版本 system("pause"); return 0; }
巧妙的补救
/*student.h(部分)*/
class Student {
//... public: Student operator+(int add) const; }; Student operator+(int add, const Student& stu); //通过全局辅助函数来完成另一个重载。
/*student.cpp中的实现(部分)*/
Student Student::operator+(int add) const { Student re = *this; re._age += add; return re; } Student operator+(int add, const Student& stu) { return stu + 3; }
一些容器(Java中叫集合)类型,很多时候需要获取容器中的第 xx个元素,这个时候重载 下标运算符[ ] 再合适不过了。
注意:由于[ ] 实质是函数调用,意味着 [ index] 索引可以是任何类型。当然不要乱用。
如果[ index] 索引的的值是整型的,最好使用 无符号类型 std::size_t 。
请考虑重载2和个版本:分别用于容器本身是 常量 / 非常量 的情况。当容器本身就是常量时,[]运算符取得的元素是const类型,这样避免了修改容器中的元素。
#ifndef _MSTRING_H__ #define _MSTRING_H__ #include<cstring> class MString { public: MString(const char* cs); ~MString(); const char& operator[](std::size_t index) const; char& operator[](std::size_t index); private: char* pstr; size_t len; }; #endif
#include"mstring.h" MString::MString(const char* cs) { len = std::strlen(cs); pstr = new char[len + 1]; std::strcpy(pstr,cs);
pstr[len] = ‘\0‘;
} MString::~MString() { delete[] pstr; }
/*用于读容器中的元素,则元素是不应该被修改的,容器也不应该被修改*/ const char& MString::operator[](std::size_t index) const { //if (index >= len || index < 0) //注意越界检查,这里没写出来了 return pstr[index]; }
//用于给容器中的元素写入新的值 char& MString::operator[](std::size_t index) { //if (index >= len || index < 0) //注意越界检查,这里没写出来了 return pstr[index]; }
感悟:用C++有限的语法优特性提供和内置类型一致的使用体验,这就要求类的设计者深厚的C++功底。
/***************************************************/
欢迎转载,请注明出处:www.cnblogs.com/lulipro
为了获得更好的阅读体验,请访问原博客地址。
代码钢琴家
/***************************************************/
标签:erro class类 申请 需要 复杂 ack tle 检查 表示
原文地址:http://www.cnblogs.com/lulipro/p/5986389.html