1、運算符重載
什麼是重載?
所謂重載,就是重新賦予新的含義。運算符重載就是賦予運算符新的含義(新功能),其本質是一個函數。
為什麼要重載運算符?
C++預定義中的運算符的操作對象隻局限于基本的内置資料類型,但是對于我們自定義的類型是沒辦法操作的,此時就需要重載運算符來實作。
運算符函數定義的一般格式如下:
<傳回類型說明符> operator <運算符符号>(<參數表>) { <函數體> } |
運算符重載時要遵循以下規則
- 除了類屬關系運算符"."、成員指針運算符".*"、作用域運算符"::"、sizeof運算符和三目運算符"?:"以外,其他運算符都可以重載。
- 重載運算符限制在C++語言中已有的運算符範圍内的允許重載的運算符之中,不能建立新的運算符
- 運算符重載實質上是函數重載,是以編譯程式對運算符重載的選擇,遵循函數重載的選擇原則。
- 重載之後的運算符不能改變運算符的優先級和結合性,也不能改變運算符操作數的個數及文法結構。
- 運算符重載不能改變該運算符用于内部類型對象的含義。它隻能和使用者自定義類型的對象一起使用,或者用于使用者自定義類型的對象和内部類型的對象混合使用時。
- 運算符重載是針對新類型資料的實際需要對原有運算符進行的适當的改造,重載的功能應當與原有功能相類似。
- 一個運算符被重載後,原有意思沒有失去,隻是定義了相對一特定類的一個新運算符。
運算符重載的兩者方法
- 重載為類成員函數來實作;
- 重載為友元函數(全局函數)來實作;
聯系與差別
- 成員函數具有this指針,友元函數沒有this指針;
- 兩者使用方法相同;
- 兩者實作方式不同,傳遞參數不同,應用場景不同;
方法選擇
- 當無法修改左操作數的類時,使用友元函數進行重載;
- =, [], ()和->操作符隻能通過成員函數進行重載;
- <<和>>需要使用友元函數實作;
- 友元函數重載運算符常用于操作符左右資料類型不同的情況,如1+a(在成員函數中不合法),但a+1合法。
執行個體
下面通過幾個執行個體熟悉重載運算符的使用
例1:全局函數重載“+”
#include <iostream> using namespace std; class Complex{ public: Complex(int a = 0, int b = 0){ this->a = a; this->b = b; } void print(){ cout << "a:b => " << a << ":" << b << endl; } public: int a; int b; }; Complex operator+(Complex &c1, Complex &c2){ Complex tmp(c1.a + c2.a, c1.b + c2.b); return tmp; } void main(){ int a = 0, b = 0; int c = a + b; //1 基礎類型編譯器知道怎麼做 //使用者定義複雜類型需要使用者重載運算符編譯才知道怎麼做 Complex c1(1, 2), c2(3, 4); Complex c3 = c1 + c2; c3.print(); system("pause"); return; } |
例2:友元函數和成員函數重載運算符
#include <iostream> using namespace std; class Complex{ public: Complex(int a = 0, int b = 0){ this->a = a; this->b = b; } void print(){ cout << a << " : " << b << endl; } public: //成員函數實作“-”運算符重載 Complex operator-(Complex &c2){ Complex tmp(this->a - c2.a, this->b - c2.b); return tmp; } //前置-- Complex& operator--(){ this->a--; this->b--; return *this; } private: int a; int b; friend Complex operator+(Complex &c1, Complex &c2); friend Complex& operator++(Complex &c1); }; //友元函數(全局函數)“+”運算符重載 Complex operator+(Complex &c1, Complex &c2){ Complex tmp(c1.a + c2.a, c1.b + c2.b); return tmp; } //前置++ Complex& operator++(Complex &c1){ c1.a++; c1.b++; return c1; } void main() { Complex c1(1, 2), c2(3, 4); // 成員函數重載運算符 Complex c3 = c1 + c2; c3.print(); // 成員函數重載運算符 Complex c4 = c2 - c1; c4.print(); // 友元函數重載運算符 ++c1; c1.print(); // 友元函數重載運算符 --c1; c1.print(); system("pause"); return; } |
運作結果:
例2重載了單目運算符“++”和“--”,這兩個運算符有兩種使用方式,分别是前置和後置,例2隻是實作了運算符之前功能的重載,那麼運算符後置功能怎麼重載呢?這就需要借助占位符來實作了,請看例子:
例3:後置“++”和“--”運算符重載
#include <iostream> using namespace std; class Complex{ public: Complex(int a = 0, int b = 0){ this->a = a; this->b = b; } void print(){ cout << a << " : " << b << endl; } public: //後置-- Complex operator--(int){ Complex tmp = *this; this->a--; this->b--; return tmp; } private: int a; int b; friend Complex operator++(Complex &c1, int); }; //後置++ Complex operator++(Complex &c1, int){ Complex tmp = c1; c1.a++; c1.b++; return tmp; // 由于是後置++,是以需要傳回++前的對象 } void main(){ Complex c1(1, 2); // 友元函數重載運算符 c1++; c1.print(); // 友元函數重載運算符 c1--; c1.print(); system("pause"); return; } |
例4:“=”運算符重載
我們知道類的預設拷貝構造函數屬于“淺拷貝”,這樣就會導緻在類執行個體化對象之間進行指派操作時可能産生記憶體洩露問題,如:分别執行個體類對象obj1和obj2,當執行obj2=obj1時,obj2的指針就會指向obj1的記憶體空間,obj2原來記憶體空間洩露;另外當obj1删除時會釋放對應的記憶體空間,而此時obj2指向的記憶體空間和obj1相同,當obj2删除時會再次釋放同一個記憶體空間,造成記憶體洩露,是以我們需要重載“=”運算符來避免這個問題。
#include <iostream> #include <String> using namespace std; class Student{ public: Student(const char *name, const int age){ int _len = strlen(name); m_name = (char *)malloc(_len + 1); // strcpy_s(m_name, _len+1, name); m_age = age; } // Student obj2 = obj1; // 方法一:手工編寫拷貝構造函數實作深copy Student(const Student& obj1){ int _len = strlen(obj1.m_name); m_name = (char *)malloc(_len + 1); strcpy_s(m_name, _len+1, obj1.m_name); m_age = obj1.m_age; } // Student obj2 = obj1; // 方法二:重載等号操作符實作深copy Student& operator=(Student &obj1){ if (this->m_name != NULL){ delete[] m_name; m_age = 0; } this->m_name = new char[strlen(obj1.m_name) + 1]; strcpy_s(m_name, strlen(obj1.m_name)+1, obj1.m_name); this->m_age = obj1.m_age; return *this; } ~Student(){ if (m_name != NULL){ free(m_name); m_name = NULL; m_age = 0; } } protected: private: char *m_name; int m_age; }; //對象析構的時候 出現coredump void test() { Student obj1("xiaoming", 10); Student obj2 = obj1; //調用使用者實作的拷貝構造函數,實作深拷貝 Student obj3("liming", 11); obj3 = obj1; // 等号操作符 obj1 = obj2 = obj3; // 需要傳回引用 } int main(){ test(); cout << "end..." << endl; system("pause"); return 0; } |
注意obj3 = obj1和obj1 = obj2 = obj3是不同的。當隻是使用obj3 = obj1時,等号運算符重載函數傳回元素和引用都可以;但是,如果要實作obj1 = obj2 = obj3功能,則必須傳回引用,因為此時需要左值操作。
例5:不要重載“||”和“&&”
操作符“||”和“&&”内置實作了短路規則,而當重載這兩個操作符是無法實作短路規則,導緻函數中的參數都會被求值,無法達到預定效果,是以不要重載這兩個運算符。
#include <cstdlib> #include <iostream> using namespace std; class Test{ public: Test(int i){ this->m_a = i; } Test operator+ (const Test& obj){ Test ret(0); ret.m_a = m_a + obj.m_a; return ret; } bool operator&& (const Test& obj){ return m_a && obj.m_a; } private: int m_a; }; // && 從左向右 int main(){ int a1 = 0; int a2 = 1; if (a1 && (a1 + a2)){ cout << "a1,a2 結果為真..." << endl; } Test t1 = 0; Test t2 = 1; if(t1 && (t1 + t2)){ cout << "t1,t2 結果為真..." << endl; } system("pause"); return 0; } |
當對以上代碼正常情況我們會認為if(t1 && (t1 + t2))不會執行(t1+t2)因為前面t1已經為假了,但當對代碼調試時,你會發現代碼進入了(t1+t2)的過程,是以重載“&&”無法實作短路規則。
例6:綜合執行個體
這裡給一個實作自定義數組的執行個體,有助于熟悉重載運算符在實際項目中的使用。
myarray.h
#pragma once #include <iostream> using namespace std; class Array{ public: Array(int length); Array(const Array& obj); ~Array(); public: void setData(int index, int valude); int getData(int index); int length(); //函數傳回值當左值,需要傳回一個引用(元素本身) int& operator[](int i); //重載= Array& operator=(Array &a1); //重載 == bool operator==(Array &a1); //重載 != bool operator!=(Array &a1); private: int m_length; int *m_space; }; |
myarray.cpp
#include "myarray.h" Array::Array(int length) { if (length < 0){ length = 0; } m_length = length; m_space = new int[m_length]; } //重寫拷貝構造函數 Array::Array(const Array& obj){ this->m_length = obj.m_length; this->m_space = new int[this->m_length]; for (int i = 0; i<m_length; i++){ this->m_space[i] = obj.m_space[i]; } } Array::~Array(){ if (m_space != NULL){ delete[] m_space; m_space = NULL; m_length = -1; } } //a1.setData(i, i); void Array::setData(int index, int valude){ m_space[index] = valude; } int Array::getData(int index){ return m_space[index]; } int Array::length(){ return m_length; } // a[i] = 1,因為要當左值使用,是以要傳回引用 int& Array::operator[](int i){ return m_space[i]; } //a2 = a1; Array& Array::operator=(Array &a1){ if (this->m_space != NULL){ delete[] m_space; m_length = 0; } m_length = a1.m_length; m_space = new int[m_length]; for (int i = 0; i<m_length; i++){ m_space[i] = a1[i]; // 因為已經重載了[]操作符 } return *this; } //if (a3 == a1) bool Array::operator==(Array &a1){ if (this->m_length != a1.m_length){ return false; } for (int i = 0; i<m_length; i++){ if (this->m_space[i] != a1[i]){ return false; } } return true; } bool Array::operator!=(Array &a1){ return !(*this == a1); } |
Test.cpp
#include <iostream> #include "myarray.h" using namespace std; int main(){ Array a1(10); { cout << "\na1: "; for (int i = 0; i<a1.length(); i++) { a1.setData(i, i); a1[i] = i; // 調用[]操作符重載函數 } for (int i = 0; i<a1.length(); i++) { cout << a1[i] << " "; } cout << endl; } Array a2 = a1; { cout << "\na2: "; for (int i = 0; i<a2.length(); i++) { cout << a2.getData(i) << " "; } cout << endl; } Array a3(5); { a3 = a1; a3 = a2 = a1; cout << "\na3: "; for (int i = 0; i<a3.length(); i++){ cout << a3[i] << " "; } cout << endl; } // ==和!= { if (a3 == a1) { printf("equality\n"); } else { printf("inequality\n"); } if (a3 != a1) { printf("inequality\n"); } else { printf("equality\n"); } } system("pause"); return 0; } |