C++ Primer 学习笔记_26_操作符重载与转换(1)--可重载/不可重载的操作符、成员函数方式重载、友元函数方式重载
引言:
明智地使用操作符重载可以使类类型的使用像内置类型一样直观!
一、重载的操作符名
像任何其他函数一样,操作符重载函数有一个返回值和一个形参表。形参表必须具有操作符数目相同的形参。比如赋值时二元运算,所以该操作符函数有两个参数:第一个形参对应着左操作数,第二个形参对应右操作数。
大多数操作符可以定义为成员函数或非成员函数。当操作符为成员函数时,它的第一个操作数隐式绑定到this指针。有些操作符(比如赋值操作符)必须时类的成员函数。例如,赋值的this绑定到指向左操作数的指针。因此,赋值操作符接受单个形参,且该形参是同一类类型的对象。右操作数一般作为const引用传递。
并非所有的操作符都是可重载的,如下表:
可重载的操作符 |
|||||
---|---|---|---|---|---|
+ |
- |
* |
/ |
% |
^ |
& |
| |
~ |
! |
, |
= |
< |
> |
<= |
>= |
++ |
-- |
<< |
>> |
== |
!= |
&& |
|| |
+= |
-= |
/= |
%= |
^= |
&= |
|= |
*= |
<<= |
>>= |
[] |
() |
-> |
->* |
new |
new[] |
delete |
delete[] |
不能重载的操作符 |
|||
---|---|---|---|
:: |
.* |
. |
?: |
1、主要牢记不能重载的运算符:(可以这样加强记忆:带“点“的都不能重载)
作用域解析运算符::
条件运算符? :
直接成员访问运算符.
类成员指针引用的运算符 .*
sizeof运算符sizeof
注:.*是C++的类成员函数指针调用运算符,是用来调用一个类函数指针的。
举例:
假设有一个ClassA类拥有一个成员函数void ClassA::func(int i),则可以这么用:
ClassA obj; fp = &ClassA::func; // 给这个类函数指针赋值 (obj.*fp)(5); // 这么用,相当于调用obj.func(5);
2、运算符重载不允许发明新的运算符。
3、重载操作符必须具有一个类类型操作数
用于内置类型的操作符,其含义不能改变:
int operator+(int,int); //Error
重载操作符必须具有至少一个类类型或枚举类型的操作数。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。
4、优先级和结合性是固定的
操作符的优先级、结合性或操作数数目不能改变。
【最佳实践】
重载逗号、取地址、逻辑与、逻辑或等操作符通常不是好做法!这些操作符都具有拥有的内置含义,如果我们定义了自己的版本,就不能再使用这些内置含义!
二、成员函数重载
成员函数原型的格式:
函数类型 operator 运算符(参数表);
成员函数定义的格式:
函数类型 类名::operator 运算符(参数表) { 函数体; }
三、非成员函数重载
友元函数原型的格式:
friend 函数类型 operator 运算符(参数表);
友元函数定义的格式:
函数类型 类名::operator 运算符(参数表) { 函数体; }
【一个实例:重载operator +】
#include<iostream> using namespace std; class Complex { public: Complex(int real, int imag); Complex(void); ~Complex(void); Complex &Add(const Complex &other); void Display() const; Complex operator+(const Complex &other); friend Complex operator+(const Complex &c1, const Complex &c2); private: int real_; int imag_; }; Complex::Complex(int real, int imag): imag_(imag), real_(real) { } Complex::Complex(void) { } Complex::~Complex(void) { } Complex &Complex::Add(const Complex &other) { real_ += other.real_; imag_ += other.imag_; return *this; } void Complex::Display() const { cout << real_ << "+" << imag_ << "i" << endl; } Complex Complex::operator+(const Complex &other) { int r = real_ + other.real_; int i = imag_ + other.imag_; return Complex(r, i); } Complex operator+(const Complex &c1, const Complex &c2) //和上一个函数等价,注释上一个函数,实现当前函数的效果也是一样的 { // int r = c1.real_ + c2.real_; // int i = c1.imag_ + c2.imag_; // return Complex(r, i); } int main(void) { Complex c1(3, 5); Complex c2(4, 6); c1.Add(c2); c1.Display(); Complex c3 = c1 + c2; // 等价于c1.opertor+(c2); 或 operator+(c1, c2); c3.Display(); return 0; }
运行结果:
7+11i
11+17i
解释:实现了Add成员函数,但c1.Add(c2); 改变的是c1 本身;如果我们想实现加号表达式,c3 = c1 + c2; 那么可以实现operator+ 运算符重载,可以是成员函数形式,也可以是友元形式,如果两者共存的话成员函数优先。
四、一些基本概念
(1)例1:下列运算符,在C++语言中不能重载的是()
A、* B、?: C、:: D、delete
解答:BC。
(2)例2:复制构造函数与赋值运算符的区别是什么?
解答:首先要说明的是,若用户没有定义,C++隐式声明一个复制构造函数和一个赋值运算符。二者很像,但是在下面这点有很大的不同:复制构造函数只在对象实例化时才会被调用,也就是说,在复制构造函数调用期间,这个对象处于一个未决状态(直到复制构造函数被成功调用),另外复制构造函数不返回任何值,void都没有。而赋值运算符则在一个现存的对象被赋予新的值时被调用,并且它有返回值。
并不是出现“=”就是调用赋值构造函数:
string book1("9-999-9"), book3; string book2 = book1; //调用复制构造函数,产生新对象book2 book3 = book1; //调用赋值运算符,没有新的对象产生
所以可以看是不是有新的对象产生,来决定到底是调用了复制构造函数,还是调用了赋值运算符。
关于复制构造函数,详情可参考:《blog:类与数据抽象(5)_初始化列表(const和引用成员)、拷贝构造函数 》
(3)例3:下列哪些函数可以被继承?
A、构造函数 B、析构函数 C、复制构造函数 D、赋值操作符重载函数
E、以上都可以 F、以上都不可以
解答:F。基类的构造函数(包括拷贝构造函数)、析构函数、赋值操作符重载函数,都不能被派生类继承。
(4)例4:C++中的空类默认产生了哪些成员函数?
解答:空类默认产生构造函数、复制构造函数、析构函数、赋值运算符重载函数、取址运算符重载函数、const取址运算符重载函数等。如下:
class Empty { public: Empty(); //默认构造函数 Empty(const Empty&); //复制构造函数 ~Empty(); //析构函数 Empty& operator=(const Empty&); //赋值运算符 Empty* operator&(); //取址运算符 const Empty* operator&(() const; //取址运算符const };
参考:
C++ primer 第四版
版权声明:本文为博主原创文章,未经博主允许不得转载。