概念: 運運算元過載是具有特殊函數名的函數,也具有其返回值型別,函數名字以及參數列,其返回值型別與參數列與普通的函數類似。
函數原型:
返回值 operator操作符(參數列)
注意:
注意以下幾點:
當我們定義過載的運運算元時,必須首先決定是將其宣告為類的成員函數還是宣告為一個普通的非成員函數。在某些時候我們別無選擇,因為有的運運算元必須作為成員;另一些情況下,運運算元作為普通函數比作為成員更好。
下面的準則有利於我們在運運算元定義為成員函數還是普通的非成員函數做出抉擇:
我們可以過載賦值運運算元,無論形參的型別是什麼,賦值運運算元都必須定義為成員函數
賦值運運算元,賦值之後,左側運算物件和右側運算物件的值相等,並且運算應該返回它左側運算物件的一個參照
特性:
Date& operator=(const Date& d)
{
// 檢測是否自己給自己賦值
if (this == &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
範例:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> //引入標頭檔案
#include<string>//C++中的字串
using namespace std; //標準名稱空間
class Maker
{
public:
Maker()
{
id = 0;
age = 0;
}
Maker(int id, int age)
{
this->id = id;
this->age = age;
}
public:
int id;
int age;
};
void test01()
{
Maker m1(10, 20);
Maker m2;
m2 = m1;
//預設的賦值運運算元過載函數進行了簡單的賦值操作
//就類似於位元組序的淺拷貝
cout << m2.age << m2.id << endl;
}
class Student
{
public:
Student(const char * name)
{
pName = new char[strlen(name)+1];
strcpy(pName, name);
}
//防止淺拷貝
Student(const Student& stu)
{
pName = new char[strlen(stu.pName) + 1];
strcpy(pName, stu.pName);
}
//重寫賦值運運算元過載函數
//為什麼要返回參照
Student& operator =(const Student& stu)//第一個引數預設是this指標
{
//1.不能確定this指向的空間是否能裝下stu中的資料,所以先釋放this指向的空間
if (this->pName!= NULL)
{
delete[] this->pName;
this->pName = NULL;
}
//2.釋放了之後再來申請堆區空間,大小由stu決定
this->pName = new char[(strlen(stu.pName) + 1)];
//3.拷貝函數
strcpy(this->pName, stu.pName);
//返回物件本身
return *this;//this中存放的是物件的地址,對地址取*表示物件本身
}
void printfStudent()
{
cout << this->pName << endl;
}
~Student()
{
delete[] pName;
pName = NULL;
}
public:
char* pName ;
};
void test02()
{
Student s1("悟空");
Student s2("唐僧");
Student s3("八戒");
s1 = s2 = s3;
s1.printfStudent();
s2.printfStudent();
s3.printfStudent();
cout << &(s2 = s3) << endl;
cout << &s2 << endl;
}
int main()
{
test01();
cout << "-------------------------------" << endl;
test02();
system("pause");
return EXIT_SUCCESS;
}
執行結果如下:
在程式碼中留下了一個問題,為什麼過載的賦值操作運運算元要返回左側物件的參照呢?
從s1 = s2 = s3開始說,賦值運運算元本來的寓意是s3賦值s2,s2賦值s1,也就是說s2=s3這個表示式要返回原來s2的物件,如果不是參照那麼s2將會生成一個新的物件,所以要返回參照
本質:Student &operator =(const Student& stu)這個函數返回的是return *this,如果加上參照Student & = *this;實際上就是this空間重新取了個名字
但是如果不是參照,那麼就是Student = *this,會重新生成一個物件。就比如 int &a = b和int a = b的區別,第一種返回的還是原來的b,第二個返回的就是新的a
一般來說,我們把算數和關係運算子定義成非成員函數以允許對左側或者右側的運算物件進行轉換。因為這些運運算元一般不需要改變運算物件的狀態,所以形參都是常數的參照。
下面的程式碼從成員函數和非成員函數都介紹算術運運算元的過載
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> //引入標頭檔案
#include<string>//C++中的字串
using namespace std; //標準名稱空間
class Maker
{
public:
Maker(int id, int age)
{
this->age = age;
this->id = id;
}
//1.成員函數,就需要一個引數,這個引數就是加號的右邊
Maker operator+(const Maker& m2)
{
//加號左邊變成this
Maker temp(this->id + m2.id, this->age + m2.age);
return temp;//返回的時候呼叫拷貝構造
}
public:
int id;
int age;
};
//2.全域性的方式
//編譯器會呼叫這個函數,編譯器會檢查引數是否對應
Maker operator+(Maker& m1, Maker &m2)
{
Maker temp(m1.id + m2.id, m1.age + m2.age);//呼叫有參構造
return temp;//返回的時候會呼叫拷貝構造
}
void test()
{
Maker m1(1, 20);
Maker m2(2, 22);
//m1+m2顯然是不可以的,要過載運運算元
Maker m3 = m1 + m2;//編譯器看到兩個物件相加,編譯器會去找有沒有operator+函數
cout << m3.id << endl;
cout << m3.age << endl;
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
執行結果如下:
bool operator == (Maker &m)
{
if(this->id == m.id && this->age == m.age){
return true;
}
return false;
}
輸出 << 運運算元 的過載
輸出運運算元應該主要負責列印物件的內容而非控制格式,輸出運運算元不應該列印換行符
輸出運運算元的第一個形參是一個非常數ostream物件的參照,之所以ostream是個非常數是因為向流寫入內容會改變其狀態;而該形參是參照是因為我們無法直接複製一個ostream物件
第二個形參一般來說是一個常數的參照,該常數就是我們想要列印的類型別。第二個形參是參照是因為我們希望避免複製實參;而之所以該形參可以是常數是因為通常情況下列印物件不會改變物件的內容
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> //引入標頭檔案
#include<string>//C++中的字串
using namespace std; //標準名稱空間
class Maker
{
//友元函數
friend ostream& operator <<(ostream& out, Maker& m);
public:
Maker(int id, string name)
{
this->id = id;
this->name = name;
}
private:
int id;
string name;
};
//1.形參和實參是一個物件,因為用的是參照,只是對同一塊空間取別名
//2.對 << 運運算元進行過載,左邊是ostream物件,右邊是Maker物件
//3.一定要用參照,因為ostream把拷貝建構函式私有化了
//4.如果要和endl一起用,那麼就必須返回ostream
ostream& operator << (ostream& out, Maker& m)
{
cout << m.id << " " << m.name << endl;
return out;
}
void test01()
{
Maker m(10, "小花");
cout << m;
cout << endl;
/*
endl是一個函數
operator << (函數指標)
operator(endl)把endl函數傳進來
*/
cout << 10;//在內部過載了基礎資料型別
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
輸入 >> 運運算元的過載
輸入運運算元必須處理輸入可能失敗的情況,而輸出運運算元不需要
輸入運運算元的第一個形參是運運算元想要讀取的流的參照,第二個形參是將要讀入到的(非常數)物件的參照。該運運算元通常會返回某個給定流的參照。第二個形參之所以必須是個非常數是因為輸入運運算元本身的目的就是將資料讀入到這個物件中
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> //引入標頭檔案
#include<string>//C++中的字串
using namespace std; //標準名稱空間
void test01()
{
int a;
cin >> a;//從鍵盤中獲取資料
cout << a << endl;
}
class Maker
{
//存取私有成員要設定為友元函數
friend istream& operator >>(istream& in, Maker& m);
public:
Maker(string name, int age)
{
this->name = name;
this->age = age;
}
int getAge()
{
return age;
}
private:
string name;
int age;
};
//過載>>右移運運算元
//同一個物件取別名
istream& operator >>(istream& in, Maker& m)
{
in >> m.age;
in >> m.name;
return in;
}
void test02()
{
Maker m("悟空", 15);
Maker m2("悟空2", 25);
cin >> m >> m2;
cout << m.getAge() << endl;
cout << m2.getAge() << endl;
}
int main()
{
test02();
system("pause");
return EXIT_SUCCESS;
}
前置++和後置++最大的區別就是返回值不同,前置是返回變化之後的值,後置是返回變化之前的值,兩個在過載是,都是operator++,我們如何區分呢?
一般operator++預設是前置++,為了區分後置++,我們通常會在參數列加一個佔位引數,且這個引數必須是int型別的,從而構造成函數過載。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> //引入標頭檔案
#include<string>//C++中的字串
using namespace std; //標準名稱空間
class Person
{
public:
Person(string name, int age)
{
this->_name = name;
this->_age = age;
}
// 前置++
Person& operator++()
{
//傳入一個this指標
this->_age++;
return *this;// 返回變化之後的值,傳參照
}
// 後置++,佔位引數(必須是int)
Person operator++(int)
{
//後置++,先返回,後++
Person ret = *this;//呼叫拷貝構造,拷貝this的一個臨時備份,用於返回
this->_age++;//this也就是傳入的物件中的_age值會改變
return ret;// 返回變化之前的值,傳值,返回的時候也呼叫的是拷貝構造
}
void Print()
{
cout << _name << "-" << _age << endl;
}
private:
string _name;
int _age;
};
int main()
{
Person p("wxj", 19);
p.Print();
cout << "前置++" << endl;
// 前置++
Person ret = ++p;
ret.Print();
p.Print();
cout << "後置++" << endl;
// 後置++
ret = p++;
ret.Print();
p.Print();
return 0;
}
執行結果如下:
值得注意的是成員函數的操作運運算元左邊第一個引數是Date* const this一個this指標,所以只要對this指標進行操作,並且返回的是參照,那麼依舊是同一個物件的值發生改變並且裡面的值也會發生改變。
表示容器的類通常可以通過元素在容器中的位置存取元素,這些類一般會定義下標運運算元operator []
如果一個類包含下標運運算元,那麼它通常會定義兩個版本:一個返回普通參照,另一個是類的常數成員並且返回常數參照
返回值型別 & operator[ ] (引數);
const 返回值型別 & operator[ ] (引數) const;
使用第一種宣告方式,[ ]
不僅可以存取元素,還可以修改元素。使用第二種宣告方式,[ ]
只能存取而不能修改元素。在實際開發中,我們應該同時提供以上兩種形式,這樣做是為了適應 const 物件,因為通過 const 物件只能呼叫 const 成員函數,如果不提供第二種形式,那麼將無法存取 const 物件的任何元素。
#include <iostream>
using namespace std;
class Array{
public:
Array(int length = 0);
~Array();
public:
int & operator[](int i);
const int & operator[](int i) const;
public:
int length() const { return m_length; }
void display() const;
private:
int m_length; //陣列長度
int *m_p; //指向陣列記憶體的指標
};
Array::Array(int length): m_length(length){
if(length == 0){
m_p = NULL;
}else{
m_p = new int[length];
}
}
Array::~Array(){
delete[] m_p;
}
int& Array::operator[](int i){
return m_p[i];
}
const int & Array::operator[](int i) const{
return m_p[i];
}
void Array::display() const{
for(int i = 0; i < m_length; i++){
if(i == m_length - 1){
cout<<m_p[i]<<endl;
}else{
cout<<m_p[i]<<", ";
}
}
}
int main(){
int n;
cin>>n;
Array A(n);
for(int i = 0, len = A.length(); i < len; i++){
A[i] = i * 5;
}
A.display();
const Array B(n);
cout<<B[n-1]<<endl; //存取最後一個元素
return 0;
}
需要說明的是,B 是 const 物件,如果 Array 類沒有提供 const 版本的operator[ ]
,那麼編譯器就會報錯。雖然只是讀取物件的資料,並沒有試圖修改物件,但是它呼叫了非 const 版本的operator[ ]
,編譯器不管實際上有沒有修改物件,只要是呼叫了非 const 的成員函數,編譯器就認為會修改物件(至少有這種風險)。
在.h檔案中將陣列類的功能和成員進行宣告
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> //引入標頭檔案
#include<string>//C++中的字串
using namespace std; //標準名稱空間
class MyArray
{
public:
MyArray();
//拷貝函數,防止淺拷貝,因為有pArray需要開闢堆區空間
MyArray(const MyArray& arr);
MyArray(int capacity, int val = 0);//有參構造
//重寫賦值運運算元過載函數
MyArray& operator = (const MyArray& m);
//要能當左右值
int& operator[](int dex);
~MyArray();
//頭插法
void PushFront(int val);
//尾插
void PushBack(int val);
//頭刪
void PopFront();
//尾刪
void PopBack();
//獲取陣列元素個數
int Size();
//獲取陣列容量
int Capacity();
//指定位置插入元素
void Insert(int pos, int val);
//獲取指定位置的值
int& Get(int pos);
//在指定位置修改
void Set(int pos, int val);
private:
int* pArray;//指向堆區空間,儲存資料
int mSize;//元素個數
int mCapacity;//容量
};
在.c檔案中對宣告的函數進行封裝,並進行測試
#include "MyArray.h"
//建構函式
MyArray::MyArray()
{
this->mCapacity = 20;
this->mSize = 0;
this->pArray = new int[this->mCapacity];
for (int i = 0; i < this->mCapacity; i++)
{
this->pArray[i] = 0;
}
}
//解構函式
MyArray::~MyArray()
{
if (this->pArray != NULL)
{
delete[] this->pArray;
this->pArray = NULL;
}
}
//拷貝建構函式
MyArray::MyArray(const MyArray& arr)
{
this->mCapacity = arr.mCapacity;
this->mSize = arr.mSize;
//申請空間
this->pArray = new int[arr.mCapacity];
//拷貝資料
for (int i = 0; i < this->mSize; i++)
{
this->pArray[i] = arr.pArray[i];
}
}
MyArray::MyArray(int capacity, int val)
{
this->mCapacity = capacity;
this->mSize = capacity;
this->pArray = new int[capacity];
for (int i = 0; i < this->mSize; i++)
{
this->pArray[i] = val;
}
}
//頭插法
void MyArray::PushFront(int val)
{
//判斷容量是否已經滿了
if (this->mSize == this->mCapacity)
{
return;
}
//頭插法,陣列元素全部後移
for (int j = this->mSize - 1; j >= 0; j--)
{
this->pArray[j + 1] = this->pArray[j];
}
//空出0的位置
this->pArray[0] = val;
//維護元素個數
this->mSize++;
}
//尾插
void MyArray::PushBack(int val)
{
//判斷容量是否已經滿了
if (this->mSize == this->mCapacity)
{
return;
}
//尾插法
this->pArray[this->mSize] = val;
this->mSize++;
}
//頭刪
void MyArray::PopFront()
{
//刪除第一個元素的資料,實際上就是整體前移,進行覆蓋
if (this->mSize == 0)
{
return;
}
for (int i = 0; i < mSize - 1; i++)
{
this->pArray[i] = this->pArray[i + 1];
}
this->mSize--;
}
//尾刪
void MyArray::PopBack()
{
if (this->mSize == 0)
{
return;
}
this->mSize--;
}
//獲取資料元素個數
int MyArray::Size()
{
return this->mSize;
}
//獲取陣列容量
int MyArray::Capacity()
{
return this->mCapacity;
}
//指定位置插入
void MyArray::Insert(int pos, int val)
{
//滿了就無法新增
if (this->mSize == this->mCapacity)
{
return;
}
//如果位置不對,就插入到尾部
if (pos<0 || pos>this->mCapacity)
{
pos = this->mSize;
}
for (int i = this->mSize - 1; i >= pos; i++) {
this->pArray[i + 1] = this->pArray[i];
}
this->pArray[pos] = val;
//將pos的位置空出來
this->mSize++;
}
//獲取指定位置的值
int& MyArray::Get(int pos)
{
return this->pArray[pos];
}
//在指定位置修改
void MyArray::Set(int pos, int val)
{
if (pos<0 || pos>mCapacity - 1)
{
return;
}
this->pArray[pos] = val;
}
//重寫賦值運運算元過載函數
MyArray& MyArray::operator=(const MyArray& m)
{
//1.釋放原來的空間
if (this->pArray != NULL)
{
delete[] this->pArray;
this->pArray = NULL;
}
this->mCapacity = m.mCapacity;
this->mSize = m.mSize;
//2.申請空間,大小m決定
this->pArray = new int[m.mCapacity];
//3.拷貝資料
for (int i = 0; i < this->mCapacity; i++)
{
this->pArray[i] = m.pArray[i];
}
return *this;
}
//重寫下標[]//要能當左值
int& MyArray::operator[](int i)
{
//賦值的時候++
/*
for (int i = 0; i < 20; i++)
{
arr[i] = i + 10;
}
for (int i = 0; i < 20; i++)
{
cout << arr[i] << endl;
}
*/
//this->mSize++;
if (this->mSize <= i)
{
this->mSize++;
}
return this->pArray[i];
}
void printMyArray(MyArray& arr)
{
for (int i = 0; i < arr.Size(); i++)
{
cout << arr.Get(i) << " ";
cout << endl;
}
}
void test01()
{
MyArray arr(20, 1);//呼叫有參構造
printMyArray(arr);
//修改陣列中的值
for (int i = 0; i < arr.Size(); i++)
{
arr.Get(i) = i + 100;
}
printMyArray(arr);
//指定位置修改值
arr.Set(2, 0);
printMyArray(arr);
//測試是否發生淺拷貝
MyArray arr2 = arr;
printMyArray(arr2);
}
void test02()
{
MyArray arr;
//新增元素
for (int i = 0; i < 10; i++)
{
//尾插
arr.PushBack(i + 10);
}
//頭插
for (int i = 0; i < 9; i++)
{
arr.PushFront(i + 20);
}
//指定位置插入
arr.Insert(10, 100);
//列印
printMyArray(arr);
arr.PopBack();
arr.PopFront();
printMyArray(arr);
}
int main()
{
test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
在.h檔案中將字串類的功能和成員進行宣告
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> //引入標頭檔案
#include<string>//C++中的字串
using namespace std; //標準名稱空間
//要過載的內容
/*
MyString s1;
MyString s1;
MyString s3=s1+s2;過載加號,拷貝構造
MyString s3=s1+「hello」;過載加號
s4+=s3 過載+=
s4+="hello" 過載+=
cout<<s4<<endl;過載<<
cin>>s4;過載>>
*/
class MyString
{
friend ostream& operator<<(ostream& out, MyString& str);
friend istream& operator>>(istream& in, MyString& str);
public:
MyString();
//使用者可以設定初始化字串,n個c組成的字串
MyString(int n, char c);
MyString(const MyString& str);
MyString& operator=(const MyString& str);
MyString operator+(const MyString& str);
MyString operator+(const char* s);
MyString& operator+=(const MyString& str);
MyString& operator+=(const char* s);
int Size();
char& operator[](int index);
~MyString();
private:
char* pM;//指向堆區空間
int mSize;
};
在.c檔案中對宣告的函數進行封裝,並進行測試
#include "Mystring.h"
//使用者可以設定初始化字串,n個c組成的字串
MyString::MyString()
{
this->pM = new char[1];
this->pM[0] = '\0';
this->mSize = 0;
}
//有參構造
MyString::MyString(int n, char c)
{
//多留出一個位置最後加上'\0'
this->pM = new char[n+1];
for (int i = 0; i < n; i++)
{
this->pM[i] = c;
}
this->pM[n] = '\0';
this->mSize = n;
}
//拷貝構造
MyString::MyString(const MyString& str)
{
this->pM = new char[strlen(str.pM) + 1];
strcpy(this->pM, str.pM);
this->mSize = str.mSize;
}
//賦值操作,返回原來的物件
MyString& MyString::operator=(const MyString& str)
{
//1.釋放原來的空間
delete[] this->pM;
this->pM = NULL;
//2.申請空間
this->pM = new char[strlen(str.pM) + 1];
//3.拷貝資料
strcpy(this->pM, str.pM);
this->mSize = str.mSize;
return *this;
}
MyString MyString::operator+(const MyString& str)
{
//s3=s1+s2,this是s1,str是s2
//1.獲取s2要開闢的空間大小
int newlen = this->mSize + str.mSize + 1;
//2.定義一個臨時變數
MyString tmp;
if (tmp.pM != NULL)
{
//2.釋放原來的空間
delete[] tmp.pM;
tmp.pM = NULL;
}
//3.申請新的空間
tmp.pM = new char[newlen];
memset(tmp.pM, 0, newlen);
tmp.mSize = this->mSize+str.mSize;
//4.追加字元到空間當中
strcat(tmp.pM, this->pM);
strcat(tmp.pM, str.pM);
return tmp;//返回的時候會呼叫拷貝構造
}
MyString MyString::operator+(const char *s)
{
int newlen = this->mSize + strlen(s);
//開闢空間
char* newspace = new char[newlen + 1];
memset(newspace, 0, newlen + 1);
//追加資料到空間
strcat(newspace, this->pM);
strcat(newspace, s);
MyString temp;
if (temp.pM != NULL)
{
delete[] temp.pM;
temp.pM = NULL;
}
temp.pM = newspace;
temp.mSize = newlen;
return temp;
}
MyString& MyString::operator+=(const MyString& str)
{
/*
s4+=s3;
*/
//1.獲取兩個字串的總字元個數
int newlen = this->mSize + str.mSize;
//2.申請新空間
char* newspace = new char[newlen + 1];
memset(newspace, 0, newlen + 1);
//3.追加資料
strcat(newspace, this->pM);
strcat(newspace, str.pM);
//4.釋放本身的空間
if (this->pM!= NULL)
{
this->pM = NULL;
}
this->pM = newspace;
this->mSize = newlen;
return *this;
}
MyString& MyString::operator+=(const char* s)
{
//1.獲取兩個字串的總字元個數
int newlen = this->mSize + strlen(s);
//2.申請新空間
char* newspace = new char[newlen + 1];
memset(newspace, 0, newlen + 1);
//3.追加資料
strcat(newspace, this->pM);
strcat(newspace, s);
//4.釋放本身的空間
if (this->pM != NULL)
{
this->pM = NULL;
}
this->pM = newspace;
this->mSize = newlen;
return *this;
}
int MyString::Size()
{
return this->mSize;
}
char& MyString::operator[](int index)
{
return this->pM[index];
}
MyString::~MyString()
{
if (this->pM != NULL)
{
delete[] this->pM;
pM = NULL;
}
}
//並不是類的成員函數
ostream& operator<<(ostream& out, MyString& str)
{
cout << str.pM;
return out;
}
istream& operator>>(istream& in, MyString& str)
{
//cin>>s4
//使用者輸入的字串要儲存到s4.pM指向的堆區空間
//1.定義臨時空間
char tmp[1024] = { 0 };
//2.獲取使用者輸入的資訊
in >> tmp;
//3.釋放s4的空間
if (str.pM != NULL)
{
delete[] str.pM;
str.pM = NULL;
}
//4.申請新的空間
str.pM = new char[strlen(tmp) + 1];
memset(str.pM, 0, strlen(tmp) + 1);
//5.拷貝使用者輸入的資訊到堆區空間
strcpy(str.pM, tmp);
str.mSize = strlen(tmp);
return in;
}
//測試
void test()
{
MyString s1(10, 'a');
cout << s1 << endl;
MyString s2(3, 'b');
cout << s2 << endl;
MyString s3 = s1 + s2;
cout << s3 << endl;
MyString s4 = s3 + "hello";
cout << s4 << endl;
for (int i = 0; i < s4.Size(); i++)
{
cout << s4[i] << endl;
}
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}