筆記:侯捷老師:面向對象編程Part1

零、無指針的類、有指針的類 設計要點

  1. 構造函數初始化儘量用列表初始化的方法

  2. 對於不會改變數據內容的函數,馬上再函數後面加上const, 萬一在定義的時候沒有加,如果對象是常量,調用函數時就會出錯。

  3. 最好所有的參數傳遞都傳遞引用,

  4. 如果不希望在函數內部修改外面的實參,只需要在形參前加上const。

  5. 最好所有的函數返回都返回引用,但是要注意不能返回局部遍歷的引用

  6. 同一個類的不同對象可以互相訪問對方的私有成員

  7. 重載<<只能寫成全局形式,因爲cout在程序中只能有一個

  8. 只要有指針,就必須重寫拷貝構造函數,拷貝賦值,析構函數

    • 拷貝構造函數:先給指針對象分配空間
    • 拷貝賦值:先清空原有內容,再分配空間,接着賦值
    • 析構函數:回收原來空間
  9. 注意,拷貝賦值要檢測自我賦值,不然刪掉原來的內容的時候可能會出錯

  10. 由系統自動分配的再棧區;由程序員手動new的再堆區,需要手動刪除

  11. 內存泄漏:指針死了,佔用的空間還在

  12. new的底層: 先在內部調用malloc()分配內存,然後進行指針轉型,接着調用構造函數

  13. delete的底層: 先調用析構函數,然後在內部調用free(),刪除內存

  14. 真實創建對象的情況在內存中會由多的cookie內存用於尋址,debug版本比release版本分配更多地址。在VC中,內存塊一定是16的倍數,當內存不是16倍數的時候會自動填充空間。因爲是16的倍數,所以地址的後四位一定是0,在被分配初期後被設置成1,釋放後又變成0。

  15. delete和delete[]的區別在於:對於數組的情況,必須用delete[]。如果用delete,整塊存放指針的地址會被刪掉,但是指針指向的空間只會刪除一個,造成內存泄漏!內存泄漏是指針指向的空間,而不是整塊地址本身。

一、複數代碼

--------------------------MyComplex.h-----------------------

#pragma once
#include<iostream>
using namespace std;
class MyComplex{
public:
	friend ostream& operator << (ostream & out, const MyComplex & a);

	MyComplex(double real, double img);
	MyComplex(const MyComplex & mycmp);

	double real() const { return m_real; }
	double img() const { return m_image; }

	MyComplex& operator += (const MyComplex& mycmp);
	

private:
	double m_real;
	double m_image;
};

--------------------------MyComplex.cpp--------------------------

#include "MyComplex.h"
#include<iostream>
using namespace std;

MyComplex::MyComplex(double real = 0, double img = 0) 
	:m_real(real), m_image(img) {}

MyComplex::MyComplex(const MyComplex & mycmp) {
	m_real = mycmp.m_real;
	m_image = mycmp.m_image;
}

MyComplex& MyComplex::operator += (const MyComplex& mycmp) {
	m_real += mycmp.m_real;
	m_image += mycmp.m_image;
	return *this;
}

ostream& operator<< (ostream & cout, const MyComplex & a)
{
	cout << "real : " << a.real() << std::endl
		<< "real : " << a.img() << std::endl;
	return cout;
}

--------------------------Main.cpp--------------------------

#include <iostream>
#include "MyComplex.h"

using namespace std;
int main(int argc,char ** argv) 
{

	MyComplex a(1.2, 2.8);
	MyComplex b(a);

	a += b;
	cout << a;

	system("pause");
	return 0;
}

二、字符串代碼

--------------------------MyString.h--------------------------

#pragma once
#include<string.h>
#include<iostream>

using namespace std;


class MyString
{
public:
	friend ostream& operator << (ostream& cout,const MyString &s);
	MyString(const char *s = 0);
	MyString(const MyString &s); 
	MyString& operator =(const MyString & s);

	char * get_c_str() const {
		return m_s;
	}

	~MyString();

private:
	char *m_s;

};

--------------------------MyString.cpp--------------------------

#define _CRT_SECURE_NO_WARNINGS

#include "MyString.h"
#include <iostream>

using namespace std;

MyString::MyString(const char *s)
{
	if (s) {
		m_s = new char[strlen(s) + 1];
		strcpy(m_s, s);
	}
	else
	{
		m_s = new char[1];
		m_s[0] = '\0';		
	}
}

MyString::MyString(const MyString &s){
	if (s.m_s)
	{
		m_s = new char[strlen(s.m_s) + 1];
		strcpy(m_s, s.m_s);
	}
	else
	{
		m_s = new char[1];
		m_s[0] = '\0';
	}

}

MyString& MyString::operator =(const MyString & s) 
{
	if (this->m_s == s.m_s)		return *this;
	
	delete[] m_s;
	m_s = new char[strlen(s.m_s) + 1];
	strcpy(m_s, s.m_s);
	return *this;
}

MyString::~MyString() {
	if (m_s)
	{
		delete[] m_s;
		m_s = nullptr;
	}
};

ostream& operator << (ostream& cout, const MyString &s) {
	cout << s.get_c_str() << endl;
	return cout;
}

--------------------------Main.cpp--------------------------

#include <iostream>
#include "MyString.h"

using namespace std;

int  main(int argc, char ** argv) {
	MyString s1("first");
	MyString s2(s1);
	MyString *str = new MyString("third");
	MyString s3;
	cout << "s2:"<<s2.get_c_str() << endl;
	s3 = s1;
	cout << "s3:"<<s3.get_c_str() << endl;
	cout << str->get_c_str() << endl;
	delete str;

	system("pause");
	return 0;
}

三、 補充內容

  1. 函數在內存中只有一份,處理多個對象依靠在成員函數中隱藏傳入this指針,也就是成員對象的地址。this指針可以在函數內部使用。加了static的參數只有一份,但是static沒有this指針,所以靜態函數只能處理靜態數據。注意static成員變量在類內聲明,在類外定義(初始化)。

    #include <iostream>
    using namespace std;
    
    class Account
    {
    public:
    	static double m_rate;
    	static void set_rate(const double &x) { m_rate = x; }
    };
    
    double Account::m_rate = 0.8;
    
    int  main(int argc, char ** argv) {
    	Account::set_rate(0.5);
    
    	system("pause");
    	return 0;
    }
    
  2. namespace std{…} 防止重名,可以在不同文件中分段編寫,最後會被拼在一起,所有std的東西都包含在其中。

四、繼承 、複合、委託

Composition複合: A類中,有B類作爲成員。符號:A黑色菱形+A指向B

 構造從內而外,先B後A;析構從外而內,先A再B

Delegation委託:A類中,有指向B類的指針作爲成員。符號:A白色菱形+A指向B

什麼時候創建對象是未知的(分配空間)

Inheritance繼承:B類繼承A類。符號:A+白色三角+B指向A

先調用父類的默認構造函數,然後再調用子類的構造函數,析構順序相反
父類的析構函數必須是virtual的
父類函數的三種寫法
- non-virtual : 不希望子類覆寫              int objectID() const;
- virtual:希望子類覆寫,但是父類有默認定義     virtual void error(const std::string& msg);
- pure virtual:子類必須覆寫,沒有默認定義     virtual void draw() const = 0;

Inheritance+Composition:

問:A類中 有B類作爲成員,C類繼承A類,ABC的構造和析構順序?
答:構造:先調用Component,再調用Base,最後調用Child。 析構相反

問:B類繼承A類,B類中 有C類作爲成員,ABC的構造和析構順序?
答:構造:先調用Base類,再調用Compnent,最後調用Child。 析構相反

Delegation+Inheritance:

Observer模式:三視圖模型
Composite模式:目錄文件模型
Prototype模式:

五、設計模式

一、Singleton(static+private)

需要用到static來直接通過類名或調用函數的方法 。

單例特點:構造函數放在private中,外界無法創建該類對象

靜態單例最好放在函數中,只有當有人調用它,static對象纔會被創建,並且在之後一直存在。

靜態單例如果在構造函數中創建而沒有使用,就會造成浪費

-------------------------------------該代碼有問題,需要修改--------------------------

#include <iostream>
using namespace std;

class A {
public:
	static A& getA() {
		static A a;
		return a;
	}
	void setup(){
		cout << "setting up...." << endl;
	}
private:
	A();
	A(const A& rhs);
};

int  main(int argc, char ** argv) {

	A::getA();

	system("pause");
	return 0;
}
二、Adapter(Composition)

Adapter的模式:用於開放部分功能的設計。如果已經有成熟的類可以滿足條件,通過Adapter設計模式進行修改以滿足接口。比如deque是雙向進出,可以通過改裝後成爲queue單向進出

template<typename T>
class queue
{
public:
	bool empty()const { return c.empty(); }
	void push(const T& x) { c.push_back(x); }
	void pop() { c.pop_front(); }
	T& front() { return c.front() };
	T& back() { return c.back() };

protected:
	deque<T> c;
};
三、pImpl(Delegation)

pImpl的模式:pointer to implementaiton:用於指針指向多種實現的設計

用一個指針指向具體實現的類,右邊任何的改動都不影響左邊,也就不影響客戶端。當需要有不同的實現的時候,這個指針可以指向不同的實現類

class String{
public:
	...
private:
	StringRep* rep;
};
四、TemplateMethod(Inheritance)

TemplateMethod的模式:父類中無法寫出的方法,等子類覆寫

子類調用父類的方法,進入到父類中,發現子類覆寫了父類的函數進入到子類中。

子類在調用父類的方法時,相當於子類的地址被隱式地傳入父類函數中,從而進入到子類

class Base{
public:
	void OnFileOpen(){
	           .....
	           Serialize();
	           .....
	}
	virtual void Serialize(){};
};

class Child{
public:
	virtual void Serialize(){
		cout <<"balabala" <<endl;
	}
};

main(){
    Child child;
    child.OnFileOpen();
}
五、Observer(Delegation+Inheritance)

Observer的模式:設計一個類,這個類中存放指針隊列,這些指針指向繼承自同一父類的子類。比如說三視圖模型:主題類中有一個指針隊列,每一個指針都指向Observer的子類,Observer是父類,其每個子類都有各自的不同實現,可以用來觀察主題的多個視角。

class Subject{
	vector<Observer * > m_views;
}

class Observer{
public:
	virtual void update(int value) =0;
};

class Observer1: public Observer{
public:
	Observer1(Subject * model){model->push_back(this)}
	void update(){...}
};
class Observer2: public Observer{
public:
	Observer2(Subject * model){model->push_back(this)}
	void update(){...}
};
class Observer3: public Observer{
public:
	Observer3(Subject * model){model->push_back(this)}
	void update(){...}
};
六、Composite(Delegation+Inheritance)

Composite模式:創建一個父類,多種子類繼承這個父類,其中一個子類內部有一個指針數組,這些指針可以指向各種子類,實現這個子類存放其他子類類型和自身的功能。比如說創建一個組件父類,文件和目錄繼承組件類,目錄類中設置一個隊列,由此目錄既可以存放目錄也可以存放文件。其他例子:大窗口裏面有小窗口等。

和Observer模式的區別在於,指針隊列的位置不同。

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);
    }
}
七、Prototype(Delegation+Inheritance)

Prototype模式:怎麼和框架配合?我的媽耶,不是很明白。

提供自我複製的功能。設計一個父類,父類中寫有克隆方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章