十七、物件的構造
阿新 • • 發佈:2018-12-09
1、成員變數的初始值
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } }; Test gt; // 全域性物件 全域性區,統一初始值為0 int main() { printf("gt.i = %d\n", gt.getI()); // 0 printf("gt.j = %d\n", gt.getJ()); // 0 Test t1; // 區域性物件 棧區 printf("t1.i = %d\n", t1.getI()); // 隨機值 printf("t1.j = %d\n", t1.getJ()); // 隨機值 Test* pt = new Test; // 類也是一個數據型別,堆區 printf("pt->i = %d\n", pt->getI()); // 堆區應該也是隨機值 printf("pt->j = %d\n", pt->getJ()); delete pt; return 0; }
2、物件的初始化
從程式設計的角度,物件只是變數,因此:
- 在棧上建立物件時,成員變數初始為隨機值
- 在堆上建立物件時,成員變數初始為隨機值
- 在靜態儲存區建立物件時,成員變數初始為0
靜態儲存區包括了全域性變數和static
修飾的區域性變數
需要解決的問題:使類的成員變數不管在哪個儲存區進行定義,它的初始值都是固定的。
物件的初始化:
- 一般而言,物件都需要一個確定的初始狀態
-
解決方案:
- 在類中提供一個
public
的initialize
函式 - 在物件建立後立即呼叫
initialize
函式進行初始化
- 在類中提供一個
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } void initialize() { i = 1; j = 2; } }; Test gt; int main() { gt.initialize(); printf("gt.i = %d\n", gt.getI()); printf("gt.j = %d\n", gt.getJ()); Test t1; t1.initialize(); printf("t1.i = %d\n", t1.getI()); printf("t1.j = %d\n", t1.getJ()); Test* pt = new Test; pt->initialize(); printf("pt->i = %d\n", pt->getI()); printf("pt->j = %d\n", pt->getJ()); delete pt; return 0; }
這種方式存在的問題:
-
initialize
只是一個普通函式,必須顯示呼叫 - 如果未呼叫
initialize
函式,執行結果是不確定的
這個初始化函式在物件建立之手就必須馬上呼叫,新建物件之手,需要人工手動新增initialize()
函式,如果可以有一個函式在建立物件後自動呼叫,初始化成員變數就是極好的。
於是C++出現了建構函式來解決這個問題
3、建構函式
C++中可以定義與類名相同的特殊成員函式:建構函式
- 建構函式沒有任何返回型別的宣告
- 建構函式在物件定義時自動被呼叫
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } void initialize() { i = 1; j = 2; } // 建構函式 // 沒有返回值,名字和類名一樣 Test() { i = 1; j = 2; } }; Test gt; int main() { //gt.initialize(); printf("gt.i = %d, gt.j = %d\n", gt.getI(), gt.getJ()); Test t1; //t1.initialize(); printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ()); Test * pt = new Test; //pt->initialize(); printf("pt->i = %d, pt->j = %d\n", pt->getI(), pt->getJ()); return 0; }
4、帶引數的建構函式
建構函式和普通函式的差別:建構函式沒有返回值,名字和型別一樣
此時就只剩下引數可以討論:建構函式也可以帶引數
帶有引數的建構函式:
- 建構函式可以根據需要定義引數
- 一個類中可以存在多個過載的建構函式
- 建構函式的過載遵循C++過載的規則
class Test
{
public:
Test(int v)
{
// use v to initialize member
}
};
注意:
物件定義和物件宣告不同:
- 物件定義——申請物件的空間並呼叫建構函式
- 物件宣告——告訴編譯器存在這樣一個物件
Test t; // 定義物件並呼叫建構函式
int main()
{
// 告訴編譯器存在名為t的Test物件
extern Test t;
return 0;
}
建構函式的自動呼叫
class Test {
public:
Test(){}
Test(int v) { }
Test(const int& cv){} // 拷貝建構函式
};
Test t; // 呼叫建構函式Test()
Test t1(1); // 定義了一個物件t1,並呼叫帶有引數的建構函式,傳入引數為1,根據過載規則,建構函式為Test(int v)
Test t2 = 1; // 用 1 來初始化物件t2,初始化需要藉助建構函式,根據過載規則,選擇Test(int v)
/*這裡的過程其實是:
首先呼叫建構函式Test(int v)建立一個臨時物件,引數為1;
然後就變成了用一個物件初始化另一個物件,此時應該是要呼叫拷貝建構函式進行成員變數值的複製,將這個臨時物件作為引數用來構造物件t2。
但是編譯器發現,可以通過過載的建構函式Test(int v)來直接初始化物件,而達到相同效果,所以將這條語句優化為Test t1(1)
*/
初始化和賦值:
#include <stdio.h>
class Test
{
public:
Test()
{
printf("Test()\n");
}
Test(int v)
{
printf("Test(int v), v = %d\n", v);
}
};
int main()
{
Test t; // 呼叫 Test()
Test t1(1); // 呼叫 Test(int v)
Test t2 = 2; // 呼叫 Test(int v)
int i = 1; // 用1來初始化變數i
i = 2; // 用2對變數i進行賦值
t = t2; // 用物件t2對物件t進行賦值
int i(100); // 用100來初始化i
printf("i = %d\n", i);
return 0;
}
初始化和賦值是不一樣的,C語言中差別不大,C++中差別很大,因為物件的初始化要呼叫建構函式
建構函式的呼叫:
- 一般情況下,建構函式在物件定義時被自動呼叫
- 一些特殊情況下,需要手工呼叫建構函式
5、建立一個數組
#include <stdio.h>
class Test
{
private:
int m_value;
public:
Test()
{
printf("Test()\n");
m_value = 0;
}
Test(int v)
{
printf("Test(int v), v = %d\n", v);
m_value = v;
}
void getValue()
{
return m_value;
}
};
int main()
{
Test ta[3]; // 呼叫3次Test() ,每個陣列元素中的m_value都按Test()來處理,不一定需要這樣的結果
Test ta2[3] = {Test(), Test(1), Test(2)}; // 手工呼叫建構函式,3個數組元素呼叫不同的建構函式
for (int i = 0; i < 3; i++)
{
printf("ta[%d].getValue() = %d\n", i, ta[i].getValue());
// 手工呼叫建構函式後,m_value初始化成不同值
}
Test t = Test(100); // 建立物件之後,呼叫建構函式來初始化物件
return 0;
}
需求:開發一個數組類解決原生陣列的安全性問題
- 提供函式獲取陣列長度
- 提供函式獲取函式元素
- 提供函式設定陣列元素
// IntArray.h
#ifndef _INTARRAY_H_
#define _INTARRAY_H_
class IntArray
{
private:
int m_length;
int* m_pointer;
public:
IntArray(int len);
int length(); // 獲取陣列長度
bool get(int index, int& value); // 得到對應位置的值
bool set(int index ,int value); // 設定對應位置的值
void free();
};
#endif
// IntArray.c
#include "IntArray.h"
// 建構函式
IntArray::IntArray(int len)
{
// 資料指標指向堆空間內的一段記憶體
m_pointer = new int[len];
// 初始值的指定
for (int i = 0; i < len; i++)
{
m_pointer[i] = 0;
}
m_length = len;
}
int IntArray::length()
{
return m_length;
}
bool IntArray::get(int index, int& value)
{
// 判斷位置是否越界
bool ret = (0 <= index) && (index < length());
if (ret)
{
value = m_pointer[index];
}
return ret;
}
bool IntArray::set(int index, int value)
{
// 判斷位置是否越界
bool ret = (0 <= index) && (index < length());
if (ret)
{
m_pointer[index] = value;
}
return ret;
}
// 用來釋放對空間
void IntArray::free()
{
delete[] m_pointer;
}
// main.c
#include <stdio.h>
#include "IntArray.h"
int main()
{
IntArray a(5); // 定義了一個物件a,陣列類,長度為5
for (int i = 0; i < a.length(); i++)
{
// 賦值操作
a.set(i, i + 1);
}
for (int i = 0; i < a.length(); i++)
{
int value = 0;
if (a.get(i, value))
{
printf("a[%d] = %d\n", i, value);
}
}
a.free();
return 0;
}
6、小結
建構函式可以根據需要定義引數建構函式之間可以存在過載關係
建構函式遵循C++中過載函式的規則
物件定義時會觸發建構函式的呼叫
在一些情況下可以手動呼叫建構函式