【C++ 语言】面向对象 ( 函数重载 | 运算符重载 | 运算符重载两种定义方式 | 拷贝构造方法 | RVO 优化 | NRVO 优化 )
文章目錄
- 函數重載
- 運算符重載 ( 類內部定義云算符重載 )
- 運算符重載 ( 類外部定義運算符重載 )
- 可重載的運算符
- 拷貝構造方法
- 編譯器優化 ( RVO 優化 | NRVO 優化 )
- 完整代碼示例
函數重載
C 中如果出現兩個同名的函數 , 就會出現沖突 , 編譯時會報錯 ;
C++ 中是允許出現兩個同名的函數 , 這里函數的參數個數 , 順序 , 類型 , 返回值類型 至少有一種是不同的 ; 如下面兩個函數就是參數個數不同 , 前者有 0 個參數 , 后者有 1 個參數 ;
void OOTest() {//在方法中直接聲明 Student 對象, student 對象處于棧內存中 , //其作用域僅限于 OOTest 函數 , 方法執行完就會清理掉Student student(18, 1); }void OOTest(int i) {//在方法中直接聲明 Student 對象, student 對象處于棧內存中 , //其作用域僅限于 OOTest 函數 , 方法執行完就會清理掉Student student(18, 1); }運算符重載 ( 類內部定義云算符重載 )
C++ 中允許重新定義運算符的行為 , 如常用的加減成熟運算符 , 都可以進行重載操作 ; 可以自定義運算符的操作 ;
類內部定義云算符重載 , 格式為 “返回值類型 ( 類名稱 ) operator運算符符號 ( const 參數類型名稱& 參數變量名稱 ) { 方法內容 }” , 參數的類型是引用類型 ;
加法運算符重載 , 對 “+” 號運算符進行重載 , 其作用是讓兩個 Operator 的 number 成員變量相加 , 然后返回一個新的 Operator 對象 , 其 number 成員變量值是兩個 Operator 的 number 成員變量值之和 ;
//運算符重載 , "+" 號運算符進行重載 , //其作用是讓兩個 Operator 的 number 成員變量相加 //運算符重載的本質是按照一定格式定義一個方法 //這個定義的方法中包含運算符 , 除運算符之外的其它符號可以省略簡寫 public:Operator operator+(const Operator& o1) {//+ 運算符的作用是 兩個 Operator 對象, 進行操作得到第三個 Operator 對象//第三個 Operator 對象的 number 變量 , 是前兩個 Operator 對象之和Operator o2;o2.number = this->number + o1.number;return o2;}運算符重載本質 , 其本質是定義一個方法 , 該方法有固定的格式去定義 , 調用該方法的時候 , 可以使用函數形式調用 , 也可以使用運算符進行運算 , 其 本質還是類的函數調用 ;
重載運算符完整調用 , 即調用上面定義的整個 operator+ 方法 , 這是采用正式的的函數調用方式 ;
//這是運算符重載的完整寫法 , //其中的 .operator 和之后的 () 可以省略變成下面的簡化寫法Operator o3 = o1.operator+(o2);//打印 o3 中的 number 變量值cout << "內部定義的運算符重載完整寫法結果 : " << o3.number << endl;運算符重載簡化調用 ( 推薦 ) , 這種調用就是運算符運算 , 寫法就是 “對象1 運算符 對象2” 結果得到的是 對象3 ; 這種調用方法與上面的區別是省略了調用時的 .operator 和參數外面的括號 () ;
//+ 是在 Operator 類中自定義的運算符重載 //其作用是返回一個對象 , 其number成員變量值是 o1 和 o2 中number成員變量之和Operator o4 = o1 + o2;//打印 o3 中的 number 變量值cout << "內部定義的運算符重載簡化寫法結果 : " << o4.number << endl;運算符重載調用完整代碼 :
//運算符重載//注意這里的 Operator 對象 o1 和 o2 都在棧內存中Operator o1;o1.number = 80;Operator o2;o2.number = 10;//運算符重載完整寫法//這是運算符重載的完整寫法 , //其中的 .operator 和之后的 () 可以省略變成下面的簡化寫法Operator o3 = o1.operator+(o2);//打印 o3 中的 number 變量值cout << "內部定義的運算符重載完整寫法結果 : " << o3.number << endl;//運算符重載簡化寫法//+ 是在 Operator 類中自定義的運算符重載 //其作用是返回一個對象 , 其number成員變量值是 o1 和 o2 中number成員變量之和Operator o4 = o1 + o2;//打印 o3 中的 number 變量值cout << "內部定義的運算符重載簡化寫法結果 : " << o4.number << endl;代碼執行結果 :
內部定義的運算符重載完整寫法結果 : 90 內部定義的運算符重載簡化寫法結果 : 90運算符重載 ( 類外部定義運算符重載 )
類外部定義運算符重載 , 運算符重載也可以定義在類的外部 , 可以是任意包含類頭文件的代碼中 , 其定義方式與定義在類的內部對比 , 只有參數是有區別的 , 在類外部定義 , 其中需要兩個參數 , 分別代表運算符運算的兩個參數 ;
乘法運算符重載 , 對 “*” 號運算符進行重載 , 其作用是讓兩個 Operator 的 number 成員變量相乘 , 然后返回一個新的 Operator 對象 , 其 number 成員變量值是兩個 Operator 的 number 成員變量值之積 ;
//類外部定義云算符重載 // 使用該重載云算符時 , 將兩個對象相乘 , 獲得的第三個對象 , // 該對象的 number 成員變量值 , 是 前兩個對象的 number 對象的乘積 Operator operator*(const Operator& o1, const Operator& o2) {//+ 運算符的作用是 兩個 Operator 對象, 進行操作得到第三個 Operator 對象//第三個 Operator 對象的 number 變量 , 是前兩個 Operator 對象之和Operator o3;o3.number = o1.number * o2.number;return o3; }已重載的運算符調用 , 可以直接調用運算符重載的 operator*() 方法 , 也可以直接使用運算符 , o1 * o2 ;
//運算符重載//注意這里的 Operator 對象 o1 和 o2 都在棧內存中Operator o1;o1.number = 80;Operator o2;o2.number = 10;//這是運算符重載的完整寫法 , //其中的 .operator 和之后的 () 可以省略變成下面的簡化寫法Operator o5 = operator*(o1, o2);//打印 o5 中的 number 變量值cout << "外部定義的運算符重載完整寫法結果 : " << o5.number << endl;//運算符重載簡化寫法//+ 是在 Operator 類中自定義的運算符重載 //其作用是返回一個對象 , 其number成員變量值是 o1 和 o2 中number成員變量之積Operator o6 = o1 * o2;//打印 o6 中的 number 變量值cout << "外部定義的運算符重載簡化寫法結果 : " << o6.number << endl;代碼執行結果
外部定義的運算符重載完整寫法結果 : 800 外部定義的運算符重載簡化寫法結果 : 800可重載的運算符
這里列舉一下可重載的運算符
| 比較運算符 ( 雙目運算符 ) | == (等于) , != (不等于) , < (小于) , > (大于 ) , <= ( 小于等于 ) , >= ( 大于等于 ) |
| 邏輯運算符 ( 雙目運算符 ) | && ( 與 ) , || ( 或 ) , ! ( 非 ) |
| 數值計算運算符 ( 雙目運算符 ) | + ( 加 ) , - ( 減 ) , * ( 乘 ) , / ( 除 ) |
| 位運算符 ( 雙目運算符 ) | | ( 按位或運算 ) , & ( 按位與運算 ) , ~ ( 按位取反運算 ) , ^ ( 按位異或運算 ) , << ( 左移運算 ) , >> ( 右移運算 ) |
| 賦值運算符 ( 雙目運算符 ) | = ( 等于 ) , += ( 加等于 ) , -= ( 減等于 ) , *= ( 乘等于 ) , /= ( 除等于 ) , % = ( 模等于 ) , &= ( 按位與等于 ) , |= ( 按位或等于 ) , ^= ( 按位異或等于 ) , <<= ( 左移等于 ) , >>= ( 右移等于 ) |
| 單目運算符 | + ( 正數符號 ) , - ( 負數符號 ) , * ( 指針類型 ) , & ( 取地址符 ) , ++ ( 自增運算符 ) , – ( 自減運算符 ) |
| 內存申請釋放運算符 | new ( 新建對象 ) , new[] ( 新建數組對象 ) , delete ( 釋放對象 ) , delete[] ( 釋放數組對象 ) |
| 函數調用運算符 | () |
| 成員訪問運算符 | -> |
| 下標運算符 | [] |
| 逗號運算符 | , |
拷貝構造方法
分析下面方法中的棧內存 ;
//運算符重載 , "+" 號運算符進行重載 , // 其作用是讓兩個 Operator 的 number 成員變量相加 // 運算符重載的本質是按照一定格式定義一個方法 // 這個定義的方法中包含運算符 , 除運算符之外的其它符號可以省略簡寫 public:Operator operator+(const Operator& o1) {//+ 運算符的作用是 兩個 Operator 對象, 進行操作得到第三個 Operator 對象//第三個 Operator 對象的 number 變量 , 是前兩個 Operator 對象之和Operator o2;o2.number = this->number + o1.number;//o2 對象是存在棧內存中 , 返回 o2 操作出現了拷貝操作//o2 會調用拷貝構造方法 , 拷貝到一個臨時對象中 //如果返回值有接收的對象 , 那么又調用拷貝構造方法 , // 將這個臨時對象又會被拷貝給接收對象return o2;}};分析上述方法中的棧內存對象 , 在運算符重載的方法中 , 涉及到了棧內存對象的生命周期問題 , Operator o2; 語句在方法的棧內存中創建了一個 Operator 對象 o2 , 該對象的生命周期只限于整個方法體 , 方法執行完畢后 , 棧內存中的該對象就要被釋放掉 ;
但是 該對象需要被返回 , 并且在方法調用完成之后 , 還要 賦值給另外一個對象 , 現在討論一下其中的運行機制 ;
從 return o2; 開始分析 , 返回 o2 對象 , 系統會將棧內存中的 o2 對象 拷貝到一個臨時對象中 , 這里調用了一次拷貝構造方法 ; 然后將臨時對象又賦值給了返回值接收的對象 , 此處又調用了一次拷貝構造方法 ; 整個操作在理論上調用了兩次拷貝構造方法 ;
拷貝構造方法實現 , 拷貝構造方法與構造方法的區別是 , 其需要傳入一個引用類型 ( 類名& 變量名 ) 的參數 , 如下示例中實現了默認的構造方法 , 同時實現了拷貝構造方法 , 在發生該對象的拷貝操作時 , 會調用該方法 , 打印相應的數據 , 這樣我們就能測試追蹤對象的拷貝操作了 ;
//默認的構造方法Operator() {}//拷貝構造方法, 每次拷貝都會調用該構造方法// 以此來驗證棧內存中 返回 棧內存中的對象 , // 將棧內存對象拷貝到臨時對象中// 在方法調用處 , 又將臨時對象拷貝給了接收返回值的對象Operator(Operator& o) {this->number = o.number;cout << "Operator 對象執行拷貝操作" << endl;}下面運行如下重載云算符調用的方法 , 分析其構造方法調用次數 , 下面是要運行的代碼 :
//+ 是在 Operator 類中自定義的運算符重載 //其作用是返回一個對象 , 其number成員變量值是 o1 和 o2 中number成員變量之和Operator o4 = o1 + o2;//打印 o3 中的 number 變量值cout << "內部定義的運算符重載簡化寫法結果 : " << o4.number << endl;運行結果如下 , 此處發現拷貝構造方法只調用了一次 , 理論上其應該調用兩次 , 這就涉及到了編譯器的 RVO 優化 ;
Operator 對象執行拷貝操作 內部定義的運算符重載完整寫法結果 : 90編譯器優化 ( RVO 優化 | NRVO 優化 )
理論上拷貝構造方法是要執行兩次的 , 在 operator+ 方法中 , 第一次將 o2 對象拷貝給臨時對象 , 第二次將 臨時對象拷貝給接收 operator+ 方法返回值的對象 ;
但是在 Visual Studio 中編譯后執行結果 只拷貝了一次, 拷貝構造函數只調用了一次, 這是由于編譯器優化的原因 ;
- Windows 上 Visual Studio 的 C++ 編譯器是 cl.exe
- MAC 上 Xcode 的 C++ 編譯器是 GNU g++
rvo 優化 , 在 VS 中, cl 編譯器在 debug 模式下,會執行 rvo (return value) 優化 , 減少了1次拷貝和析構的操作 ;
nrvo 優化 , 在 release 模式下 , 會執行 nrvo 優化 , 會進行 0 次拷貝 , 減少了 2 次拷貝和析構的操作 , 其優化方式是改寫方法 , 直接將接收對象放入參數 , 在方法中就將返回對象賦值給接收對象了 ;
完整代碼示例
操作符重載類代碼 :
#pragma onceusing namespace std;class Operator {public : int number;//默認的構造方法Operator() {}//拷貝構造方法, 每次拷貝都會調用該構造方法// 以此來驗證棧內存中 返回 棧內存中的對象 , // 將棧內存對象拷貝到臨時對象中// 在方法調用處 , 又將臨時對象拷貝給了接收返回值的對象Operator(Operator& o) {this->number = o.number;cout << "Operator 對象執行拷貝操作" << endl;}//這里針對拷貝操作進行說明 : rvo 優化 , nrvo 優化//理論上 拷貝 構造方法 是要執行兩次的 , 在 operator+ 方法中 , // 第一次將 o2 對象拷貝給臨時對象// 第二次將 臨時對象拷貝給接收 operator+ 方法返回值的對象//但是在 Visual Studio 中編譯后執行結果只拷貝了一次, 這是由于編譯器優化的原因.// Windows 上 Visual Studio 的 C++ 編譯器是 cl.exe// MAC 上 Xcode 的 C++ 編譯器是 GNU g++//在 VS 中, cl 編譯器在 debug 模式下,會執行 rvo (return value) 優化// rvo 優化 , 減少了1次拷貝和析構的操作//在 release 模式下 , 會執行 nrvo 優化// nrvo 優化 , 會進行 0 次拷貝 , 減少了 2 次拷貝和析構的操作// 其優化方式是改寫方法 , 直接將接收對象放入參數 , 在方法中就將返回對象賦值給接收對象了//這兩種優化都是編譯器針對返回值進行的優化//類內部定義云算符重載//運算符重載 , "+" 號運算符進行重載 , // 其作用是讓兩個 Operator 的 number 成員變量相加 // 運算符重載的本質是按照一定格式定義一個方法 // 這個定義的方法中包含運算符 , 除運算符之外的其它符號可以省略簡寫 public:Operator operator+(const Operator& o1) {//+ 運算符的作用是 兩個 Operator 對象, 進行操作得到第三個 Operator 對象//第三個 Operator 對象的 number 變量 , 是前兩個 Operator 對象之和Operator o2;o2.number = this->number + o1.number;//o2 對象是存在棧內存中 , 返回 o2 操作出現了拷貝操作//o2 會調用拷貝構造方法 , 拷貝到一個臨時對象中 //如果返回值有接收的對象 , 那么又調用拷貝構造方法 , // 將這個臨時對象又會被拷貝給接收對象return o2;}};//類外部定義云算符重載 // 使用該重載云算符時 , 將兩個對象相乘 , 獲得的第三個對象 , // 該對象的 number 成員變量值 , 是 前兩個對象的 number 對象的乘積 Operator operator*(const Operator& o1, const Operator& o2) {//+ 運算符的作用是 兩個 Operator 對象, 進行操作得到第三個 Operator 對象//第三個 Operator 對象的 number 變量 , 是前兩個 Operator 對象之和Operator o3;o3.number = o1.number * o2.number;//o2 對象是存在棧內存中 , 返回 o2 操作出現了拷貝操作//o2 會調用拷貝構造方法 , 拷貝到一個臨時對象中 //如果返回值有接收的對象 , 那么又調用拷貝構造方法 , // 將這個臨時對象又會被拷貝給接收對象return o3; }main 函數代碼 :
// 003_Object_Oriented.cpp: 定義應用程序的入口點。 //#include "003_Object_Oriented.h"//引用 Student 類聲明的頭文件 #include "Student.h"#include "Instance.h"#include "Operator.h"using namespace std;void OOTest() {//在方法中直接聲明 Student 對象, student 對象處于棧內存中 , //其作用域僅限于 OOTest 函數 , 方法執行完就會清理掉Student student(18, 1); }void OOTest(int i) {//在方法中直接聲明 Student 對象, student 對象處于棧內存中 , //其作用域僅限于 OOTest 函數 , 方法執行完就會清理掉Student student(18, 1); }//友元函數實現 , 在類的外部修改類中的私有成員變量 age void changeAge(Student* student){student->age = 88; }int main() {cout << "Hello Student" << endl;OOTest();//在上面的 OOTest() 方法中的棧內存中創建了 Student 對象//當 OOTest() 方法執行完畢后 , 就會釋放掉 Student 對象//使用 new 創建對象 , 注意該對象在堆內存中創建的 //用完之后需要使用 delete 釋放該對象Student* student = new Student(18, 1);//調用友元函數, 修改 student 對象類私有變量 age 的值changeAge(student);//調用 getAge() 常量函數獲取 student 對象的 age 成員變量值//并將該值打印出來cout<< "age : " << student->getAge() <<endl;//釋放使用 new 申請的堆內存中的內存delete student;//創建單例對象Instance* instance = Instance::getInstance();//打印單例對象中的變量值cout << "單例 instance->number : " << instance->number << endl;//釋放單例類delete instance;//運算符重載//注意這里的 Operator 對象 o1 和 o2 都在棧內存中Operator o1;o1.number = 80;Operator o2;o2.number = 10;//運算符重載完整寫法//這是運算符重載的完整寫法 , //其中的 .operator 和之后的 () 可以省略變成下面的簡化寫法Operator o3 = o1.operator+(o2);//打印 o3 中的 number 變量值cout << "內部定義的運算符重載完整寫法結果 : " << o3.number << endl;//運算符重載簡化寫法//+ 是在 Operator 類中自定義的運算符重載 //其作用是返回一個對象 , 其number成員變量值是 o1 和 o2 中number成員變量之和Operator o4 = o1 + o2;//打印 o3 中的 number 變量值cout << "內部定義的運算符重載簡化寫法結果 : " << o4.number << endl;//這里對棧內存說明一下//在運算符重載實現的方法中 , 創建了 Operator 對象, //這個對象在方法返回時先拷貝給了一個臨時對象//這個臨時對象是一個不可見的匿名對象 , 對外透明的//返回該臨時對象后 , 發現有 Operator o3 變量接收該對象//再次將臨時對象拷貝給 o3 對象 //測試類外部的運算符重載//運算符重載完整寫法//這是運算符重載的完整寫法 , //其中的 .operator 和之后的 () 可以省略變成下面的簡化寫法Operator o5 = operator*(o1, o2);//打印 o5 中的 number 變量值cout << "外部定義的運算符重載完整寫法結果 : " << o5.number << endl;//運算符重載簡化寫法//+ 是在 Operator 類中自定義的運算符重載 //其作用是返回一個對象 , 其number成員變量值是 o1 和 o2 中number成員變量之積Operator o6 = o1 * o2;//打印 o6 中的 number 變量值cout << "外部定義的運算符重載簡化寫法結果 : " << o6.number << endl;return 0; }運行結果 :
Hello Student Student() 構造方法 ~Student() 析構方法 Student() 構造方法 age : 88 ~Student() 析構方法 單例 instance->number : 888 Operator 對象執行拷貝操作 內部定義的運算符重載完整寫法結果 : 90 Operator 對象執行拷貝操作 內部定義的運算符重載簡化寫法結果 : 90 Operator 對象執行拷貝操作 外部定義的運算符重載完整寫法結果 : 800 Operator 對象執行拷貝操作 外部定義的運算符重載簡化寫法結果 : 800總結
以上是生活随笔為你收集整理的【C++ 语言】面向对象 ( 函数重载 | 运算符重载 | 运算符重载两种定义方式 | 拷贝构造方法 | RVO 优化 | NRVO 优化 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【C++ 语言】面向对象 ( 成员函数
- 下一篇: 【C++ 语言】面向对象 ( 继承 |