一、运算符重载
1、为什么需要运算符重载
原因:
目的是让类类型的对象像基本数据类型一样去操作,例如可以实现对象+、-、*、/、%、==、!=、[]、() 、= 、<<、 >>
class A
{
public:
A(int i = 0):m_i(i){}
private:
int m_i;
}
class STR
{
public:
STR(const char* str = "\0")
{
m_str = new char[strlen(str) + 1];
//strcpy(m_str,str);//高级编译器会提示不安全
strcpy_s(m_str,strlen(str) + 1,str);
}
private:
char *m_str;
}
void main()
{
//A a(5),b(7);
//STR s("123456");
//想要实现以下代码,使其能像基本类型一样就行相加操作
//s[2] = 'k';
//cout<<a+b<<endl;//error
//cout << a.m_i + b.m_i << endl; //error,m_i为私有成员
//例如库函数string类: (头文件:#include <string> )
string str = "123456789";
cout << str << endl;
str[3] = 'k';
cout << str << endl;
string s1 = "123",s2 = "abc";
string s3 = s1 + s2;
cout << s1 << "+" << s2 <<"=" <<s3 <<endl;
//例如基本数据类型
int aa = 10,bb = 20;
cout<<aa + bb<<endl;
}
运行结果:
强行实现(可实现,但可读性和视觉效果很差):
class A
{
public:
A(int i = 0,int j = 0):m_i(i),m_j(j){}
const int &GetI()const
{
return m_i;
}
const int &GetJ()const
{
return m_j;
}
A Add(const A& b)//声明一个Add函数,将传进来的数据进行相加
{
return A(m_i + b.m_i,m_j + b.m_j);
}
void Print()
{
cout<<m_i<<" "<<m_j<<endl;
}
private:
int m_i;
int m_j;
};
void main()
{
A a(5),b(7);
//想要实现如下操作:(想要类像基本类型一样去操作)
int aa = 10,bb = 20;
cout << aa + bb << endl;
//cout<<a + b<<endl;//error
//cout<<a.m_i + b.m_i<<endl;//error
//以下代码可行,但可读性和视觉效果很差
cout<<"i="<<a.GetI() + b.GetI()<<endl;
cout<<"j="<<a.GetJ() + b.GetJ()<<endl;
a.Add(b).Print();
}
2、使用前提1:值返回 、引用返回
返回的都是对象:(a 和 b 是个对象)
a+b、 a-b 、a*b 、a/b …
a=b、 a[4] 、cout<<a
值返回
会产生拷贝构造
所以一般避免值返回,能引用返回就引用返回;
只能作为=表达式的右边,则必须值返回
引用返回
结论:
只能作为=(赋值)表达式的右边,则必须值返回;
如果可以作为=(赋值)表达式的左边,建议引用返回;
代码理解:
void main()
{
int a = 10, b = 3;
int c = 0;
c = a + b;//这句话的意思:从a的内存空间把a的值取出来,从b的内存空间把b的值取出来,然后把两个值进行相加,将相加后的值将其放在左边,写入c的内存空间
//其中a + b的表达式只能放在左边
//a + b = c;//error,这句话的意思为:将c的值从其确定的内存单元中取出来,放入到左边(将a的值将其确定的内存单元中取出来,将b的值从其确定的内存单元中取出来,然后将这两个值进行相加,生成一个没有确定内存单元的临时值),将a的值写入到这个临时值当中,
//但临时空间只能进行临时存放,不能最终结果存放
++a = c;//前置++,先执行++,再执行= 所以a变为11,在进行赋值, 左边的表达式为11 ,将c的值放到a的内存单元 ,++a返回的是引用,返回的是对象本身,可以作为左值
//b++ = c;//error,b++求的是b++表达式的值,不是b不是,其值在临时空间存放着,
cout<<a<<endl;//a的值为c的值
(a = b) = c;//=(赋值号)可以做为左值 ,先执行小(),先将b的值给a,所以最终结果再a的内存空间存放在,再执行a=c,将c的值写入a的内存空间
}
3、使用前提2:a + b 的解读
对于 a + b ;
可以被解读为两种形式:
1)a + (b)
可以用a对象区调用 + ,再将b对象传进去;
能用a对象去调用的,说明这种形式是重载成成员形式;
2) +(a,b)
调 + ,传两个参数,一个 a对象,一个b对象。
这个形式是我们的非成员形式,是普通函数,需要调用到有源
4、运算符重载基础使用
形式:
返回类型 operator 运算符(参数列表)
使用形式示例:
//返回类型 operator 运算符(参数列表)
// | | | |
// A operator + (const A &b)
//对a+b 的运算符重载
A operator+(const A &b) //a+b a.operator+(b)本质即是实现对象的数据成员的加法
{
}
示例代码:
class A
{
public:
A(int i = 0):m_i(i){}
void Print()
{
cout<<m_i<<endl;
}
//重载函数
A operator+(const A &b) //a+b a.operator+(b)本质即是 实现对象的数据成员的加法
{
return m_i + b.m_i;//返回值为整型,类类型可以实现类型转换
}
//A operator-(const A &a) //b-a b.operator-(a)
//{
// return this->m_i - a.m_i;
//}
A operator-(const A &s) //b-a b.operator-(a) s接收第二个操作数
{
return this->m_i - s.m_i;
}
A& operator++() //++a a.++()
{
++m_i; //将a自己的数据成员进行修改
return *this; //修改完后,返回自己本身
}
/*
b++,表达式的值是b没有加以前的值,表达式执行完后,b的值变成加1之后的值,
----所以在重载后++的时候,得体现出两种值
*/
A operator++(int) //C++规定,在后++的时候,参数里面写个int,这个int没有实际作用,只是为了区分于前++
{
//int t = m_i; //先保存以前的值
//m_i = m_i +1; //自己本身加1
//return t; //返回没加以前保存的值
//等价于
return m_i++;
}
private:
int m_i;
};
void main()
{
A a(2),b(10);
//a + b;//a.+(b) +(a,b)
(a + b).Print(); //a.+(b) +(a,b) 12
(b - a).Print(); //a.-(b) 8
(a - b).Print(); //a.-(b) -8
(++a).Print(); //a.++() 3
a.Print(); //值为3,++a将值改变 3
(b++).Print(); //表达式b++的值,是b,应该输出没有加以前的值 为10,但这为11,表面其重载的是前++ 10
b.Print(); //b的值是上面自加后的值 11
}
运行结果:
5、强行实现 a+b = c; (只做测试,会违背平时的规则)
//-----如下写,只做测试讲解,会违背平时的规则
#if 1class A
{
public:
A(int i = 0):m_i(i){}
void Print()
{
cout<<m_i<<endl;
}
A &operator +(A &t) //a+b
{
m_i = m_i + t.m_i; //thie->m_i = this->m_i+ b.m_i 相当于 a.m_i = a.m_i + b.m_i
return *this; //return a
}
private:
int m_i;
};
//强行实现 a+b = c;
//要如此写,a+b必须有确定的内存单元,可以将结果放到a或者b中
void mian()
{
A a(7),b(10);
(a+b).Print();
a.Print();
b.Print();
((a+b) = 6).Print();
}
6、类中默认的赋值运算符重载函数
示例代码:
//类中有默认的赋值运算符重载函数
/*
如果程序员没有提供赋值运算符重载,则类会提供一个默认的,默认的功能为:
用右边的对象的数据成员依次给左边对象的数据成员赋值
*/
#if 1
class A
{
public:
A(int i = 0, int j = 0) :m_i(i), m_j(j) {}
void Print()
{
cout << m_i << " " << m_j << endl;
}
private:
int m_i;
int m_j;
};
void main()
{
A a(2, 6);
A b;
a.Print(); //2 6
b.Print(); //0 0
b = a; //用a的值重新给b赋值,调用了默认的赋值运算符重载函数
b.Print();
}
#endif
运行结果:
7、为什么要自己写运算符重载函数 – 指针
示例代码:
//为什么要自己写运算符重载函数
/*
总结:
如果有指针作为数据成员,则赋值运算符重载也要写出来,让两个不同的对象的指针分别指向内存单元,不过里面的内容保持相同
赋值=能作为左值,所以用引用返回
a = b
*/
#if 1
class Person
{
public:
Person(const char *name = "\0")
{
m_name = new char[strlen(name) + 1];
strcpy_s(m_name,strlen(name)+1,name);
}
void Print()
{
cout << m_name <<endl;
}
~Person()
{
delete[] m_name;
m_name = NULL;
//cout << "~ " << endl;//用于显示的表明析构的调用,用于代码观察
}
Person &operator=(const Person& s)//可认为 析构+拷贝构造
{
if(this == &s) //判断自赋值
return *this;
//先将左边对象原有的内存空间释放掉
delete[]m_name;
//接着开辟和右边对象s相同大小的内存单元
m_name = new char[strlen(s.m_name) + 1];
strcpy_s(m_name,strlen(s.m_name)+1,s.m_name);
return *this;
}
/*
Person &operator=(const Person& s)//可认为 析构+拷贝构造
{
if(this != &s)//判断自赋值
{
//创建一个临时实例tmp,在当前{}结束后,会自动调用自己的析构函数释放相关内存
Person Tmp(s);
//把Tmp.m_name 和实例自身的m_name进行交换 当临时变量Tmp析构时,因为进行了交换,所以会释放原来m_name的内存
char* tmp = Tmp.m_name;
Tmp.m_name = m_name;
m_name = tmp;
}
}
*/
private:
char *m_name;
};
void main()
{
Person p1("lisi");
Person p2("zhangsan");
p1.Print(); //lisi
p2.Print(); //zhangsan
p2 = p1; //error 目的是用p1 给p2 重新赋值
//如果调用默认的赋值重载,会报错,会使得p2的指针重新指向了p1的指针所指向的空间"lisi"
//p1和p2的指针指向同一块空间,但是p2原本的空间还在,导致函数释放时,释放了两次这个内存单元,而p2原本所指向的空间反而没有没释放
//用p1给p2重新赋值,如果调用默认的赋值函数,则让p2的指针重新指向了p1的指针
p2.Print();
//cout << "main end " << endl;
}
#endif
运行结果:
调用默认赋值函数的结果:
可以输出结果,但再再{}结束前会出错;
而且可以发现是再析构p2的时候出现了问题;
原因:
如果调用默认的赋值重载,会报错,会使得p2的指针重新指向了p1的指针所指向的空间"lisi"
p1和p2的指针指向同一块空间,但是p2原本的空间还在,导致函数释放时,释放了两次这个内存单元,而p2原本所指向的空间反而没有没释放
用p1给p2重新赋值,如果调用默认的赋值函数,则让p2的指针重新指向了p1的指针
8、运算符重载的使用 — 带指针 :+、[]、==
示例代码:
class STR
{
public:
STR(const char *str = "\0")
{
m_str = new char[strlen(str) + 1];
strcpy_s(m_str,strlen(str) + 1,str);
}
STR(const STR& s)//拷贝构造函数 ,用旧对象去构造新对象
{
m_str = new char[strlen(s.m_str) + 1];
strcpy_s(m_str,strlen(s.m_str) + 1,s.m_str);
}
~STR() //析构函数
{
delete[]m_str;
m_str = NULL;
/*if(m_str != NULL)
{
delete[]m_str;
m_str = NULL;
}*/
}
STR operator+(const STR& s) //a+b 实际就是将两个对象中的字符串合并
{
//正确方法
char* temp = new char[strlen(m_str) + strlen(s.m_str) + 1];
strcpy_s(temp,strlen(m_str) + 1,m_str);
//strcat也是不安全,串接
strcat_s(temp,strlen(m_str) + strlen(s.m_str) + 1,s.m_str);//第二个参数是拼接后的字符串大小
STR ss(temp);
delete[]temp;//在出当前{}前,要将其temp这个新开辟的局部变量释放
temp = NULL;
return ss;
/*可强行实现方法,但不合适,会出现问题,需要在析构函数内加if(m_str != NULL)判断才可行,否则赋值可正常运行,但运行完会报错
STR ss;//在退出这个函数,这个对象也需要析构,但出错没有析构
delete[]ss.m_str;//需要将其原本的空间释放掉
ss.m_str = new char[strlen(m_str) + strlen(s.m_str) + 1];
strcpy_s(ss.m_str,strlen(m_str) + 1,m_str);
strcat_s(ss.m_Str,strlen(m_str) + strlen(m_str) + 1,s.m_str);
temp = NULL;
return ss;
*/
}
//----- 常考题 -----
STR& operator=(const STR& s) //要考虑空间是否够,不知道赋值对象的大小,所以不能直接赋值
{
//判断自赋值
if(this == &s) //5分
return *this;
//先释放,再开辟
delete[]m_str; //3分
m_str = new char[strlen(s.m_str) + 1];
strcpy_s(m_str,strlen(s.m_str) + 1,s.m_str);
return *this;
}
void Print()
{
cout << "m_str = " <<m_str << endl;
}
char& operator[](int index)
{
if(index >= 0 && index < strlen(m_str)) //"1234" =的话,不行只能取出123,所以是<
return m_str[index];
}
bool operator==(const STR& s)
{
//return strcmp(m_str, s.m_str);//不可行,没有值输出
return strcmp(m_str, s.m_str)==0;
/*
if (strcmp(m_str, s.m_str) == 0)
{
cout << "yes" << endl;
return 1;
}
else
{
cout << "no" << endl;
return 0;
}*/
}
private:
char *m_str;
};
void main()
{
STR a("111"),b("222");
STR c = a + b; //a+b 拷贝构造 --思路,先开辟总大小的内存单元,在逐个将两个值写入,再
//STR d;
STR d("hell");
d = c; //把c的值赋给d, 重新给d赋值 调用 =(赋值)运算符重载
//d = a;
cout<<"c = ";
c.Print();
cout<<"d = ";
d.Print();
c[5] = 'h'; // c.[](5)
cout << c[5] << endl;
cout<<"c' = ";
c.Print();
int n = (a == b);
cout << n << endl;
cout << (a == b) << endl; //a.==(b)
}
运行结果
9、()的运算符重载 – 将当前的类名当成函数名来使用
目的是:将当前的类名当成函数名来使用,要让当前的类名和函数名一样去使用。
class Greate
{
public:
int operator()(int x, int y)
{
return x > y;
}
};
class SMall
{
public:
int operator()(int x, int y)
{
cout << "operator()" << endl;
return x < y;
}
/* int& operator[](int n)
{
}*/
};
思考:
如果定义一个 SMall sm;
类比于sm[3] -> sm.[](3)
是否可以实现:sm()(5,7)
?
直接使用,不行
()重载调用,可行:
class SMall
{
public:
int operator()(int x, int y)
{
cout << "operator()" << endl;//用于显示的表明是否调用operator()
return x < y;
}
/* int& operator[](int n)
{
}*/
};
#if 1
void main()
{
SMall sm;
cout << sm(5, 8) << endl;//函数对象(仿函数 -- 把对象名当中函数名使用)
}
运行结果:
其中调用sm(5, 8) ,相当于sm.()(5,8) ,sm调用了一个(),()其中的参数为5,8
类比于:cout << sm[7] << endl;//sm.[](7)
所以 sm(5, 8) ,给一个对象后加(),()其中还传参,
相当于要去调用当前对象所对应类里所重载的()。也就是说,把当前对象当作函数名来使用,即为函数对象(仿函数 – 把对象名当中函数名使用)。
把对象名能当函数名来使用的前提为:
在当前这个对象所对应的这个类里面,必须重载()。
这里不能这样写//cout << SMall(5, 8) << endl;//error
但作为函数的参数来使用的话,可以实现:
class SMall
{
public:
int operator()(int x, int y)
{
cout << "operator()" << endl;//用于显示的表明是否调用operator()
return x < y;
}
/* int& operator[](int n)
{
}*/
};
void main()
{
//假设要对这段元素进行排序
int a[] = {12,5,36,2,5,48,12,33,21,10,5,9,7};
int n = sizeof(a) / sizeof(a[0]);
sort(a, a + n, SMall());//函数对象 调用的是SMall类中的()重载 , 这里其实生成了一个无名的对象,其调用(),不传参
//sort(a, a + n, Greate());
int i;
for(i = 0;i < n;i++)
cout << a[i] << " ";
cout << endl;
}
运行结果;
测试代码:
#if 1
#include <algorithm>
//sort(first,)
/*
int My_Great(int x,int y) //普通函数
{
return x > y;
}
int My_Small(int x,int y)
{
return x < y;
}
class Greate
{
public:
int operator()(int x, int y)
{
return x > y;
}
};
*/
class SMall
{
public:
int operator()(int x, int y)
{
cout << "operator()" << endl;//用于显示的表明是否调用operator()
return x < y;
}
/* int& operator[](int n)
{
}*/
};
#if 0
void main()
{
SMall sm;
cout << sm(5, 8) << endl;
//cout << sm[7] << endl;//sm.[](7)
//cout<<sm(5, 7)<<endl;//函数对象(仿函数) sm.()(5,7)
}
#endif
#if 1
void main()
{
//假设要对这段元素进行排序
int a[] = {12,5,36,2,5,48,12,33,21,10,5,9,7};
int n = sizeof(a) / sizeof(a[0]);
//这里调用类库里的一个函数sort,头文件为#include <algorithm>
//sort(a,a + n);//默认是从小到大
//sort(a,a + n,less<int>);//从小到大排序 <>为模板,是一个类模板 less是stl标准库库文件里的一个类
//sort(a,a + n,greater<int>{});
//sort(a,a + n,My_Great);
//sort(a,a + n,My_Small);
sort(a, a + n, SMall());//函数对象 调用的是SMall类中的()重载
//sort(a, a + n, Greate());
int i;
for(i = 0;i < n;i++)
cout << a[i] << " ";
cout << endl;
}
#endif
#endif
10、输出运算符的存在 – 有源
二、总结
1、运算符重载的总结:
运算符的重载实际是一种特殊的函数重载,必须定义一个函数,并告诉C++编译器,当遇到该重载的运算符时调用此函数。这个函数叫做运算符重载函数,通常为类的成员函数。
定义运算符重载函数的一般格式:
返回值类型 类名::operator重载的运算符(参数表)
{…}
operator是关键字,它与重载的运算符一起构成函数名。因函数名的特殊性,C++编译器可以将这类函数识别出来。
2、运算符重载函数的总结
1)运算符重载函数的函数名必须为关键字operator加一个合法的运算符。
在调用该函数时,将右操作数作为函数的实参。
2)当用类的成员函数实现运算符的重载时,运算符重载函数的参数(当为双目运算符时) 为一个或 (当为单目运算符时) 没有。
运算符的左操作数一定是对象,因为重载的运算符是该对象的成员函数,而右操作数是该函数的参数。
例如:
A a;
A c = a + 1;// a.+(1)
3)单目运算符“++"和”–"存在前置与后置问题。
前置"++"格式为:
返回类型类名::operator++(){......}
而后置"++"格式为:
返回类型类名::operator++(int){......}
后置"++"中的参数int仅用作区分,并无实际意义,可以给一个变量名,也可以不给变量名。
重载++的时候,有个规则:int和引用不见面。
前面返回值有如果引用,后面一定不要写int,
如果后面写了int,那么前面一定不要写返回值为引用。
原因:
++i,前置++ ,返回值为&引用,
i++,后置++ ,返回值为值返回,参数写int
4)C++中只有极少数的运算符不允许重载
还有# , ## , // , /**/
注意:sizeof是个运算符
5) 重载运算符有以下几种限制
不可臆造新的运算符。
不能改变运算符原有的优先级、结合性和语法结构,不能改变运算符操作数的个数.
运算符重载不宜使用过多。
重载运算符含义必须清楚,不能有二义性.