一、對象的初始化
生活中存在的對象都是被初始化後才上市的,初始狀態是對象普遍存在的一個狀態的。C++中如何給對象初始化呢?
解決方案
1. 爲每個類都提供一個public的initialize函數
2. 對象創建後立即調用initialize函數進行初始化
#include <stdio.h>
class Test
{
private:
int i;
public:
void initialize()
{
i = 0;
}
int getI()
{
return i;
}
};
int main()
{
Test t1;
Test t2;
Test t3;
t1.initialize();
t2.initialize();
t3.initialize();
printf("t1.i = %d\n", t1.getI());
printf("t2.i = %d\n", t2.getI());
printf("t3.i = %d\n", t3.getI());
printf("Press any key to continue...");
getchar();
return 0;
}
編譯運行結果如下:
initialize只是一個普通的函數,必須顯示的調用。一旦由於失誤的原因,對象沒有初始化,那麼結果將是不確定的。沒有初始化的對象,其內部成員變量的值是不定的。
#include <stdio.h>
class Test
{
private:
int i;
int j;
int k;
public:
void initialize()
{
i = 0;
j = 0;
k = 0;
}
void print()
{
printf("i = %d, j = %d, k = %d\n", i, j, k);
}
};
int main()
{
Test t1;
Test t2;
Test t3;
t1.print();
t2.print();
t3.print();
printf("Press any key to continue...");
getchar();
return 0;
}
編譯運行結果如下:
二、構造函數
C++中的類可以定義與類名相同的特殊成員函數。這種與類名相同的成員函數叫做構造函數。構造函數在定義時可以有參數,但是沒有任何返回類型的聲明。
構造函數的調用。一般情況下C++編譯器會自動調用構造函數。在一些情況下則需要手工調用構造函數。
#include <stdio.h>
class Test
{
private:
int i;
int j;
int k;
public:
Test(int v)
{
i = v;
j = v;
k = v;
}
void print()
{
printf("i = %d, j = %d, k = %d\n", i, j, k);
}
};
int main()
{
Test t1(4);
Test t2 = 5;
Test t3 = Test(6);
t1.print();
t2.print();
t3.print();
Test tA[3] = {Test(1), Test(2), Test(3)};
for(int i=0; i<3; i++)
{
tA[i].print();
}
printf("Press any key to continue...");
getchar();
return 0;
}
編譯運行結果如下:
成員函數的重載,類的成員函數和普通函數一樣可以進行重載,並遵守相同的重載規則。
#include <stdio.h>
class Test
{
private:
int i;
int j;
int k;
public:
Test()
{
i = 0;
j = 0;
k = 0;
}
Test(int v)
{
i = v;
j = v;
k = v;
}
void print()
{
printf("i = %d, j = %d, k = %d\n", i, j, k);
}
void print(int v)
{
printf("v = %d\n", v);
}
};
int main()
{
Test t1(4);
Test t2 = 5;
Test t3 = Test(6);
Test t4;
t4.print();
t1.print();
t2.print();
t3.print();
Test tA[3];
for(int i=0; i<3; i++)
{
tA[i].print();
}
printf("Press any key to continue...");
getchar();
return 0;
}
編譯運行結果如下:
兩個特殊的構造函數
1. 無參構造函數:當類中沒有定義任意一個構造函數時,編譯器默認提供一個無參構造函數,並且其函數體爲空。
2. 拷貝構造函數:當類中沒有定義拷貝構造函數時,編譯器默認提供一個拷貝構造函數,簡單的進行成員變量的值複製。
#include <stdio.h>
/*
注意:
1. 當類中沒有定義任何一個構造函數,C++編譯器會爲提供無參構造函數和拷貝構造函數
2. 當類中定義了任意的非拷貝構造函數時,C++編譯器不會爲提供無參構造函數
*/
class Test
{
public:
Test()
{
printf("Test()\n");
}
Test(const Test& obj)
{
printf("Test(const Test& obj)\n");
}
};
int main()
{
Test t1;
Test t2 = t1;
printf("Press any key to continue...");
getchar();
return 0;
}
編譯運行結果如下:
#include <stdio.h>
class Test
{
private:
int i;
int j;
int k;
public:
void print()
{
printf("i = %d, j = %d, k = %d\n", i, j, k);
}
};
int main()
{
Test t1;
Test t2 = t1;
t1.print();
t2.print();
printf("Press any key to continue...");
getchar();
return 0;
}
編譯運行結果如下:
三、數組類的創建
//main.cpp
#include <stdio.h>
#include "Array.h"
int main()
{
Array a1(10);
for(int i=0; i<a1.length(); i++)
{
a1.setData(i, i);
}
for(int i=0; i<a1.length(); i++)
{
printf("Element %d: %d\n", i, a1.getData(i));
}
Array a2 = a1;
for(int i=0; i<a2.length(); i++)
{
printf("Element %d: %d\n", i, a2.getData(i));
}
a1.destory();
a2.destory();
printf("Press any key to continue...");
getchar();
return 0;
}
//Array.cpp
#include "Array.h"
Array::Array(int length)
{
if( length < 0 )
{
length = 0;
}
mLength = length;
mSpace = new int[mLength];
}
Array::Array(const Array& obj)
{
mLength = obj.mLength;
mSpace = new int[mLength];
for(int i=0; i<mLength; i++)
{
mSpace[i] = obj.mSpace[i];
}
}
int Array::length()
{
return mLength;
}
void Array::setData(int index, int value)
{
mSpace[index] = value;
}
int Array::getData(int index)
{
return mSpace[index];
}
void Array::destory()
{
mLength = -1;
delete[] mSpace;
}
//Array.h
#ifndef _ARRAY_H_
#define _ARRAY_H_
class Array
{
private:
int mLength;
int* mSpace;
public:
Array(int length);
Array(const Array& obj);
int length();
void setData(int index, int value);
int getData(int index);
void destory();
};
#endif
編譯運行結果如下:
四、C++中的對象組合
C++中的類可以使用其它類定義成員變量,如何給對象成員進行初始化呢?
對象組合示例:
C++中提供了初始化列表對成員變量進行初始化,語法規則如下:
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
// some other assignment operation
}
注意:
1. 成員變量的初始化順序與聲明的順序相關,與在初始化列表中的順序無關;
2. 初始化列表先於構造函數的函數體執行;
#include <stdio.h>
class M
{
private:
int mI;
public:
M(int i)
{
printf("M(int i), i = %d\n", i);
mI = i;
}
int getI()
{
return mI;
}
};
class Test
{
private:
const int c;
M m1;
M m2;
public:
Test() : c(1), m2(3), m1(2)
{
printf("Test()\n");
}
void print()
{
printf("c = %d, m1.mI = %d, m2.mI = %d\n", c, m1.getI(), m2.getI());
}
};
void run()
{
Test t1;
t1.print();
}
int main()
{
run();
printf("Press any key to continue...");
getchar();
return 0;
}
編譯運行結果如下:
小插曲:
類中的const成員是肯定會被分配空間的,類中的const成員變量只是一個只讀變量。編譯器無法直接得到const成員變量的初始值,因此無法進入符號表成爲真正意義上的常量。
初始化與賦值不同,初始化是用已存在的對象或值對正在創建的對象進行初值設置。賦值是用已存在的對象或值對已經存在的對象進行值設置。
區別:
初始化:被初始化的對象正在創建;
賦值:被賦值的對象已經存在;
五、C++中的析構函數
生活中存在的對象都是被初始化後才上市的,生活中的對象被銷燬前會做一些清理工作。如何清理被銷燬的對象?
解決方案:
1. 爲每個類都提供一個public的destroy函數;
2. 對象不再被需要時立即調用destroy函數進行清理;
destroy只是一個普通的函數,必須顯示的調用。如果對象銷燬前沒有做清理,那麼很可能造成資源泄漏。在構造函數中申請的資源,需要在對象銷燬前釋放。
C++編譯器是否能夠自動調用某個特殊的函數進行對象的清理?
C++中的類可以定義一個特殊的成員函數清理對象。這個特殊的成員函數叫做析構函數。
1. 定義:~ClassName()
2. 析構函數沒有參數也沒有任何返回類型的聲明;
3. 析構函數在對象銷燬時自動被調用;
#include <stdio.h>
class Test
{
private:
int mI;
public:
Test(int i) : mI(i)
{
printf("Test(), mI = %d\n", mI);
}
~Test()
{
printf("~Test(), mI = %d\n", mI);
}
};
void run()
{
Test t1(1);
Test t2(2);
}
int main()
{
run();
printf("Press any key to continue...");
getchar();
return 0;
}
編譯運行結果如下:
六、數組類的進化
前面我們創建了一個數組類,下面通過剛學的析構函數進行改進。
//Array.cpp
#include "Array.h"
Array::Array(int length)
{
if( length < 0 )
{
length = 0;
}
mLength = length;
mSpace = new int[mLength];
}
Array::Array(const Array& obj)
{
mLength = obj.mLength;
mSpace = new int[mLength];
for(int i=0; i<mLength; i++)
{
mSpace[i] = obj.mSpace[i];
}
}
int Array::length()
{
return mLength;
}
void Array::setData(int index, int value)
{
mSpace[index] = value;
}
int Array::getData(int index)
{
return mSpace[index];
}
Array::~Array()
{
mLength = -1;
delete[] mSpace;
}
//Array.h
#ifndef _ARRAY_H_
#define _ARRAY_H_
class Array
{
private:
int mLength;
int* mSpace;
public:
Array(int length);
Array(const Array& obj);
int length();
void setData(int index, int value);
int getData(int index);
~Array();
};
#endif
//main.cpp
#include <stdio.h>
#include "Array.h"
int main()
{
Array a1(10);
for(int i=0; i<a1.length(); i++)
{
a1.setData(i, i);
}
for(int i=0; i<a1.length(); i++)
{
printf("Element %d: %d\n", i, a1.getData(i));
}
Array a2 = a1;
for(int i=0; i<a2.length(); i++)
{
printf("Element %d: %d\n", i, a2.getData(i));
}
printf("Press any key to continue...");
getchar();
return 0;
}
編譯運行結果如下:
構造函數與析構函數的調用秩序
1.當類中有成員變量是其它類的對象時;
2. 首先調用成員變量的構造函數;
3. 調用順序與聲明順序相同;
4. 之後調用自身類的構造函數;
5. 析構函數的調用秩序與對應的構造函數調用秩序相反;
#include <stdio.h>
class Test
{
private:
int mI;
public:
Test()
{
printf("Test()\n");
mI = -1;
}
Test(int i)
{
printf("Test(int i), i = %d\n", i);
mI = i;
}
Test(const Test& obj)
{
printf("Test(const Test& obj), i = %d\n", obj.mI);
mI = obj.mI;
}
~Test()
{
printf("~Test(), i = %d\n", mI);
}
};
void func(Test t)
{
Test r(1);
}
void run()
{
Test t(0);
func(t);
}
int main()
{
run();
printf("Press any key to continue...");
getchar();
return 0;
}
編譯運行結果如下:
小結:
1. 析構函數是C++中對象銷燬時做清理工作的特殊函數;
2. 析構函數在對象銷燬時自動被調用;
3. 析構函數是對象所使用的資源及時釋放的保障;
4. 析構函數的調用秩序與構造函數相反;
#include <stdio.h>
class Test
{
private:
int mI;
int mJ;
const char* mS;
public:
Test()
{
printf("Test()\n");
mI = 0;
mJ = 0;
}
Test(const char* s)
{
printf("Test(const char* s)\n");
Test();
mS = s;
}
~Test()
{
printf("~Test()\n");
}
void print()
{
printf("mI = %d, mJ = %d, mS = %s\n", mI, mJ, mS);
}
};
void run()
{
Test t = Test("Delphi Tang"); // Test t("Delphi Tang");
t.print();
}
int main()
{
run();
printf("Press any key to continue...");
getchar();
return 0;
}
編譯運行結果如下: