面向對象編程(上)
零、無指針的類、有指針的類 設計要點
-
構造函數初始化儘量用列表初始化的方法
-
對於不會改變數據內容的函數,馬上再函數後面加上const, 萬一在定義的時候沒有加,如果對象是常量,調用函數時就會出錯。
-
最好所有的參數傳遞都傳遞引用,
-
如果不希望在函數內部修改外面的實參,只需要在形參前加上const。
-
最好所有的函數返回都返回引用,但是要注意不能返回局部遍歷的引用
-
同一個類的不同對象可以互相訪問對方的私有成員
-
重載<<只能寫成全局形式,因爲cout在程序中只能有一個
-
只要有指針,就必須重寫拷貝構造函數,拷貝賦值,析構函數
- 拷貝構造函數:先給指針對象分配空間
- 拷貝賦值:先清空原有內容,再分配空間,接着賦值
- 析構函數:回收原來空間
-
注意,拷貝賦值要檢測自我賦值,不然刪掉原來的內容的時候可能會出錯
-
由系統自動分配的再棧區;由程序員手動new的再堆區,需要手動刪除
-
內存泄漏:指針死了,佔用的空間還在
-
new的底層: 先在內部調用malloc()分配內存,然後進行指針轉型,接着調用構造函數
-
delete的底層: 先調用析構函數,然後在內部調用free(),刪除內存
-
真實創建對象的情況在內存中會由多的cookie內存用於尋址,debug版本比release版本分配更多地址。在VC中,內存塊一定是16的倍數,當內存不是16倍數的時候會自動填充空間。因爲是16的倍數,所以地址的後四位一定是0,在被分配初期後被設置成1,釋放後又變成0。
-
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;
}
三、 補充內容
-
函數在內存中只有一份,處理多個對象依靠在成員函數中隱藏傳入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; }
-
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模式:怎麼和框架配合?我的媽耶,不是很明白。
提供自我複製的功能。設計一個父類,父類中寫有克隆方法