1. 程式人生 > 其它 >筆記:C++面向物件高階程式設計--侯捷

筆記: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;