之前有寫過一個 Point 的 structure (其實就是二維陣列上的向量)
那我們今天來做一個 multi-dimensional vector
struct MyVector
{
int n;
int* m; // 為了要動態的儲存 因為是 n 維向量,你不知道會是多少
void init(int dim);
void print();
};
void MyVector::init(int dim) // dim = dimension
{
n = dim;
m = new int[n];
for (int i = 0; i < n; ++i) // initialization
m[i] = 0;
}
void MyVector::print(
{
cout << "(";
for (int i = 0; i < n - 1; ++i)
cout << m[i] << ", ";
cout << m[n - 1] << ")\n";
}
int main()
{
MyVector v;
v.init(3);
v.m[0] = 3;
v.print(); // (3, 0, 0)
delete [] v.m;
return 0;
}
這樣子寫其實已經可以滿足我們原本想要的需求,但是還是有幾個缺點
所以我們可能希望我們寫的 structure 可以
這時候 class 就可以派上用場了,因為它可以:
在使用之前,我們必須先知道:
variable 分為兩種
function 分為兩種
class MyVector
{
int n;
int* m; // 為了要動態的儲存 因為是 n 維向量,你不知道會是多少
void init(int dim);
void print();
};
void MyVector::init(int dim) // dim = dimension
{
n = dim;
m = new int[n];
for (int i = 0; i < n; ++i) // initialization
m[i] = 0;
}
void MyVector::print(
{
cout << "(";
for (int i = 0; i < n - 1; ++i)
cout << m[i] << ", ";
cout << m[n - 1] << ")\n";
}
int main()
{
MyVector v;
v.init(3);
v.m[0] = 3;
v.print(); // (3, 0, 0)
delete [] v.m;
return 0;
}
這時候你會發現,好像沒辦法 compile
主要是因為 class 需要設定 visbility (class 與 struct 最大差別)
我們必須在 class 裡面設定三種 member
在預設值下,所有的 member 都是 private,所以我們要做打開或是關起來這件事情。
就像這樣:
class MyVector
{
private:
int n;
int* m; // 為了要動態的儲存 因為是 n 維向量,你不知道會是多少
public:
void init(int dim);
void print();
};
void MyVector::init(int dim) // dim = dimension
{
n = dim;
m = new int[n];
for (int i = 0; i < n; ++i) // initialization
m[i] = 0;
}
void MyVector::print(
{
cout << "(";
for (int i = 0; i < n - 1; ++i)
cout << m[i] << ", ";
cout << m[n - 1] << ")\n";
}
int main()
{
MyVector v;
v.init(5); // 這時候就可以
v.m[0] = 3;
v.print(); // (3, 0, 0)
delete [] v.m; // 這樣不行
return 0;
}
像是在 main function 裡面,如果我們要存取 init 這個函數,因為我們已經把它改成 public ,因此如果你宣告他就可行,但是下面的 delete [] v.m
,因為我們把 m 設置成 private ,這時候就不能叫到他了(只能在 class 裡面存取他)。
data hiding (Encapsulation 封包)**
簡單說,我們把一坨東西封包起來(像是手機或是電視),再告訴使用者要怎麼使用(說明書),你沒辦法拿他做其他的事情(就像是電視就只能看電視)。
Instance function overloading: (函式多載)
也就是說可以傳入多種情況,函式都可以運行,且可做不同結果
class MyVector
{
private:
int n;
int* m; // 為了要動態的儲存 因為是 n 維向量,你不知道會是多少
public:
void init();
void init(int dim);
void init(int dim, int value)
};
void MyVector::init()
{
n = 0;
m = nullptr;
}
void MyVector::init(int dim) // dim = dimension
{
init(dim, 0);
}
void MyVector::init(int dim, int value)
{
n = dim;
m = new int[n];
for (int i = 0; i < n; i++)
m[i] = value;
}
除此之外,class 也可以:
還記得我們剛剛想要完成的幾件事嗎?
我們目前大概只完成了第2 3 項,1 4 則需要下面的方式來達成。
他是一個在 class 中的 function,可以做到: (在物件被建立的時候)
因此他可以達成我們 自動呼叫且初始化的功能。
特性:
Constructor 的名字就跟 class 一樣。
class MyVector
{
private:
int n;
int* m;
public:
MyVector(); // Constructor
MyVector(int dim);
MyVector(int dim, int value)
};
且他不會回傳任何東西(連 void 都沒有)。
可以 overloading
沒有 parameter 的 constructor (Myvector
)會被稱為 default constructor,如果你沒有建立一個 constructor的話,系統會自己幫你建立一個(也就是說 class 裡面一定會做一個 constructor),且裡面不會做任何事情
所以把 constructor 加到我們剛剛做的程式裡:
class MyVector
{
private:
int n;
int* m;
public:
MyVector init();
MyVector inti(int dim, int value = 0); // 如果只傳一個 parameter -> value 用 0 來做
void print;
};
MyVector::MyVector()
{
n = 0;
m = nullptr;
}
Myvector::Myvector(int dim, int value)
{
n = dim;
m = new int[n];
for (int i = 0; i < n; i++)
m[i] = value;
}
void Myvector::print()
{
cout << "(";
for (int i = 0; i < n - 1; ++i)
cout << m[i] << ", ";
cout << m[n - 1] << ")\n";
}
使用:
int main()
{
Myvector v1(1);
Myvector v2(3, 8); // 現在宣告的時候就可以順便初始化
v1.print();
v2.print();
return 0;
}
這樣子我們就可以做到自動的初始化了
剩下的 release 動態配置的空間則會使用到 destructor →
destructors:
Define:
class MyVector
{
private:
int n;
int* m;
public:
~MyVector();
};
MyVector::~Myvector()
{
delete [] m;
}
MyVector::MyVector(int dim, int value)
{
n = dim;
m = new int[n];
for (int i = 0; i < n; i++)
m[i] = value;
}
int main()
{
if (true)
MyVector v1(1);
return 0;
}
如此一來,我們宣告一個 object 的時候,就不會發生 memory leak?
Order?
class A
{
public:
A(){ cout << "A\n";}
~A(){ cout << "a\n";}
};
class B
{
private:
A a;
public:
B(){ cout << "B\n";}
~B(){ cout << "b\n";}
};
int main()
{
B b;
return 0;
}
螢幕會印出:
A
B
b
a
這就是如果有 class 包含在 class 裡面的 constructor 和 destructor 呼叫的順序。
在大多數的情況下,instance variable 會是 private 的,因此為了存取他們,我們必須使用 getter & setter 來對他們做一些事。
class MyVector
{
private:
int n;
int* m;
public:
int getN { return n; }
void setN(int v){ n = v; }
};
我們可以想像,如果今天我們想要開一個權限(像是你手機的密碼),你不可能會跟陌生人說吧,所以一定是開給你感情比較好的朋友。
朋友可以是:
像是下面這個 class
class MyVector
{
//....
friend void test();
friend class Test;
};
我們可以知道 friend 的幾個特性: (以上面為例):
所以我們可以在 test 裡面使用 n
void test{
MyVector v;
v.n = 100; // 因為是朋友!
cout << v.n
}
在 Test 裡面也可以使用:
class Test{
public:
void test(MyVector){
v.n = 203;
cout << v.n;
}
};
在 class 裡面,每一個 object 都擁有自己的 instance variables 和 functions。
(這時候就會稱這些 variables & functions 是 object-specific)
但是反過來,member variable 或是 function 可以是一個 class 的屬性( attribute) 或是 operation。
(這個時候這些 variable 和 function 就會被稱為 class specific)
他們就會被稱為 static members (靜態成員: 保持不變)
舉個例子: 像是 windows 中的視窗,每一個視窗都是一個 object。這些視窗都有自己的名稱、自己專屬的大小 這些特性就會被稱為 object-specific attribute。而每一個視窗,都會有一些相同的東西,像是每個視窗都會有 title bar、這些 bar 都有一樣的顏色、都會有 — [ ] X (縮小 / 全螢幕/ 關閉) 等按鈕,這些他們擁有的相同屬性,就可以稱為一個 class-specific attribute。
class Window
{
private:
int width;
int height;
int locationX;
int locationY;
int status; // 0: min, 1:usual, 2: max
static int barColor; // 0: gray....
//.....
public:
static int getBarColor();
static void setBarColor(int color);
//....
};
另外,我們必須在 global 的環境下 初始化一個 static variable(因為全部都要用),像是這樣:
int Window::barColor = 0; // default
int Window::getBarColor()
{
return barColor;
}
void Window::setBarColor(int color)
{
barColor = color;
}
那如果我們要在 int main() 存取 static member要用
class name::member name
如果是要存取 instance 的 member:
object name.member name
使用 static member:
int main()
{
Window w;
cout << Window::getBarColor();
cout << "\n";
Window::setBarColor(1);
return 0;
}
所以現在我們就有了四種 member type:
他們倆者的關係是這樣:
w.getBarColor()
這樣子透過 object 存取 static member,但是 非常不建議 這麼使用,最好用 class 呼叫他
如果你今天要寫一個每一個 object 都要使用的特性或是 function,這時候就要使用 static member → 可以維持一致性,
不要 object 來呼叫 static member
盡量用 class 來呼叫:
int Window::getBarColor()
{
return Window::barColor;
}
instance :
找出有幾個人被 constuct
class A
{
private:
static int count;
public:
A() {A::count++; } // 因為被大家共用,所以只要有人被建立的時候,就++
static int getCount(){ return A::count; }
};
int A::count = 0;
int main(int argc, char const *argv[])
{
A a1, a2, a3;
cout << A::getCount() << "n"; // 3
return 0;
}
instance:
找出有幾個人目前還活著(alive)
class A
{
private:
static int count;
public:
A() {A::count++; }
~A() {A::count--; }
static int getCount(){ return A::count; }
};
int A::count = 0;
int main(int argc, char const *argv[])
{
if (true)
A a1, a2, a3;
cout << A::getCount() << "n"; // 0
return 0;
}
另外,在中間的那一行就是前面所說的,static variable 要在 global 初始化。
因為 class 是一種我們自定義的 data type
且 pointer 可以指向任何一種 data type
instnace:
int main()
{
MyVector v(5);
MyVector* ptrv = &v; // object pointer
return 0;
}
object pointer 的使用?
因為 pointer 就是存取 object (例如說 a ) 的位置(其實就是代表 a 的意思),所以我們可以用 *ptrA
去存取 a 中的 function,像是這樣: (*ptrA).print()
但是有另一個更簡單存取的方式,就是直接用 ->
所以上面的存取就可以寫成 ptrA ->print();
WHY OBJECT POINTERS?
當我們要做一個 object array 的時候,可以用 pointer 來延遲 constructor 的呼叫,就會害我們失去初始化的機會。
像是這個:
int main()
{
MyVector v[3]; // an object array
v[0].print(); // run-time error 因為 m 是 nullptr
return 0;
}
如果我們先呼叫了v[3],會因為我們沒辦法初始化,會導致 array 裡面n = 0, m = nullptr。
所以可以用 Dynamic object arrays 這個方法來解決:
int main()
{
MyVector* ptrV = new MyVector(5); //呼叫 constructor
ptrV->print();
delete ptrV;
return 0;
}
❌ 因為我們宣告了 5 個 object,可是只叫了一個 pointer
int main()
{
MyVector* ptrV = new MyVector[5]; // 宣告動態 array
ptrV[0].print(); // run-time error
delete [] ptrV;
return 0;
}
✅ 我們宣告了 5 個 pointer,每一次都 create一個 object,再去做 constructor (中間的 for loop),就可以完成。
int main()
{
MyVector* ptrArray[5]; //no constructor invocation
for(int i = 0; i < 5; i++)
ptrArray[i] = new MyVector(i + 1); // constructor
ptrArray[0]->print();
// some delete statements
return 0;
}
Passing object into a function
如果我們今天要寫一個程式,讓我們把每一個 vector 的質相加
MyVector sum(MyVector v1, MyVector v2, MyVector v3 )
{
// assume that their dimensions are identical
int n = v1.getN();
int* sov = new int [n]; //sov = sum of vectors
for (int i = 0; i < n; ++i)
sov[i] = v1.getM(i) + v2.getM(i) + v3.getM(i); // 把他們相加
MyVector sumOfVec(n, sov); // constructor -> 後面要寫
return sumOfVec;
}
int MyVector::getN() { return n; }
int MyVector::getM(int i) { return m[i]; }
MyVector::MyVector(int d, int v[]) // sov 是一個 array
{
n = d;
for (int i = 0; i < n; ++i)
m[i] = v[i];
}
在這個程式裡面,有 4 個 MyVector object 被創造,但是如果有更多的 object ,這時候就有點麻煩。
所以可以改成用 pointer 來寫:
MyVector sum(MyVector* v1, MyVector* v2, MyVector* v3)
{
int n = v1->getN();
int* sov = new int [n];
for (int i = 0; i < n; ++i)
sov[i] = v1->getM[i] + v2->getM[i] + v3->getM[i];
MyVector sumOfVec(n, sov);
return sumOfVec;
}
如此一來,我們就只需要創造一個 object 就好了。
但是很有可能因為 object 不夠多,所以用 pointer 的時候花的時間會更多,這樣就不太划算,因此建議 object 比較少的時候可以直接使用 object 來比較快。
Passing object references
MyVector sum(const MyVector& v1,const MyVector& v2,const MyVector& v3)
{
int n = v1->getN();
int* sov = new int [n];
for (int i = 0; i < n; ++i)
sov[i] = v1.getM[i] + v2.getM[i] + v3.getM[i];
MyVector sumOfVec(n, sov);
return sumOfVec;
}
同樣的,我們也可以傳入 reference ,而下面就把 references 當作一般變數,直接用 .getM[i]
就可以了。
而在 argument 的部分,因為我們不想要這些 reference 被更改,所以我們要用 const
來保護他們不被更改。
很多時候我們寫出來的程式,不是為了要做出甚麼功能,而是要避免一些事情的發生。
class A
{
private:
int i;
public:
A() { cout << "A"; }
};
void f(A a1, A a2, A a3)
{
A a4;
}
int main()
{
A a1, a2, a3; // AAA
cout << "\n===\n";
f(a1, a2, a3); // A
return 0;
}
這段程式會傳出:
AAA
===
A
為什麼當我們呼叫 f 的時候,只會傳出 A 而已? 而不是傳出 4 個 A(4 次 constructor)
In general, when we pass by value, a local variable will be created.
int main()
{
A a1, a2, a3; // AAA
cout << "\n===\n";
A a4 = a1; // nothing
return 0;
}
那如果我們把程式改成這樣,就會甚麼東西都不會傳出。
這是因為:
Creating an object by "copying" and object is a special operation. It happens:
when we pass an object into a function using call by value mechanism.
f(a1, a2, a3);
when we assign an object to another object.
A a4 = a1;
when we create an object with another object as the argument of the constructor.
A a5(a1);
COPY CONSTRUCTOR
這一個機制會被稱為 "copy constructor" (也就是用 copy object 的方式來建立 object)(這也是一個 default copy constructor),他甚麼事情都不會做。我們也可以手動的設定他要做甚麼事情:
class A
{
private:
int i;
public:
A() { cout << "A"; }
A( const A& a) { cout << "a"; }
};
void f(A a1, A a2, A a3)
{
A a4;
}
int main()
{
A a1, a2, a3; // AAA
cout << "\n===\n; // ===
f(a1, a2, a3); // aaaA
A a4(a1); // a
A a4 = a1; // a
return 0;
}
像是在 f(a1, a2, a3);
的時候,就會因為 copy constructor 被啟動,所以就會印出 a
。而像下面的 A a4(a1);
,也是會印出 a
; A a4 = a1;
的結果也是相同的。
我們自己也可以手動的寫出 copy constructor,大概會長:
MyVector::MyVector(const MyVector& v)
{
n = v.n;
m = v.m;
}
Shallow copy :
這個就是一般的 default copy constructor 會做的事情(前提: member中沒有 array 或是 pointer)。
那如果有 array 或是 pointer,因為 m 這個指標是指向一塊空間,但是如果是一個 array 的話,就會產生不同的指標指向同樣的空間的情況。
int main()
{
MyVector v1(5, 1);
MyVector v2(v1); //??
}
這時候記憶體中會變成:
這時候如果我們改了 v1 的時候,v2 都會一起被改。
Deep copy:
為了避免我們上述講的情形,我們就需要在 copy 的時候把一個一個 element 改掉。
首先我們要手動的宣告一個 dynamic array, 讓 m 儲存他的地址。最後在把 v.m[i]
的值取出來。
MyVector::MyVector(const MyVector& v)
{
n = v.n;
m = new int[n]; // deep copy
for(int i = 0; i < n; i++)
m[i] = v.m[i];
}
以前學 python 的時候碰到 class 的機率很少,幾乎沒寫過。
現在終於知道在幹嘛了。