筆記:C++面向物件高階程式設計--侯捷
Complex--class without pointer member(s)
防衛式申明
防止標頭檔案被重複包含
#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif
建構函式
class complex { public: complex (double r = 0, double i = 0) : re (r), im (i) //initialization list { } /* complex (double r = 0, double i = 0) assignments賦值 { re = r; im = i; } */ complex& operator += (const complex&); double real () const { return re; } double imag () const { return im; } private: double re, im; friend complex& __doapl (complex*, const complex&); };
建構函式優先使用初始化列表,效率更高。使用賦值函式的形式效率更低。
對於單純訪問成員變數,而不對成員變數進行修改的函式,加上const對變數進行保護。
函式的入參儘量使用引用傳參(pass by reference)
函式的返回值,如果返回函式內部的區域性變數,返回值使用值傳遞(pass by reference),若返回值為外部變數,在函式執行完畢後,變數的生命週期未結束,使用引用傳遞作。
class template 模板類
template<typename T> class complex { public: complex (T r = 0, T i = 0): re (r), im (i){ } //函式1 complex& operator += (const complex&); T real () const { return re; } //函式2 T imag () const { return im; } //函式3 private: T re, im; friend complex& __doapl (complex*, const complex&); }; { complex<double> c1(2.5,1.5); complex<int> c2(2,6); ... }
函式若在class body內定義完成(上述函式1、2、3),便自動成為inline候選人,inline只是對編譯器的一種建議,若函式複雜,即使加上了inline,編譯器仍然將其編譯為正常函式。
建構函式放在privite---單例模式
class A { public: static A& getInstance(); setup() { ... } private: A(); A(const A& rhs); ... }; A& A::getInstance() { static A a; return a; }
friend (友元)
有元函式自由取得friend的private成員變數。
inline complex& __doapl (complex* ths, const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
相同 class 的各個 objects 互為 friends (友元)。
class complex
{
public:
complex (double r = 0, double i = 0): re (r), im (i)
{ }
int func(const complex& param)
{ return param.re + param.im; } //隨意訪問不同objects的private成員變數
private:
double re, im;
};
操作符過載---operator overloading
成員函式過載(含this指標)
對於操作符作用於兩側的函式,可以將其設定為成員函式,呼叫的時候如:
inline complex& __doapl(complex* ths, const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
inline complex& complex::operator += (const complex& r)
{
return __doapl (this, r);
}
{
complex c1(2,1);
complex c2(5);
complex c3(6);
c2 += c1;
c3 += c2 += c1;
}
return by reference 語法分析:
如上述操作符過載operator +=返回值不是return by reference,而是return by value,則在連加時,第一個+=呼叫完畢後,返回的是臨時變數,而不是c3.故第二個+=不會作用在c3上,導致連續的+=失效。
非成員函式過載(不含this指標)
二元操作符
inline double imag(const complex& x)
{
return x.imag ();
}
inline double real(const complex& x)
{
return x.real ();
}
//為了應對使用者可能出現的三種用法,這裡對應開發了三個函式
inline complex operator + (const complex& x, const complex& y) //複數+複數
{
return complex (real (x) + real (y), imag (x) + imag (y));
}
inline complex operator + (const complex& x, double y) //複數+實數
{
return complex (real (x) + y, imag (x));
}
inline complex operator + (double x, const complex& y) //實數+複數
{
return complex (x + real (y), imag (y));
}
{
complex c1(2,1);
complex c2;
c2 = c1 + c2;
c2 = c1 + 5;
c2 = 7 + c1;
}
上述函式均不能使用return by reference,而是return by value,因為返回值都是local object 。
一元操作符
inline complex operator + (const complex& x) //可以return by reference
{
return x;
}
inline complex operator - (const complex& x) //local object,不能return by reference
{
return complex (-real (x), -imag (x));
}
非成員函式
#include <iostream.h>
ostream& operator << (ostream& os, const complex& x)
{
return os << '(' << real (x) << ','<< imag (x) << ')';
}
{
cout << c1;
cout << c1 << c2;
}
如果不將operator <<設定為外部函式,而將其設定為complex的成員函式,則在呼叫的時:
c1 << cout; //不符合常規的用法
String--class with pointer member(s)
Big Three
由於物件帶有指標變數,必須重寫拷貝建構函式、拷貝賦值函式、解構函式。
class String
{
public:
String(const char* cstr = 0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
}
建構函式和解構函式
inline String::String(const char* cstr = 0)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else { // 未指定初值
m_data = new char[1];
*m_data = '\0';
}
}
inline String::~String()
{
delete[] m_data;
}
//示例
{
String s1(),
String s2("hello");
String* p = new String("hello");
delete p;
}
拷貝建構函式
inline String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
//示例
{
String s1("hello ");
String s2(s1);
// String s2 = s1; 仍然呼叫的是拷貝建構函式
}
必須為pass by reference,如果pass by value,則會導致在入參時不斷重複呼叫拷貝建構函式。
拷貝賦值函式
inline String& String::operator=(const String& str)
{
if (this == &str) //檢測自我賦值
return *this;
delete[] m_data; //釋放原有記憶體
m_data = new char[ strlen(str.m_data) + 1 ]; //申請新的記憶體
strcpy(m_data, str.m_data); //拷貝內容
return *this;
}
//示例
{
String s1("hello ");
String s2(s1);
s2 = s1;
}
上述檢測自我賦值if (this == &str)的必要性:
- 提高演算法的執行效率
- 若無檢測自我賦值,進入函式後,第一時間釋放了原有資源,後續申請記憶體和拷貝內容都會出錯,因為原有的空間已經被釋放掉了
output 函式
#include <iostream.h>
ostream& operator<<(ostream& os, const String& str) //外部函式
{
os << str.get_c_str();
return os
}
Stack和Heap
Stack是存在於某作用域 (scope) 的一塊記憶體空間,例如當你呼叫函式,函式本身會形成一個stack來放置它所接收的引數以及返回地址。在函式本體內申明的任何變數,其所在的記憶體塊都取自上述Stack。
Heap,也稱為systerm heap,是指由作業系統提供的一塊global記憶體空間,程式可以動態分配(dynamic allocated) ,從中獲取若干區塊 (blocks)
class Complex { ... };
...
Complex c3(1,2);
{
Complex c1(1,2); //local object
static Complex c2(1,2); //static local object
Complex* p = new Complex;
...
delete p
}
c1 是 stack object,其生命在作用域 (scope) 結束之後結束。這種作用域內的 object,又稱為 auto object,因為它會被自動清理。
c2 是 static object,其生命在作用域 (scope)結束之後仍然存在,直到整個程式結束。
c3 是 global object,其生命在整個程式結束之後才結束。你也可以把它視為一種 static object,其作用域是整個程式。
P 所指的是 heap object,其生命在它被 deleted 之後結束。
記憶體洩漏(memory leak)
class Complex { … };
...
{
Complex* p = new Complex;
}
以上程式會出現記憶體洩漏 (memory leak),因為當作用域結束後,p所指的heap object 仍然存在,但指標 p 的生命卻結束了,作用域之外再也看不到p,也就沒有機會delete p,p所指向的記憶體空間在程式執行期間無法被再次使用。
new
Complex *pc;
void* mem = operator new( sizeof(Complex) ); //分配記憶體
pc = static_cast<Complex*>(mem); //型別轉換
pc->Complex::Complex(1,2); //建構函式
new的過程分為以上三個步驟,首先,呼叫operator new申請記憶體空間,operator new內部最終會呼叫到malloc(),然後進行型別轉換,將其轉化為對應的資料型別,最後呼叫建構函式。
delete
String::~String(ps); // 解構函式
operator delete(ps); // 釋放記憶體
delete的過程分為兩步,首先呼叫解構函式,然後呼叫operator delete釋放記憶體,operator delete最終會呼叫到free(ps)。
delete與delete[]
String* p = new String[3];
...
delete[] p; //喚起3次解構函式
String* p = new String[3];
...
delete p; //喚起1次解構函式
上述第二種情況會造成記憶體洩漏,不是p所指向的記憶體空間洩漏,而是陣列元素所指向的記憶體空間洩漏。
Static
static成員變數不屬於任何一個例項化的物件, 不佔用物件的記憶體大小。
static成員函式入參無this指標,所以無法訪問任何一個具體成員的非靜態成員變數,只能訪問靜態成員變數。
呼叫static成員函式的方法:
- 通過object呼叫
- 通過class name呼叫
Composition (複合).has-a
template <class T, class Sequence = deque<T> >
class queue {
...
protected:
Sequence c; // 底層容器
public:
// 以下完全利用 c 的操作函式完成
bool empty() const { return c.empty(); }
size_type size() const { return c.size(); }
reference front() { return c.front(); }
reference back() { return c.back(); }
void push(const value_type& x) { c.push_back(x); }
void pop() { c.pop_front(); }
};
Composition是一種強耦合關係,在構建queue時,會完成c的構建。
上述其實使用的是介面卡模式,所以queue也被稱為介面卡容器。
Delegation (委託). Composition by reference
class StringRep;
class String {
public:
String();
String(const char* s);
String(const String& s);
String &operator=(const String& s);
~String();
. . . .
private:
StringRep* rep; // pimpl
};
class StringRep {
friend class String;
StringRep(const char* s);
~StringRep();
int count;
char* rep;
};
Delegation 是一種弱耦合關係,在構建String時,不一定需要完成StringRep的構建。
上述其實使用的是引用計數的方式,多個char*指標指向相同的內容時,不需要重複申請記憶體空間。智慧指標使用的是相同的方法。
Inheritance (繼承).表示 is-a
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
template<typename _Tp>
struct _List_node : public _List_node_base
{
_Tp _M_data;
};
繼承是一種強耦合關係,其構建順序是先構建base類,再完成構建Derived類。
Derived::Derived Derived::Derived((……): ): Base() Base() {{ …… }; };
其析構順序是先執行Derived類解構函式,再執行base類解構函式
Derived::~Derived Derived::~Derived((……){ ){ …… ~Base()};
存在繼承關係時必須將父類的解構函式設定為virtual ,防止出現記憶體洩漏。
Template Method--模板方法模式
#include <iostream>
using namespace std;
class CDocument
{
public:
void OnFileOpen()
{
// 這是個演算法,每一個cout代表一個實際的操作
cout << "dialog..." << endl;
cout << "check file status..." << endl;
cout << "open file..." << endl;
Serialize();
cout << "close file..." << endl;
cout << "update all views..." << endl;
}
virtual void Serialize() { };
};
class CMyDoc : public CDocument
{
public:
virtual void Serialize()
{
// 具體的業務場景去寫具體的實現
cout << "CMyDoc::Serialize()" << endl;
}
};
Composite--複合模式
Delegation (委託) + Inheritance (繼承)
class Component
{
int value;
public:
Component(int val) { value = val; }
virtual void add( Component* ) { }
};
class Primitive: public Component
{
public:
Primitive(int val): Component(val) {}
};
class Composite: public Component
{
vector <Component*> c;
public:
Composite(int val): Component(val) { }
void add(Component* elem) {
c.push_back(elem);
}
…
};
應用場景:如檔案系統中,每一個資料夾內部既可以包含具體檔案,有可以包含資料夾。
Observer--觀察者模式
class Subject
{
int m_value;
vector<Observer*> m_views;
public:
void attach(Observer* obs)
{
m_views.push_back(obs);
}
void set_val(int value)
{
m_value = value;
notify();
}
void notify()
{
for (int i = 0; i < m_views.size(); ++i)
m_views[i]->update(this, m_value);
}
};
class Observer
{
public:
virtual void update(Subject* sub, int value) = 0;
};
Prototype --原型模式
Prototype模式提供了一個通過已存在物件進行新物件建立的介面(Clone)
#include <iostream.h>
enum imageType
{
LSAT, SPOT
};
class Image
{
public:
virtual void draw() = 0;
static Image *findAndClone(imageType);
protected:
virtual imageType returnType() = 0;
virtual Image *clone() = 0;
static void addPrototype(Image *image)
{
_prototypes[_nextSlot++] = image;
}
private:
static Image *_prototypes[10];
static int _nextSlot;
};
Image *Image::_prototypes[]; //申請空間儲存原型類
int Image::_nextSlot;
Image *Image::findAndClone(imageType type)
{
for (int i = 0; i < _nextSlot; i++)
if (_prototypes[i]->returnType() == type)
return _prototypes[i]->clone();
}
class LandSatImage: public Image
public:
imageType returnType() {
return LSAT;
}
void draw() {
cout << "LandSatImage::draw " << _id << endl;
}
Image *clone() {
return new LandSatImage(1);
}
protected:
LandSatImage(int dummy) {
_id = _count++;
}
private:
static LandSatImage _landSatImage;
LandSatImage() {
addPrototype(this);
}
int _id;
static int _count;
};
LandSatImage LandSatImage::_landSatImage;
int LandSatImage::_count = 1;