970 likes | 1.15k Views
23. 樣版類別-向量和矩陣的定義. 向量和矩陣是線性代數和數值分析的基礎。藉由這兩個類別,我們介紹如何建立樣版類別 (template class) 。此外,我們還要在本章中討論動態建立和移除物件的語法。. 樣版類別-向量和矩陣的定義. 23.1 向量 23.2 Vector 樣版類別 23.3 矩陣 23.4 Matrix 樣版類別 23.5 物件陣列的動態創造和刪除 23.6 常犯的錯誤. 23.1 向量. 把 向量 (vector) 定義成物件 的好處 : 1. 將向量視為獨立的單元。
E N D
23 樣版類別-向量和矩陣的定義 向量和矩陣是線性代數和數值分析的基礎。藉由這兩個類別,我們介紹如何建立樣版類別 (template class)。此外,我們還要在本章中討論動態建立和移除物件的語法。
樣版類別-向量和矩陣的定義 • 23.1 向量 • 23.2Vector樣版類別 • 23.3 矩陣 • 23.4Matrix樣版類別 • 23.5 物件陣列的動態創造和刪除 • 23.6 常犯的錯誤
23.1 向量 把向量(vector)定義成物件的好處: 1. 將向量視為獨立的單元。 2.透過運算子重載,將向量運算以最精簡、最接近數學式的方式表達。 例如,向量間的加法和內積可以分別簡潔地寫成「V1 + V2」和「V1 * V2」。
使用指標定義向量(1/2) • 考慮線性代數一般的使用習慣,可以犧牲第一個元素不用,而將記憶空間安排為N+1個元素: int N = 5; float* V = new float [N+1]; 圖示如下:
使用指標定義向量(2/2) • 可以使用「delete」回收不再使用的記憶體 資源。 • delete [] V;
23.2 Vector樣版類別 • 定義樣版類別 (class template)。以下是一個 簡化的例子: template <class T> //「T」代表一個尚未決定的資料型態 class Vector { private: int Size; T* V; public: Vector(int); // 建構函數不使用Vector <T> Vector(Vector<T>&); // 類別名稱以Vector <T> 表示 ~Vector(); // 解構函數不使用Vector <T> };
定義樣版類別 (一個以上的未定類別) • 定義樣版類別 (一個以上的未定類別) template <class T1, class T2> class Vector { // ... 其他敘述 };
以樣版類別Vector定義物件 • 分別用來宣告一個整數向量Vi和一個浮點數向量Vd: Vector <int> Vi; Vector <double> Vd;
成員函數的定義 在實作樣版類別的成員函數時,必需在每個成員函數的定義前都加上「template <class T>」的宣告。 例如,Vector <T> 的建構函數可以寫成: template <class T> Vector<T>::Vector(int N) { Size = N; V= new T[N+1]; return; }
Vector <T> 的解構函數 template <class T> Vector<T>::~Vector() { delete [] V; }
加法operator+() 的重載成員函數 template <class T> Vector<T> Vector<T>::operator + (const Vector<T>& V2) { Vector<T> Temp(Size); for (int i=1; i<= Size; i++) Temp.V[i]= V[i]+V2.V[i]; return Temp; }
完整的Vector樣版類別 樣版類別Vector的完整程式寫成檔案Vector.h。 這個樣版類別的定義中包括了:建構函數 (constructor)複製建構函數 (copy constructor)解構函數 (destructor)指派運算子 (assignment operator, =)「+」、「-」、「*」、「/」等四個運算子 的重載。
運算子「*」 在程式Vector.h中,運算子「*」被重載了三次: (1) 純量乘以向量。 例如 V1 = 2.5 * V2; (2) 向量乘以純量。 例如 V1 = V2 * 2.5; (3) 向量的內積。 例如 double x = V1 * V2; 其中 (1)「純量乘以向量」只能以friend函數寫成,因為運算子的左側運算元不是Vector物件。
專門用來發出檢查訊息的函數 (1/2) 例如,做向量加法前,要檢查兩個向量的長度是否相同。 inline void Check(bool ErrorCondition, const string& Message = "Requirement failed") { if (ErrorCondition) { cerr << Message << endl; exit(1); } }
專門用來發出檢查訊息的函數 (2/2) 將判斷式和錯誤訊息以參數的方式傳遞 // 定義加法運算子 operator + () template <class T> Vector<T> Vector<T>::operator+(const Vector<T>& V2) { Check(Size!=V2.Size, "加法運算錯誤, 長度不相同!"); Vector<T> Temp(Size); for (int i=1; i<= Size; i++) Temp.V[i]= V[i]+V2.V[i]; return Temp; }
配置新記憶空間的成員函數 • 建構函數,複製建構函數,和指派運算子「=」 都有配置新記憶空間的動作。因此,我們將這個動作 集中寫成inline 函數Create(): template <class T> void Vector<T>::Create(int N) { if(N<1) {Size = 0; V = 0;} else {Size = N; V= new T[N+1];} }
配置新記憶空間 • 例如,複製建構函數的定義可以比較簡潔地寫成: template <class T> Vector<T>::Vector(Vector<T>& OldV) { Create(OldV.Size); for (int i=1; i<= Size; i++) V[i]= OldV.V[i]; return; }
指派運算子operator = () • 指派運算子必需重新定義,因為成員內有指標,而且等號左右兩邊的向量不一定一樣長。指派運算子的定義如下: template <class T> Vector<T> Vector<T>::operator=(const Vector<T>& V2) { if (Size!=V2.Size) Create(V2.Size); for (int i=1; i<= Size; i++) V[i] = V2.V[i]; return *this; }
重載運算子 [] (1/2) • 我們將operator[]() 的返回資料型態設定為參照。如此一來,它才可以是左值lvalue,也可以是右值rvalue,例如: V[2] = 4.8; float x = V[6];
重載運算子 [] (2/2) • operator []() 的inline成員函數定義如下所示: T& operator[](int i) { Check(i>Size, "索引錯誤, 超過邊界!"); return V[i]; }
專門用來顯示向量的函數Display() template <class T> void Vector<T>::Display() { for(int i = 1; i <= Size; i++) cout << setiosflags(ios::right) << setiosflags(ios::fixed) << setiosflags(ios::showpoint) << setprecision(4) << setw(12) << V[i]; cout << endl; return; }
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(1/18) // Vector.h #ifndef Vector_H #define Vector_H #include <iostream> #include <iomanip> using namespace std; inline void Check(bool ErrorCondition, const string& Message = "Requirement failed")
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(2/18) { if (ErrorCondition) { cerr << Message << endl; exit(1); } } // =========== Vector 樣版類別 ================================= template <class T> class Vector { // 宣告乘法運算子-1 operator * () friend Vector<T> operator*(const T& , const Vector<T>& ); // 宣告乘法運算子-2 operator * ()
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(3/18) friend Vector<T> operator*(const Vector<T>& , const T& ); private: int Size; T* V; inline void Create(int); public: // 宣告建構函數 Vector(int); Vector(int, const T*);
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(4/18) // 宣告複製建構函數 Vector(Vector<T>&); // 宣告解構函數 ~Vector(); // Operators // 宣告指派運算子 operator = () Vector<T> operator=(const Vector<T>&); // 宣告加法運算子 operator + () Vector<T> operator+(const Vector<T>&); // 宣告減法運算子 operator - ()
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(5/18) Vector<T> operator-(const Vector<T>&); // 宣告乘法運算子-3 operator * () T operator*(const Vector<T>&); // 宣告除法運算子 operator / () Vector<T> operator/(const T&); // 宣告索引運算子 operator []() T& operator[](int i)
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(6/18) { Check(i>Size, "索引錯誤, 超過邊界!"); return V[i]; } // 宣告成員函數 Display() void Display(); };
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(7/18) // 函數 Create() 的定義 template <class T> void Vector<T>::Create(int N) { if(N<1) {Size = 0; V = 0;} else {Size = N; V= new T[N+1];} }
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(8/18) // 建構函數的定義 template <class T> Vector<T>::Vector(int N) { Create(N); return; } // 解構函數的定義 template <class T> Vector<T>::~Vector() { delete [] V; }
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(9/18) // 複製建構函數的定義-1 template <class T> Vector<T>::Vector(int N, const T* OldV) { Create(N); for (int i=1; i<= Size; i++) V[i]= OldV[i-1]; return; }
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(10/18) // 複製建構函數的定義-2 template <class T> Vector<T>::Vector(Vector<T>& OldV) { Create(OldV.Size); for (int i=1; i<= Size; i++) V[i]= OldV.V[i]; return; }
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(11/18) // 定義指派運算子 operator = () template <class T> Vector<T> Vector<T>::operator=(const Vector<T>& V2) { if (Size!=V2.Size) Create(V2.Size); for (int i=1; i<= Size; i++) V[i] = V2.V[i]; return *this; }
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(12/18) // 定義加法運算子 operator + () template <class T> Vector<T> Vector<T>::operator+(const Vector<T>& V2) { Check(Size!=V2.Size,"加法運算錯誤, 長度不相同!"); Vector<T> Temp(Size); for (int i=1; i<= Size; i++) Temp.V[i]= V[i]+V2.V[i]; return Temp; }
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(13/18) // 定義減法運算子 operator - () template <class T> Vector<T> Vector<T>::operator-(const Vector<T>& V2) { Check(Size!=V2.Size,"減法運算錯誤, 長度不相同!"); Vector<T> Temp(Size); for (int i=1; i<= Size; i++) Temp.V[i]= V[i]-V2.V[i]; return Temp; }
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(14/18) // 定義乘法運算子-1 operator * () template <class T> Vector<T> operator*(const T& f, const Vector<T>& V1) { Vector<T> Temp(V1.Size); for(int i=1; i<=V1.Size; i++) Temp.V[i] = f * V1.V[i]; return Temp; }
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(15/18) // 定義乘法運算子-2 operator * () template <class T> Vector<T> operator*(const Vector<T>& V1, const T& f) { Vector<T> Temp(V1.Size); for(int i=1; i<=V1.Size; i++) Temp.V[i] = f * V1.V[i]; return Temp; }
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(16/18) // 定義乘法運算子-3 operator * () template <class T> T Vector<T>::operator*(const Vector<T>& V2) { Check(Size!=V2.Size, "內積運算錯誤, 長度不相同!"); T Product = 0; for (int i=1; i<= Size; i++) Product += V[i]*V2.V[i]; return Product; }
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(17/18) // 定義成員函數 Display() template <class T> void Vector<T>::Display() { for(int i = 1; i <= Size; i++) cout << setiosflags(ios::right) << setiosflags(ios::fixed) << setiosflags(ios::showpoint) << setprecision(4) << setw(12) << V[i]; cout << endl; return; }
範例程式檔案 Vector.h 定義樣版類別Vector的完整程式(18/18) // 定義除法運算子 operator / () template <class T> Vector<T> Vector<T>::operator/(const T& f) { Vector<T> Temp(Size); for (int i=1; i<= Size; i++) Temp.V[i]= V[i]/f; return Temp; } #endif
範例程式檔案TestVector.cpp(1/6) • 分別使用Vector<int>和Vector<double>去宣告包含int和double兩種資料型態元素的向量:DV1、DV2、IV1和IV2,並驗證向量除以浮點數、向量間加法和向量內積的正確性。我們也藉由DVb[1]、DVb[2] 和DVb[3] 驗證了索引運算子[]。
範例程式檔案TestVector.cpp(2/6) // TestVector.cpp #include "Vector.h" int main () { double Ddata1[]={2, 0.5, 4.6}; double Ddata2[]={4, 6.5, 3.8}; int Idata1[]={2, 5, 6}; int Idata2[]={4, 65, 38};
範例程式檔案TestVector.cpp(3/6) Vector<double> DV1(3, Ddata1); Vector<double> DV2(3, Ddata2), DVa(3), DVb(3); Vector<int> IV1(3, Idata1); Vector<int> IV2(3, Idata2), IVa(3), IVb(3); int Idot; double Ddot; cout << "\n---------------------" << endl; cout << "測試 Vector<double>: " << endl; cout << " DV1 的值是: " << endl;
範例程式檔案TestVector.cpp(4/6) DV1.Display(); cout << " DV2 的值是: " << endl; DV2.Display(); DVa=DV1+DV2; cout << " (DV1 + DV2) 的值是:" << endl; DVa.Display(); DVb=DV1/2.0; cout << " (DV1/2.0) 的值是: " << endl;
範例程式檔案TestVector.cpp(5/6) cout << " " << DVb[1] << " " << DVb[2] << " " << DVb[3] << endl; Ddot = DV1*DV2; cout << " DV1 和 DV2 的內積是 : " << Ddot << endl; cout << "\n-------------------- " << endl; cout << "測試 Vector<int>: " << endl; cout << " IV1 的值是: " << endl;
範例程式檔案TestVector.cpp(6/6) IV1.Display(); cout << " IV2 的值是: " << endl; IV2.Display(); IVa=IV1+IV2; cout << " (IV1 + IV2)的值是:" << endl; IVa.Display(); IVb=IV1/2.0; cout << " (IV1/2.0) 的值是:" << endl; IVb.Display(); Idot = IV1*IV2; cout << " IV1 和 IV2 的內積是 : "<< Idot << endl; return 0;}
程式執行結果(1/2) ------------------------------------ 測試 Vector<double>: DV1 的值是: 2.0000 0.5000 4.6000 DV2 的值是: 4.0000 6.5000 3.8000 (DV1 + DV2) 的值是: 6.0000 7.0000 8.4000 (DV1/2.0) 的值是: 1.0000 0.2500 2.3000 DV1 和 DV2 的內積是 : 28.7300 -------------------------------------
程式執行結果(2/2) 測試 Vector<int>: IV1 的值是: 2 5 6 IV2 的值是: 4 65 38 (IV1 + IV2)的值是: 6 70 44 (IV1/2.0) 的值是: 1 2 3 IV1 和 IV2 的內積是 : 561
23.3 矩陣 • 二維陣列稱為矩陣(matrix)。 • 向量的外積(outer product)也會產生矩陣。 • 矩陣內的單一行或單一列抽離出來也可形成向量。
使用指標定義矩陣(1/7) • 以動態配置的方式在執期間設定矩陣語法有兩種: (1) 將各列在記憶體中獨立設定 int M = 3; int N = 4; float** A = new float *[M]; for (int i=0; i<M; i++) A[i] = new float [N];
使用指標定義矩陣(2/7) • 將各列在記憶體中獨立設定 相當於在記憶中動態配置了圖23.3.1中所示的指標和記憶空間: 圖23.3.1 二維陣列的動態記憶體配置