數據結構(02)_模板庫的基礎設施搭建
8.1.引言
數據額結果課程專註於數據元素之間的關系,和數據元素的操作方法,並不關系具體的數據類型,所以選擇支持泛型編程的語言最為合適數據結構課程的學習。
8.2.泛型編程的概念
不考慮具體的數據類型的編程方式稱為泛型編程,舉例,對於swap函數考慮下面的泛型寫法。
void swap(T a, T b)
{
T t = a;
a = b;
b = t
}
這裏的T不是指具體的數據類型,而是泛指任意的數據類型。在C++語言中泛型編程通過模板實現。
8.3.C++的函模板模板
函數模板是一種特殊的函數,可以使用不同類型進行調用,看起來和普通函數很相似,區別是類型可以被參數化。
函數模板的使用有兩種方式:
8.4.C++類模板
以相同的方式處理不同的類型,在類聲明前使用template進行標識。
類模板的應用:
只能顯示的指定具體的類型,無法自動推導,使用具體類型定義對象:
編程實驗:
#include <iostream> using namespace std; template <typename T> void Swap(T& a, T& b) { T t = a; a = b; b = t; } template <typename T> class Op { public: T process(T v) { return v * v; } }; int main() { int a = 2; int b = 1; Swap(a, b); cout << "a = " << a << " " << "b = " << b << endl; double c = 0.01; double d = 0.02; Swap<double>(d, c); cout << "c = " << c << " " << "d = " << d << endl; Op<int> opInt; Op<double> opDouble; cout << "5 * 5 = " << opInt.process(5) << endl; cout << "0.3 * 0.3 = " << opDouble.process(0.3) << endl; return 0; }
9.智能指針
9.1.內存泄漏
動態內存申請後,用完後不歸還會導致內存泄漏;C++語言中沒有垃圾回收機制,指針無法控制所執行的堆空間的生命周期。
9.2.智能指針
使用指針對象代替原生指針,這樣在指針生命周期結束時,可以自動調用析構函數,歸還對象所使用的堆空間
實現思路:重載指針操作符( *和-> )
一片堆空間只能由一個指針類標識,杜絕指針運算(重載拷貝構造函數、和賦值操作符完成堆空間所有權的轉接)
編程實驗
#ifndef SMARTPOINTER_H #define SMARTPOINTER_H namespace DTLib { template<typename T> class SmartPointer { protected: T* m_pointer; public: SmartPointer(T* p =NULL) { m_pointer = p; } SmartPointer(const SmartPointer<T>& obj) { m_pointer = obj.m_pointer; const_cast<SmartPointer<T>&>(obj).m_pointer = NULL; } SmartPointer<T>& operator=(const SmartPointer<T>& obj) { if(this != &obj) { delete m_pointer; m_pointer = obj.m_pointer; const_cast<SmartPointer<T>&>(obj).m_pointer = NULL; } return *this; } T* operator->() { return m_pointer; } T& operator*() { return *m_pointer; } bool isNULL() { return(m_pointer == NULL); } T* get() { return m_pointer; } ~SmartPointer() { delete m_pointer; } }; } #endif // SMARTPOINTER_H
10.C++異常簡介
C++中的異常處理:
try處理正常邏輯、throw用於拋出異常、catch用於捕獲異常
如果一個異常沒有被處理,會沿著函數的調用棧向上傳播,直至被處理,或著程序異常終止。
catch捕獲異常時會嚴格匹配,不進行任何形式的轉換,catch(…)用與捕獲所有異常,放在最後,每一個異常只能被捕獲一次
父子兼容原則適用、所以捕獲子類的異常在上、父類在下(子類對象可以看做一個父類對象)
編程實驗:
#include <iostream>
using namespace std;
double divide(double a, double b)
{
const double delta = 0.000000000000001;
double ret = 0;
if( !((-delta < b) && (b < delta)) ) {
ret = a / b;
}
else {
throw 0; // 產生除 0 異常
}
return ret;
}
void Demo1()
{
try
{
//throw 3;
//throw 5.0;
throw ‘c‘;
}
catch(int i)
{
cout << "catch(int i)" << endl;
}
catch(double d)
{
cout << "catch(double d)" << endl;
}
catch(char c)
{
cout << "catch(char c)" << endl;
}
}
void Demo2()
{
//throw 0.0001;
//throw "D.T.Software";//const char*
}
int main()
{
cout << "main() begin" << endl;
try
{
double c = divide(1, 1);
cout << "c = " << c << endl;
}
catch(...)
{
cout << "Divided by zero..." << endl;
}
Demo1();
try
{
Demo2();
}
catch(char* c)
{
cout << "catch(char* c)" << endl;
}
catch(const char* cc)
{
cout << "catch(char* cc)" << endl;
}
catch(...)
{
cout << "catch(...)" << endl;
}
cout << "main() end" << endl;
return 0;
}
11.異常類構建
11.1. 標準庫異常類族
現代C++庫必然包含沖要的異常類族
11.2. 自定義異常類族
異常類中的接口定義:
#define THROW_EXCEPTION(e, m) (throw e(m, __FILE__, __LINE__))
class Exception
{
protected:
char* m_message;
char* m_location;
public:
void init(const char* message, const char* file, int line);
Exception(const char* message);
Exception(const char* file, int line);
Exception(const char* message, const char* file, int line);
Exception(const Exception& e);
Exception& operator =(const Exception& e);
virtual const char* message() const;
virtual const char* location() const;
virtual ~Exception() = 0;
};
編程實現,Exception.cpp
#include "Exception.h"
#include <cstdlib>
#include <cstring>
using namespace std;
namespace DTLib
{
void Exception::init(const char *message, const char *file, int line)
{
m_message = ( message ? strdup(message) : NULL);
if(NULL != file)
{
char sl[16] {0};
itoa(line, sl, 10);
m_location = static_cast<char*>(malloc(strlen(sl) +strlen(file) + 2));
m_location = strcpy(m_location, file);
m_location = strcat(m_location, ":");
m_location = strcat(m_location, sl);
}
else
{
m_location = NULL;
}
}
Exception::Exception(const char *message)
{
init(message, NULL, 0);
}
Exception::Exception(const char* file, int line)
{
init(NULL, file, line);
}
Exception::Exception(const char *message, const char *file, int line)
{
init(message, file, line);
}
Exception::Exception(const Exception& e)
{
m_message = strdup(e.m_message);
m_location = strdup(e.m_location);
}
Exception& Exception::operator= (const Exception& e)
{
if( this != &e)
{
free(m_message);
free(m_location);
m_message = strdup(e.m_message);
m_location = strdup(e.m_location);
}
return *this;
}
const char* Exception::message() const
{
return m_message;
}
const char* Exception::location() const
{
return m_location;
}
Exception::~Exception()
{
free(m_location);
free(m_message);
}
}
編程實現,Exception.h
#ifndef EXCEPTION_H
#define EXCEPTION_H
#include "Object.h"
namespace DTLib
{
#define THROW_EXCEPTION(e, m) (throw e(m, __FILE__, __LINE__))
class Exception : public Object
{
protected:
char* m_message;
char* m_location;
void init(const char *message, const char *file, int line);
public:
Exception(const char *message);
Exception(const char* file, int line);
Exception(const char *message, const char *file, int line);
Exception(const Exception& e);
Exception& operator= (const Exception& e);
virtual const char* message() const;
virtual const char* location() const;
virtual ~Exception() = 0;
};
class ArithmeticException : public Exception
{
public:
ArithmeticException() : Exception(0){}
ArithmeticException(const char* message) : Exception(message){}
ArithmeticException(const char* file, int line) : Exception(file, line){}
ArithmeticException(const char* message, const char* file, int line) : Exception(message, file, line){}
ArithmeticException(const ArithmeticException& e) : Exception(e) {}
ArithmeticException& operator =(const ArithmeticException& e)
{
Exception :: operator =(e);
return *this;
}
};
class NoEnoughMemoryException : public Exception
{
public:
NoEnoughMemoryException() : Exception(0){}
NoEnoughMemoryException(const char* message) : Exception(message){}
NoEnoughMemoryException(const char* file, int line) : Exception(file, line){}
NoEnoughMemoryException(const char* message, const char* file, int line) : Exception(message, file, line){}
NoEnoughMemoryException(const NoEnoughMemoryException& e) : Exception(e) {}
NoEnoughMemoryException& operator =(const NoEnoughMemoryException& e)
{
Exception :: operator =(e);
return *this;
}
};
class IndexOutOfBoundsException : public Exception
{
public:
IndexOutOfBoundsException() : Exception(0){}
IndexOutOfBoundsException(const char* message) : Exception(message){}
IndexOutOfBoundsException(const char* file, int line) : Exception(file, line){}
IndexOutOfBoundsException(const char* message, const char* file, int line) : Exception(message, file, line){}
IndexOutOfBoundsException(const IndexOutOfBoundsException& e) : Exception(e) {}
IndexOutOfBoundsException& operator =(const IndexOutOfBoundsException& e)
{
Exception :: operator =(e);
return *this;
}
};
class NullPointerException : public Exception
{
public:
NullPointerException() : Exception(0){}
NullPointerException(const char* message) : Exception(message){}
NullPointerException(const char* file, int line) : Exception(file, line){}
NullPointerException(const char* message, const char* file, int line) : Exception(message, file, line){}
NullPointerException(const NullPointerException& e) : Exception(e) {}
NullPointerException& operator =(const NullPointerException& e)
{
Exception :: operator =(e);
return *this;
}
};
class InvaildParemeterException : public Exception
{
public:
InvaildParemeterException() : Exception(0){}
InvaildParemeterException(const char* message) : Exception(message){}
InvaildParemeterException(const char* file, int line) : Exception(file, line){}
InvaildParemeterException(const char* message, const char* file, int line) : Exception(message, file, line){}
InvaildParemeterException(const InvaildParemeterException& e) : Exception(e) {}
InvaildParemeterException& operator =(const InvaildParemeterException& e)
{
Exception :: operator =(e);
return *this;
}
};
class InvalidOperationException : public Exception
{
public:
InvalidOperationException() : Exception(0){}
InvalidOperationException(const char* message) : Exception(message){}
InvalidOperationException(const char* file, int line) : Exception(file, line){}
InvalidOperationException(const char* message, const char* file, int line) : Exception(message, file, line){}
InvalidOperationException(const InvalidOperationException& e) : Exception(e) {}
InvalidOperationException& operator =(const InvalidOperationException& e)
{
Exception :: operator =(e);
return *this;
}
};
}
#endif // EXCEPTION_H
11.3. .設計原則
在構建可復用的庫時,盡量使用面向對象技術進行架構,盡量使用異常處理機制分離正常邏輯和異常邏輯。
註意:
1.註意對於重虛函數一般不實現,在子類中才會實現,但是析構函數例外,一但定義,就必須要有實現,否則析構過程會出錯。
2.為什麽不直接將message賦值給初始化函數
原因在於message所指向的字符串可能位於棧、堆、全局數據區,我們無法區控制其生命周期,這樣做不夠安全。
12.頂層父類的創建
12. 1.當代軟件架構實踐中的經驗:
—盡量使用單純繼承的方式進行系統設計
—盡量保持系統中只存在單一的繼承樹
—盡量使用組合關系代替繼承關系
12.2.不幸的事實:
C++的語法足夠強大、靈活,使得代碼中可以存在多個繼承樹
C++編譯器的差異使得同樣的代碼可能表現不同的行為(譬如new失敗通常會返回一個空指針,但有寫編譯器會選擇拋出一個異常)
12.3.自定義頂層父類
EricLib::Object的意義:
遵循經典的設計原則,所有的數據結構都繼承自Object類
規範動態類型申請的行為(new失敗返回一個空指針),提高代碼的可移植性
接口定義如下:
class Object
{
public:
void* operator new (unsigned int size) throw();
void operator delete (void* p);
void* operator new[] (unsigned int size) throw();
void operator delete[] (void* p);
virtual ~Object() = 0;
};
編程實現,Object.cpp
#include "Object.h"
#include <cstdlib>
using namespace std;
namespace DTLib
{
void* Object::operator new(unsigned int size) throw()
{
return malloc(size);
}
void Object::operator delete(void *p) throw()
{
free(p);
}
void* Object::operator new[](unsigned int size) throw()
{
return malloc(size);
}
void Object::operator delete[](void *p) throw()
{
free(p);
}
bool Object::operator == (const Object& obj)
{
return this == &obj;
}
bool Object::operator != (const Object& obj)
{
return this != &obj;
}
Object::~Object()
{
}
}
Object.h
#ifndef OBJECT_H
#define OBJECT_H
namespace DTLib
{
class Object
{
public:
// don‘t throw any exception,even if alloc fail.
void* operator new(unsigned int size) throw();
void operator delete(void *p) throw();
void* operator new[](unsigned int size) throw();
void operator delete[](void *p) throw();
bool operator == (const Object& obj);
bool operator != (const Object& obj);
virtual ~Object() =0; // Heavy virtual function(inherited only).
};
}
#endif // OBJECT_H
13.單一繼承樹優化
1.遵循經典的設計原則,所有的EricLib中的類位於單一的繼承樹
2.改進的關鍵點:
- Exception類繼承自Object類,堆空間創建對象失敗,返回NULL
- 新增InvalidOperationException異常類,調用狀態不正確時拋出異常
- SmartPointer類繼承自Object類
3.EricLib的開發方式和註意事項 - 叠×××發,每次完成一個小目標
- 單一繼承樹
- 只拋出異常但不處理異常
使用THROW_EXCEPTION拋出異常,提高可移植性(在一些比較老的C++編譯器中是不支持異常處理機制的,其次有些軟件公司也不允許使用異常處理機制)。如果將來用於不支持異常處理的情況時,我們只要將THROW_EXCEPTION這個宏定義為空即可。 - 若耦合性,盡量不使用標準庫中的類和函數
編程實驗:略
註意:
1.為什麽沒有在init函數中內存申請失敗時拋出異常,見代碼
1)從代碼允許邏輯來講如果此處拋出異常,最終會生成一個Exception對象構造時將再次回到這裏
2)從邏輯分析,如果拋出異常,則應該拋出NoEnoughMemoryException這個子類對象,父類對象都沒有生成子類對象如何產生
數據結構(02)_模板庫的基礎設施搭建