Motivations(为什麽要做 operation overloading) and prerequisites (基本知识)
( overloading 的情境) :
Overloading comparison and indexing operators
Overloading assignment and self-assignment operators
Overloading addition operators
Recall MyVector
class
class MyVector
{
private:
int n;
double* m;
public:
// constructors
MyVector();
MyVector(int dim, double v[]);
// copy default constructor
MyVector(const MyVector& v);
// destructor
~MyVector();
// print function
void print();
};
MyVector::MyVector()
{
n = 0;
m = nullptr;
}
MyVector::MyVector(int dim, double v[])
{
n = dim;
m = new double[dim];
for (int i = 0; i < dim; i++)
m[i] = v[i];
}
MyVector::MyVector(const MyVector& v) // called by reference
{
n = v.n;
m = new double[n];
for (int i = 0; i < n; i++)
m[i] = v.m[i];
}
MyVector::~MyVector()
{
delete[] m;
}
void MyVector::print()
{
cout << "(";
for (int i = 0; i < n - 1; i++)
cout << m[i] << ", ";
cout << m[n - 1] << ")\n";
}
可加入一些 member function:
MyVector
中的 objects (也就是向量): (当他们的 dimensions 都相同时)
在这个例子里,因为是比较两个 vector 之间的大小,所以理论上应该就是 instance function。
class MyVector
{
private:
int n;
double* m;
public:
// constructors
MyVector();
MyVector(int dim, double v[]);
// copy default constructor
MyVector(const MyVector& v);
// destructor
~MyVector();
// print function
void print();
bool isEqual(const MyVector& v); // 传reference 会比较省时 const 要记得
};
bool MyVector::isEqual(const MyVector& v)
{
if (n != v.n)
return false;
else
{
for (int i = 0; i < n; i++)
{
if (m[i] != v.m[i])
return false;
}
}
return true;
}
所以可以理解等等 function 的使用就会是 u.isEqual(v)
这样就可以比较两个 vector 了。
使用:
int main()
{
double d1[5] = { 1, 2, 3, 4, 5 };
MyVector a1(5, d1); // (1)
double d2[4] = { 1, 2, 3, 4 };
MyVector a2(4, d1); // (2)
MyVector a3(a1); // (3)
cout << (a1.isEqual(a2)? "Y" : "N"); // N
cout << "\n";
cout << (a1.isEqual(a3) ? "Y" : "N"); // Y
cout << "\n";
}
在这边,可以看到 isEqual
成功的判断了 a1, a2不同;a1, a3 相同(因为基本上就是直接 deep copy)。
虽然 isEqual
很方便,但是在比较两个变数是不是相等时,我们很常会使用 if(a1 == a2)
这样子,不但方便又快速。
但是问题是,因为 MyVector
是我们自己创造的type,电脑并不知道这个==
对於两个 MyVector
要做甚麽事。
因此 ! 我们需要对这个 ==
去 define 当他遇到两个 MyVector
时该做甚麽事(这也就叫做 overloading)。
前情提要 Restriction of Overloading :
当你建立一个 object 时,势必会占据一段 memory space。
而每个 object 都拥有他们自己的位置。
this 是一个指标,储存 object 的地址。
#include<iostream>
using namespace std;
class A
{
private:
int a;
public:
void f() { cout << this << "\n"; }
A* g() { return this; }
};
int main()
{
A obj;
cout << &obj << "\n"; // 0053FD0C
obj.f(); //0053FD0C
cout << (&obj == obj.g()) << "\n"; // 1 (true)
return 0;
}
那 this 可以拿来干嘛?
print()
我们原本写的 print()
是这样:
void MyVector::print()
{
cout << "(";
for (int i = 0; i < n - 1; i++)
cout << m[i] << ", ";
cout << m[n - 1] << ")\n";
}
如果用了 this 可以写成:
void MyVector::print()
{
cout << "(";
for (int i = 0; i < this->n - 1; i++)
cout << this->m[i] << ", ";
cout << this->m[this->n - 1] << ")\n";
}
看起来好像没差,this->
的概念,就是去那块记录的地址取出来,其实等同於(*this).n
。
说了这麽多好像还是没甚麽优点,this
的其中一个好处 :
在一个函数中,你可以同时使用名字相同的 variable & argument
没有 this 时:
MyVector::MyVector(int d, int v[])
{
n = d;
for (int i = 0; i < n; i++)
m[i] = v[i];
}
有了 this 之後:
MyVector::MyVector(int n, int m[])
{
this->n = n;
for (int i = 0; i < n; i++)
this->m[i] = m[i];
}
这样就可以让程序变得更乾净,更容易理解那个 n, m是谁,不会在多很多变数。
!!写程序好习惯!!
this->
this->
会让程序变得更乾净变数有 constants,object 也可以有:
double d[3] = {0, 0, 0};
const MyVector ORIGIN_3D(3, d);
// This object represent the original point
我们可以在初始化这个 object 的时候,对她做 const,这时候这个 object 就不能被改动了。
class MyVector
{
private:
int n;
int* m;
public:
MyVector();
MyVector(int dim, int v[]);
void print() const;
}
如果你的 function 里面,会改动 object 的值,这时候就不能使用 constant。反之,像是 print 只是把他们的值印出,并没有改动任何东西,这时候就可以在他们的 header 後面加上一个 const。
!!写程序好习惯!!
const function 只能呼叫 const function,因此要养成好习惯,如果那个 function 是 const,就在後面加上 const。
constant instance variables:
在 class 里面,instance variable 也可以指派为 const。
class MyVector
{
private:
int n;
int* m;
public:
MyVector();
MyVector(int dim, int v[]);
MyVector(const MyVector& v);
void print() const;
}
但是如果当 constructor 要对这个 variable 做事的时候,就会产生 compilation error。
MyVector::MyVector()
{
n = 0; // error
m = nullptr;
}
这个时候,我们就要使用到 member initializer。
MyVector() : n(0)
{
m = nullptr;
}
MyVector(int dim, int v[]) : n(dim)
{
for(int i = 0; i < n; i++)
m[i] = v[i];
}
MyVector(const MyVector& v) : n[v.n]
{
m = new double[n];
for(int i = 0; i < n; i++)
m[i] = v.m[i];
}
在 function 的後面,加上: n(括号里面的东西就是要 assign 给 n 的咚咚)
因此,这个 member initializer 也可以对其他的 variable 做 initialize。大概像这样:
MyVector::MyVector(): n(0), m(NULL)
{
}
instance:
class MyVector
{
private:
const int n;
double* m;
public:
// constructors
MyVector();
MyVector(int dim, double v[]);
// copy default constructor
MyVector(const MyVector& v);
// destructor
~MyVector();
// print function
void print() const;
bool isEqual(const MyVector& v) const; // 传reference 会比较省时 const 要记得
};
MyVector::MyVector(): n(0), m(NULL)
{
}
MyVector::MyVector(int n, double m[])
{
this->n = n;
this->m = new double[n];
for(int i = 0; i < n; i++)
this->m[i] = m[i];
}
MyVector::MyVector(const MyVector& v)
{
this->n = v.n;
this->m = new double[n];
for(int i = 0; i < n; i++)
this->m[i] = v.m[i];
}
MyVector::~MyVector()
{
delete [] m;
}
void MyVector::print() const
{
cout << "(";
for(int i = 0; i < n - 1; i++)
cout << m[i] << ", ";
cout << m[n-1] << ")\n";
}
An operator is overloaded by "implementing a special instance function".
要怎麽做 overload 呢? 公式是这样的:
operatorop
op 是那个 operator。
我们来 overload "==" 这个 operator 吧 !
class MyVector
{
////.......
bool operator==(const MyVector& v) const;
}
bool MyVector::operator==(const MyVector& v) const
{
if(this->n != v.n)
return false;
else
{
for(int i = 0; i < n; i++)
{
if(this->m[i] != v.m[i])
return false;
}
}
return true;
}
这时候你就可以透过 "==" 来呼叫 isEqual
,像是这样。
int main()
{
double d1[5] = { 1, 2, 3, 4, 5 };
MyVector a1(5, d1); // (1)
double d2[4] = { 1, 2, 3, 4 };
MyVector a2(4, d1); // (2)
MyVector a3(a1); // (3)
cout << (a1 == (a2)? "Y" : "N"); // N
cout << "\n";
cout << (a1 == (a3) ? "Y" : "N"); // Y
cout << "\n";
}
这个时候就可以把程序变得更简洁,更美观 & 直观!
或甚至可以这样写:
cout << (a1.operator== (a2)? "Y" : "N"); // N
cout << "\n";
cout << (a1.operator== (a3) ? "Y" : "N"); // Y
cout << "\n";
那我们就可以来如法炮制一个 operator<
的 function 了!
bool operator< (const MyVector& v) const
{
if (this->n != v.n)
return false;
else
{
for(int i = 0; i < n; i++)
{
if (this->m[i] >= v.m[i])
return false;
}
}
return true
}
或是 overloading !=
。由於 != 就是 == 的反面,也就是说我们可以利用 == 来做 != 的 overloading。
bool MyVector::operator!=(const MyVector& v) const
{
if(*this == v) // *this 就是我自己!
return false;
else
return true;
// or return !(*this == v)
}
这时候我们就可以看到 this 独特的使用地方了!
Restriction
在 overloaded 的 operator function 里面
简单说就是给程序一个 index,程序会回传 vector v
的element v_i
值或是更改他的值。
就像是 array 一样, 这边也可以使用indexing operator: []
。
int main()
{
double d1[5] = { 1, 2, 3, 4, 5 };
MyVector a1(5, d1);
cout << a1[3] << endl; // 其实 endl 也是一个object
a1[1] = 4;
return 0;
}
我们设想可以这样子使用 MyVector
作为提取他 v_i
的方式
class MyVector
{
//...
double operator[](int i) const;
};
double MyVector::operator[](int i) const
{
if (i < 0 || i >= n)
exit(1); // terminate the program (in <cstdlib>)
return m[i];
}
exit()
是个新朋友,他可以瞬间终止这个程序。(帮助我们做 i
发生在我们不预期的时候)
()里面的1,是 exit() 会回传 1 给 operating system。
回传 0 : Normal terminal
回传其他数字: DIFFERENT ERROR
如果我们写好了上面那段,准备来跑的时候会发现:
int main()
{
double d1[5] = { 1, 2, 3, 4, 5 };
MyVector a1(5, d1);
cout << a1[3] << endl; // 成功传出来
a1[1] = 4; // error!!!
return 0;
}
这是因为a1[1]
回传的是一个 value,所以当我们写a1[1] = 4;
这件事情的时候,就像是在写4 = 3
一样了 ! 当然是不行的。
所以要这样子写:
class MyVector
{
//...
double operator[](int i) const;
double& operator[](int i);
};
double MyVector::operator[](int i) const
{
if(i < 0 || i >= n)
exit(1);
return m[i];
}
double& MyVector::operator[](int i) // 回传 reference!!
{
if(i < 0 || i >= n) // same
exit(1);
return m[i];
}
我们再多做一次 overloading,这样就可以让a1[1]
的意义变成回传她的地址,也就是代表那个变数。
这两个 overloading,可以用是否是 const 来区别,如果当我们传入的值是 constant,程序就会传入有 const 的 function 里,若不是的话就会传入 non-const function 里面。
implements:
#include <iostream>
#include <cstdlib>
using namespace std;
// class definition of MyVector
class MyVector
{
friend const MyVector operator+(const MyVector& v, double d);
private:
int n;
double* m;
public:
MyVector();
MyVector(int n, double m[]);
MyVector(const MyVector& v);
~MyVector();
void print() const;
bool operator==(const MyVector& v) const;
bool operator!=(const MyVector& v) const;
bool operator<(const MyVector& v) const;
double operator[](int i) const;
double& operator[](int i);
const MyVector& operator=(const MyVector& v);
const MyVector& operator+=(const MyVector& v);
};
//instance functions
MyVector::MyVector(): n(0), m(NULL)
{
}
MyVector::MyVector(int n, double m[])
{
this->n = n;
this->m = new double[n];
for(int i = 0; i < n; i++)
this->m[i] = m[i];
}
MyVector::MyVector(const MyVector& v)
{
this->n = v.n;
this->m = new double[n];
for(int i = 0; i < n; i++)
this->m[i] = v.m[i];
}
MyVector::~MyVector()
{
delete [] m;
}
void MyVector::print() const
{
cout << "(";
for(int i = 0; i < n - 1; i++)
cout << m[i] << ", ";
cout << m[n-1] << ")\n";
}
// end of MyVector's instance functions
// MyVector's overloaded operators
bool MyVector::operator==(const MyVector& v) const
{
if(this->n != v.n)
return false;
else
{
for(int i = 0; i < n; i++)
{
if(this->m[i] != v.m[i])
return false;
}
}
return true;
}
bool MyVector::operator!=(const MyVector& v) const
{
return !(*this == v);
}
bool MyVector::operator<(const MyVector& v) const
{
if(this->n != v.n)
return false;
else
{
for(int i = 0; i < n; i++)
{
if(this->m[i] >= v.m[i])
return false;
}
}
return true;
}
double MyVector::operator[](int i) const
{
if(i < 0 || i >= n)
exit(1);
return m[i];
}
double& MyVector::operator[](int i)
{
if(i < 0 || i >= n)
exit(1);
return m[i];
}
const MyVector& MyVector::operator=(const MyVector& v)
{
if(this != &v)
{
if(this->n != v.n)
{
delete [] this->m;
this->n = v.n;
this->m = new double[this->n];
}
for(int i = 0; i < n; i++)
this->m[i] = v.m[i];
}
return *this;
}
const MyVector& MyVector::operator+=(const MyVector& v)
{
if(this->n == v.n)
{
for(int i = 0; i < n; i++)
this->m[i] += v.m[i];
}
return *this;
}
// main function
int main()
{
double d1[5] = { 1, 2, 3, 4, 5 };
MyVector a1(5, d1); // (1)
double d2[4] = { 1, 2, 3, 4 };
MyVector a2(5, d2);
const MyVector a3(a1);
a2[0] = 999;
if (a1 == a3)
cout << a2[0] << " " << a3[0];
return 0;
}
如果我们跑了这段程序,会跑出 999 1
,但是如果我们把这段程序
double& MyVector::operator[](int i)
{
if(i < 0 || i >= n)
exit(1);
return m[i];
}
删掉,就会发现在 a2[0] = 999
那一行发生了 compilation error。
这时候可以用这样的方式验证一下这段程序到底在我们的 main function 里面做了甚麽?
double& MyVector::operator[](int i)
{
COUT << "...";
if(i < 0 || i >= n)
exit(1);
return m[i];
}
就会发现,最後这段 ... 印了两次,结果像是这样:
......999 1
因此也可以证明,这两个const 与 non-const 的函式,当你要做 assignment (a2[0] = 999
)的时候,这两者是缺一不可的。
上面的 comparison 和 indexing operation ,皆不会使 calling object 改变,但是有些 operation 则会使 calling object 改变,最简单的就像是 "="。
int main()
{
double d1[3] = { 4, 8 ,7 };
double d2[4] = { 1, 2, 3, 4 };
MyVector a1(3, d1);
MyVector a2(4, d2);
a2.print();
a2 = a1; // dangerous
// syntax error if n = constant
// a2.print();
// a2[0] = 9;
// a.print();
return 0;
}
像是上面这边的程序,我们本来就不用写任何东西就可以完成 a2 = a1
这件 assignment,但是这麽做会有一点危险,若 n 为 constant 的时候,就会出现 syntax error。
如果你再写入下面那三个注解掉的三行程序,结果会显示:
(1, 2, 3, 4)
(4, 8, 7)
(9, 8, 7)
你会发现 a1 原本是 4 8 7,却因为 a2[0] 被改成 9 而变成了 9 8 7。这种情形其实就长得像我们之前遇到过的 deep copy & shallow copy。
Default assignment:
= 在预设中其实长这样:
MyVector& MyVector::operator=(const MyVector& v)
{ // default
this->n = v.n;
this->m = v.m;
}
因此,在 a1 = a2
,的时候就会发生下面这件事(红色为 assign 後发生的事情)。
原本 a1 的 n 被改成 a2 的 n (也就是 4) ,而 m 这个指标则会改成 a2 的 m,所以 a1 就会指向原本 a2 指向的那个空间了。
那接下来,我们来手动制作这个 overload operator:
const MyVector& MyVector::operator=(const MyVector& v)
{
if(this != &v) // avoid self-assignment
{
if(this->n != v.n)
{
delete [] this->m;
this->n = v.n;
this->m = new double[this->n];
}
for(int i = 0; i < n; i++) // 每个 element copy
this->m[i] = v.m[i];
}
return *this;
}
这麽一来,我们就可以解决原本指向 m 那段空间的 bug
而一开始包着大家的 if 指的就是,如果你传进来的 parameter 是你自己的话,就不做任何事情,这样就可以避免当有猪队友写 a1 = a1;
时可能会发生的问题了。
那如果有人做了这件事: a1 = a2 = a3;
(可以把他想成 a3 assign 给 a2,再把 a2 assign 给 a3)
为了避免这种情况,我们可以使用 const
来避免 a3 被 assign 给其他人。
另外,可以注意到我们回传的值是*this
,这是因为我们想要 return 的是一个 reference。
在向量里面,我们可能会想要加减他们,像是有两个向量u,v。而我们可能也会想要做一个 operation u += v
使 u_i
会变成 u_i + v_i
(for all i)。
const MyVector& MyVector::operator+=(const MyVector& v)
{
if(this->n == v.n)
{
for(int i = 0; i < n; i++)
this->m[i] += v.m[i];
}
return *this;
}
例如我们要做一个 加法 的 overloading,我们可能要做这几件事情:
const MyVector&
const MyVector
让 a1 + a2 + a3
可以运作;但是可以避免 (a1 + a2) = a3
实作:
const MyVector operator+(const MyVector& v, double d)
{
MyVector sum(*this); // creating a local variable
sum += v; // using the overloaded +=
return sum;
}
为什麽是 return 一个 object?
sum 在这个 function 里面,在函式结果的时候会被release(因为她是 local variable),所以回传 object的效果就是创立一个新的 object 储存结果,否则就会被 memory released。
且,我们甚至也可以让 + 做出不同的变化:
int main()
{
double d1[5] = { 1, 2, 3 };
MyVector a1(3, d1);
MyVector a2(3, d1);
a1 = a1 + a2;
a1.print();
a1 = a2 + 4.2;
a1.print();
return 0;
}
这时候 function 就必须写成:
class MyVector
{
//...
const MyVector operator+(double d);
const MyVector operator+(const MyVector& v, double d);
}
const MyVector operator+(const MyVector& v, double d)
{
MyVector sum(v);
for(int i = 0; i < v.n; i++)
sum[i] += d;
return sum;
}
因为加法有互换率(也就是 a+b = b+a),这时候如果我们写成:
a1 = 4.2 + a1;
就会变得很奇怪(因为在原本的 function 没有这样的定义),而且我们也不能对 double 里面的函式去做改变。
因此这时候就必须使用 global function 来做 operator overloading。
const MyVector operator+(const MyVector& v, double d)
{ // need to be friend of MyVector
MyVector sum(v);
for(int i = 0; i < v.n; i++)
sum[i] += d; // pairwise addition
return sum;
}
const MyVector operator+(double d, const MyVector& v)
{
return v + d; // using the previous definition
}
const MyVector operator+(const MyVector& v1, const MyVector& v2)
{
MyVector sum(v1);
sum += v2; // using overloaded +=
return sum;
}
这样子就可以处理 double 在前,object 在後的情况了!
这时候你就可以写出像是这种的式子:
a3 = 3 + a1 + 4 + a3;
这次说的东西,真的是完全没有想像过 !
C++ 也太酷。
>>: Day19 X Application Shell Architecture
前言 在更新Linkedkin 个人档案的时候 偶然发现他有技术检定测验 如果总成绩在前30%,会发...
铁人30天,实际在不简单,在这30天的过程,中间一度想要放弃、不知道要写什麽。 能持续产出优质文章的...
虽然现在欧洲还在work from home的期间,AWS ML Specialist Soluti...
While working on Microsoft outlook account, I am n...
今天我们来一起看看 Powershell 中的运算符。 首先,我们来一起看看,Powershell ...