?? c++10.dat
字號:
private:
int width;
int height;
//...
};
Image::Image (const int w, const int h)
{
width = w;
height = h;
//...
}
其實,類Image中的width和height的初始化也可以通過成員初始化表來初始化,例如:
程序段2
class Image {
public:
Image(const int w, const int h);
private:
int width;
int height;
//...
};
Image::Image (const int w, const int h) : width(w), height(h)
{
//...
}
這個定義的效果是width被w初始化,height被w初始化.用成員初始化表初始化類成員與用構造函數初始化類成員的區別在于:前者的初始化是在構造函數被執行以前進行的.
成員初始化表可用于初始化類的任意數據成員(后面要介紹的靜態數據成員除外),它被放在構造函數的頭和體之間,并用冒號將它與構造函數的頭分隔開.它由逗號分隔的數據成員表組成,初值放在一對圓括號中.
2.3 析構函數
我們已經知道,當對象創建時,會自動調用構造函數進行初始化.當對象銷毀時,也會自動調用析構函數進行一些清理工作.與構造函數類似的是:析構函數也與類同名,但在名字前有一個~符號,析構函數也沒有返回類型和返回值.但析構函數不帶參數,不能重載,所以析構函數只有一個.
若一個對象中有指針數據成員,該指針數據成員指向某一個內存塊.在對象銷毀前,往往通過析構函數釋放該指針指向的內存塊.例如,Set類中elems指針指向一個動態數組,我們應該給Set類再定義一個析構函數,使elems指向的內存塊能夠在析構函數中被釋放:
class Set
{
public:
Set (const int size);
~Set(void) {delete elems;} // 析構函數
//...
private:
int *elems; // 集合元素
int maxCard; // 集合最大尺寸
int card; // 集合元素個數
};
下面我們通過一個例子,分析析構函數是如何工作的:
void Foo (void)
{
Set s(10);
//...
}
當函數Foo被調用時,創建對象s,并調用其構造函數,為s.elems分配內存及初始化其它對象成員.在Foo函數返回以前,s的析構函數被調用,釋放s.elems指向的內存區.
注意:對象的析構函數在對象銷毀前被調用,對象何時銷毀也與其作用域有關.例如,全局對象是在程序運行結束時銷毀,自動對象是在離開其作用域時銷毀,而動態對象則是在使用delete運算符時銷毀.析構函數的調用順序與構造函數的調用順序相反.
用析構函數確保對象的清除
我們常常不會忽略初始化的重要性,卻很少想到清除的重要性.實際上,清除也很重要,例如,我們在堆中申請了一些內存,如果不在用完后就釋放,就會造成內存泄露,它會導致應用程序運行效率降低,甚至崩潰,我們不可掉以輕心.在C++中,提供了析構函數,保證對象清除工作的自動執行.
析構函數與構造函數不同點是:析構函數的功能與構造函數相反,也不帶任何參數,所以,它不能被重載.當對象超出其作用域被銷毀時,析構函數會被自動調用.
例
#include <iostream.h>
class Tree
{
int height;
public:
Tree (int initialHeight); //Constructor
~Tree (); //Destructor
void grow(int years);
void printsize();
};
Tree:: Tree (int initialHeight)
{
height = initialHeight;
}
Tree::~ Tree ()
{
cout<<"inside tree destructor"<<endl;
printsize();
}
void Tree::grow(int years)
{
height += years;
}
void Tree::printsize()
{
cout<<"tree height is "<<height<<endl;
}
void main()
{
cout<<"before opening brace"<<endl;
{
Tree t(12);
cout<<"after tree creation"<<endl;
t.printsize();
t.grow(4);
cout<<"before closing brace"<<endl;
}
cout<<"after closing brace"<<endl;
}
下面是上面程序的輸出結果:
before opening brace
after tree creation
tree height is 12
before closing brace
inside tree destructor
tree height is 16
after closing brace
我們可以看到,析構函數在包含它的右括號處調用.
關于返回值
構造函數和析構函數是兩個非常特殊的函數:它們沒有返回值.這與返回值為void的函數是不同的.后者雖然也不返回任何值,但有返回類型,而構造函數和析構函數根本就沒有返回類型.
2.4 缺省的構造函數和析構函數
前面我們介紹了構造函數和析構函數的特點、功能及應用.當用戶未顯式定義構造函數和析構函數時,編譯器會隱式定義一個內聯的、公有的構造函數和析構函數.
缺省的構造函數執行創建一個對象所需要的一些初始化操作,但它并不涉及用戶定義的數據成員或申請的內存的初始化.例
class C
{
private:
int n;
char *p;
public:
virtual ~C() {}
};
void f()
{
C obj; //隱式定義的構造函數被調用
}
類C沒有顯式定義構造函數,一個隱式缺省的構造函數被定義.在函數f中,定義對象obj時,調用該構造函數,但它并不初始化數據成員n和p,也不為后者分配內存.
同樣,缺省的的析構函數也不涉及釋放用戶申請的內存的釋放等清理工作.
第三節 復制構造函數
對一個簡單變量的初始化方法是用一個常量或變量初始化另一個變量,例如:
int m = 80;
int n = m;
我們已經會用構造函數初始化對象,那么我們能不能象簡單變量的初始化一樣,直接用一個對象來初始化另一個對象呢?答案是肯定的.我們以前面定義的Point類為例:
Point pt1(15, 25);
Point pt2 = pt1;
后一個語句也可以寫成:
Point pt2( pt1);
它是用pt1初始化pt2,此時,pt2各個成員的值與pt1各個成員的值相同,也就是說,pt1各個成員的值被復制到pt2相應的成員當中.在這個初始化過程當中,實際上調用了一個復制構造函數.當我們沒有顯式定義一個復制構造函數時,編譯器會隱式定義一個缺省的復制構造函數,它是一個內聯的、公有的成員,它具有下面的原型形式:
Point:: Point (const Point &);
可見,復制構造函數與構造函數的不同之處在于形參,前者的形參是Point對象的引用,其功能是將一個對象的每一個成員復制到另一個對象對應的成員當中.
雖然沒有必要,我們也可以為Point類顯式定義一個復制構造函數:
Point:: Point (const Point &pt)
{
xVal=pt. xVal;
yVal=pt. yVal;
}
如果一個類中有指針成員,使用缺省的復制構造函數初始化對象就會出現問題.為了說明存在的問題,我們假定對象A與對象B是相同的類,有一個指針成員,指向對象C.當用對象B初始化對象A時,缺省的復制構造函數將B中每一個成員的值復制到A的對應的成員當中,但并沒有復制對象C.也就是說,對象A和對象B中的指針成員均指向對象C.
第四節 類作用域
我們已經學習了局部作用域和全局作用域,下面介紹類作用域,所有的類成員是屬于類作用域的.
類本身可被定義在三種作用域內:
1. 全局作用域.這是所謂全局類,絕大多數的C++類是定義在該作用域中,我們在前面定義的所有類都是在全局作用域中.
2. 在另一個類的作用域中.這是所謂嵌套類,即一個類包含在另一個類中.
3. 在一個塊的局部作用域中.這是所謂局部類,該類完全被塊包含.
例如:
int fork (void); // 全局fork
class Process {
int fork (void);
//...
};
成員函數fork隱藏了全局函數fork,前者能通過單目域運算符調用后者:
int Process::fork (void)
{
int pid = ::fork(); // 使用全局fork
//...
}
下面舉一個嵌套類的例子.
例
class Rectangle {
public:
Rectangle (int, int, int, int);
//..
private:
class Point {
public:
Point (int, int);
private:
int x, y;
};
Point topLeft, botRight;
};
類Point嵌套在Rectangle類中,Point成員函數可定義為內聯的,也可在全局作用域中,但后者對成員函數名需要更多的限制,例如:
Rectangle::Point::Point (int x, int y)
{
//...
}
同樣,在類域外訪問嵌套類需要限制類名,例如:
Rectangle::Point pt(1,1);
下面我們再看一個局部類的例子:
例
void Render (Image &image)
{
class ColorTable {
public:
ColorTable(void) { /* ... */ }
AddEntry(int r, int g, int b) { /* ... */ }
//...
};
ColorTable colors;
//...
}
類ColorTable是在函數Render中的局部類.與嵌套類不同的是:局部類不能在其局部作用域外訪問,例如:
ColorTable ct; // 非法,沒有定義ColorTable類!
局部類必須完全定義在局部作用域內.所以,它的所有成員函數必須是內聯的,這就決定了局部類的成員函數都是很簡單的.
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -