c++_狄泰总结03操作符号重载
- 1)操作符重载(本质函数定义) ==>使用对象:类
- 2)完善复数类:操作符重载
- 3)"<<" 左移操作符合重载
- 4)数组操作符重载( [ ] )
- 5)函数对象--重载函数操作符号"()" (在工程中取代函数指针)
- 实际案例
- 6)赋值操作符(=)重载 重看视频
- 为什么需要赋值操作符重载?
- 6.1)实际例子:赋值操作浅拷贝导致系统奔溃
- 7)智能指针(本质:对象)-->操作符重载(->和*),利用一个对象来代替指针行为
- 8)前置操作符(++i)与后置操作符(i++)重载,"--"操作符同理
- 9)逻辑操作符无法重载(重载后无法保持短路法则,违反语义原则)
- 10)工程中不要重载逗号操作符(重载后无法从左向右计算表达式,违反语义原则)
1)操作符重载(本质函数定义) ==>使用对象:类
c++的重载可以拓展操作符的功能
操作符的重载是以函数的方式进行
本质:用特殊形式的函数扩展操作符的功能
通过关键字operator 可以定义特殊的函数
operator 的本质:通过函数重载操作符
语法:
Type operator sign(const Type& p1, const Type& p2)
{ //sign是标准的 + -*/ ...
type ret;
return ret
}
特点:可以将操作符重载函数定义为类的成员函数,优点如下:
- 比全局操作符重载函数少一个参数(左操作数,用隐藏的this指针)
- 不需要依赖友元函数就可以完成操作符的重载
- 编译器优先在成员函数中寻找操作符重载函数
#include <stdio.h>
class Complex
{
int a;
int b;
public:
Complex(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
int getA()
{
return a;
}
int getB()
{
return b;
}
Complex operator + (const Complex& p)
{
Complex ret;
printf("Complex operator + (const Complex& p)\n");
ret.a = this->a + p.a;
ret.b = this->b + p.b;
return ret;
}
friend Complex operator + (const Complex& p1, const Complex& p2);
};
Complex operator + (const Complex& p1, const Complex& p2) //要用友元才能访问private对象
{//但这样破坏了C++的封装性
Complex ret;
printf("Complex operator + (const Complex& p1, const Complex& p2)\n");
ret.a = p1.a + p2.a;
ret.b = p1.b + p2.b;
return ret;
}
int main()
{
Complex c1(1, 2);
Complex c2(3, 4);
Complex c3 = c1 + c2; // c1.operator + (c2)这里编译器优先采用类的成员函数(操作符合重载函数)
printf("c3.a = %d, c3.b = %d\n", c3.getA(), c3.getB());
return 0;
}
操作符重载原则:
赋值操作符(=)只能重载为成员函数
操作符重载不能改变原操作符的优先等级
操作符重载不能改变操作数的个数
操作符重载不能改变操作符的原有语义
2)完善复数类:操作符重载
运算"+" "-" "* "/"
比较 "==" "!="
赋值 "=" //c++规定只能重载为成员函数
求模 "modulus"
**
1)复数的加减乘除是分别先算出实部和虚部,然后在创建对象,利用其带参构造函数
Complex ret(na, nb);
return ret;
2)复数的比较是返回 a b 的判断表达式
return (a == c.a) && (b == c.b);
return !(*this == c);
3)复数的赋值是先比较是否一样,后a b直接赋值
#ifndef _COMPLEX_H_
#define _COMPLEX_H_
class Complex
{
double a;
double b;
public:
Complex(double a = 0, double b = 0);
double getA();
double getB();
double getModulus();
Complex operator + (const Complex& c);
Complex operator - (const Complex& c);
Complex operator * (const Complex& c);
Complex operator / (const Complex& c);
bool operator == (const Complex& c);
bool operator != (const Complex& c);
Complex& operator = (const Complex& c);
};
#endif
#include "Complex.h"
#include "math.h"
Complex::Complex(double a, double b)
{
this->a = a;
this->b = b;
}
double Complex::getA()
{
return a;
}
double Complex::getB()
{
return b;
}
double Complex::getModulus()
{
return sqrt(a * a + b * b);
}
Complex Complex::operator + (const Complex& c)
{
double na = a + c.a;
double nb = b + c.b;
Complex ret(na, nb);
return ret;
}
Complex Complex::operator - (const Complex& c)
{
double na = a - c.a;
double nb = b - c.b;
Complex ret(na, nb);
return ret;
}
Complex Complex::operator * (const Complex& c)
{
double na = a * c.a - b * c.b;
double nb = a * c.b + b * c.a;
Complex ret(na, nb);
return ret;
}
Complex Complex::operator / (const Complex& c)
{
double cm = c.a * c.a + c.b * c.b;
double na = (a * c.a + b * c.b) / cm;
double nb = (b * c.a - a * c.b) / cm;
Complex ret(na, nb);
return ret;
}
bool Complex::operator == (const Complex& c)
{
return (a == c.a) && (b == c.b);
}
bool Complex::operator != (const Complex& c)
{
return !(*this == c);
}
Complex& Complex::operator = (const Complex& c)
{
if( this != &c )
{
a = c.a;
b = c.b;
}
return *this;
}
**
3)"<<" 左移操作符合重载
**
操作符合<<的原生语义是按位左移,意义:将整数1按位左移2位
重载左移操作符,将变量或常量左移到一个对象中
Console& operator << (const char* s) //输出到终端
{
printf("%s", s);
return *this;
}
#include <iostream>
#include <cmath>
using namespace std; //std命名空间里有c++标准库定义的类和对象
int main()
{
cout << "Hello world!" << endl; //这里采用了c++标准库,已经进行了左移操作符的重载了
double a = 0;
double b = 0;
cout << "Input a: ";
cin >> a;
cout << "Input b: ";
cin >> b;
double c = sqrt(a * a + b * b);
cout << "c = " << c << endl;
return 0;
}
4)数组操作符重载( [ ] )
数组访问符是C/C++的内置操作符
数字访问符的原生意义是:数组访问和指针运算:
a[n] =(a+n) ==>(n+a) ==>n[a]
要求:
只能通过类的成员函数重载
重载函数能且只能使用一个参数
可以定义不同参数的多个重载函数
class Test
{
int a[5];
public:
int& operator [] (int i) //操作符重载
{
return a[i];
}
int& operator [] (const string& s)
{
if( s == "1st" )
{
return a[0];
}
....
}
int main()
{
Test t;
...
cout << t["5th"] << endl; //a[4]
cout << t["4th"] << endl; //a[3]
}
数组操作符号重载的意义:
1)可以用英文等其他形式调用a[0]等
2)让(数组)类可以向数组一样来使用
class IntArray
{
....
public:
int& IntArray::operator [] (int index);
IntArray& self();
~IntArray();
};
Inarry.cpp
int& IntArray::operator [] (int index)
{
return m_pointer[index];
}
IntArray& IntArray::self()
{
return *this;
}
int main()
Inarry* a =Inarry::NewInstance(9);
if( a != NULL )
{
Inarry& array = a->self(); //这一步的意义? array指向这个a的数组
cout << array[i] << endl;
//main.c:35:26: error: no match for ‘operator[]’ (operand types are ‘Inarry’ and ‘int’) 因为array不是一个数组而是一个数组类,所以才需要操作符重载
cout << "array.length() = " << array.getLength() << endl;
5)函数对象–重载函数操作符号"()" (在工程中取代函数指针)
特性:
只能通过类的成员函数重载
可以定义不同参数的多个重载函数
实际案例
客户需求:
1)函数可以获得斐波拉契数列每项的值
2)每调用一次返回一个值
3)函数可根据需要重复使用
这个方案:利用静态局部变量,实现累加
缺点:
静态局部变量处于函数内部,外界无法改变
函数为全局函数,一旦开始,无法从头执行
无法制定某个具体的数列项作为初始项目
#include <iostream>
#include <string>
using namespace std;
int fib()
{
static int a0 = 0;
static int a1 = 1;
int ret = a1;
a1 = a0 + a1;
a0 = ret;
return ret;
}
int main()
{
for(int i=0; i<10; i++)
{
cout << fib() << endl; //从1到55
}
cout << endl;
{
cout << fib() << endl;
}
return 0;
}
问题优化:利用操作符号重载() ,可反复调用函数对象,并预设初值
#include <iostream>
#include <string>
using namespace std;
class Fib
{
int a0;
int a1;
public:
Fib()
{
a0 = 0;
a1 = 1;
}
Fib(int n)
{
a0 = 0;
a1 = 1;
for(int i=2; i<=n; i++)
{
int t = a1;
a1 = a0 + a1;
a0 = t;
}
}
int operator () ()
{
int ret = a1;
a1 = a0 + a1;
a0 = ret;
return ret;
}
};
int main()
{
Fib fib;
for(int i=0; i<10; i++)
{
cout << fib() << endl;
}
cout << endl;
for(int i=0; i<5; i++) //可以接着继续
{
cout << fib() << endl;
}
cout << endl;
Fib fib2(10); //可以反复调用,且能确定从哪开始
for(int i=0; i<5; i++)
{
cout << fib2() << endl;
}
return 0;
}
**
6)赋值操作符(=)重载 重看视频
**
当函数无赋值操作符重载时,编译器自动补充赋值操作符(=)重载函数,但这个只完成浅拷贝的操作,
当需要进行深拷贝时必须重载赋值操作符
赋值操作符与拷贝构造函数有相同的存在意义
原则:重载赋值操作符,必须要实现深拷贝??
为什么需要赋值操作符重载?
Test t2 = Test t1
==>由于编译器提供的赋值操作符只提供了浅拷贝的动作,,浅拷贝,t2跟t1的物理状态一样里面的m_point指向了同一块地址,
赋值操作符函数编写原则:
原则1: 返回类型是引用,为了能够连续赋值
原则2: 参数是const Test&
原则3:判断这个不是自赋值(i=i),用this指针跟参数的地址比较,自赋值是没有意义的,只是为了兼容C语言
原则4:将当前对象返回
Test& operator = (const Test& obj)
{
if( this != &obj )
{
delete m_pointer; //这里删除原本的空间
m_pointer = new int(*obj.m_pointer); //m_point,重新申请一个空间,空间的内容是对象的内容
}
return *this;
}
**
6.1)实际例子:赋值操作浅拷贝导致系统奔溃
**
/*由于编译器提供的赋值操作符只提供了浅拷贝的动作,
浅拷贝,t2跟t1的物理状态一样里面的m_point指向了同一块地址,
main函数执行完,销毁对象,Test2释放了内存后,Test1想再次释放同一块内存,于是系统崩溃
*/
int main()
{
Test t1 = 1;
Test t2;
/*当只进行浅拷贝(无自定义拷贝构造函数/赋值操作符时,浅拷贝,t2跟t1的物理状态一样)
里面的m_point指向了同一块地址
*/
t2 = t1; //t2 的m_point 从null 变成了跟 t1一样指向相同的内存空间
t1.print(); //m_pointer = 0x202ac20
t2.print(); //m_pointer = 0x202ac40
return 0;
}
test.cpp
class Test
{
int* m_pointer;
public:
Test()
{
m_pointer = NULL;
}
Test(int i)
{
m_pointer = new int(i);
}
/*如果没有这两个,编译器会自动补充拷贝构造函数和赋值操作符重载函数,
赋值操作符只进行浅拷贝动作,由于类里面的成员是m_point,他会使用到系统的堆内存 ,
浅拷贝动作带来的是对象的物理状态保存一致,即都指向了同一块内存空间(指向的地址一样)
*/
Test(const Test& obj)
{
m_pointer = new int(*obj.m_pointer);
}
Test& operator = (const Test& obj) 原则1: 返回类型是引用,为了能够连续赋值
{ 原则2: 参数是const Test& (const 引用类型)
if( this != &obj ) 原则3:判断是否不等于自身,用this指针跟参数的地址比较
{
delete m_pointer; //这里删除原本的空间
m_pointer = new int(*obj.m_pointer); //m_point,重新申请一个空间,空间的内容是对象的内容
}
return *this; 原则4:返回 *this ,引用值
}
===========================================
void print()
{
cout << "m_pointer = " << hex << m_pointer << endl;
}
~Test()
{
delete m_pointer;
}
7)智能指针(本质:对象)-->操作符重载(->和*),利用一个对象来代替指针行为
解决问题:内存泄漏
- 动态申请堆空间,用完不还 -->长时间变卡,或出bug
- Java C#引入垃圾回收机制 ==> C++没有垃圾回收机制
- 指针无法控制所指堆空间的生命周期
==>引入智能指针(本质,动态申请没有归还)
智能指针需要解决的问题:
- 在指针生命周期结束时主动释放堆空间 ==>类对象里的析构函数释放指针
- 一片堆空间**最多只能有一个指针标识
**=>避免多次释放==>如果两个指针指向同一片堆空间,有可能造成重复释放的bug
==>拷贝构造函数重载和赋值符合函数重载
/*拷贝构造函数避免多个指针指向同一个内存的思路:
1)释放类本身所指向的指针
2)指针指向新对象的内存
3)赋值对象的指针指向为空
*/
3)杜绝指针运算和指针比较 ==> 避免野指针,指针越界
==>对象本来就不行,除非再重载 "++ " "-- " “==” “!=”
智能指针方案:用一个对象模拟指针的行为
0)需要一个特殊指针 —>对象模拟指针(外号:智能指针)
1)重载指针操作符 “->” 和 "*"
2)只能通过类的成员函数重载
3)重载函数不能使用参数 ==>只能定义一个重载函数
class Pointer //先建一个类,才能有对象
{
Test* mp; //加入模板技术,可以使得point类指向各种类型
public:
Pointer(Test* p = NULL) //被堆空间上面的内存地址初始化,先给默认值,默认值为空
{
mp = p;
}
Pointer(const Pointer& obj)
{
/*拷贝构造函数避免多个指针指向同一个内存的思路:
1)释放类本身所指向的指针
2)指针指向新对象的内存
3)赋值对象的指针指向为空
*/
//delete mp; 这里本来应该像赋值操作符重载一样有这个,但由于main函数里的p2并没
mp = obj.mp;
const_cast<Pointer&>(obj).mp = NULL; //由于obj是connst(只读)属性,所以需要解引用
}
Pointer& operator = (const Pointer& obj) //由于采用了mp指针,深拷贝
{
if( this != &obj )
{
delete mp;
mp = obj.mp;
const_cast<Pointer&>(obj).mp = NULL;
}
return *this;
}
Test* operator -> () //无参,只能定义一个
{
return mp; //返回 mp这个成员指针
}
Test& operator * () // *的意义==>返回当前的变量或对象
{
return *mp; //返回这个对象
}
bool isNull() //判断当前指针是否为空
{
return (mp == NULL);
}
~Pointer() //析构函数,释放mp指针 //满足第一个需求:在指针生命周期结束时**主动释放堆空间**
{
delete mp;
}
};
int main()
{
Pointer p1 = new Test(0); //这里创建一个对象(智能指针)
cout << p1->value() << endl; //用对象模拟指针
Pointer p2 = p1;
cout << p1.isNull() << endl;
cout << p2->value() << endl;
return 0;
}
8)前置操作符(++i)与后置操作符(i++)重载,"–"操作符同理
编译器优化后 ++i 跟i++的语义是一样的
问题: 前置操作符(++i)与后置操作符(i++)可以重载吗 ?怎么区分前置还是后置?
==>全局函数和成员函数均可以重载 =>推荐用成员函数
==>前置++不需要参数, 后置++要一个占位参数表示
(前置++需要返回引用,因为重载自加运算符后可以返回对象的引用, 以方便在表达式中连续使用。而后置++返回的不是引用,所以不能进行连续使用。)
i++与++i有区别吗?
===>对于基本类型(int)的变量,前置++跟后置++效率一样(编译器会进行优化)
==>对于类类型的对象,前置++的效率高(后置++需要调用构造函数,析构函数,而且)
class Test
{
int mValue;
public:
Test& operator ++ () //++i ,加一之后,要返回自身 this指针指向的对象
{
++mValue;
return *this;
}
Test operator ++ (int) //i++,返回自身,后++
{
Test ret(mValue); //将当前对象的值保存下来
mValue++;
return ret; //返回保存的当前对象
}
9)逻辑操作符无法重载(重载后无法保持短路法则,违反语义原则)
工程开发不要重载逻辑操作符
通过重载比较操作符替换逻辑操作符重载
通过专用成员函数替换逻辑操作符重载
10)工程中不要重载逗号操作符(重载后无法从左向右计算表达式,违反语义原则)
重载:
进入函数体前必须完成所有参数的计算,但函数参数的计算次序是不定的