c/c++面试题(6)运算符重载详解

1.操作符函数:

在特定条件下,编译器有能力把一个由操作数和操作符共同组成的表达式,解释为对

一个全局或成员函数的调用,该全局或成员函数被称为操作符函数.该全局或成员函数

被称为操作符函数.通过定义操作符函数,可以实现针对自定义类型的运算法则,并使之

与内置类型一样参与各种表达式运算.

2.首先我们先介绍下左值和右值,因为我们在运用运算符的时候要尽量和内置类型的一致性.

左值:有名的可以直接取地址的我们称之为左值,左值的特性是可以修改的.

右值:右值主要是一些临时变量,匿名变量,字符串字面值常量,字符常量;

表达式有的是左值有的是右值,例如+-*/运算返回的是右值,而赋值运算,复合赋值运算

返回的是左值.前++返回的是左值,后++返回的是右值.

类型转换(无论是显示的还是隐式的)都伴随着临时变量的产生.函数的返回值当返回的

不是引用的时候也是一个右值,但是一个引用的时候返回的是一个左值.

下面给出一个经典的全面左值和右值的示例:

#include <iostream>
int g = ;
using namespace std;
int foo(void)
{
return g;
} int& bar(void)
{
return g;
} void func(int& i)
{
cout << "func1" << endl;
}
void func(const int& i)
{
cout << "func2" << endl;
} int main(void)
{
int i; //左值,有名字可以取地址.
int* p = &i;
i = ; //可修改
//p = &10;这里的10是个右值,上面10赋值给i的时候产生的一个临时变量,是右值.所以10++也是错误的. // foo() = 10; 这里返回值也是临时变量是右值,是只读的属性,不能修改;++foo();也是错误的.    bar() = ; //这里返回的是一个左值引用,所以它是可以赋值的
cout << g << endl;
int a = ,b = ,c;
c = a + b;//a + b是右值.
//a + b = 10;是非法的,因为表达式的值也是用一个临时变量来存储的.是右值.
//无论是显示的还是隐式的类型转换,都会产生临时变量.
(a = b) = ; //赋值表达式返回的是左值.
++a = ; //前++表达式返回的是左值.
//a++ = 2000; 这里是不行的,后++运算符的表达式的值是右值.
//a++++;这也是不行的,对右值做++是不行的.
++++a;//这个是可以的.++a的返回值还是a本身.
cout << a << endl; //
char ch = ;
i = ch; //这里是可以的.
//int& r = ch;它会先把ch转换成一个临时变量,然后把这个临时;变量赋值给r,而引用是不能引用一个临时变量的.
//非常引用只能引用一个左值,不能是右值.
const int& r = ch; //常左引用又叫万能引用,它可以引用左值和右值.
func(ch);//这里它会调用参数是常属性的函数,因为它要类型转换,类型转换产生临时变量.
//所谓类型转换,无论是显示还是隐式的.都不是改变了原来的变量的类型,只能产生一个临时变量.
return ;
}

3.操作符重载的一般规则

a.C++不允许用户自己定义新的运算符,只能对已经存在的操作符进行重载.

b.C++大部分的运算符都可以重载,但是有一部分运算符是不能重载的主要有下面几种

.成员访问运算符; ::作用域解析运算符;.* 成员指针访问运算符;sizeof运算符;三目运算符;

.成员访问运算符和.*成员指针访问运算符不能重载的原因是为了保证成员访问的功能不能被改变;

::和sizeof运算符操作的对象是类型而不是一般的变量和表达式,不具备重载的特征.

c.重载不能改变操作的对象操作数的个数;

d.重载不能改变运算符的优先级;

e.重载函数的参数不能有默认的缺省参数值,因为它会改变运算符的操作数和前面的规则矛盾;

f.重载的参数不能全部都是C++的基本类型,因为这样会改变原有的用于标准的运算符的性质.

g.应当尽量使自定义的重载操作符和系统用于标准类型的运算符具有相似的功能;

h.运算符重载可以是类的成员函数,还可以是类的友元函数,还可以是普通的全局函数;

4.运算类双目操作符:+ - * /等   a.左右操作数均可以左值或右值;

b.表达式的值为右值.

c.成员函数形式:

class LEFT

{

const RESULT operator#(const RIGHT& right)const {}

};

全局函数的形式:

const RESULT operator#(const LEFT& left,const RIGHT& right){...}

#include <iostream>
using namespace std;
class Complex
{
public:
Complex(int r = ,int i = ):m_r(r),m_i(i){}
//运算符+-/*表达式的结果是右值,不能是引用.
/*参数用常引用的好处:
即避免了拷贝构造的开销,它又能接收常右操作数
且可以保证右操作数不被修改.
第三个const是可以支持常属性的左操作数.
因为非常对象依然可以调用常函数.
第一const为了追求语义上的一致性,使其返回值是一个
临时变量.不能被赋值.
*/
/*从做到右的三个const依次表示:
1.第一个const:返回常对象,禁止对加号表达式进行赋值.
2.第二个const:支持常右操作数,并且可以避免其被修改.
3.第三个const:支持常左操作数.
*/
const Complex operator+(const Complex& c) const
{
return Complex(m_r + c.m_r,m_i + c.m_i);
}
const Complex operator*(const Complex& c) const
{
return Complex(m_r*c.m_r,m_i*c.m_i);
}
const Complex operator/(const Complex& c) const
{
return Complex(m_r/c.m_r,m_i/c.m_i);
}
void print(void) const
{
cout << m_r << '+' << m_i << "i" << endl;
}
/*用全局函数来实现(友元)
const Complex operator-(const Complex& c) const
{
return Complex(m_r - c.m_r,m_i - c.m_i);
}
*/
private:
int m_r;
int m_i;
friend const Complex operator-(const Complex&,const Complex&);
};
const Complex operator-(const Complex& left,
const Complex& right)//这里没有const了,因为
//const是修饰this指针的,而全局函数是没有this指针的;
{
return Complex(left.m_r-right.m_r,left.m_i - right.m_i);
}
int main(void)
{
Complex c1(,),c2(,);
Complex c3 = c1 + c2;
//编译器翻译成Complex c3 = c1.operator+(c2);
c3.print();
Complex c4 = c1 + c2 + c3;
//Complex c4 = c1.operator+(c2).operator+(c3);
c4.print();
//(c1 + c2) = c3;如果加号运算符没有第一个const这里编译会通过.
c4 = c3 - c1;
//c4 = ::operator-(c3,c1);也可以处理成这样
c4.print();
c4 = c1*c2;
c4.print();
c4 = c2/c1;
c4.print();
return ;
}

5.赋值类操作运算符:=,+=,-=,*=,/=,^=,&=,|=等

a.左操作数必须是左值,右操作数可以是左值或者右值.

b.表达式的值为左值,返回自引用.是操作数本身,而非副本.

成员函数形式:

class LEFT

{

LEFT& operator#(const RIGHT& right){...}

}

全局函数形式:

LEFT& operator#(LEFT& left,const RIGHT& right){...}

#include <iostream>
using namespace std;
class Complex
{
public:
Complex(int r = ,int i = ):m_r(r),m_i(i){}
void print(void)const
{
cout << m_r << '+' << m_i << 'i' << endl;
}
//这里不能是const函数,因为左操作数要改变的.
Complex& operator+=(const Complex& c) //这里不能带const
{
m_r += c.m_r;
m_i += c.m_i;
return *this;
}
friend Complex& operator-=(Complex& left,const Complex& right)//友元函数没有this指针,也就没有常函数之说.
{
left.m_r -= right.m_r;
left.m_i -= right.m_i;
return left;
}
Complex& operator*=(const Complex& c)
{
m_r *= c.m_r;
m_i *= c.m_i;
return *this;
}
Complex& operator/=(const Complex& c)
{
m_r /= c.m_r;
m_i /= c.m_i;
}
private:
int m_r;
int m_i;
};
int main(void)
{
Complex c1(,),c2(,),c3(,);
(c1 += c2) = c3;
//c1.operator+=(c2).operator=(c3);
c1.print();
c3 -= c1;
c3.print();
//::operator-=(c3,c1);
c3 *= c1;
c3.print();
c3 /= c1;
c3.print();
return ;
}

6.运算类单目操作符:-,~,!等

a.操作数为左值或右值.

b.表达式的值为右值.

const RESULT operator#(void)const{...}

const RESULT operator#(const OPERAND& operand){...}

#include <iostream>
using namespace std;
class Integer
{
public:
Integer(int i = ):m_i(i){}
void print(void) const
{
cout << m_i << endl;
}
const Integer operator-(void) const
{
return Integer(-m_i);
}
friend const Integer operator~(const Integer& i)
{
return Integer(i.m_i * i.m_i);
}
const Integer operator!(void) const
{
return m_i?Integer():Integer();
}
private:
int m_i;
};
int main(void)
{
Integer i();
Integer j = -i;
//Integer j = i.operator-();
j.print();
j = ~i;
//j = ::operator~(i)求其平方.用~完成一个平方的效果.
j.print();
j = !i; //取反,表示真假之间的转换.
j.print();
return ;
}

7.前++前--后++后--运算符:

a.前自增减单目操作符:操作数为左值,表达式的值为左值,且为操作数本身(而非副本)

成员函数形式:

OPERAND& operator#(void){...}

全局函数形式:

OPERAND& operator#(OPERAND& operand){...}

b.后自增减单目操作符:操作数为左值,表达式的值为右值,且为自增减以前的值.

成员函数形式:

const OPERAND operator#(int){...}

全局函数形式:

const OPERAND operator#(OPERAND& operand,int){...}

8.输出操作符:<<

a.左操作数为左值形式的输出流(ostream)对象里面的成员,右操作数为左值或右值.

b.表达式的值为左值,且为左操作数本身(而非副本)

c.左操作数的类型为ostream,若以成员函数重载该操作符,就应该将其定义为ostream类的

成员,该类为标准库提供,无法添加新的成员,因此只能以全局函数的形式重载该操作符

ostream& operator<<(ostream& os,const RIGHT& right){...}

输入操作符:>>

a.左操作数为左值形式的输入流(istream)对象,右操作数为左值.

b.表达式的值为左值,且为左操作数本身(而非副本)

c.左操作数的类型为istream,若以成员函数形式重载该操作符,就应该将其定义为istream类的成员,

该类为标准库提供,无法添加新的成员,因此只能以全局函数的形式重载该操作符

istream& operator>>(istream& is,RIGHT& right){...}

#include <iostream>
using namespace std;
class Complex
{
public:
Complex(int r = ,int i = ):m_r(r),m_i(i){}
friend ostream& operator<<(ostream& os,const Complex& c)
{
return os << c.m_r << '+' << c.m_i << 'i';
}
friend istream& operator>>(istream& is,Complex& c)
{
return is >> c.m_r >> c.m_i;
}
private:
int m_r;
int m_i;
};
int main(void)
{
Complex c1(,),c2(,);
cout << c1 << ", " << c2 << endl;
cout << "Please input :" << endl;
cin >> c1 >> c2;
cout << c1 << ", " << c2 << endl;
return ;
}

9.下标运算符:[]

a.常用于在容器类型中以下标方式获取数据元素.

b.非常容器元素为左值,常容器的元素为右值.一个是非const成员,一个是const成员.并且必须定义为成员函数.

#include <iostream>
using namespace std;
class Array
{
public:
Array(size_t size):m_data(new int[size]),m_size(size){}
~Array(void)
{
if(m_data != NULL)
delete[] m_data;
m_data = NULL;
}
int& operator[] (size_t i)
{
return *(m_data + i);
}
//常版本
const int& operator[] (size_t i) const
{
return const_cast<Array&>(*this)[i];
}
private:
int* m_data;
size_t m_size;
};
int main(void)
{
Array a();
a[] = ; //a.operator[](0) = 13;
a[] = ;
a[] = ;
cout << a[] << ' ' << a[] << ' ' << a[] << endl;
const Array& r = a;
cout << r[] << ' ' << r[] << ' ' << endl;
return ;
}

10.小括号函数操作符()

a.如果一个类重载了函数操作符,那么该类的对象就可以被做函数来调用,其参数和返回值就是函数

操作符函数的参数和返回值.

b.参数的个数,类型以及返回值的类型,没有限制

c.唯一可以带有缺省参数的操作符函数

#include <iostream>
using namespace std;
class Square
{
public:
double operator()(double x) const
{
return x * x;
}
int operator()(int a,int b,int c = ) const
{
return a + b - c;
}
};
int main(void)
{
Square square;
cout << square(.) << endl;
//cout << square.operator()(13.) << endl;
cout << square(,,) << endl;
cout << square(,) << endl;
return ;
}

11.解引用和间接成员访问操作符:* 以及 ->

a.如果一个类重载了解引用和间接成员访问操作符,那么该类的对象就可以被当作指针来使用.

上一篇:C++运算符重载规则


下一篇:C#高级编程笔记2016年10月12日 运算符重载