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++面试题(6)运算符重载详解的更多相关文章
- CPP-基础:运算符重载详解
1.运算符重载定义: C++中预定义的运算符的操作对象只能是基本数据类型.但实际上,对于许多用户自定义类型(例如类),也需要类似的运算操作.这时就必须在C++中重新定义这些运算符,赋予已有运算符新的功 ...
- C++运算符重载详解
1.什么是运算符重载 运算符重载是一种函数重载. 运算符函数的格式:operatorop(argument-list)例如,operator+()重载+运算符.其中的op,必须是有效的C++运算符,如 ...
- C/C++对bool operator < (const p &a)const的认识,运算符重载详解(杂谈)
下面来进行这段代码的分析: struct node { //定义一个结构体node(节点) int x; int y; int len; //node中有3个成员变量x,y,l ...
- C++11运算符重载详解与向量类重载实例(<<,>>,+,-,*等)
1. C++运算符重载介绍 C ++ 中预定义的运算符的操作对象只能是基本数据类型.但实际上,对于许多用户自定义类型(例如类),也需要类似的运算操作.这时就必须在C ++ 中重新定义这些运算符,赋予已 ...
- C++重载>>和<<(输入和输出运算符)详解
转载:http://c.biancheng.net/view/2311.html 在C++中,标准库本身已经对左移运算符<<和右移运算符>>分别进行了重载,使其能够用于不同数据 ...
- Kotlin——最详细的操作符与操作符重载详解(上)
本篇文章为大家详细的介绍Koltin特有的操作符重载.或许对于有编程经验的朋友来说,操作符这个词绝对不陌生,就算没有任何编辑基础的朋友,数学中的算数运算符也绝不陌生.例如(+.-.*./.>.& ...
- [java基础] 002 - 位运算符的详解和妙用
一:位运算符详解 位运算符主要用来对操作数二进制的位进行运算.按位运算表示按每个二进制位(bit)进行计算,其操作数和运算结果都是整型值. Java 语言中的位运算符分为位逻辑运算符和位移运算符两类, ...
- 算法进阶面试题01——KMP算法详解、输出含两次原子串的最短串、判断T1是否包含T2子树、Manacher算法详解、使字符串成为最短回文串
1.KMP算法详解与应用 子序列:可以连续可以不连续. 子数组/串:要连续 暴力方法:逐个位置比对. KMP:让前面的,指导后面. 概念建设: d的最长前缀与最长后缀的匹配长度为3.(前缀不能到最后一 ...
- C++函数重载详解
我们在开瓶瓶罐罐的时候,经常会遭遇因各种瓶口规格不同而找不到合适的工具的尴尬.所以有时候就为了开个瓶,家里要备多种规格的开瓶器.同样是开个瓶子嘛,何必这么麻烦?于是有人发明了多功能开瓶器,不管啤酒瓶汽 ...
随机推荐
- jquery中没有innerHTML
本人正在学习使用jQuery. 发现如果我在div或者其他非表单的标签中赋值,原本用普通的js就直接document.getElementById("id").innerHtml( ...
- EasyUI中在表单提交之前进行验证
使用EasyUi我们可以在客户端表单提交之前进行验证,过程如下:只需在onSubmit的时候使用return $("#form1").form('validate')方法即可,E ...
- ASP.NET多个Button的页面,回车执行按钮事件(转)
主要有两种实现方法分别是:JavaScript的方法与Panel的方法 一.JavaScript的方法 ①单输入框(文本框)单按钮的实现方法 以下功能实现:在输入框中输入内容之后,按回车键就执行按钮事 ...
- JAVA封装、继承
封装 1.概念: 将类的某些信息隐藏在类的内部,不允许外部程序访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问. 2.好处: a.只能通过规定的方法访问数据 b.隐藏类的实例细节,方便修改和实 ...
- Less入门到上手——前端开发利器<二>深入了解
接着昨天的继续... ... 4.嵌套: HTML部分 <table> <tr> <th colspan="3">测试列表标题</th&g ...
- POSTGRESQL 数据库导入导出
导入整个数据库 psql -U postgres(用户名) 数据库名(缺省时同用户名) < /data/dum.sql 导出整个数据库 pg_dump -h localhost -U po ...
- ReentRantLock使用
synchronized原语和ReentrantLock在一般情况下没有什么区别,但是在非常复杂的同步应用中,请考虑使用ReentrantLock,特别是遇到下面2种需求的时候. 1.某个线程在等待一 ...
- 软件测试--测试Demo
视频地址(第二课时):https://pan.baidu.com/s/1gfLVC2n 软件安装好了! 软件默认的浏览器是火狐. 如果需要IE,chrome,都在前一篇的安装包里有. 测试结果 视频里 ...
- Android 控件知识点
一.引入布局 在xml文件中引入另一个布局 <include layout="@layout/XXX" /> 个人理解就是在父布局的某个位置在嵌套一个布局. 二.自定义 ...
- JS历史
JavaScript伴随着互联网的发展一起发展.互联网周边技术的快速发展,刺激和推动了JavaScript语言的发展. 2006年,jQuery函数库诞生,作者为John Resig.jQuery为操 ...