#include <iostream>
using namespace std;
class Test
{
public:
Test(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
public:
int a;
int b;
public:
//全局函數
Test T_add(Test &t1, Test &t2)
{
Test t3;
t3.a = t1.a + t2.a;
t3.b = t1.b + t2.b;
return t3;
}
Test& add(Test &t2) //面向對象程式設計
{
a = a + t2.a;
b = b + t2.b;
return *this;
}
};
//全局函數
Test T_add(Test &t1, Test &t2)
{
Test t3;
t3.a = t1.a + t2.a;
t3.b = t1.b + t2.b;
return t3;
}
Test T_add01(Test *pthis, Test &t1, Test &t2)
{
Test t3;
t3.a = t1.a + t2.a;
t3.b = t1.b + t2.b;
return t3;
}
//從成員函數轉換為全局函數,隻需要加一個this指針(一個指向本類的指針)
//從全局函數轉換為類的成員函數,隻需要減去一個左操作數
void Demo01()
{
Test t1(2, 3), t2(3, 4);
Test t3 = T_add(t1, t2);
t3 = t1.T_add(t1, t2);
}
void main()
{
system("pause");
}
從成員函數轉換為全局函數,隻需要加一個this指針(一個指向本類的指針),而從從全局函數轉換為類的成員函數,隻需要減去一個左操作數即可,正如上面的代碼所示。
下面是關于友元函數的一個例子:
#include <iostream>
using namespace std;
//const c 莫安排活
//register cpu身邊的小太監
//typedef 混号王
class Test
{
public:
Test(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
int getA()
{
return a;
}
int getB()
{
return b;
}
//友元函數的特點是有一個參數肯定是友元類的指針或者引用
friend int OpMen(Test *p, int a); //函數聲明放在哪裡都一樣
private:
int a;
int b;
};
int OpMen(Test *p, int a) //Test類的好朋友,友元函數
{
p->a = a;
return 0;
}
void main()
{
Test t1;
OpMen(&t1, 5);
system("pause");
}
友元函數并不是類的成員函數,它是一個全局函數,隻需要在類裡面聲明一下就行了,放在public,private,protect任意一個域下都,沒關系,使用友元函數唯一的好處是可以在函數裡通路類的私有資料成員,就好像類打開了一扇窗一樣,友元函數一般用的不多(C+++設計的敗筆),可是有時候會用,了解一下即可。
運算符重載練習
#include <iostream>
using namespace std;
class Complex
{
public:
int a;
int b;
public:
Complex(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
void print()
{
printf("%d+%di\n", a, b);
}
};
Complex add(Complex &lop, Complex &rop)
{
Complex result;
result.a = lop.a + rop.a;
result.b = lop.b + rop.b;
return result;
}
Complex operator+(Complex &lop, Complex &rop)//重載
{
Complex result;
result.a = lop.a + rop.a;
result.b = lop.b + rop.b;
return result;
}
void Demo01()
{
Complex c1(1, 2), c2(3, 4);
//c1 = c1 + c2; //編譯器壓根不知道怎麼加,但是編譯器會給你提供一種機制,會讓你實作自定義加操作
Complex c3 = add(c1, c2); //直接調用函數add
}
void Demo02()
{
Complex c1(1, 2), c2(3, 4);
//操作符重載首先是通過函數實作的
//+操作符有兩個參數,左操作數和右操作數,特别留意左操作數
Complex c3 = c1 + c2; //實作了重載
//Complex c3 = operator+(c1, c2);//這樣寫也不會錯
}
void main()
{
Demo02();
system("pause");
}
一般的類變量是設定成private屬性的,是以我們一般都用友元函數實作運算符重載。
#include <iostream>
using namespace std;
class Complex
{
private:
int a;
int b;
public:
Complex(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
//通過友元函數實作操作符重載
friend Complex operator+(Complex &lop, Complex &rop);
friend Complex &operator++(Complex &lop);
friend Complex operator++(Complex &lop, int/*占位符*/);
//通過類的成員函數實作減号操作
Complex operator-(Complex &rop) //左操作數隐藏在this指針裡面
{
Complex res;
res.a = a - rop.a;
res.a = b - rop.b;
return res;
}
Complex &operator--()
{
a--;
b--;
return *this;
}
Complex operator--(int) //後置--的成員函數實作
{
Complex tmp = *this;
this->a--;
this->b--;
return tmp;
}
void print()
{
printf("%d + %di\n", a, b);
}
};
Complex operator++(Complex &lop, int/*占位符*/)
{
Complex tmp = lop;
lop.a++;
lop.b++;
return tmp;
}
//全局函數原型推導
Complex &operator++(Complex &lop)
{
lop.a++;
lop.b++;
return lop;
}
Complex operator+(Complex &lop, Complex &rop)
{
Complex result;
result.a = lop.a + rop.a;
result.b = lop.b + rop.b;
return result;
}
void Demo01()
{
Complex c1(1, 2), c2(3, 4);
//c1 = c1 + c2; //編譯器壓根不知道怎麼加,但是編譯器會給你提供一種機制,會讓你實作自定義加
Complex c4 = c1 - c2;
Complex c5 = c1.operator-(c2);
//目标,通過類的成員函數完成操作符重載
//1.要承認操作符操作是一個函數,要寫函數原型
//2.寫出函數調用語言
//3.完善函數原型
}
void Demo02()
{
Complex c1(1, 2), c2(3, 4);
//Complex c3 = operator+(c1, c2);
//操作符重載首先是通過函數實作的
//+操作符有兩個參數,左操作數和右操作數,特别留意左操作數
Complex c3 = c1 + c2;
}
void Demo03()
{
Complex c1(1, 2), c2(3, 4);
c1++;
c1.print();
--c2;
c2++;
//先使用c2的屬性,然後讓屬性+++
//operator++(c2, int/*占位符*/);//後置++
c2.print();
//成員函數實作後置--
}
void main()
{
Demo03();
system("pause");
}
使用友元函數和類的成員函數都可以實作操作符重載,正如同前面所提到的,成員函數由于已經包含了this指針,是以省略了一個操作數,其實兩者差别不大。不過也存在差别,成員函數和友元函數重載操作符各有用處。規範的寫法是,一般可以用成員函數實作的操作符重載就不用友元函數,是以使用友元函數重載的地方就很少了,一處場景如下下段代碼所示。
關于前置++和後置++需要注意幾點:
前置++的版本一般是這麼寫:foo&operator++()//前置++,前置傳回引用
後置++一般這麼寫:foooperator++(int)//後置++,後置傳回對象
前置傳回引用,後置傳回對象,這是通用做法。
class foo
{
public:
foo operator++() //後置++,後置傳回對象
{
a++;
return *this;
}
foo& operator++(int) //前置++,前置傳回引用,int占位符,隻是标記,将前置和後置區分開來
{
a++;
return *this;
}
public:
foo(int a = 0)
{
this->a = a;
}
private:
int a;
};
在實際中,我們一般不用友元函數重載運算符,一般都用成員函數,但是使用友元函數重載運算符也有用武之地,下面是規範的寫法:
#include <iostream>
using namespace std;
class Complex
{
private:
int a;
int b;
public:
Complex(int a = 0, int b = 0)
{
this->a = a;
this->b = b;
}
//通過類的成員函數實作減号操作
Complex operator-(Complex &rop) //左操作數隐藏在this指針裡面
{
Complex res;
res.a = a - rop.a;
res.a = b - rop.b;
return res;
}
Complex operator+(Complex &rop) //左操作數隐藏在this指針裡面
{
Complex res;
res.a = a + rop.a;
res.a = b + rop.b;
return res;
}
Complex &operator++()
{
a++;
b++;
return *this;
}
Complex operator++(int) //後置--的成員函數實作
{
Complex tmp = *this;
this->a++;
this->b++;
return tmp;
}
Complex &operator--()
{
a--;
b--;
return *this;
}
Complex operator--(int) //後置--的成員函數實作
{
Complex tmp = *this;
this->a--;
this->b--;
return tmp;
}
void print()
{
printf("%d + %di\n", a, b);
}
friend ostream& operator<<(ostream &out, Complex &op); //這裡才是friend函數的真正用武之地
protected:
private:
};
ostream& operator<<(ostream &out, Complex &op)
{
out << op.a << " " << op.b << endl;
return out;
}
void Demo01()
{
Complex c1(1, 2), c2(3, 4);
int a = 10;
char *p = "abc";
cout << p << endl;
//沒有辦法在cout類裡面添加函數operator<<隻能通過全局函數實作
cout << c1;
cout << c1 << endl << "鍊式程式設計測試" << endl;
//若void operator<<(ostream &out, Complex &op);
//operator<<(cout, &c1);
//void << endl;
//函數傳回值當左值的時候,需要傳回一個對象的引用
}
void main()
{
Demo01();
system("pause");
}
特别需要注意的地方是,如果ostream&operator<<(ostream&out,Complex&op)寫成了voidoperator<<(ostream&out,Complex&op),雖然執行cout<<c1;不會出錯,但是執行cout<<c1<<endl<<"鍊式程式設計測試"<<endl;會出錯,這是因為operator=的傳回值的緣故,cout<<c1相當于執行operator<<(cout,c1),傳回void,接下來執行void <<endl,不錯才怪。
同時,上面的ostream&operator<<(ostream&out,Complex&op)是友元函數運用的絕好時期,因為如果要用成員函數實作<<運算符重載,那麼必須在cout裡面實作,cout并沒有在成員函數裡實作operator<<(Complex&op),當然它也不可能實作,怎麼辦呢?恰好運用友元函數可以解決這個問題。
接下來是運算符重載的一個很好的例子:
/*Array.h*/
#ifndef _ARRAY_H_
#define _ARRAY_H_
#include <iostream>
class Array
{
private:
int mLength;
int* mSpace;
public:
Array(int length);
Array(const Array& obj);
int length();
void setData(int index, int value);
int getData(int index);
~Array();
public:
int& operator[](int index);
Array& operator=(Array &rop);
bool operator==(Array &rop);
bool operator!=(Array &rop);
};
//[] = == !=
//
#endif
/*Array.cpp*/
#include "iostream"
#include "Array.h"
using namespace std;
//差別 如果不加引用會構造一個匿名對象(臨時對象)
//開銷會非常大
Array::Array(int length)
{
if (length < 0)
{
length = 0;
}
mLength = length;
mSpace = new int[mLength];
}
Array::Array(const Array& obj)
{
mLength = obj.mLength;
mSpace = new int[mLength];
for (int i = 0; i<mLength; i++)
{
mSpace[i] = obj.mSpace[i];
}
}
int Array::length()
{
return mLength;
}
void Array::setData(int index, int value)
{
mSpace[index] = value;
}
int Array::getData(int index)
{
return mSpace[index];
}
Array::~Array()
{
mLength = -1;
delete[] mSpace;
}
//需要當左值,傳回引用
//傳回引用是要注意生命周期
/************************************************************************/
/*
Complex &operator-(operator &rop)
{
Complex tmp;
tmp.a = this.a - rop.a;
tmp.b = this.b - rop.b;
return tmp; //這樣傳回會出問題
return rop;//這樣傳回沒問題
}
*/
/************************************************************************/
//printf("%d", a[i]);
//a[i] = 10;
int& Array::operator[](int index) //函數傳回值當左值 a[i] = 10;
{
if (index > mLength)
{
}
return mSpace[index];
}
//測試用例
//a2 = a3;
//a1 = a2 = a3;連等操作 =從右向左 先執行a2 = a3傳回 a2再執行 a1 = a2;
//a1.operator=(a2.operator=(a3)))
//函數傳回值當左值
Array& Array::operator=(Array &rop)
{
if (mSpace != NULL)
{
delete[]mSpace;
mLength = 0;
mSpace = NULL;
}
mSpace = new int[rop.mLength];
mLength = rop.length();
for (int i = 0; i < rop.mLength; ++i)
{
mSpace[i] = rop.getData(i);
}
return *this;
}
bool Array::operator==(Array &rop)
{//先判斷長度是否相等,在判斷資料是否相等
if (rop.length() != mLength)
{
return false;
}
for (int i = 0; i < mLength; ++i)
{
if (mSpace[i] != rop.getData(i))
{
return false;
}
}
return true;
}
bool Array::operator!=(Array &rop)
{
return !this->operator==(rop);
}
要特别注意運算符重載的傳回值,就像上面示範的代碼一樣,int&Array::operator[](intindex) 之是以傳回int&,是因為它的性質所決定的,int a = array[5];[]的傳回值可以做右邊的操作數,這時候傳回int是沒問題的,不過array[5] = 10;這一句[]的傳回值要做左值,就不能傳回int了,因為傳回的int無法修改,是以要傳回int&。
同時也要注意傳回類還是類的引用,傳回類的時候,如
Complexoperator--(int)//後置--的成員函數實作
{
Complextmp=*this;
this->a--;
this->b--;
return tmp;
}
在主函數裡假設這麼調用Complex t2= t1--;
開銷是比較大的,傳回tmp時c++會建立一個臨時對象,調用拷貝構造函數用tmp初始化該臨時對象,此時C++做了優化,會将t2和該臨時對象綁定起來。
若這麼調用Complex t2; t2 = t1--;,這時候開支更大了,傳回tmp時c++會建立一個臨時對象,調用拷貝構造函數用tmp初始化該臨時對象,将該臨時對象賦給t2,調用t2的=操作,調用該操作之後,該臨時對象會立馬析構掉。
而傳回引用時省略了臨時變量,是以你要考量值不值。
并非所有時候都能傳回引用,如如上面的代碼,如果傳回tmp的引用,這是肯定不行的,因為tmp的生命期隻在operator—函數裡,傳回了引用是要出問題的。當然生命期長的,如在堆上配置設定的,調用函數傳過來的,傳回它們的引用自然沒問題。